1#![allow(rustdoc::private_intra_doc_links)]
2//! This is the place where everything editor-related is stored (data-wise) and displayed (ui-wise).
3//! The main point of interest in this crate is [`Editor`] type, which is used in every other Zed part as a user input element.
4//! It comes in different flavors: single line, multiline and a fixed height one.
5//!
6//! Editor contains of multiple large submodules:
7//! * [`element`] — the place where all rendering happens
8//! * [`display_map`] - chunks up text in the editor into the logical blocks, establishes coordinates and mapping between each of them.
9//! Contains all metadata related to text transformations (folds, fake inlay text insertions, soft wraps, tab markup, etc.).
10//! * [`inlay_hint_cache`] - is a storage of inlay hints out of LSP requests, responsible for querying LSP and updating `display_map`'s state accordingly.
11//!
12//! All other submodules and structs are mostly concerned with holding editor data about the way it displays current buffer region(s).
13//!
14//! If you're looking to improve Vim mode, you should check out Vim crate that wraps Editor and overrides its behavior.
15pub mod actions;
16mod blink_manager;
17mod clangd_ext;
18pub mod code_context_menus;
19pub mod display_map;
20mod editor_settings;
21mod editor_settings_controls;
22mod element;
23mod git;
24mod highlight_matching_bracket;
25mod hover_links;
26pub mod hover_popover;
27mod indent_guides;
28mod inlay_hint_cache;
29pub mod items;
30mod jsx_tag_auto_close;
31mod linked_editing_ranges;
32mod lsp_colors;
33mod lsp_ext;
34mod mouse_context_menu;
35pub mod movement;
36mod persistence;
37mod proposed_changes_editor;
38mod rust_analyzer_ext;
39pub mod scroll;
40mod selections_collection;
41pub mod tasks;
42
43#[cfg(test)]
44mod code_completion_tests;
45#[cfg(test)]
46mod editor_tests;
47#[cfg(test)]
48mod inline_completion_tests;
49mod signature_help;
50#[cfg(any(test, feature = "test-support"))]
51pub mod test;
52
53pub(crate) use actions::*;
54pub use actions::{AcceptEditPrediction, OpenExcerpts, OpenExcerptsSplit};
55use aho_corasick::AhoCorasick;
56use anyhow::{Context as _, Result, anyhow};
57use blink_manager::BlinkManager;
58use buffer_diff::DiffHunkStatus;
59use client::{Collaborator, DisableAiSettings, ParticipantIndex};
60use clock::{AGENT_REPLICA_ID, ReplicaId};
61use collections::{BTreeMap, HashMap, HashSet, VecDeque};
62use convert_case::{Case, Casing};
63use dap::TelemetrySpawnLocation;
64use display_map::*;
65pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
66pub use editor_settings::{
67 CurrentLineHighlight, DocumentColorsRenderMode, EditorSettings, HideMouseMode,
68 ScrollBeyondLastLine, ScrollbarAxes, SearchSettings, ShowMinimap, ShowScrollbar,
69};
70use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
71pub use editor_settings_controls::*;
72use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
73pub use element::{
74 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
75};
76use futures::{
77 FutureExt, StreamExt as _,
78 future::{self, Shared, join},
79 stream::FuturesUnordered,
80};
81use fuzzy::{StringMatch, StringMatchCandidate};
82use lsp_colors::LspColorData;
83
84use ::git::blame::BlameEntry;
85use ::git::{Restore, blame::ParsedCommitMessage};
86use code_context_menus::{
87 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
88 CompletionsMenu, ContextMenuOrigin,
89};
90use git::blame::{GitBlame, GlobalBlameRenderer};
91use gpui::{
92 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
93 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
94 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
95 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
96 MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render, ScrollHandle,
97 SharedString, Size, Stateful, Styled, Subscription, Task, TextStyle, TextStyleRefinement,
98 UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
99 div, point, prelude::*, pulsating_between, px, relative, size,
100};
101use highlight_matching_bracket::refresh_matching_bracket_highlights;
102use hover_links::{HoverLink, HoveredLinkState, InlayHighlight, find_file};
103pub use hover_popover::hover_markdown_style;
104use hover_popover::{HoverState, hide_hover};
105use indent_guides::ActiveIndentGuidesState;
106use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
107pub use inline_completion::Direction;
108use inline_completion::{EditPredictionProvider, InlineCompletionProviderHandle};
109pub use items::MAX_TAB_TITLE_LEN;
110use itertools::Itertools;
111use language::{
112 AutoindentMode, BlockCommentConfig, BracketMatch, BracketPair, Buffer, Capability, CharKind,
113 CodeLabel, CursorShape, DiagnosticEntry, DiffOptions, EditPredictionsMode, EditPreview,
114 HighlightedText, IndentKind, IndentSize, Language, OffsetRangeExt, Point, Selection,
115 SelectionGoal, TextObject, TransactionId, TreeSitterOptions, WordsQuery,
116 language_settings::{
117 self, InlayHintSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
118 all_language_settings, language_settings,
119 },
120 point_from_lsp, text_diff_with_options,
121};
122use language::{BufferRow, CharClassifier, Runnable, RunnableRange, point_to_lsp};
123use linked_editing_ranges::refresh_linked_ranges;
124use markdown::Markdown;
125use mouse_context_menu::MouseContextMenu;
126use persistence::DB;
127use project::{
128 BreakpointWithPosition, CompletionResponse, ProjectPath,
129 debugger::{
130 breakpoint_store::{
131 BreakpointEditAction, BreakpointSessionState, BreakpointState, BreakpointStore,
132 BreakpointStoreEvent,
133 },
134 session::{Session, SessionEvent},
135 },
136 git_store::{GitStoreEvent, RepositoryEvent},
137 project_settings::{DiagnosticSeverity, GoToDiagnosticSeverityFilter},
138};
139
140pub use git::blame::BlameRenderer;
141pub use proposed_changes_editor::{
142 ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
143};
144use std::{cell::OnceCell, iter::Peekable, ops::Not};
145use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
146
147pub use lsp::CompletionContext;
148use lsp::{
149 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
150 LanguageServerId, LanguageServerName,
151};
152
153use language::BufferSnapshot;
154pub use lsp_ext::lsp_tasks;
155use movement::TextLayoutDetails;
156pub use multi_buffer::{
157 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, PathKey,
158 RowInfo, ToOffset, ToPoint,
159};
160use multi_buffer::{
161 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
162 MultiOrSingleBufferOffsetRange, ToOffsetUtf16,
163};
164use parking_lot::Mutex;
165use project::{
166 CodeAction, Completion, CompletionIntent, CompletionSource, DocumentHighlight, InlayHint,
167 Location, LocationLink, PrepareRenameResponse, Project, ProjectItem, ProjectTransaction,
168 TaskSourceKind,
169 debugger::breakpoint_store::Breakpoint,
170 lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
171 project_settings::{GitGutterSetting, ProjectSettings},
172};
173use rand::prelude::*;
174use rpc::{ErrorExt, proto::*};
175use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide};
176use selections_collection::{
177 MutableSelectionsCollection, SelectionsCollection, resolve_selections,
178};
179use serde::{Deserialize, Serialize};
180use settings::{Settings, SettingsLocation, SettingsStore, update_settings_file};
181use smallvec::{SmallVec, smallvec};
182use snippet::Snippet;
183use std::sync::Arc;
184use std::{
185 any::TypeId,
186 borrow::Cow,
187 cell::RefCell,
188 cmp::{self, Ordering, Reverse},
189 mem,
190 num::NonZeroU32,
191 ops::{ControlFlow, Deref, DerefMut, Range, RangeInclusive},
192 path::{Path, PathBuf},
193 rc::Rc,
194 time::{Duration, Instant},
195};
196pub use sum_tree::Bias;
197use sum_tree::TreeMap;
198use text::{BufferId, FromAnchor, OffsetUtf16, Rope};
199use theme::{
200 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, Theme, ThemeSettings,
201 observe_buffer_font_size_adjustment,
202};
203use ui::{
204 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
205 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*,
206};
207use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
208use workspace::{
209 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
210 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
211 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
212 item::{ItemHandle, PreviewTabsSettings, SaveOptions},
213 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
214 searchable::SearchEvent,
215};
216use zed_actions;
217
218use crate::{
219 code_context_menus::CompletionsMenuSource,
220 hover_links::{find_url, find_url_from_range},
221};
222use crate::{
223 editor_settings::MultiCursorModifier,
224 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
225};
226
227pub const FILE_HEADER_HEIGHT: u32 = 2;
228pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
229pub const DEFAULT_MULTIBUFFER_CONTEXT: u32 = 2;
230const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
231const MAX_LINE_LEN: usize = 1024;
232const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
233const MAX_SELECTION_HISTORY_LEN: usize = 1024;
234pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
235#[doc(hidden)]
236pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
237const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
238
239pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
240pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
241pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
242
243pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
244pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
245pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
246
247pub type RenderDiffHunkControlsFn = Arc<
248 dyn Fn(
249 u32,
250 &DiffHunkStatus,
251 Range<Anchor>,
252 bool,
253 Pixels,
254 &Entity<Editor>,
255 &mut Window,
256 &mut App,
257 ) -> AnyElement,
258>;
259
260struct InlineValueCache {
261 enabled: bool,
262 inlays: Vec<InlayId>,
263 refresh_task: Task<Option<()>>,
264}
265
266impl InlineValueCache {
267 fn new(enabled: bool) -> Self {
268 Self {
269 enabled,
270 inlays: Vec::new(),
271 refresh_task: Task::ready(None),
272 }
273 }
274}
275
276#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
277pub enum InlayId {
278 InlineCompletion(usize),
279 DebuggerValue(usize),
280 // LSP
281 Hint(usize),
282 Color(usize),
283}
284
285impl InlayId {
286 fn id(&self) -> usize {
287 match self {
288 Self::InlineCompletion(id) => *id,
289 Self::DebuggerValue(id) => *id,
290 Self::Hint(id) => *id,
291 Self::Color(id) => *id,
292 }
293 }
294}
295
296pub enum ActiveDebugLine {}
297pub enum DebugStackFrameLine {}
298enum DocumentHighlightRead {}
299enum DocumentHighlightWrite {}
300enum InputComposition {}
301pub enum PendingInput {}
302enum SelectedTextHighlight {}
303
304pub enum ConflictsOuter {}
305pub enum ConflictsOurs {}
306pub enum ConflictsTheirs {}
307pub enum ConflictsOursMarker {}
308pub enum ConflictsTheirsMarker {}
309
310#[derive(Debug, Copy, Clone, PartialEq, Eq)]
311pub enum Navigated {
312 Yes,
313 No,
314}
315
316impl Navigated {
317 pub fn from_bool(yes: bool) -> Navigated {
318 if yes { Navigated::Yes } else { Navigated::No }
319 }
320}
321
322#[derive(Debug, Clone, PartialEq, Eq)]
323enum DisplayDiffHunk {
324 Folded {
325 display_row: DisplayRow,
326 },
327 Unfolded {
328 is_created_file: bool,
329 diff_base_byte_range: Range<usize>,
330 display_row_range: Range<DisplayRow>,
331 multi_buffer_range: Range<Anchor>,
332 status: DiffHunkStatus,
333 },
334}
335
336pub enum HideMouseCursorOrigin {
337 TypingAction,
338 MovementAction,
339}
340
341pub fn init_settings(cx: &mut App) {
342 EditorSettings::register(cx);
343}
344
345pub fn init(cx: &mut App) {
346 init_settings(cx);
347
348 cx.set_global(GlobalBlameRenderer(Arc::new(())));
349
350 workspace::register_project_item::<Editor>(cx);
351 workspace::FollowableViewRegistry::register::<Editor>(cx);
352 workspace::register_serializable_item::<Editor>(cx);
353
354 cx.observe_new(
355 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
356 workspace.register_action(Editor::new_file);
357 workspace.register_action(Editor::new_file_vertical);
358 workspace.register_action(Editor::new_file_horizontal);
359 workspace.register_action(Editor::cancel_language_server_work);
360 workspace.register_action(Editor::toggle_focus);
361 },
362 )
363 .detach();
364
365 cx.on_action(move |_: &workspace::NewFile, cx| {
366 let app_state = workspace::AppState::global(cx);
367 if let Some(app_state) = app_state.upgrade() {
368 workspace::open_new(
369 Default::default(),
370 app_state,
371 cx,
372 |workspace, window, cx| {
373 Editor::new_file(workspace, &Default::default(), window, cx)
374 },
375 )
376 .detach();
377 }
378 });
379 cx.on_action(move |_: &workspace::NewWindow, cx| {
380 let app_state = workspace::AppState::global(cx);
381 if let Some(app_state) = app_state.upgrade() {
382 workspace::open_new(
383 Default::default(),
384 app_state,
385 cx,
386 |workspace, window, cx| {
387 cx.activate(true);
388 Editor::new_file(workspace, &Default::default(), window, cx)
389 },
390 )
391 .detach();
392 }
393 });
394}
395
396pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
397 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
398}
399
400pub trait DiagnosticRenderer {
401 fn render_group(
402 &self,
403 diagnostic_group: Vec<DiagnosticEntry<Point>>,
404 buffer_id: BufferId,
405 snapshot: EditorSnapshot,
406 editor: WeakEntity<Editor>,
407 cx: &mut App,
408 ) -> Vec<BlockProperties<Anchor>>;
409
410 fn render_hover(
411 &self,
412 diagnostic_group: Vec<DiagnosticEntry<Point>>,
413 range: Range<Point>,
414 buffer_id: BufferId,
415 cx: &mut App,
416 ) -> Option<Entity<markdown::Markdown>>;
417
418 fn open_link(
419 &self,
420 editor: &mut Editor,
421 link: SharedString,
422 window: &mut Window,
423 cx: &mut Context<Editor>,
424 );
425}
426
427pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
428
429impl GlobalDiagnosticRenderer {
430 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
431 cx.try_global::<Self>().map(|g| g.0.clone())
432 }
433}
434
435impl gpui::Global for GlobalDiagnosticRenderer {}
436pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
437 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
438}
439
440pub struct SearchWithinRange;
441
442trait InvalidationRegion {
443 fn ranges(&self) -> &[Range<Anchor>];
444}
445
446#[derive(Clone, Debug, PartialEq)]
447pub enum SelectPhase {
448 Begin {
449 position: DisplayPoint,
450 add: bool,
451 click_count: usize,
452 },
453 BeginColumnar {
454 position: DisplayPoint,
455 reset: bool,
456 mode: ColumnarMode,
457 goal_column: u32,
458 },
459 Extend {
460 position: DisplayPoint,
461 click_count: usize,
462 },
463 Update {
464 position: DisplayPoint,
465 goal_column: u32,
466 scroll_delta: gpui::Point<f32>,
467 },
468 End,
469}
470
471#[derive(Clone, Debug, PartialEq)]
472pub enum ColumnarMode {
473 FromMouse,
474 FromSelection,
475}
476
477#[derive(Clone, Debug)]
478pub enum SelectMode {
479 Character,
480 Word(Range<Anchor>),
481 Line(Range<Anchor>),
482 All,
483}
484
485#[derive(Clone, PartialEq, Eq, Debug)]
486pub enum EditorMode {
487 SingleLine,
488 AutoHeight {
489 min_lines: usize,
490 max_lines: Option<usize>,
491 },
492 Full {
493 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
494 scale_ui_elements_with_buffer_font_size: bool,
495 /// When set to `true`, the editor will render a background for the active line.
496 show_active_line_background: bool,
497 /// When set to `true`, the editor's height will be determined by its content.
498 sized_by_content: bool,
499 },
500 Minimap {
501 parent: WeakEntity<Editor>,
502 },
503}
504
505impl EditorMode {
506 pub fn full() -> Self {
507 Self::Full {
508 scale_ui_elements_with_buffer_font_size: true,
509 show_active_line_background: true,
510 sized_by_content: false,
511 }
512 }
513
514 #[inline]
515 pub fn is_full(&self) -> bool {
516 matches!(self, Self::Full { .. })
517 }
518
519 #[inline]
520 pub fn is_single_line(&self) -> bool {
521 matches!(self, Self::SingleLine { .. })
522 }
523
524 #[inline]
525 fn is_minimap(&self) -> bool {
526 matches!(self, Self::Minimap { .. })
527 }
528}
529
530#[derive(Copy, Clone, Debug)]
531pub enum SoftWrap {
532 /// Prefer not to wrap at all.
533 ///
534 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
535 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
536 GitDiff,
537 /// Prefer a single line generally, unless an overly long line is encountered.
538 None,
539 /// Soft wrap lines that exceed the editor width.
540 EditorWidth,
541 /// Soft wrap lines at the preferred line length.
542 Column(u32),
543 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
544 Bounded(u32),
545}
546
547#[derive(Clone)]
548pub struct EditorStyle {
549 pub background: Hsla,
550 pub border: Hsla,
551 pub local_player: PlayerColor,
552 pub text: TextStyle,
553 pub scrollbar_width: Pixels,
554 pub syntax: Arc<SyntaxTheme>,
555 pub status: StatusColors,
556 pub inlay_hints_style: HighlightStyle,
557 pub inline_completion_styles: InlineCompletionStyles,
558 pub unnecessary_code_fade: f32,
559 pub show_underlines: bool,
560}
561
562impl Default for EditorStyle {
563 fn default() -> Self {
564 Self {
565 background: Hsla::default(),
566 border: Hsla::default(),
567 local_player: PlayerColor::default(),
568 text: TextStyle::default(),
569 scrollbar_width: Pixels::default(),
570 syntax: Default::default(),
571 // HACK: Status colors don't have a real default.
572 // We should look into removing the status colors from the editor
573 // style and retrieve them directly from the theme.
574 status: StatusColors::dark(),
575 inlay_hints_style: HighlightStyle::default(),
576 inline_completion_styles: InlineCompletionStyles {
577 insertion: HighlightStyle::default(),
578 whitespace: HighlightStyle::default(),
579 },
580 unnecessary_code_fade: Default::default(),
581 show_underlines: true,
582 }
583 }
584}
585
586pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
587 let show_background = language_settings::language_settings(None, None, cx)
588 .inlay_hints
589 .show_background;
590
591 HighlightStyle {
592 color: Some(cx.theme().status().hint),
593 background_color: show_background.then(|| cx.theme().status().hint_background),
594 ..HighlightStyle::default()
595 }
596}
597
598pub fn make_suggestion_styles(cx: &mut App) -> InlineCompletionStyles {
599 InlineCompletionStyles {
600 insertion: HighlightStyle {
601 color: Some(cx.theme().status().predictive),
602 ..HighlightStyle::default()
603 },
604 whitespace: HighlightStyle {
605 background_color: Some(cx.theme().status().created_background),
606 ..HighlightStyle::default()
607 },
608 }
609}
610
611type CompletionId = usize;
612
613pub(crate) enum EditDisplayMode {
614 TabAccept,
615 DiffPopover,
616 Inline,
617}
618
619enum InlineCompletion {
620 Edit {
621 edits: Vec<(Range<Anchor>, String)>,
622 edit_preview: Option<EditPreview>,
623 display_mode: EditDisplayMode,
624 snapshot: BufferSnapshot,
625 },
626 Move {
627 target: Anchor,
628 snapshot: BufferSnapshot,
629 },
630}
631
632struct InlineCompletionState {
633 inlay_ids: Vec<InlayId>,
634 completion: InlineCompletion,
635 completion_id: Option<SharedString>,
636 invalidation_range: Range<Anchor>,
637}
638
639enum EditPredictionSettings {
640 Disabled,
641 Enabled {
642 show_in_menu: bool,
643 preview_requires_modifier: bool,
644 },
645}
646
647enum InlineCompletionHighlight {}
648
649#[derive(Debug, Clone)]
650struct InlineDiagnostic {
651 message: SharedString,
652 group_id: usize,
653 is_primary: bool,
654 start: Point,
655 severity: lsp::DiagnosticSeverity,
656}
657
658pub enum MenuInlineCompletionsPolicy {
659 Never,
660 ByProvider,
661}
662
663pub enum EditPredictionPreview {
664 /// Modifier is not pressed
665 Inactive { released_too_fast: bool },
666 /// Modifier pressed
667 Active {
668 since: Instant,
669 previous_scroll_position: Option<ScrollAnchor>,
670 },
671}
672
673impl EditPredictionPreview {
674 pub fn released_too_fast(&self) -> bool {
675 match self {
676 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
677 EditPredictionPreview::Active { .. } => false,
678 }
679 }
680
681 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
682 if let EditPredictionPreview::Active {
683 previous_scroll_position,
684 ..
685 } = self
686 {
687 *previous_scroll_position = scroll_position;
688 }
689 }
690}
691
692pub struct ContextMenuOptions {
693 pub min_entries_visible: usize,
694 pub max_entries_visible: usize,
695 pub placement: Option<ContextMenuPlacement>,
696}
697
698#[derive(Debug, Clone, PartialEq, Eq)]
699pub enum ContextMenuPlacement {
700 Above,
701 Below,
702}
703
704#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
705struct EditorActionId(usize);
706
707impl EditorActionId {
708 pub fn post_inc(&mut self) -> Self {
709 let answer = self.0;
710
711 *self = Self(answer + 1);
712
713 Self(answer)
714 }
715}
716
717// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
718// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
719
720type BackgroundHighlight = (fn(&Theme) -> Hsla, Arc<[Range<Anchor>]>);
721type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
722
723#[derive(Default)]
724struct ScrollbarMarkerState {
725 scrollbar_size: Size<Pixels>,
726 dirty: bool,
727 markers: Arc<[PaintQuad]>,
728 pending_refresh: Option<Task<Result<()>>>,
729}
730
731impl ScrollbarMarkerState {
732 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
733 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
734 }
735}
736
737#[derive(Clone, Copy, PartialEq, Eq)]
738pub enum MinimapVisibility {
739 Disabled,
740 Enabled {
741 /// The configuration currently present in the users settings.
742 setting_configuration: bool,
743 /// Whether to override the currently set visibility from the users setting.
744 toggle_override: bool,
745 },
746}
747
748impl MinimapVisibility {
749 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
750 if mode.is_full() {
751 Self::Enabled {
752 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
753 toggle_override: false,
754 }
755 } else {
756 Self::Disabled
757 }
758 }
759
760 fn hidden(&self) -> Self {
761 match *self {
762 Self::Enabled {
763 setting_configuration,
764 ..
765 } => Self::Enabled {
766 setting_configuration,
767 toggle_override: setting_configuration,
768 },
769 Self::Disabled => Self::Disabled,
770 }
771 }
772
773 fn disabled(&self) -> bool {
774 match *self {
775 Self::Disabled => true,
776 _ => false,
777 }
778 }
779
780 fn settings_visibility(&self) -> bool {
781 match *self {
782 Self::Enabled {
783 setting_configuration,
784 ..
785 } => setting_configuration,
786 _ => false,
787 }
788 }
789
790 fn visible(&self) -> bool {
791 match *self {
792 Self::Enabled {
793 setting_configuration,
794 toggle_override,
795 } => setting_configuration ^ toggle_override,
796 _ => false,
797 }
798 }
799
800 fn toggle_visibility(&self) -> Self {
801 match *self {
802 Self::Enabled {
803 toggle_override,
804 setting_configuration,
805 } => Self::Enabled {
806 setting_configuration,
807 toggle_override: !toggle_override,
808 },
809 Self::Disabled => Self::Disabled,
810 }
811 }
812}
813
814#[derive(Clone, Debug)]
815struct RunnableTasks {
816 templates: Vec<(TaskSourceKind, TaskTemplate)>,
817 offset: multi_buffer::Anchor,
818 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
819 column: u32,
820 // Values of all named captures, including those starting with '_'
821 extra_variables: HashMap<String, String>,
822 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
823 context_range: Range<BufferOffset>,
824}
825
826impl RunnableTasks {
827 fn resolve<'a>(
828 &'a self,
829 cx: &'a task::TaskContext,
830 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
831 self.templates.iter().filter_map(|(kind, template)| {
832 template
833 .resolve_task(&kind.to_id_base(), cx)
834 .map(|task| (kind.clone(), task))
835 })
836 }
837}
838
839#[derive(Clone)]
840pub struct ResolvedTasks {
841 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
842 position: Anchor,
843}
844
845#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
846struct BufferOffset(usize);
847
848// Addons allow storing per-editor state in other crates (e.g. Vim)
849pub trait Addon: 'static {
850 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
851
852 fn render_buffer_header_controls(
853 &self,
854 _: &ExcerptInfo,
855 _: &Window,
856 _: &App,
857 ) -> Option<AnyElement> {
858 None
859 }
860
861 fn to_any(&self) -> &dyn std::any::Any;
862
863 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
864 None
865 }
866}
867
868struct ChangeLocation {
869 current: Option<Vec<Anchor>>,
870 original: Vec<Anchor>,
871}
872impl ChangeLocation {
873 fn locations(&self) -> &[Anchor] {
874 self.current.as_ref().unwrap_or(&self.original)
875 }
876}
877
878/// A set of caret positions, registered when the editor was edited.
879pub struct ChangeList {
880 changes: Vec<ChangeLocation>,
881 /// Currently "selected" change.
882 position: Option<usize>,
883}
884
885impl ChangeList {
886 pub fn new() -> Self {
887 Self {
888 changes: Vec::new(),
889 position: None,
890 }
891 }
892
893 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
894 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
895 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
896 if self.changes.is_empty() {
897 return None;
898 }
899
900 let prev = self.position.unwrap_or(self.changes.len());
901 let next = if direction == Direction::Prev {
902 prev.saturating_sub(count)
903 } else {
904 (prev + count).min(self.changes.len() - 1)
905 };
906 self.position = Some(next);
907 self.changes.get(next).map(|change| change.locations())
908 }
909
910 /// Adds a new change to the list, resetting the change list position.
911 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
912 self.position.take();
913 if let Some(last) = self.changes.last_mut()
914 && group
915 {
916 last.current = Some(new_positions)
917 } else {
918 self.changes.push(ChangeLocation {
919 original: new_positions,
920 current: None,
921 });
922 }
923 }
924
925 pub fn last(&self) -> Option<&[Anchor]> {
926 self.changes.last().map(|change| change.locations())
927 }
928
929 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
930 self.changes.last().map(|change| change.original.as_slice())
931 }
932
933 pub fn invert_last_group(&mut self) {
934 if let Some(last) = self.changes.last_mut() {
935 if let Some(current) = last.current.as_mut() {
936 mem::swap(&mut last.original, current);
937 }
938 }
939 }
940}
941
942#[derive(Clone)]
943struct InlineBlamePopoverState {
944 scroll_handle: ScrollHandle,
945 commit_message: Option<ParsedCommitMessage>,
946 markdown: Entity<Markdown>,
947}
948
949struct InlineBlamePopover {
950 position: gpui::Point<Pixels>,
951 hide_task: Option<Task<()>>,
952 popover_bounds: Option<Bounds<Pixels>>,
953 popover_state: InlineBlamePopoverState,
954 keyboard_grace: bool,
955}
956
957enum SelectionDragState {
958 /// State when no drag related activity is detected.
959 None,
960 /// State when the mouse is down on a selection that is about to be dragged.
961 ReadyToDrag {
962 selection: Selection<Anchor>,
963 click_position: gpui::Point<Pixels>,
964 mouse_down_time: Instant,
965 },
966 /// State when the mouse is dragging the selection in the editor.
967 Dragging {
968 selection: Selection<Anchor>,
969 drop_cursor: Selection<Anchor>,
970 hide_drop_cursor: bool,
971 },
972}
973
974enum ColumnarSelectionState {
975 FromMouse {
976 selection_tail: Anchor,
977 display_point: Option<DisplayPoint>,
978 },
979 FromSelection {
980 selection_tail: Anchor,
981 },
982}
983
984/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
985/// a breakpoint on them.
986#[derive(Clone, Copy, Debug, PartialEq, Eq)]
987struct PhantomBreakpointIndicator {
988 display_row: DisplayRow,
989 /// There's a small debounce between hovering over the line and showing the indicator.
990 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
991 is_active: bool,
992 collides_with_existing_breakpoint: bool,
993}
994
995/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
996///
997/// See the [module level documentation](self) for more information.
998pub struct Editor {
999 focus_handle: FocusHandle,
1000 last_focused_descendant: Option<WeakFocusHandle>,
1001 /// The text buffer being edited
1002 buffer: Entity<MultiBuffer>,
1003 /// Map of how text in the buffer should be displayed.
1004 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1005 pub display_map: Entity<DisplayMap>,
1006 pub selections: SelectionsCollection,
1007 pub scroll_manager: ScrollManager,
1008 /// When inline assist editors are linked, they all render cursors because
1009 /// typing enters text into each of them, even the ones that aren't focused.
1010 pub(crate) show_cursor_when_unfocused: bool,
1011 columnar_selection_state: Option<ColumnarSelectionState>,
1012 add_selections_state: Option<AddSelectionsState>,
1013 select_next_state: Option<SelectNextState>,
1014 select_prev_state: Option<SelectNextState>,
1015 selection_history: SelectionHistory,
1016 defer_selection_effects: bool,
1017 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1018 autoclose_regions: Vec<AutocloseRegion>,
1019 snippet_stack: InvalidationStack<SnippetState>,
1020 select_syntax_node_history: SelectSyntaxNodeHistory,
1021 ime_transaction: Option<TransactionId>,
1022 pub diagnostics_max_severity: DiagnosticSeverity,
1023 active_diagnostics: ActiveDiagnostic,
1024 show_inline_diagnostics: bool,
1025 inline_diagnostics_update: Task<()>,
1026 inline_diagnostics_enabled: bool,
1027 diagnostics_enabled: bool,
1028 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1029 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1030 hard_wrap: Option<usize>,
1031
1032 // TODO: make this a access method
1033 pub project: Option<Entity<Project>>,
1034 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1035 completion_provider: Option<Rc<dyn CompletionProvider>>,
1036 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1037 blink_manager: Entity<BlinkManager>,
1038 show_cursor_names: bool,
1039 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1040 pub show_local_selections: bool,
1041 mode: EditorMode,
1042 show_breadcrumbs: bool,
1043 show_gutter: bool,
1044 show_scrollbars: ScrollbarAxes,
1045 minimap_visibility: MinimapVisibility,
1046 offset_content: bool,
1047 disable_expand_excerpt_buttons: bool,
1048 show_line_numbers: Option<bool>,
1049 use_relative_line_numbers: Option<bool>,
1050 show_git_diff_gutter: Option<bool>,
1051 show_code_actions: Option<bool>,
1052 show_runnables: Option<bool>,
1053 show_breakpoints: Option<bool>,
1054 show_wrap_guides: Option<bool>,
1055 show_indent_guides: Option<bool>,
1056 placeholder_text: Option<Arc<str>>,
1057 highlight_order: usize,
1058 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1059 background_highlights: TreeMap<HighlightKey, BackgroundHighlight>,
1060 gutter_highlights: TreeMap<TypeId, GutterHighlight>,
1061 scrollbar_marker_state: ScrollbarMarkerState,
1062 active_indent_guides_state: ActiveIndentGuidesState,
1063 nav_history: Option<ItemNavHistory>,
1064 context_menu: RefCell<Option<CodeContextMenu>>,
1065 context_menu_options: Option<ContextMenuOptions>,
1066 mouse_context_menu: Option<MouseContextMenu>,
1067 completion_tasks: Vec<(CompletionId, Task<()>)>,
1068 inline_blame_popover: Option<InlineBlamePopover>,
1069 inline_blame_popover_show_task: Option<Task<()>>,
1070 signature_help_state: SignatureHelpState,
1071 auto_signature_help: Option<bool>,
1072 find_all_references_task_sources: Vec<Anchor>,
1073 next_completion_id: CompletionId,
1074 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1075 code_actions_task: Option<Task<Result<()>>>,
1076 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1077 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1078 document_highlights_task: Option<Task<()>>,
1079 linked_editing_range_task: Option<Task<Option<()>>>,
1080 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1081 pending_rename: Option<RenameState>,
1082 searchable: bool,
1083 cursor_shape: CursorShape,
1084 current_line_highlight: Option<CurrentLineHighlight>,
1085 collapse_matches: bool,
1086 autoindent_mode: Option<AutoindentMode>,
1087 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1088 input_enabled: bool,
1089 use_modal_editing: bool,
1090 read_only: bool,
1091 leader_id: Option<CollaboratorId>,
1092 remote_id: Option<ViewId>,
1093 pub hover_state: HoverState,
1094 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1095 gutter_hovered: bool,
1096 hovered_link_state: Option<HoveredLinkState>,
1097 edit_prediction_provider: Option<RegisteredInlineCompletionProvider>,
1098 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1099 active_inline_completion: Option<InlineCompletionState>,
1100 /// Used to prevent flickering as the user types while the menu is open
1101 stale_inline_completion_in_menu: Option<InlineCompletionState>,
1102 edit_prediction_settings: EditPredictionSettings,
1103 inline_completions_hidden_for_vim_mode: bool,
1104 show_inline_completions_override: Option<bool>,
1105 menu_inline_completions_policy: MenuInlineCompletionsPolicy,
1106 edit_prediction_preview: EditPredictionPreview,
1107 edit_prediction_indent_conflict: bool,
1108 edit_prediction_requires_modifier_in_indent_conflict: bool,
1109 inlay_hint_cache: InlayHintCache,
1110 next_inlay_id: usize,
1111 _subscriptions: Vec<Subscription>,
1112 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1113 gutter_dimensions: GutterDimensions,
1114 style: Option<EditorStyle>,
1115 text_style_refinement: Option<TextStyleRefinement>,
1116 next_editor_action_id: EditorActionId,
1117 editor_actions: Rc<
1118 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1119 >,
1120 use_autoclose: bool,
1121 use_auto_surround: bool,
1122 auto_replace_emoji_shortcode: bool,
1123 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1124 show_git_blame_gutter: bool,
1125 show_git_blame_inline: bool,
1126 show_git_blame_inline_delay_task: Option<Task<()>>,
1127 git_blame_inline_enabled: bool,
1128 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1129 serialize_dirty_buffers: bool,
1130 show_selection_menu: Option<bool>,
1131 blame: Option<Entity<GitBlame>>,
1132 blame_subscription: Option<Subscription>,
1133 custom_context_menu: Option<
1134 Box<
1135 dyn 'static
1136 + Fn(
1137 &mut Self,
1138 DisplayPoint,
1139 &mut Window,
1140 &mut Context<Self>,
1141 ) -> Option<Entity<ui::ContextMenu>>,
1142 >,
1143 >,
1144 last_bounds: Option<Bounds<Pixels>>,
1145 last_position_map: Option<Rc<PositionMap>>,
1146 expect_bounds_change: Option<Bounds<Pixels>>,
1147 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1148 tasks_update_task: Option<Task<()>>,
1149 breakpoint_store: Option<Entity<BreakpointStore>>,
1150 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1151 hovered_diff_hunk_row: Option<DisplayRow>,
1152 pull_diagnostics_task: Task<()>,
1153 in_project_search: bool,
1154 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1155 breadcrumb_header: Option<String>,
1156 focused_block: Option<FocusedBlock>,
1157 next_scroll_position: NextScrollCursorCenterTopBottom,
1158 addons: HashMap<TypeId, Box<dyn Addon>>,
1159 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1160 load_diff_task: Option<Shared<Task<()>>>,
1161 /// Whether we are temporarily displaying a diff other than git's
1162 temporary_diff_override: bool,
1163 selection_mark_mode: bool,
1164 toggle_fold_multiple_buffers: Task<()>,
1165 _scroll_cursor_center_top_bottom_task: Task<()>,
1166 serialize_selections: Task<()>,
1167 serialize_folds: Task<()>,
1168 mouse_cursor_hidden: bool,
1169 minimap: Option<Entity<Self>>,
1170 hide_mouse_mode: HideMouseMode,
1171 pub change_list: ChangeList,
1172 inline_value_cache: InlineValueCache,
1173 selection_drag_state: SelectionDragState,
1174 next_color_inlay_id: usize,
1175 colors: Option<LspColorData>,
1176 folding_newlines: Task<()>,
1177}
1178
1179#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1180enum NextScrollCursorCenterTopBottom {
1181 #[default]
1182 Center,
1183 Top,
1184 Bottom,
1185}
1186
1187impl NextScrollCursorCenterTopBottom {
1188 fn next(&self) -> Self {
1189 match self {
1190 Self::Center => Self::Top,
1191 Self::Top => Self::Bottom,
1192 Self::Bottom => Self::Center,
1193 }
1194 }
1195}
1196
1197#[derive(Clone)]
1198pub struct EditorSnapshot {
1199 pub mode: EditorMode,
1200 show_gutter: bool,
1201 show_line_numbers: Option<bool>,
1202 show_git_diff_gutter: Option<bool>,
1203 show_code_actions: Option<bool>,
1204 show_runnables: Option<bool>,
1205 show_breakpoints: Option<bool>,
1206 git_blame_gutter_max_author_length: Option<usize>,
1207 pub display_snapshot: DisplaySnapshot,
1208 pub placeholder_text: Option<Arc<str>>,
1209 is_focused: bool,
1210 scroll_anchor: ScrollAnchor,
1211 ongoing_scroll: OngoingScroll,
1212 current_line_highlight: CurrentLineHighlight,
1213 gutter_hovered: bool,
1214}
1215
1216#[derive(Default, Debug, Clone, Copy)]
1217pub struct GutterDimensions {
1218 pub left_padding: Pixels,
1219 pub right_padding: Pixels,
1220 pub width: Pixels,
1221 pub margin: Pixels,
1222 pub git_blame_entries_width: Option<Pixels>,
1223}
1224
1225impl GutterDimensions {
1226 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1227 Self {
1228 margin: Self::default_gutter_margin(font_id, font_size, cx),
1229 ..Default::default()
1230 }
1231 }
1232
1233 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1234 -cx.text_system().descent(font_id, font_size)
1235 }
1236 /// The full width of the space taken up by the gutter.
1237 pub fn full_width(&self) -> Pixels {
1238 self.margin + self.width
1239 }
1240
1241 /// The width of the space reserved for the fold indicators,
1242 /// use alongside 'justify_end' and `gutter_width` to
1243 /// right align content with the line numbers
1244 pub fn fold_area_width(&self) -> Pixels {
1245 self.margin + self.right_padding
1246 }
1247}
1248
1249struct CharacterDimensions {
1250 em_width: Pixels,
1251 em_advance: Pixels,
1252 line_height: Pixels,
1253}
1254
1255#[derive(Debug)]
1256pub struct RemoteSelection {
1257 pub replica_id: ReplicaId,
1258 pub selection: Selection<Anchor>,
1259 pub cursor_shape: CursorShape,
1260 pub collaborator_id: CollaboratorId,
1261 pub line_mode: bool,
1262 pub user_name: Option<SharedString>,
1263 pub color: PlayerColor,
1264}
1265
1266#[derive(Clone, Debug)]
1267struct SelectionHistoryEntry {
1268 selections: Arc<[Selection<Anchor>]>,
1269 select_next_state: Option<SelectNextState>,
1270 select_prev_state: Option<SelectNextState>,
1271 add_selections_state: Option<AddSelectionsState>,
1272}
1273
1274#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1275enum SelectionHistoryMode {
1276 Normal,
1277 Undoing,
1278 Redoing,
1279 Skipping,
1280}
1281
1282#[derive(Clone, PartialEq, Eq, Hash)]
1283struct HoveredCursor {
1284 replica_id: u16,
1285 selection_id: usize,
1286}
1287
1288impl Default for SelectionHistoryMode {
1289 fn default() -> Self {
1290 Self::Normal
1291 }
1292}
1293
1294#[derive(Debug)]
1295/// SelectionEffects controls the side-effects of updating the selection.
1296///
1297/// The default behaviour does "what you mostly want":
1298/// - it pushes to the nav history if the cursor moved by >10 lines
1299/// - it re-triggers completion requests
1300/// - it scrolls to fit
1301///
1302/// You might want to modify these behaviours. For example when doing a "jump"
1303/// like go to definition, we always want to add to nav history; but when scrolling
1304/// in vim mode we never do.
1305///
1306/// Similarly, you might want to disable scrolling if you don't want the viewport to
1307/// move.
1308#[derive(Clone)]
1309pub struct SelectionEffects {
1310 nav_history: Option<bool>,
1311 completions: bool,
1312 scroll: Option<Autoscroll>,
1313}
1314
1315impl Default for SelectionEffects {
1316 fn default() -> Self {
1317 Self {
1318 nav_history: None,
1319 completions: true,
1320 scroll: Some(Autoscroll::fit()),
1321 }
1322 }
1323}
1324impl SelectionEffects {
1325 pub fn scroll(scroll: Autoscroll) -> Self {
1326 Self {
1327 scroll: Some(scroll),
1328 ..Default::default()
1329 }
1330 }
1331
1332 pub fn no_scroll() -> Self {
1333 Self {
1334 scroll: None,
1335 ..Default::default()
1336 }
1337 }
1338
1339 pub fn completions(self, completions: bool) -> Self {
1340 Self {
1341 completions,
1342 ..self
1343 }
1344 }
1345
1346 pub fn nav_history(self, nav_history: bool) -> Self {
1347 Self {
1348 nav_history: Some(nav_history),
1349 ..self
1350 }
1351 }
1352}
1353
1354struct DeferredSelectionEffectsState {
1355 changed: bool,
1356 effects: SelectionEffects,
1357 old_cursor_position: Anchor,
1358 history_entry: SelectionHistoryEntry,
1359}
1360
1361#[derive(Default)]
1362struct SelectionHistory {
1363 #[allow(clippy::type_complexity)]
1364 selections_by_transaction:
1365 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1366 mode: SelectionHistoryMode,
1367 undo_stack: VecDeque<SelectionHistoryEntry>,
1368 redo_stack: VecDeque<SelectionHistoryEntry>,
1369}
1370
1371impl SelectionHistory {
1372 #[track_caller]
1373 fn insert_transaction(
1374 &mut self,
1375 transaction_id: TransactionId,
1376 selections: Arc<[Selection<Anchor>]>,
1377 ) {
1378 if selections.is_empty() {
1379 log::error!(
1380 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1381 std::panic::Location::caller()
1382 );
1383 return;
1384 }
1385 self.selections_by_transaction
1386 .insert(transaction_id, (selections, None));
1387 }
1388
1389 #[allow(clippy::type_complexity)]
1390 fn transaction(
1391 &self,
1392 transaction_id: TransactionId,
1393 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1394 self.selections_by_transaction.get(&transaction_id)
1395 }
1396
1397 #[allow(clippy::type_complexity)]
1398 fn transaction_mut(
1399 &mut self,
1400 transaction_id: TransactionId,
1401 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1402 self.selections_by_transaction.get_mut(&transaction_id)
1403 }
1404
1405 fn push(&mut self, entry: SelectionHistoryEntry) {
1406 if !entry.selections.is_empty() {
1407 match self.mode {
1408 SelectionHistoryMode::Normal => {
1409 self.push_undo(entry);
1410 self.redo_stack.clear();
1411 }
1412 SelectionHistoryMode::Undoing => self.push_redo(entry),
1413 SelectionHistoryMode::Redoing => self.push_undo(entry),
1414 SelectionHistoryMode::Skipping => {}
1415 }
1416 }
1417 }
1418
1419 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1420 if self
1421 .undo_stack
1422 .back()
1423 .map_or(true, |e| e.selections != entry.selections)
1424 {
1425 self.undo_stack.push_back(entry);
1426 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1427 self.undo_stack.pop_front();
1428 }
1429 }
1430 }
1431
1432 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1433 if self
1434 .redo_stack
1435 .back()
1436 .map_or(true, |e| e.selections != entry.selections)
1437 {
1438 self.redo_stack.push_back(entry);
1439 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1440 self.redo_stack.pop_front();
1441 }
1442 }
1443 }
1444}
1445
1446#[derive(Clone, Copy)]
1447pub struct RowHighlightOptions {
1448 pub autoscroll: bool,
1449 pub include_gutter: bool,
1450}
1451
1452impl Default for RowHighlightOptions {
1453 fn default() -> Self {
1454 Self {
1455 autoscroll: Default::default(),
1456 include_gutter: true,
1457 }
1458 }
1459}
1460
1461struct RowHighlight {
1462 index: usize,
1463 range: Range<Anchor>,
1464 color: Hsla,
1465 options: RowHighlightOptions,
1466 type_id: TypeId,
1467}
1468
1469#[derive(Clone, Debug)]
1470struct AddSelectionsState {
1471 groups: Vec<AddSelectionsGroup>,
1472}
1473
1474#[derive(Clone, Debug)]
1475struct AddSelectionsGroup {
1476 above: bool,
1477 stack: Vec<usize>,
1478}
1479
1480#[derive(Clone)]
1481struct SelectNextState {
1482 query: AhoCorasick,
1483 wordwise: bool,
1484 done: bool,
1485}
1486
1487impl std::fmt::Debug for SelectNextState {
1488 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1489 f.debug_struct(std::any::type_name::<Self>())
1490 .field("wordwise", &self.wordwise)
1491 .field("done", &self.done)
1492 .finish()
1493 }
1494}
1495
1496#[derive(Debug)]
1497struct AutocloseRegion {
1498 selection_id: usize,
1499 range: Range<Anchor>,
1500 pair: BracketPair,
1501}
1502
1503#[derive(Debug)]
1504struct SnippetState {
1505 ranges: Vec<Vec<Range<Anchor>>>,
1506 active_index: usize,
1507 choices: Vec<Option<Vec<String>>>,
1508}
1509
1510#[doc(hidden)]
1511pub struct RenameState {
1512 pub range: Range<Anchor>,
1513 pub old_name: Arc<str>,
1514 pub editor: Entity<Editor>,
1515 block_id: CustomBlockId,
1516}
1517
1518struct InvalidationStack<T>(Vec<T>);
1519
1520struct RegisteredInlineCompletionProvider {
1521 provider: Arc<dyn InlineCompletionProviderHandle>,
1522 _subscription: Subscription,
1523}
1524
1525#[derive(Debug, PartialEq, Eq)]
1526pub struct ActiveDiagnosticGroup {
1527 pub active_range: Range<Anchor>,
1528 pub active_message: String,
1529 pub group_id: usize,
1530 pub blocks: HashSet<CustomBlockId>,
1531}
1532
1533#[derive(Debug, PartialEq, Eq)]
1534
1535pub(crate) enum ActiveDiagnostic {
1536 None,
1537 All,
1538 Group(ActiveDiagnosticGroup),
1539}
1540
1541#[derive(Serialize, Deserialize, Clone, Debug)]
1542pub struct ClipboardSelection {
1543 /// The number of bytes in this selection.
1544 pub len: usize,
1545 /// Whether this was a full-line selection.
1546 pub is_entire_line: bool,
1547 /// The indentation of the first line when this content was originally copied.
1548 pub first_line_indent: u32,
1549}
1550
1551// selections, scroll behavior, was newest selection reversed
1552type SelectSyntaxNodeHistoryState = (
1553 Box<[Selection<usize>]>,
1554 SelectSyntaxNodeScrollBehavior,
1555 bool,
1556);
1557
1558#[derive(Default)]
1559struct SelectSyntaxNodeHistory {
1560 stack: Vec<SelectSyntaxNodeHistoryState>,
1561 // disable temporarily to allow changing selections without losing the stack
1562 pub disable_clearing: bool,
1563}
1564
1565impl SelectSyntaxNodeHistory {
1566 pub fn try_clear(&mut self) {
1567 if !self.disable_clearing {
1568 self.stack.clear();
1569 }
1570 }
1571
1572 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1573 self.stack.push(selection);
1574 }
1575
1576 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1577 self.stack.pop()
1578 }
1579}
1580
1581enum SelectSyntaxNodeScrollBehavior {
1582 CursorTop,
1583 FitSelection,
1584 CursorBottom,
1585}
1586
1587#[derive(Debug)]
1588pub(crate) struct NavigationData {
1589 cursor_anchor: Anchor,
1590 cursor_position: Point,
1591 scroll_anchor: ScrollAnchor,
1592 scroll_top_row: u32,
1593}
1594
1595#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1596pub enum GotoDefinitionKind {
1597 Symbol,
1598 Declaration,
1599 Type,
1600 Implementation,
1601}
1602
1603#[derive(Debug, Clone)]
1604enum InlayHintRefreshReason {
1605 ModifiersChanged(bool),
1606 Toggle(bool),
1607 SettingsChange(InlayHintSettings),
1608 NewLinesShown,
1609 BufferEdited(HashSet<Arc<Language>>),
1610 RefreshRequested,
1611 ExcerptsRemoved(Vec<ExcerptId>),
1612}
1613
1614impl InlayHintRefreshReason {
1615 fn description(&self) -> &'static str {
1616 match self {
1617 Self::ModifiersChanged(_) => "modifiers changed",
1618 Self::Toggle(_) => "toggle",
1619 Self::SettingsChange(_) => "settings change",
1620 Self::NewLinesShown => "new lines shown",
1621 Self::BufferEdited(_) => "buffer edited",
1622 Self::RefreshRequested => "refresh requested",
1623 Self::ExcerptsRemoved(_) => "excerpts removed",
1624 }
1625 }
1626}
1627
1628pub enum FormatTarget {
1629 Buffers(HashSet<Entity<Buffer>>),
1630 Ranges(Vec<Range<MultiBufferPoint>>),
1631}
1632
1633pub(crate) struct FocusedBlock {
1634 id: BlockId,
1635 focus_handle: WeakFocusHandle,
1636}
1637
1638#[derive(Clone)]
1639enum JumpData {
1640 MultiBufferRow {
1641 row: MultiBufferRow,
1642 line_offset_from_top: u32,
1643 },
1644 MultiBufferPoint {
1645 excerpt_id: ExcerptId,
1646 position: Point,
1647 anchor: text::Anchor,
1648 line_offset_from_top: u32,
1649 },
1650}
1651
1652pub enum MultibufferSelectionMode {
1653 First,
1654 All,
1655}
1656
1657#[derive(Clone, Copy, Debug, Default)]
1658pub struct RewrapOptions {
1659 pub override_language_settings: bool,
1660 pub preserve_existing_whitespace: bool,
1661}
1662
1663impl Editor {
1664 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1665 let buffer = cx.new(|cx| Buffer::local("", cx));
1666 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1667 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1668 }
1669
1670 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1671 let buffer = cx.new(|cx| Buffer::local("", cx));
1672 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1673 Self::new(EditorMode::full(), buffer, None, window, cx)
1674 }
1675
1676 pub fn auto_height(
1677 min_lines: usize,
1678 max_lines: usize,
1679 window: &mut Window,
1680 cx: &mut Context<Self>,
1681 ) -> Self {
1682 let buffer = cx.new(|cx| Buffer::local("", cx));
1683 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1684 Self::new(
1685 EditorMode::AutoHeight {
1686 min_lines,
1687 max_lines: Some(max_lines),
1688 },
1689 buffer,
1690 None,
1691 window,
1692 cx,
1693 )
1694 }
1695
1696 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1697 /// The editor grows as tall as needed to fit its content.
1698 pub fn auto_height_unbounded(
1699 min_lines: usize,
1700 window: &mut Window,
1701 cx: &mut Context<Self>,
1702 ) -> Self {
1703 let buffer = cx.new(|cx| Buffer::local("", cx));
1704 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1705 Self::new(
1706 EditorMode::AutoHeight {
1707 min_lines,
1708 max_lines: None,
1709 },
1710 buffer,
1711 None,
1712 window,
1713 cx,
1714 )
1715 }
1716
1717 pub fn for_buffer(
1718 buffer: Entity<Buffer>,
1719 project: Option<Entity<Project>>,
1720 window: &mut Window,
1721 cx: &mut Context<Self>,
1722 ) -> Self {
1723 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1724 Self::new(EditorMode::full(), buffer, project, window, cx)
1725 }
1726
1727 pub fn for_multibuffer(
1728 buffer: Entity<MultiBuffer>,
1729 project: Option<Entity<Project>>,
1730 window: &mut Window,
1731 cx: &mut Context<Self>,
1732 ) -> Self {
1733 Self::new(EditorMode::full(), buffer, project, window, cx)
1734 }
1735
1736 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1737 let mut clone = Self::new(
1738 self.mode.clone(),
1739 self.buffer.clone(),
1740 self.project.clone(),
1741 window,
1742 cx,
1743 );
1744 self.display_map.update(cx, |display_map, cx| {
1745 let snapshot = display_map.snapshot(cx);
1746 clone.display_map.update(cx, |display_map, cx| {
1747 display_map.set_state(&snapshot, cx);
1748 });
1749 });
1750 clone.folds_did_change(cx);
1751 clone.selections.clone_state(&self.selections);
1752 clone.scroll_manager.clone_state(&self.scroll_manager);
1753 clone.searchable = self.searchable;
1754 clone.read_only = self.read_only;
1755 clone
1756 }
1757
1758 pub fn new(
1759 mode: EditorMode,
1760 buffer: Entity<MultiBuffer>,
1761 project: Option<Entity<Project>>,
1762 window: &mut Window,
1763 cx: &mut Context<Self>,
1764 ) -> Self {
1765 Editor::new_internal(mode, buffer, project, None, window, cx)
1766 }
1767
1768 fn new_internal(
1769 mode: EditorMode,
1770 buffer: Entity<MultiBuffer>,
1771 project: Option<Entity<Project>>,
1772 display_map: Option<Entity<DisplayMap>>,
1773 window: &mut Window,
1774 cx: &mut Context<Self>,
1775 ) -> Self {
1776 debug_assert!(
1777 display_map.is_none() || mode.is_minimap(),
1778 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1779 );
1780
1781 let full_mode = mode.is_full();
1782 let is_minimap = mode.is_minimap();
1783 let diagnostics_max_severity = if full_mode {
1784 EditorSettings::get_global(cx)
1785 .diagnostics_max_severity
1786 .unwrap_or(DiagnosticSeverity::Hint)
1787 } else {
1788 DiagnosticSeverity::Off
1789 };
1790 let style = window.text_style();
1791 let font_size = style.font_size.to_pixels(window.rem_size());
1792 let editor = cx.entity().downgrade();
1793 let fold_placeholder = FoldPlaceholder {
1794 constrain_width: true,
1795 render: Arc::new(move |fold_id, fold_range, cx| {
1796 let editor = editor.clone();
1797 div()
1798 .id(fold_id)
1799 .bg(cx.theme().colors().ghost_element_background)
1800 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1801 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1802 .rounded_xs()
1803 .size_full()
1804 .cursor_pointer()
1805 .child("⋯")
1806 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1807 .on_click(move |_, _window, cx| {
1808 editor
1809 .update(cx, |editor, cx| {
1810 editor.unfold_ranges(
1811 &[fold_range.start..fold_range.end],
1812 true,
1813 false,
1814 cx,
1815 );
1816 cx.stop_propagation();
1817 })
1818 .ok();
1819 })
1820 .into_any()
1821 }),
1822 merge_adjacent: true,
1823 ..FoldPlaceholder::default()
1824 };
1825 let display_map = display_map.unwrap_or_else(|| {
1826 cx.new(|cx| {
1827 DisplayMap::new(
1828 buffer.clone(),
1829 style.font(),
1830 font_size,
1831 None,
1832 FILE_HEADER_HEIGHT,
1833 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1834 fold_placeholder,
1835 diagnostics_max_severity,
1836 cx,
1837 )
1838 })
1839 });
1840
1841 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1842
1843 let blink_manager = cx.new(|cx| {
1844 let mut blink_manager = BlinkManager::new(CURSOR_BLINK_INTERVAL, cx);
1845 if is_minimap {
1846 blink_manager.disable(cx);
1847 }
1848 blink_manager
1849 });
1850
1851 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1852 .then(|| language_settings::SoftWrap::None);
1853
1854 let mut project_subscriptions = Vec::new();
1855 if full_mode {
1856 if let Some(project) = project.as_ref() {
1857 project_subscriptions.push(cx.subscribe_in(
1858 project,
1859 window,
1860 |editor, _, event, window, cx| match event {
1861 project::Event::RefreshCodeLens => {
1862 // we always query lens with actions, without storing them, always refreshing them
1863 }
1864 project::Event::RefreshInlayHints => {
1865 editor
1866 .refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1867 }
1868 project::Event::LanguageServerAdded(..)
1869 | project::Event::LanguageServerRemoved(..) => {
1870 if editor.tasks_update_task.is_none() {
1871 editor.tasks_update_task =
1872 Some(editor.refresh_runnables(window, cx));
1873 }
1874 editor.update_lsp_data(true, None, window, cx);
1875 }
1876 project::Event::SnippetEdit(id, snippet_edits) => {
1877 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1878 let focus_handle = editor.focus_handle(cx);
1879 if focus_handle.is_focused(window) {
1880 let snapshot = buffer.read(cx).snapshot();
1881 for (range, snippet) in snippet_edits {
1882 let editor_range =
1883 language::range_from_lsp(*range).to_offset(&snapshot);
1884 editor
1885 .insert_snippet(
1886 &[editor_range],
1887 snippet.clone(),
1888 window,
1889 cx,
1890 )
1891 .ok();
1892 }
1893 }
1894 }
1895 }
1896 _ => {}
1897 },
1898 ));
1899 if let Some(task_inventory) = project
1900 .read(cx)
1901 .task_store()
1902 .read(cx)
1903 .task_inventory()
1904 .cloned()
1905 {
1906 project_subscriptions.push(cx.observe_in(
1907 &task_inventory,
1908 window,
1909 |editor, _, window, cx| {
1910 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1911 },
1912 ));
1913 };
1914
1915 project_subscriptions.push(cx.subscribe_in(
1916 &project.read(cx).breakpoint_store(),
1917 window,
1918 |editor, _, event, window, cx| match event {
1919 BreakpointStoreEvent::ClearDebugLines => {
1920 editor.clear_row_highlights::<ActiveDebugLine>();
1921 editor.refresh_inline_values(cx);
1922 }
1923 BreakpointStoreEvent::SetDebugLine => {
1924 if editor.go_to_active_debug_line(window, cx) {
1925 cx.stop_propagation();
1926 }
1927
1928 editor.refresh_inline_values(cx);
1929 }
1930 _ => {}
1931 },
1932 ));
1933 let git_store = project.read(cx).git_store().clone();
1934 let project = project.clone();
1935 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
1936 match event {
1937 GitStoreEvent::RepositoryUpdated(
1938 _,
1939 RepositoryEvent::Updated {
1940 new_instance: true, ..
1941 },
1942 _,
1943 ) => {
1944 this.load_diff_task = Some(
1945 update_uncommitted_diff_for_buffer(
1946 cx.entity(),
1947 &project,
1948 this.buffer.read(cx).all_buffers(),
1949 this.buffer.clone(),
1950 cx,
1951 )
1952 .shared(),
1953 );
1954 }
1955 _ => {}
1956 }
1957 }));
1958 }
1959 }
1960
1961 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1962
1963 let inlay_hint_settings =
1964 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1965 let focus_handle = cx.focus_handle();
1966 if !is_minimap {
1967 cx.on_focus(&focus_handle, window, Self::handle_focus)
1968 .detach();
1969 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1970 .detach();
1971 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1972 .detach();
1973 cx.on_blur(&focus_handle, window, Self::handle_blur)
1974 .detach();
1975 cx.observe_pending_input(window, Self::observe_pending_input)
1976 .detach();
1977 }
1978
1979 let show_indent_guides = if matches!(
1980 mode,
1981 EditorMode::SingleLine { .. } | EditorMode::Minimap { .. }
1982 ) {
1983 Some(false)
1984 } else {
1985 None
1986 };
1987
1988 let breakpoint_store = match (&mode, project.as_ref()) {
1989 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
1990 _ => None,
1991 };
1992
1993 let mut code_action_providers = Vec::new();
1994 let mut load_uncommitted_diff = None;
1995 if let Some(project) = project.clone() {
1996 load_uncommitted_diff = Some(
1997 update_uncommitted_diff_for_buffer(
1998 cx.entity(),
1999 &project,
2000 buffer.read(cx).all_buffers(),
2001 buffer.clone(),
2002 cx,
2003 )
2004 .shared(),
2005 );
2006 code_action_providers.push(Rc::new(project) as Rc<_>);
2007 }
2008
2009 let mut editor = Self {
2010 focus_handle,
2011 show_cursor_when_unfocused: false,
2012 last_focused_descendant: None,
2013 buffer: buffer.clone(),
2014 display_map: display_map.clone(),
2015 selections,
2016 scroll_manager: ScrollManager::new(cx),
2017 columnar_selection_state: None,
2018 add_selections_state: None,
2019 select_next_state: None,
2020 select_prev_state: None,
2021 selection_history: SelectionHistory::default(),
2022 defer_selection_effects: false,
2023 deferred_selection_effects_state: None,
2024 autoclose_regions: Vec::new(),
2025 snippet_stack: InvalidationStack::default(),
2026 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2027 ime_transaction: None,
2028 active_diagnostics: ActiveDiagnostic::None,
2029 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2030 inline_diagnostics_update: Task::ready(()),
2031 inline_diagnostics: Vec::new(),
2032 soft_wrap_mode_override,
2033 diagnostics_max_severity,
2034 hard_wrap: None,
2035 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2036 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2037 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2038 project,
2039 blink_manager: blink_manager.clone(),
2040 show_local_selections: true,
2041 show_scrollbars: ScrollbarAxes {
2042 horizontal: full_mode,
2043 vertical: full_mode,
2044 },
2045 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2046 offset_content: !matches!(mode, EditorMode::SingleLine { .. }),
2047 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2048 show_gutter: full_mode,
2049 show_line_numbers: (!full_mode).then_some(false),
2050 use_relative_line_numbers: None,
2051 disable_expand_excerpt_buttons: !full_mode,
2052 show_git_diff_gutter: None,
2053 show_code_actions: None,
2054 show_runnables: None,
2055 show_breakpoints: None,
2056 show_wrap_guides: None,
2057 show_indent_guides,
2058 placeholder_text: None,
2059 highlight_order: 0,
2060 highlighted_rows: HashMap::default(),
2061 background_highlights: TreeMap::default(),
2062 gutter_highlights: TreeMap::default(),
2063 scrollbar_marker_state: ScrollbarMarkerState::default(),
2064 active_indent_guides_state: ActiveIndentGuidesState::default(),
2065 nav_history: None,
2066 context_menu: RefCell::new(None),
2067 context_menu_options: None,
2068 mouse_context_menu: None,
2069 completion_tasks: Vec::new(),
2070 inline_blame_popover: None,
2071 inline_blame_popover_show_task: None,
2072 signature_help_state: SignatureHelpState::default(),
2073 auto_signature_help: None,
2074 find_all_references_task_sources: Vec::new(),
2075 next_completion_id: 0,
2076 next_inlay_id: 0,
2077 code_action_providers,
2078 available_code_actions: None,
2079 code_actions_task: None,
2080 quick_selection_highlight_task: None,
2081 debounced_selection_highlight_task: None,
2082 document_highlights_task: None,
2083 linked_editing_range_task: None,
2084 pending_rename: None,
2085 searchable: !is_minimap,
2086 cursor_shape: EditorSettings::get_global(cx)
2087 .cursor_shape
2088 .unwrap_or_default(),
2089 current_line_highlight: None,
2090 autoindent_mode: Some(AutoindentMode::EachLine),
2091 collapse_matches: false,
2092 workspace: None,
2093 input_enabled: !is_minimap,
2094 use_modal_editing: full_mode,
2095 read_only: is_minimap,
2096 use_autoclose: true,
2097 use_auto_surround: true,
2098 auto_replace_emoji_shortcode: false,
2099 jsx_tag_auto_close_enabled_in_any_buffer: false,
2100 leader_id: None,
2101 remote_id: None,
2102 hover_state: HoverState::default(),
2103 pending_mouse_down: None,
2104 hovered_link_state: None,
2105 edit_prediction_provider: None,
2106 active_inline_completion: None,
2107 stale_inline_completion_in_menu: None,
2108 edit_prediction_preview: EditPredictionPreview::Inactive {
2109 released_too_fast: false,
2110 },
2111 inline_diagnostics_enabled: full_mode,
2112 diagnostics_enabled: full_mode,
2113 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2114 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
2115 gutter_hovered: false,
2116 pixel_position_of_newest_cursor: None,
2117 last_bounds: None,
2118 last_position_map: None,
2119 expect_bounds_change: None,
2120 gutter_dimensions: GutterDimensions::default(),
2121 style: None,
2122 show_cursor_names: false,
2123 hovered_cursors: HashMap::default(),
2124 next_editor_action_id: EditorActionId::default(),
2125 editor_actions: Rc::default(),
2126 inline_completions_hidden_for_vim_mode: false,
2127 show_inline_completions_override: None,
2128 menu_inline_completions_policy: MenuInlineCompletionsPolicy::ByProvider,
2129 edit_prediction_settings: EditPredictionSettings::Disabled,
2130 edit_prediction_indent_conflict: false,
2131 edit_prediction_requires_modifier_in_indent_conflict: true,
2132 custom_context_menu: None,
2133 show_git_blame_gutter: false,
2134 show_git_blame_inline: false,
2135 show_selection_menu: None,
2136 show_git_blame_inline_delay_task: None,
2137 git_blame_inline_enabled: full_mode
2138 && ProjectSettings::get_global(cx).git.inline_blame_enabled(),
2139 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2140 serialize_dirty_buffers: !is_minimap
2141 && ProjectSettings::get_global(cx)
2142 .session
2143 .restore_unsaved_buffers,
2144 blame: None,
2145 blame_subscription: None,
2146 tasks: BTreeMap::default(),
2147
2148 breakpoint_store,
2149 gutter_breakpoint_indicator: (None, None),
2150 hovered_diff_hunk_row: None,
2151 _subscriptions: (!is_minimap)
2152 .then(|| {
2153 vec![
2154 cx.observe(&buffer, Self::on_buffer_changed),
2155 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
2156 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2157 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2158 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2159 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2160 cx.observe_window_activation(window, |editor, window, cx| {
2161 let active = window.is_window_active();
2162 editor.blink_manager.update(cx, |blink_manager, cx| {
2163 if active {
2164 blink_manager.enable(cx);
2165 } else {
2166 blink_manager.disable(cx);
2167 }
2168 });
2169 if active {
2170 editor.show_mouse_cursor(cx);
2171 }
2172 }),
2173 ]
2174 })
2175 .unwrap_or_default(),
2176 tasks_update_task: None,
2177 pull_diagnostics_task: Task::ready(()),
2178 colors: None,
2179 next_color_inlay_id: 0,
2180 linked_edit_ranges: Default::default(),
2181 in_project_search: false,
2182 previous_search_ranges: None,
2183 breadcrumb_header: None,
2184 focused_block: None,
2185 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2186 addons: HashMap::default(),
2187 registered_buffers: HashMap::default(),
2188 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2189 selection_mark_mode: false,
2190 toggle_fold_multiple_buffers: Task::ready(()),
2191 serialize_selections: Task::ready(()),
2192 serialize_folds: Task::ready(()),
2193 text_style_refinement: None,
2194 load_diff_task: load_uncommitted_diff,
2195 temporary_diff_override: false,
2196 mouse_cursor_hidden: false,
2197 minimap: None,
2198 hide_mouse_mode: EditorSettings::get_global(cx)
2199 .hide_mouse
2200 .unwrap_or_default(),
2201 change_list: ChangeList::new(),
2202 mode,
2203 selection_drag_state: SelectionDragState::None,
2204 folding_newlines: Task::ready(()),
2205 };
2206
2207 if is_minimap {
2208 return editor;
2209 }
2210
2211 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2212 editor
2213 ._subscriptions
2214 .push(cx.observe(breakpoints, |_, _, cx| {
2215 cx.notify();
2216 }));
2217 }
2218 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2219 editor._subscriptions.extend(project_subscriptions);
2220
2221 editor._subscriptions.push(cx.subscribe_in(
2222 &cx.entity(),
2223 window,
2224 |editor, _, e: &EditorEvent, window, cx| match e {
2225 EditorEvent::ScrollPositionChanged { local, .. } => {
2226 if *local {
2227 let new_anchor = editor.scroll_manager.anchor();
2228 let snapshot = editor.snapshot(window, cx);
2229 editor.update_restoration_data(cx, move |data| {
2230 data.scroll_position = (
2231 new_anchor.top_row(&snapshot.buffer_snapshot),
2232 new_anchor.offset,
2233 );
2234 });
2235 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2236 editor.inline_blame_popover.take();
2237 }
2238 }
2239 EditorEvent::Edited { .. } => {
2240 if !vim_enabled(cx) {
2241 let (map, selections) = editor.selections.all_adjusted_display(cx);
2242 let pop_state = editor
2243 .change_list
2244 .last()
2245 .map(|previous| {
2246 previous.len() == selections.len()
2247 && previous.iter().enumerate().all(|(ix, p)| {
2248 p.to_display_point(&map).row()
2249 == selections[ix].head().row()
2250 })
2251 })
2252 .unwrap_or(false);
2253 let new_positions = selections
2254 .into_iter()
2255 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
2256 .collect();
2257 editor
2258 .change_list
2259 .push_to_change_list(pop_state, new_positions);
2260 }
2261 }
2262 _ => (),
2263 },
2264 ));
2265
2266 if let Some(dap_store) = editor
2267 .project
2268 .as_ref()
2269 .map(|project| project.read(cx).dap_store())
2270 {
2271 let weak_editor = cx.weak_entity();
2272
2273 editor
2274 ._subscriptions
2275 .push(
2276 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2277 let session_entity = cx.entity();
2278 weak_editor
2279 .update(cx, |editor, cx| {
2280 editor._subscriptions.push(
2281 cx.subscribe(&session_entity, Self::on_debug_session_event),
2282 );
2283 })
2284 .ok();
2285 }),
2286 );
2287
2288 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2289 editor
2290 ._subscriptions
2291 .push(cx.subscribe(&session, Self::on_debug_session_event));
2292 }
2293 }
2294
2295 // skip adding the initial selection to selection history
2296 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2297 editor.end_selection(window, cx);
2298 editor.selection_history.mode = SelectionHistoryMode::Normal;
2299
2300 editor.scroll_manager.show_scrollbars(window, cx);
2301 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &buffer, cx);
2302
2303 if full_mode {
2304 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2305 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2306
2307 if editor.git_blame_inline_enabled {
2308 editor.start_git_blame_inline(false, window, cx);
2309 }
2310
2311 editor.go_to_active_debug_line(window, cx);
2312
2313 if let Some(buffer) = buffer.read(cx).as_singleton() {
2314 if let Some(project) = editor.project.as_ref() {
2315 let handle = project.update(cx, |project, cx| {
2316 project.register_buffer_with_language_servers(&buffer, cx)
2317 });
2318 editor
2319 .registered_buffers
2320 .insert(buffer.read(cx).remote_id(), handle);
2321 }
2322 }
2323
2324 editor.minimap =
2325 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2326 editor.colors = Some(LspColorData::new(cx));
2327 editor.update_lsp_data(false, None, window, cx);
2328 }
2329
2330 if editor.mode.is_full() {
2331 editor.report_editor_event("Editor Opened", None, cx);
2332 }
2333
2334 editor
2335 }
2336
2337 pub fn deploy_mouse_context_menu(
2338 &mut self,
2339 position: gpui::Point<Pixels>,
2340 context_menu: Entity<ContextMenu>,
2341 window: &mut Window,
2342 cx: &mut Context<Self>,
2343 ) {
2344 self.mouse_context_menu = Some(MouseContextMenu::new(
2345 self,
2346 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2347 context_menu,
2348 window,
2349 cx,
2350 ));
2351 }
2352
2353 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2354 self.mouse_context_menu
2355 .as_ref()
2356 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2357 }
2358
2359 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2360 self.key_context_internal(self.has_active_inline_completion(), window, cx)
2361 }
2362
2363 fn key_context_internal(
2364 &self,
2365 has_active_edit_prediction: bool,
2366 window: &Window,
2367 cx: &App,
2368 ) -> KeyContext {
2369 let mut key_context = KeyContext::new_with_defaults();
2370 key_context.add("Editor");
2371 let mode = match self.mode {
2372 EditorMode::SingleLine { .. } => "single_line",
2373 EditorMode::AutoHeight { .. } => "auto_height",
2374 EditorMode::Minimap { .. } => "minimap",
2375 EditorMode::Full { .. } => "full",
2376 };
2377
2378 if EditorSettings::jupyter_enabled(cx) {
2379 key_context.add("jupyter");
2380 }
2381
2382 key_context.set("mode", mode);
2383 if self.pending_rename.is_some() {
2384 key_context.add("renaming");
2385 }
2386
2387 match self.context_menu.borrow().as_ref() {
2388 Some(CodeContextMenu::Completions(menu)) => {
2389 if menu.visible() {
2390 key_context.add("menu");
2391 key_context.add("showing_completions");
2392 }
2393 }
2394 Some(CodeContextMenu::CodeActions(menu)) => {
2395 if menu.visible() {
2396 key_context.add("menu");
2397 key_context.add("showing_code_actions")
2398 }
2399 }
2400 None => {}
2401 }
2402
2403 if self.signature_help_state.has_multiple_signatures() {
2404 key_context.add("showing_signature_help");
2405 }
2406
2407 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2408 if !self.focus_handle(cx).contains_focused(window, cx)
2409 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2410 {
2411 for addon in self.addons.values() {
2412 addon.extend_key_context(&mut key_context, cx)
2413 }
2414 }
2415
2416 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2417 if let Some(extension) = singleton_buffer
2418 .read(cx)
2419 .file()
2420 .and_then(|file| file.path().extension()?.to_str())
2421 {
2422 key_context.set("extension", extension.to_string());
2423 }
2424 } else {
2425 key_context.add("multibuffer");
2426 }
2427
2428 if has_active_edit_prediction {
2429 if self.edit_prediction_in_conflict() {
2430 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2431 } else {
2432 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2433 key_context.add("copilot_suggestion");
2434 }
2435 }
2436
2437 if self.selection_mark_mode {
2438 key_context.add("selection_mode");
2439 }
2440
2441 key_context
2442 }
2443
2444 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2445 if self.mouse_cursor_hidden {
2446 self.mouse_cursor_hidden = false;
2447 cx.notify();
2448 }
2449 }
2450
2451 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2452 let hide_mouse_cursor = match origin {
2453 HideMouseCursorOrigin::TypingAction => {
2454 matches!(
2455 self.hide_mouse_mode,
2456 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2457 )
2458 }
2459 HideMouseCursorOrigin::MovementAction => {
2460 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2461 }
2462 };
2463 if self.mouse_cursor_hidden != hide_mouse_cursor {
2464 self.mouse_cursor_hidden = hide_mouse_cursor;
2465 cx.notify();
2466 }
2467 }
2468
2469 pub fn edit_prediction_in_conflict(&self) -> bool {
2470 if !self.show_edit_predictions_in_menu() {
2471 return false;
2472 }
2473
2474 let showing_completions = self
2475 .context_menu
2476 .borrow()
2477 .as_ref()
2478 .map_or(false, |context| {
2479 matches!(context, CodeContextMenu::Completions(_))
2480 });
2481
2482 showing_completions
2483 || self.edit_prediction_requires_modifier()
2484 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2485 // bindings to insert tab characters.
2486 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2487 }
2488
2489 pub fn accept_edit_prediction_keybind(
2490 &self,
2491 accept_partial: bool,
2492 window: &Window,
2493 cx: &App,
2494 ) -> AcceptEditPredictionBinding {
2495 let key_context = self.key_context_internal(true, window, cx);
2496 let in_conflict = self.edit_prediction_in_conflict();
2497
2498 let bindings = if accept_partial {
2499 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2500 } else {
2501 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2502 };
2503
2504 // TODO: if the binding contains multiple keystrokes, display all of them, not
2505 // just the first one.
2506 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2507 !in_conflict
2508 || binding
2509 .keystrokes()
2510 .first()
2511 .map_or(false, |keystroke| keystroke.modifiers.modified())
2512 }))
2513 }
2514
2515 pub fn new_file(
2516 workspace: &mut Workspace,
2517 _: &workspace::NewFile,
2518 window: &mut Window,
2519 cx: &mut Context<Workspace>,
2520 ) {
2521 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2522 "Failed to create buffer",
2523 window,
2524 cx,
2525 |e, _, _| match e.error_code() {
2526 ErrorCode::RemoteUpgradeRequired => Some(format!(
2527 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2528 e.error_tag("required").unwrap_or("the latest version")
2529 )),
2530 _ => None,
2531 },
2532 );
2533 }
2534
2535 pub fn new_in_workspace(
2536 workspace: &mut Workspace,
2537 window: &mut Window,
2538 cx: &mut Context<Workspace>,
2539 ) -> Task<Result<Entity<Editor>>> {
2540 let project = workspace.project().clone();
2541 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2542
2543 cx.spawn_in(window, async move |workspace, cx| {
2544 let buffer = create.await?;
2545 workspace.update_in(cx, |workspace, window, cx| {
2546 let editor =
2547 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2548 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2549 editor
2550 })
2551 })
2552 }
2553
2554 fn new_file_vertical(
2555 workspace: &mut Workspace,
2556 _: &workspace::NewFileSplitVertical,
2557 window: &mut Window,
2558 cx: &mut Context<Workspace>,
2559 ) {
2560 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2561 }
2562
2563 fn new_file_horizontal(
2564 workspace: &mut Workspace,
2565 _: &workspace::NewFileSplitHorizontal,
2566 window: &mut Window,
2567 cx: &mut Context<Workspace>,
2568 ) {
2569 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2570 }
2571
2572 fn new_file_in_direction(
2573 workspace: &mut Workspace,
2574 direction: SplitDirection,
2575 window: &mut Window,
2576 cx: &mut Context<Workspace>,
2577 ) {
2578 let project = workspace.project().clone();
2579 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2580
2581 cx.spawn_in(window, async move |workspace, cx| {
2582 let buffer = create.await?;
2583 workspace.update_in(cx, move |workspace, window, cx| {
2584 workspace.split_item(
2585 direction,
2586 Box::new(
2587 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2588 ),
2589 window,
2590 cx,
2591 )
2592 })?;
2593 anyhow::Ok(())
2594 })
2595 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2596 match e.error_code() {
2597 ErrorCode::RemoteUpgradeRequired => Some(format!(
2598 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2599 e.error_tag("required").unwrap_or("the latest version")
2600 )),
2601 _ => None,
2602 }
2603 });
2604 }
2605
2606 pub fn leader_id(&self) -> Option<CollaboratorId> {
2607 self.leader_id
2608 }
2609
2610 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2611 &self.buffer
2612 }
2613
2614 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2615 self.workspace.as_ref()?.0.upgrade()
2616 }
2617
2618 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2619 self.buffer().read(cx).title(cx)
2620 }
2621
2622 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2623 let git_blame_gutter_max_author_length = self
2624 .render_git_blame_gutter(cx)
2625 .then(|| {
2626 if let Some(blame) = self.blame.as_ref() {
2627 let max_author_length =
2628 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2629 Some(max_author_length)
2630 } else {
2631 None
2632 }
2633 })
2634 .flatten();
2635
2636 EditorSnapshot {
2637 mode: self.mode.clone(),
2638 show_gutter: self.show_gutter,
2639 show_line_numbers: self.show_line_numbers,
2640 show_git_diff_gutter: self.show_git_diff_gutter,
2641 show_code_actions: self.show_code_actions,
2642 show_runnables: self.show_runnables,
2643 show_breakpoints: self.show_breakpoints,
2644 git_blame_gutter_max_author_length,
2645 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2646 scroll_anchor: self.scroll_manager.anchor(),
2647 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2648 placeholder_text: self.placeholder_text.clone(),
2649 is_focused: self.focus_handle.is_focused(window),
2650 current_line_highlight: self
2651 .current_line_highlight
2652 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2653 gutter_hovered: self.gutter_hovered,
2654 }
2655 }
2656
2657 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2658 self.buffer.read(cx).language_at(point, cx)
2659 }
2660
2661 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2662 self.buffer.read(cx).read(cx).file_at(point).cloned()
2663 }
2664
2665 pub fn active_excerpt(
2666 &self,
2667 cx: &App,
2668 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2669 self.buffer
2670 .read(cx)
2671 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2672 }
2673
2674 pub fn mode(&self) -> &EditorMode {
2675 &self.mode
2676 }
2677
2678 pub fn set_mode(&mut self, mode: EditorMode) {
2679 self.mode = mode;
2680 }
2681
2682 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2683 self.collaboration_hub.as_deref()
2684 }
2685
2686 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2687 self.collaboration_hub = Some(hub);
2688 }
2689
2690 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2691 self.in_project_search = in_project_search;
2692 }
2693
2694 pub fn set_custom_context_menu(
2695 &mut self,
2696 f: impl 'static
2697 + Fn(
2698 &mut Self,
2699 DisplayPoint,
2700 &mut Window,
2701 &mut Context<Self>,
2702 ) -> Option<Entity<ui::ContextMenu>>,
2703 ) {
2704 self.custom_context_menu = Some(Box::new(f))
2705 }
2706
2707 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2708 self.completion_provider = provider;
2709 }
2710
2711 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2712 self.semantics_provider.clone()
2713 }
2714
2715 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2716 self.semantics_provider = provider;
2717 }
2718
2719 pub fn set_edit_prediction_provider<T>(
2720 &mut self,
2721 provider: Option<Entity<T>>,
2722 window: &mut Window,
2723 cx: &mut Context<Self>,
2724 ) where
2725 T: EditPredictionProvider,
2726 {
2727 self.edit_prediction_provider =
2728 provider.map(|provider| RegisteredInlineCompletionProvider {
2729 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2730 if this.focus_handle.is_focused(window) {
2731 this.update_visible_inline_completion(window, cx);
2732 }
2733 }),
2734 provider: Arc::new(provider),
2735 });
2736 self.update_edit_prediction_settings(cx);
2737 self.refresh_inline_completion(false, false, window, cx);
2738 }
2739
2740 pub fn placeholder_text(&self) -> Option<&str> {
2741 self.placeholder_text.as_deref()
2742 }
2743
2744 pub fn set_placeholder_text(
2745 &mut self,
2746 placeholder_text: impl Into<Arc<str>>,
2747 cx: &mut Context<Self>,
2748 ) {
2749 let placeholder_text = Some(placeholder_text.into());
2750 if self.placeholder_text != placeholder_text {
2751 self.placeholder_text = placeholder_text;
2752 cx.notify();
2753 }
2754 }
2755
2756 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2757 self.cursor_shape = cursor_shape;
2758
2759 // Disrupt blink for immediate user feedback that the cursor shape has changed
2760 self.blink_manager.update(cx, BlinkManager::show_cursor);
2761
2762 cx.notify();
2763 }
2764
2765 pub fn set_current_line_highlight(
2766 &mut self,
2767 current_line_highlight: Option<CurrentLineHighlight>,
2768 ) {
2769 self.current_line_highlight = current_line_highlight;
2770 }
2771
2772 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2773 self.collapse_matches = collapse_matches;
2774 }
2775
2776 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2777 let buffers = self.buffer.read(cx).all_buffers();
2778 let Some(project) = self.project.as_ref() else {
2779 return;
2780 };
2781 project.update(cx, |project, cx| {
2782 for buffer in buffers {
2783 self.registered_buffers
2784 .entry(buffer.read(cx).remote_id())
2785 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2786 }
2787 })
2788 }
2789
2790 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2791 if self.collapse_matches {
2792 return range.start..range.start;
2793 }
2794 range.clone()
2795 }
2796
2797 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2798 if self.display_map.read(cx).clip_at_line_ends != clip {
2799 self.display_map
2800 .update(cx, |map, _| map.clip_at_line_ends = clip);
2801 }
2802 }
2803
2804 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2805 self.input_enabled = input_enabled;
2806 }
2807
2808 pub fn set_inline_completions_hidden_for_vim_mode(
2809 &mut self,
2810 hidden: bool,
2811 window: &mut Window,
2812 cx: &mut Context<Self>,
2813 ) {
2814 if hidden != self.inline_completions_hidden_for_vim_mode {
2815 self.inline_completions_hidden_for_vim_mode = hidden;
2816 if hidden {
2817 self.update_visible_inline_completion(window, cx);
2818 } else {
2819 self.refresh_inline_completion(true, false, window, cx);
2820 }
2821 }
2822 }
2823
2824 pub fn set_menu_inline_completions_policy(&mut self, value: MenuInlineCompletionsPolicy) {
2825 self.menu_inline_completions_policy = value;
2826 }
2827
2828 pub fn set_autoindent(&mut self, autoindent: bool) {
2829 if autoindent {
2830 self.autoindent_mode = Some(AutoindentMode::EachLine);
2831 } else {
2832 self.autoindent_mode = None;
2833 }
2834 }
2835
2836 pub fn read_only(&self, cx: &App) -> bool {
2837 self.read_only || self.buffer.read(cx).read_only()
2838 }
2839
2840 pub fn set_read_only(&mut self, read_only: bool) {
2841 self.read_only = read_only;
2842 }
2843
2844 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2845 self.use_autoclose = autoclose;
2846 }
2847
2848 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2849 self.use_auto_surround = auto_surround;
2850 }
2851
2852 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2853 self.auto_replace_emoji_shortcode = auto_replace;
2854 }
2855
2856 pub fn toggle_edit_predictions(
2857 &mut self,
2858 _: &ToggleEditPrediction,
2859 window: &mut Window,
2860 cx: &mut Context<Self>,
2861 ) {
2862 if self.show_inline_completions_override.is_some() {
2863 self.set_show_edit_predictions(None, window, cx);
2864 } else {
2865 let show_edit_predictions = !self.edit_predictions_enabled();
2866 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2867 }
2868 }
2869
2870 pub fn set_show_edit_predictions(
2871 &mut self,
2872 show_edit_predictions: Option<bool>,
2873 window: &mut Window,
2874 cx: &mut Context<Self>,
2875 ) {
2876 self.show_inline_completions_override = show_edit_predictions;
2877 self.update_edit_prediction_settings(cx);
2878
2879 if let Some(false) = show_edit_predictions {
2880 self.discard_inline_completion(false, cx);
2881 } else {
2882 self.refresh_inline_completion(false, true, window, cx);
2883 }
2884 }
2885
2886 fn inline_completions_disabled_in_scope(
2887 &self,
2888 buffer: &Entity<Buffer>,
2889 buffer_position: language::Anchor,
2890 cx: &App,
2891 ) -> bool {
2892 let snapshot = buffer.read(cx).snapshot();
2893 let settings = snapshot.settings_at(buffer_position, cx);
2894
2895 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2896 return false;
2897 };
2898
2899 scope.override_name().map_or(false, |scope_name| {
2900 settings
2901 .edit_predictions_disabled_in
2902 .iter()
2903 .any(|s| s == scope_name)
2904 })
2905 }
2906
2907 pub fn set_use_modal_editing(&mut self, to: bool) {
2908 self.use_modal_editing = to;
2909 }
2910
2911 pub fn use_modal_editing(&self) -> bool {
2912 self.use_modal_editing
2913 }
2914
2915 fn selections_did_change(
2916 &mut self,
2917 local: bool,
2918 old_cursor_position: &Anchor,
2919 effects: SelectionEffects,
2920 window: &mut Window,
2921 cx: &mut Context<Self>,
2922 ) {
2923 window.invalidate_character_coordinates();
2924
2925 // Copy selections to primary selection buffer
2926 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2927 if local {
2928 let selections = self.selections.all::<usize>(cx);
2929 let buffer_handle = self.buffer.read(cx).read(cx);
2930
2931 let mut text = String::new();
2932 for (index, selection) in selections.iter().enumerate() {
2933 let text_for_selection = buffer_handle
2934 .text_for_range(selection.start..selection.end)
2935 .collect::<String>();
2936
2937 text.push_str(&text_for_selection);
2938 if index != selections.len() - 1 {
2939 text.push('\n');
2940 }
2941 }
2942
2943 if !text.is_empty() {
2944 cx.write_to_primary(ClipboardItem::new_string(text));
2945 }
2946 }
2947
2948 let selection_anchors = self.selections.disjoint_anchors();
2949
2950 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
2951 self.buffer.update(cx, |buffer, cx| {
2952 buffer.set_active_selections(
2953 &selection_anchors,
2954 self.selections.line_mode,
2955 self.cursor_shape,
2956 cx,
2957 )
2958 });
2959 }
2960 let display_map = self
2961 .display_map
2962 .update(cx, |display_map, cx| display_map.snapshot(cx));
2963 let buffer = &display_map.buffer_snapshot;
2964 if self.selections.count() == 1 {
2965 self.add_selections_state = None;
2966 }
2967 self.select_next_state = None;
2968 self.select_prev_state = None;
2969 self.select_syntax_node_history.try_clear();
2970 self.invalidate_autoclose_regions(&selection_anchors, buffer);
2971 self.snippet_stack.invalidate(&selection_anchors, buffer);
2972 self.take_rename(false, window, cx);
2973
2974 let newest_selection = self.selections.newest_anchor();
2975 let new_cursor_position = newest_selection.head();
2976 let selection_start = newest_selection.start;
2977
2978 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
2979 self.push_to_nav_history(
2980 *old_cursor_position,
2981 Some(new_cursor_position.to_point(buffer)),
2982 false,
2983 effects.nav_history == Some(true),
2984 cx,
2985 );
2986 }
2987
2988 if local {
2989 if let Some(buffer_id) = new_cursor_position.buffer_id {
2990 if !self.registered_buffers.contains_key(&buffer_id) {
2991 if let Some(project) = self.project.as_ref() {
2992 project.update(cx, |project, cx| {
2993 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
2994 return;
2995 };
2996 self.registered_buffers.insert(
2997 buffer_id,
2998 project.register_buffer_with_language_servers(&buffer, cx),
2999 );
3000 })
3001 }
3002 }
3003 }
3004
3005 let mut context_menu = self.context_menu.borrow_mut();
3006 let completion_menu = match context_menu.as_ref() {
3007 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3008 Some(CodeContextMenu::CodeActions(_)) => {
3009 *context_menu = None;
3010 None
3011 }
3012 None => None,
3013 };
3014 let completion_position = completion_menu.map(|menu| menu.initial_position);
3015 drop(context_menu);
3016
3017 if effects.completions {
3018 if let Some(completion_position) = completion_position {
3019 let start_offset = selection_start.to_offset(buffer);
3020 let position_matches = start_offset == completion_position.to_offset(buffer);
3021 let continue_showing = if position_matches {
3022 if self.snippet_stack.is_empty() {
3023 buffer.char_kind_before(start_offset, true) == Some(CharKind::Word)
3024 } else {
3025 // Snippet choices can be shown even when the cursor is in whitespace.
3026 // Dismissing the menu with actions like backspace is handled by
3027 // invalidation regions.
3028 true
3029 }
3030 } else {
3031 false
3032 };
3033
3034 if continue_showing {
3035 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
3036 } else {
3037 self.hide_context_menu(window, cx);
3038 }
3039 }
3040 }
3041
3042 hide_hover(self, cx);
3043
3044 if old_cursor_position.to_display_point(&display_map).row()
3045 != new_cursor_position.to_display_point(&display_map).row()
3046 {
3047 self.available_code_actions.take();
3048 }
3049 self.refresh_code_actions(window, cx);
3050 self.refresh_document_highlights(cx);
3051 self.refresh_selected_text_highlights(false, window, cx);
3052 refresh_matching_bracket_highlights(self, window, cx);
3053 self.update_visible_inline_completion(window, cx);
3054 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3055 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
3056 self.inline_blame_popover.take();
3057 if self.git_blame_inline_enabled {
3058 self.start_inline_blame_timer(window, cx);
3059 }
3060 }
3061
3062 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3063 cx.emit(EditorEvent::SelectionsChanged { local });
3064
3065 let selections = &self.selections.disjoint;
3066 if selections.len() == 1 {
3067 cx.emit(SearchEvent::ActiveMatchChanged)
3068 }
3069 if local {
3070 if let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3071 let inmemory_selections = selections
3072 .iter()
3073 .map(|s| {
3074 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3075 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3076 })
3077 .collect();
3078 self.update_restoration_data(cx, |data| {
3079 data.selections = inmemory_selections;
3080 });
3081
3082 if WorkspaceSettings::get(None, cx).restore_on_startup
3083 != RestoreOnStartupBehavior::None
3084 {
3085 if let Some(workspace_id) =
3086 self.workspace.as_ref().and_then(|workspace| workspace.1)
3087 {
3088 let snapshot = self.buffer().read(cx).snapshot(cx);
3089 let selections = selections.clone();
3090 let background_executor = cx.background_executor().clone();
3091 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3092 self.serialize_selections = cx.background_spawn(async move {
3093 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3094 let db_selections = selections
3095 .iter()
3096 .map(|selection| {
3097 (
3098 selection.start.to_offset(&snapshot),
3099 selection.end.to_offset(&snapshot),
3100 )
3101 })
3102 .collect();
3103
3104 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3105 .await
3106 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
3107 .log_err();
3108 });
3109 }
3110 }
3111 }
3112 }
3113
3114 cx.notify();
3115 }
3116
3117 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3118 use text::ToOffset as _;
3119 use text::ToPoint as _;
3120
3121 if self.mode.is_minimap()
3122 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3123 {
3124 return;
3125 }
3126
3127 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
3128 return;
3129 };
3130
3131 let snapshot = singleton.read(cx).snapshot();
3132 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
3133 let display_snapshot = display_map.snapshot(cx);
3134
3135 display_snapshot
3136 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
3137 .map(|fold| {
3138 fold.range.start.text_anchor.to_point(&snapshot)
3139 ..fold.range.end.text_anchor.to_point(&snapshot)
3140 })
3141 .collect()
3142 });
3143 self.update_restoration_data(cx, |data| {
3144 data.folds = inmemory_folds;
3145 });
3146
3147 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
3148 return;
3149 };
3150 let background_executor = cx.background_executor().clone();
3151 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3152 let db_folds = self.display_map.update(cx, |display_map, cx| {
3153 display_map
3154 .snapshot(cx)
3155 .folds_in_range(0..snapshot.len())
3156 .map(|fold| {
3157 (
3158 fold.range.start.text_anchor.to_offset(&snapshot),
3159 fold.range.end.text_anchor.to_offset(&snapshot),
3160 )
3161 })
3162 .collect()
3163 });
3164 self.serialize_folds = cx.background_spawn(async move {
3165 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3166 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3167 .await
3168 .with_context(|| {
3169 format!(
3170 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3171 )
3172 })
3173 .log_err();
3174 });
3175 }
3176
3177 pub fn sync_selections(
3178 &mut self,
3179 other: Entity<Editor>,
3180 cx: &mut Context<Self>,
3181 ) -> gpui::Subscription {
3182 let other_selections = other.read(cx).selections.disjoint.to_vec();
3183 self.selections.change_with(cx, |selections| {
3184 selections.select_anchors(other_selections);
3185 });
3186
3187 let other_subscription =
3188 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
3189 EditorEvent::SelectionsChanged { local: true } => {
3190 let other_selections = other.read(cx).selections.disjoint.to_vec();
3191 if other_selections.is_empty() {
3192 return;
3193 }
3194 this.selections.change_with(cx, |selections| {
3195 selections.select_anchors(other_selections);
3196 });
3197 }
3198 _ => {}
3199 });
3200
3201 let this_subscription =
3202 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
3203 EditorEvent::SelectionsChanged { local: true } => {
3204 let these_selections = this.selections.disjoint.to_vec();
3205 if these_selections.is_empty() {
3206 return;
3207 }
3208 other.update(cx, |other_editor, cx| {
3209 other_editor.selections.change_with(cx, |selections| {
3210 selections.select_anchors(these_selections);
3211 })
3212 });
3213 }
3214 _ => {}
3215 });
3216
3217 Subscription::join(other_subscription, this_subscription)
3218 }
3219
3220 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3221 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3222 /// effects of selection change occur at the end of the transaction.
3223 pub fn change_selections<R>(
3224 &mut self,
3225 effects: SelectionEffects,
3226 window: &mut Window,
3227 cx: &mut Context<Self>,
3228 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3229 ) -> R {
3230 if let Some(state) = &mut self.deferred_selection_effects_state {
3231 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3232 state.effects.completions = effects.completions;
3233 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3234 let (changed, result) = self.selections.change_with(cx, change);
3235 state.changed |= changed;
3236 return result;
3237 }
3238 let mut state = DeferredSelectionEffectsState {
3239 changed: false,
3240 effects,
3241 old_cursor_position: self.selections.newest_anchor().head(),
3242 history_entry: SelectionHistoryEntry {
3243 selections: self.selections.disjoint_anchors(),
3244 select_next_state: self.select_next_state.clone(),
3245 select_prev_state: self.select_prev_state.clone(),
3246 add_selections_state: self.add_selections_state.clone(),
3247 },
3248 };
3249 let (changed, result) = self.selections.change_with(cx, change);
3250 state.changed = state.changed || changed;
3251 if self.defer_selection_effects {
3252 self.deferred_selection_effects_state = Some(state);
3253 } else {
3254 self.apply_selection_effects(state, window, cx);
3255 }
3256 result
3257 }
3258
3259 /// Defers the effects of selection change, so that the effects of multiple calls to
3260 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3261 /// to selection history and the state of popovers based on selection position aren't
3262 /// erroneously updated.
3263 pub fn with_selection_effects_deferred<R>(
3264 &mut self,
3265 window: &mut Window,
3266 cx: &mut Context<Self>,
3267 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3268 ) -> R {
3269 let already_deferred = self.defer_selection_effects;
3270 self.defer_selection_effects = true;
3271 let result = update(self, window, cx);
3272 if !already_deferred {
3273 self.defer_selection_effects = false;
3274 if let Some(state) = self.deferred_selection_effects_state.take() {
3275 self.apply_selection_effects(state, window, cx);
3276 }
3277 }
3278 result
3279 }
3280
3281 fn apply_selection_effects(
3282 &mut self,
3283 state: DeferredSelectionEffectsState,
3284 window: &mut Window,
3285 cx: &mut Context<Self>,
3286 ) {
3287 if state.changed {
3288 self.selection_history.push(state.history_entry);
3289
3290 if let Some(autoscroll) = state.effects.scroll {
3291 self.request_autoscroll(autoscroll, cx);
3292 }
3293
3294 let old_cursor_position = &state.old_cursor_position;
3295
3296 self.selections_did_change(true, &old_cursor_position, state.effects, window, cx);
3297
3298 if self.should_open_signature_help_automatically(&old_cursor_position, cx) {
3299 self.show_signature_help(&ShowSignatureHelp, window, cx);
3300 }
3301 }
3302 }
3303
3304 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3305 where
3306 I: IntoIterator<Item = (Range<S>, T)>,
3307 S: ToOffset,
3308 T: Into<Arc<str>>,
3309 {
3310 if self.read_only(cx) {
3311 return;
3312 }
3313
3314 self.buffer
3315 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3316 }
3317
3318 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3319 where
3320 I: IntoIterator<Item = (Range<S>, T)>,
3321 S: ToOffset,
3322 T: Into<Arc<str>>,
3323 {
3324 if self.read_only(cx) {
3325 return;
3326 }
3327
3328 self.buffer.update(cx, |buffer, cx| {
3329 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3330 });
3331 }
3332
3333 pub fn edit_with_block_indent<I, S, T>(
3334 &mut self,
3335 edits: I,
3336 original_indent_columns: Vec<Option<u32>>,
3337 cx: &mut Context<Self>,
3338 ) where
3339 I: IntoIterator<Item = (Range<S>, T)>,
3340 S: ToOffset,
3341 T: Into<Arc<str>>,
3342 {
3343 if self.read_only(cx) {
3344 return;
3345 }
3346
3347 self.buffer.update(cx, |buffer, cx| {
3348 buffer.edit(
3349 edits,
3350 Some(AutoindentMode::Block {
3351 original_indent_columns,
3352 }),
3353 cx,
3354 )
3355 });
3356 }
3357
3358 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3359 self.hide_context_menu(window, cx);
3360
3361 match phase {
3362 SelectPhase::Begin {
3363 position,
3364 add,
3365 click_count,
3366 } => self.begin_selection(position, add, click_count, window, cx),
3367 SelectPhase::BeginColumnar {
3368 position,
3369 goal_column,
3370 reset,
3371 mode,
3372 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3373 SelectPhase::Extend {
3374 position,
3375 click_count,
3376 } => self.extend_selection(position, click_count, window, cx),
3377 SelectPhase::Update {
3378 position,
3379 goal_column,
3380 scroll_delta,
3381 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3382 SelectPhase::End => self.end_selection(window, cx),
3383 }
3384 }
3385
3386 fn extend_selection(
3387 &mut self,
3388 position: DisplayPoint,
3389 click_count: usize,
3390 window: &mut Window,
3391 cx: &mut Context<Self>,
3392 ) {
3393 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3394 let tail = self.selections.newest::<usize>(cx).tail();
3395 self.begin_selection(position, false, click_count, window, cx);
3396
3397 let position = position.to_offset(&display_map, Bias::Left);
3398 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3399
3400 let mut pending_selection = self
3401 .selections
3402 .pending_anchor()
3403 .expect("extend_selection not called with pending selection");
3404 if position >= tail {
3405 pending_selection.start = tail_anchor;
3406 } else {
3407 pending_selection.end = tail_anchor;
3408 pending_selection.reversed = true;
3409 }
3410
3411 let mut pending_mode = self.selections.pending_mode().unwrap();
3412 match &mut pending_mode {
3413 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3414 _ => {}
3415 }
3416
3417 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3418 SelectionEffects::scroll(Autoscroll::fit())
3419 } else {
3420 SelectionEffects::no_scroll()
3421 };
3422
3423 self.change_selections(effects, window, cx, |s| {
3424 s.set_pending(pending_selection, pending_mode)
3425 });
3426 }
3427
3428 fn begin_selection(
3429 &mut self,
3430 position: DisplayPoint,
3431 add: bool,
3432 click_count: usize,
3433 window: &mut Window,
3434 cx: &mut Context<Self>,
3435 ) {
3436 if !self.focus_handle.is_focused(window) {
3437 self.last_focused_descendant = None;
3438 window.focus(&self.focus_handle);
3439 }
3440
3441 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3442 let buffer = &display_map.buffer_snapshot;
3443 let position = display_map.clip_point(position, Bias::Left);
3444
3445 let start;
3446 let end;
3447 let mode;
3448 let mut auto_scroll;
3449 match click_count {
3450 1 => {
3451 start = buffer.anchor_before(position.to_point(&display_map));
3452 end = start;
3453 mode = SelectMode::Character;
3454 auto_scroll = true;
3455 }
3456 2 => {
3457 let position = display_map
3458 .clip_point(position, Bias::Left)
3459 .to_offset(&display_map, Bias::Left);
3460 let (range, _) = buffer.surrounding_word(position, false);
3461 start = buffer.anchor_before(range.start);
3462 end = buffer.anchor_before(range.end);
3463 mode = SelectMode::Word(start..end);
3464 auto_scroll = true;
3465 }
3466 3 => {
3467 let position = display_map
3468 .clip_point(position, Bias::Left)
3469 .to_point(&display_map);
3470 let line_start = display_map.prev_line_boundary(position).0;
3471 let next_line_start = buffer.clip_point(
3472 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3473 Bias::Left,
3474 );
3475 start = buffer.anchor_before(line_start);
3476 end = buffer.anchor_before(next_line_start);
3477 mode = SelectMode::Line(start..end);
3478 auto_scroll = true;
3479 }
3480 _ => {
3481 start = buffer.anchor_before(0);
3482 end = buffer.anchor_before(buffer.len());
3483 mode = SelectMode::All;
3484 auto_scroll = false;
3485 }
3486 }
3487 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3488
3489 let point_to_delete: Option<usize> = {
3490 let selected_points: Vec<Selection<Point>> =
3491 self.selections.disjoint_in_range(start..end, cx);
3492
3493 if !add || click_count > 1 {
3494 None
3495 } else if !selected_points.is_empty() {
3496 Some(selected_points[0].id)
3497 } else {
3498 let clicked_point_already_selected =
3499 self.selections.disjoint.iter().find(|selection| {
3500 selection.start.to_point(buffer) == start.to_point(buffer)
3501 || selection.end.to_point(buffer) == end.to_point(buffer)
3502 });
3503
3504 clicked_point_already_selected.map(|selection| selection.id)
3505 }
3506 };
3507
3508 let selections_count = self.selections.count();
3509 let effects = if auto_scroll {
3510 SelectionEffects::default()
3511 } else {
3512 SelectionEffects::no_scroll()
3513 };
3514
3515 self.change_selections(effects, window, cx, |s| {
3516 if let Some(point_to_delete) = point_to_delete {
3517 s.delete(point_to_delete);
3518
3519 if selections_count == 1 {
3520 s.set_pending_anchor_range(start..end, mode);
3521 }
3522 } else {
3523 if !add {
3524 s.clear_disjoint();
3525 }
3526
3527 s.set_pending_anchor_range(start..end, mode);
3528 }
3529 });
3530 }
3531
3532 fn begin_columnar_selection(
3533 &mut self,
3534 position: DisplayPoint,
3535 goal_column: u32,
3536 reset: bool,
3537 mode: ColumnarMode,
3538 window: &mut Window,
3539 cx: &mut Context<Self>,
3540 ) {
3541 if !self.focus_handle.is_focused(window) {
3542 self.last_focused_descendant = None;
3543 window.focus(&self.focus_handle);
3544 }
3545
3546 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3547
3548 if reset {
3549 let pointer_position = display_map
3550 .buffer_snapshot
3551 .anchor_before(position.to_point(&display_map));
3552
3553 self.change_selections(
3554 SelectionEffects::scroll(Autoscroll::newest()),
3555 window,
3556 cx,
3557 |s| {
3558 s.clear_disjoint();
3559 s.set_pending_anchor_range(
3560 pointer_position..pointer_position,
3561 SelectMode::Character,
3562 );
3563 },
3564 );
3565 };
3566
3567 let tail = self.selections.newest::<Point>(cx).tail();
3568 let selection_anchor = display_map.buffer_snapshot.anchor_before(tail);
3569 self.columnar_selection_state = match mode {
3570 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3571 selection_tail: selection_anchor,
3572 display_point: if reset {
3573 if position.column() != goal_column {
3574 Some(DisplayPoint::new(position.row(), goal_column))
3575 } else {
3576 None
3577 }
3578 } else {
3579 None
3580 },
3581 }),
3582 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3583 selection_tail: selection_anchor,
3584 }),
3585 };
3586
3587 if !reset {
3588 self.select_columns(position, goal_column, &display_map, window, cx);
3589 }
3590 }
3591
3592 fn update_selection(
3593 &mut self,
3594 position: DisplayPoint,
3595 goal_column: u32,
3596 scroll_delta: gpui::Point<f32>,
3597 window: &mut Window,
3598 cx: &mut Context<Self>,
3599 ) {
3600 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3601
3602 if self.columnar_selection_state.is_some() {
3603 self.select_columns(position, goal_column, &display_map, window, cx);
3604 } else if let Some(mut pending) = self.selections.pending_anchor() {
3605 let buffer = &display_map.buffer_snapshot;
3606 let head;
3607 let tail;
3608 let mode = self.selections.pending_mode().unwrap();
3609 match &mode {
3610 SelectMode::Character => {
3611 head = position.to_point(&display_map);
3612 tail = pending.tail().to_point(buffer);
3613 }
3614 SelectMode::Word(original_range) => {
3615 let offset = display_map
3616 .clip_point(position, Bias::Left)
3617 .to_offset(&display_map, Bias::Left);
3618 let original_range = original_range.to_offset(buffer);
3619
3620 let head_offset = if buffer.is_inside_word(offset, false)
3621 || original_range.contains(&offset)
3622 {
3623 let (word_range, _) = buffer.surrounding_word(offset, false);
3624 if word_range.start < original_range.start {
3625 word_range.start
3626 } else {
3627 word_range.end
3628 }
3629 } else {
3630 offset
3631 };
3632
3633 head = head_offset.to_point(buffer);
3634 if head_offset <= original_range.start {
3635 tail = original_range.end.to_point(buffer);
3636 } else {
3637 tail = original_range.start.to_point(buffer);
3638 }
3639 }
3640 SelectMode::Line(original_range) => {
3641 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3642
3643 let position = display_map
3644 .clip_point(position, Bias::Left)
3645 .to_point(&display_map);
3646 let line_start = display_map.prev_line_boundary(position).0;
3647 let next_line_start = buffer.clip_point(
3648 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3649 Bias::Left,
3650 );
3651
3652 if line_start < original_range.start {
3653 head = line_start
3654 } else {
3655 head = next_line_start
3656 }
3657
3658 if head <= original_range.start {
3659 tail = original_range.end;
3660 } else {
3661 tail = original_range.start;
3662 }
3663 }
3664 SelectMode::All => {
3665 return;
3666 }
3667 };
3668
3669 if head < tail {
3670 pending.start = buffer.anchor_before(head);
3671 pending.end = buffer.anchor_before(tail);
3672 pending.reversed = true;
3673 } else {
3674 pending.start = buffer.anchor_before(tail);
3675 pending.end = buffer.anchor_before(head);
3676 pending.reversed = false;
3677 }
3678
3679 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3680 s.set_pending(pending, mode);
3681 });
3682 } else {
3683 log::error!("update_selection dispatched with no pending selection");
3684 return;
3685 }
3686
3687 self.apply_scroll_delta(scroll_delta, window, cx);
3688 cx.notify();
3689 }
3690
3691 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3692 self.columnar_selection_state.take();
3693 if self.selections.pending_anchor().is_some() {
3694 let selections = self.selections.all::<usize>(cx);
3695 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3696 s.select(selections);
3697 s.clear_pending();
3698 });
3699 }
3700 }
3701
3702 fn select_columns(
3703 &mut self,
3704 head: DisplayPoint,
3705 goal_column: u32,
3706 display_map: &DisplaySnapshot,
3707 window: &mut Window,
3708 cx: &mut Context<Self>,
3709 ) {
3710 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3711 return;
3712 };
3713
3714 let tail = match columnar_state {
3715 ColumnarSelectionState::FromMouse {
3716 selection_tail,
3717 display_point,
3718 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(&display_map)),
3719 ColumnarSelectionState::FromSelection { selection_tail } => {
3720 selection_tail.to_display_point(&display_map)
3721 }
3722 };
3723
3724 let start_row = cmp::min(tail.row(), head.row());
3725 let end_row = cmp::max(tail.row(), head.row());
3726 let start_column = cmp::min(tail.column(), goal_column);
3727 let end_column = cmp::max(tail.column(), goal_column);
3728 let reversed = start_column < tail.column();
3729
3730 let selection_ranges = (start_row.0..=end_row.0)
3731 .map(DisplayRow)
3732 .filter_map(|row| {
3733 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3734 || start_column <= display_map.line_len(row))
3735 && !display_map.is_block_line(row)
3736 {
3737 let start = display_map
3738 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3739 .to_point(display_map);
3740 let end = display_map
3741 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3742 .to_point(display_map);
3743 if reversed {
3744 Some(end..start)
3745 } else {
3746 Some(start..end)
3747 }
3748 } else {
3749 None
3750 }
3751 })
3752 .collect::<Vec<_>>();
3753
3754 let ranges = match columnar_state {
3755 ColumnarSelectionState::FromMouse { .. } => {
3756 let mut non_empty_ranges = selection_ranges
3757 .iter()
3758 .filter(|selection_range| selection_range.start != selection_range.end)
3759 .peekable();
3760 if non_empty_ranges.peek().is_some() {
3761 non_empty_ranges.cloned().collect()
3762 } else {
3763 selection_ranges
3764 }
3765 }
3766 _ => selection_ranges,
3767 };
3768
3769 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3770 s.select_ranges(ranges);
3771 });
3772 cx.notify();
3773 }
3774
3775 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3776 self.selections
3777 .all_adjusted(cx)
3778 .iter()
3779 .any(|selection| !selection.is_empty())
3780 }
3781
3782 pub fn has_pending_nonempty_selection(&self) -> bool {
3783 let pending_nonempty_selection = match self.selections.pending_anchor() {
3784 Some(Selection { start, end, .. }) => start != end,
3785 None => false,
3786 };
3787
3788 pending_nonempty_selection
3789 || (self.columnar_selection_state.is_some() && self.selections.disjoint.len() > 1)
3790 }
3791
3792 pub fn has_pending_selection(&self) -> bool {
3793 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
3794 }
3795
3796 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3797 self.selection_mark_mode = false;
3798 self.selection_drag_state = SelectionDragState::None;
3799
3800 if self.clear_expanded_diff_hunks(cx) {
3801 cx.notify();
3802 return;
3803 }
3804 if self.dismiss_menus_and_popups(true, window, cx) {
3805 return;
3806 }
3807
3808 if self.mode.is_full()
3809 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
3810 {
3811 return;
3812 }
3813
3814 cx.propagate();
3815 }
3816
3817 pub fn dismiss_menus_and_popups(
3818 &mut self,
3819 is_user_requested: bool,
3820 window: &mut Window,
3821 cx: &mut Context<Self>,
3822 ) -> bool {
3823 if self.take_rename(false, window, cx).is_some() {
3824 return true;
3825 }
3826
3827 if hide_hover(self, cx) {
3828 return true;
3829 }
3830
3831 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3832 return true;
3833 }
3834
3835 if self.hide_context_menu(window, cx).is_some() {
3836 return true;
3837 }
3838
3839 if self.mouse_context_menu.take().is_some() {
3840 return true;
3841 }
3842
3843 if is_user_requested && self.discard_inline_completion(true, cx) {
3844 return true;
3845 }
3846
3847 if self.snippet_stack.pop().is_some() {
3848 return true;
3849 }
3850
3851 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3852 self.dismiss_diagnostics(cx);
3853 return true;
3854 }
3855
3856 false
3857 }
3858
3859 fn linked_editing_ranges_for(
3860 &self,
3861 selection: Range<text::Anchor>,
3862 cx: &App,
3863 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3864 if self.linked_edit_ranges.is_empty() {
3865 return None;
3866 }
3867 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3868 selection.end.buffer_id.and_then(|end_buffer_id| {
3869 if selection.start.buffer_id != Some(end_buffer_id) {
3870 return None;
3871 }
3872 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3873 let snapshot = buffer.read(cx).snapshot();
3874 self.linked_edit_ranges
3875 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3876 .map(|ranges| (ranges, snapshot, buffer))
3877 })?;
3878 use text::ToOffset as TO;
3879 // find offset from the start of current range to current cursor position
3880 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3881
3882 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3883 let start_difference = start_offset - start_byte_offset;
3884 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3885 let end_difference = end_offset - start_byte_offset;
3886 // Current range has associated linked ranges.
3887 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3888 for range in linked_ranges.iter() {
3889 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3890 let end_offset = start_offset + end_difference;
3891 let start_offset = start_offset + start_difference;
3892 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3893 continue;
3894 }
3895 if self.selections.disjoint_anchor_ranges().any(|s| {
3896 if s.start.buffer_id != selection.start.buffer_id
3897 || s.end.buffer_id != selection.end.buffer_id
3898 {
3899 return false;
3900 }
3901 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3902 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3903 }) {
3904 continue;
3905 }
3906 let start = buffer_snapshot.anchor_after(start_offset);
3907 let end = buffer_snapshot.anchor_after(end_offset);
3908 linked_edits
3909 .entry(buffer.clone())
3910 .or_default()
3911 .push(start..end);
3912 }
3913 Some(linked_edits)
3914 }
3915
3916 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3917 let text: Arc<str> = text.into();
3918
3919 if self.read_only(cx) {
3920 return;
3921 }
3922
3923 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
3924
3925 let selections = self.selections.all_adjusted(cx);
3926 let mut bracket_inserted = false;
3927 let mut edits = Vec::new();
3928 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3929 let mut new_selections = Vec::with_capacity(selections.len());
3930 let mut new_autoclose_regions = Vec::new();
3931 let snapshot = self.buffer.read(cx).read(cx);
3932 let mut clear_linked_edit_ranges = false;
3933
3934 for (selection, autoclose_region) in
3935 self.selections_with_autoclose_regions(selections, &snapshot)
3936 {
3937 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3938 // Determine if the inserted text matches the opening or closing
3939 // bracket of any of this language's bracket pairs.
3940 let mut bracket_pair = None;
3941 let mut is_bracket_pair_start = false;
3942 let mut is_bracket_pair_end = false;
3943 if !text.is_empty() {
3944 let mut bracket_pair_matching_end = None;
3945 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3946 // and they are removing the character that triggered IME popup.
3947 for (pair, enabled) in scope.brackets() {
3948 if !pair.close && !pair.surround {
3949 continue;
3950 }
3951
3952 if enabled && pair.start.ends_with(text.as_ref()) {
3953 let prefix_len = pair.start.len() - text.len();
3954 let preceding_text_matches_prefix = prefix_len == 0
3955 || (selection.start.column >= (prefix_len as u32)
3956 && snapshot.contains_str_at(
3957 Point::new(
3958 selection.start.row,
3959 selection.start.column - (prefix_len as u32),
3960 ),
3961 &pair.start[..prefix_len],
3962 ));
3963 if preceding_text_matches_prefix {
3964 bracket_pair = Some(pair.clone());
3965 is_bracket_pair_start = true;
3966 break;
3967 }
3968 }
3969 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
3970 {
3971 // take first bracket pair matching end, but don't break in case a later bracket
3972 // pair matches start
3973 bracket_pair_matching_end = Some(pair.clone());
3974 }
3975 }
3976 if let Some(end) = bracket_pair_matching_end
3977 && bracket_pair.is_none()
3978 {
3979 bracket_pair = Some(end);
3980 is_bracket_pair_end = true;
3981 }
3982 }
3983
3984 if let Some(bracket_pair) = bracket_pair {
3985 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
3986 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
3987 let auto_surround =
3988 self.use_auto_surround && snapshot_settings.use_auto_surround;
3989 if selection.is_empty() {
3990 if is_bracket_pair_start {
3991 // If the inserted text is a suffix of an opening bracket and the
3992 // selection is preceded by the rest of the opening bracket, then
3993 // insert the closing bracket.
3994 let following_text_allows_autoclose = snapshot
3995 .chars_at(selection.start)
3996 .next()
3997 .map_or(true, |c| scope.should_autoclose_before(c));
3998
3999 let preceding_text_allows_autoclose = selection.start.column == 0
4000 || snapshot.reversed_chars_at(selection.start).next().map_or(
4001 true,
4002 |c| {
4003 bracket_pair.start != bracket_pair.end
4004 || !snapshot
4005 .char_classifier_at(selection.start)
4006 .is_word(c)
4007 },
4008 );
4009
4010 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4011 && bracket_pair.start.len() == 1
4012 {
4013 let target = bracket_pair.start.chars().next().unwrap();
4014 let current_line_count = snapshot
4015 .reversed_chars_at(selection.start)
4016 .take_while(|&c| c != '\n')
4017 .filter(|&c| c == target)
4018 .count();
4019 current_line_count % 2 == 1
4020 } else {
4021 false
4022 };
4023
4024 if autoclose
4025 && bracket_pair.close
4026 && following_text_allows_autoclose
4027 && preceding_text_allows_autoclose
4028 && !is_closing_quote
4029 {
4030 let anchor = snapshot.anchor_before(selection.end);
4031 new_selections.push((selection.map(|_| anchor), text.len()));
4032 new_autoclose_regions.push((
4033 anchor,
4034 text.len(),
4035 selection.id,
4036 bracket_pair.clone(),
4037 ));
4038 edits.push((
4039 selection.range(),
4040 format!("{}{}", text, bracket_pair.end).into(),
4041 ));
4042 bracket_inserted = true;
4043 continue;
4044 }
4045 }
4046
4047 if let Some(region) = autoclose_region {
4048 // If the selection is followed by an auto-inserted closing bracket,
4049 // then don't insert that closing bracket again; just move the selection
4050 // past the closing bracket.
4051 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4052 && text.as_ref() == region.pair.end.as_str()
4053 && snapshot.contains_str_at(region.range.end, text.as_ref());
4054 if should_skip {
4055 let anchor = snapshot.anchor_after(selection.end);
4056 new_selections
4057 .push((selection.map(|_| anchor), region.pair.end.len()));
4058 continue;
4059 }
4060 }
4061
4062 let always_treat_brackets_as_autoclosed = snapshot
4063 .language_settings_at(selection.start, cx)
4064 .always_treat_brackets_as_autoclosed;
4065 if always_treat_brackets_as_autoclosed
4066 && is_bracket_pair_end
4067 && snapshot.contains_str_at(selection.end, text.as_ref())
4068 {
4069 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4070 // and the inserted text is a closing bracket and the selection is followed
4071 // by the closing bracket then move the selection past the closing bracket.
4072 let anchor = snapshot.anchor_after(selection.end);
4073 new_selections.push((selection.map(|_| anchor), text.len()));
4074 continue;
4075 }
4076 }
4077 // If an opening bracket is 1 character long and is typed while
4078 // text is selected, then surround that text with the bracket pair.
4079 else if auto_surround
4080 && bracket_pair.surround
4081 && is_bracket_pair_start
4082 && bracket_pair.start.chars().count() == 1
4083 {
4084 edits.push((selection.start..selection.start, text.clone()));
4085 edits.push((
4086 selection.end..selection.end,
4087 bracket_pair.end.as_str().into(),
4088 ));
4089 bracket_inserted = true;
4090 new_selections.push((
4091 Selection {
4092 id: selection.id,
4093 start: snapshot.anchor_after(selection.start),
4094 end: snapshot.anchor_before(selection.end),
4095 reversed: selection.reversed,
4096 goal: selection.goal,
4097 },
4098 0,
4099 ));
4100 continue;
4101 }
4102 }
4103 }
4104
4105 if self.auto_replace_emoji_shortcode
4106 && selection.is_empty()
4107 && text.as_ref().ends_with(':')
4108 {
4109 if let Some(possible_emoji_short_code) =
4110 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4111 {
4112 if !possible_emoji_short_code.is_empty() {
4113 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
4114 let emoji_shortcode_start = Point::new(
4115 selection.start.row,
4116 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4117 );
4118
4119 // Remove shortcode from buffer
4120 edits.push((
4121 emoji_shortcode_start..selection.start,
4122 "".to_string().into(),
4123 ));
4124 new_selections.push((
4125 Selection {
4126 id: selection.id,
4127 start: snapshot.anchor_after(emoji_shortcode_start),
4128 end: snapshot.anchor_before(selection.start),
4129 reversed: selection.reversed,
4130 goal: selection.goal,
4131 },
4132 0,
4133 ));
4134
4135 // Insert emoji
4136 let selection_start_anchor = snapshot.anchor_after(selection.start);
4137 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4138 edits.push((selection.start..selection.end, emoji.to_string().into()));
4139
4140 continue;
4141 }
4142 }
4143 }
4144 }
4145
4146 // If not handling any auto-close operation, then just replace the selected
4147 // text with the given input and move the selection to the end of the
4148 // newly inserted text.
4149 let anchor = snapshot.anchor_after(selection.end);
4150 if !self.linked_edit_ranges.is_empty() {
4151 let start_anchor = snapshot.anchor_before(selection.start);
4152
4153 let is_word_char = text.chars().next().map_or(true, |char| {
4154 let classifier = snapshot
4155 .char_classifier_at(start_anchor.to_offset(&snapshot))
4156 .ignore_punctuation(true);
4157 classifier.is_word(char)
4158 });
4159
4160 if is_word_char {
4161 if let Some(ranges) = self
4162 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4163 {
4164 for (buffer, edits) in ranges {
4165 linked_edits
4166 .entry(buffer.clone())
4167 .or_default()
4168 .extend(edits.into_iter().map(|range| (range, text.clone())));
4169 }
4170 }
4171 } else {
4172 clear_linked_edit_ranges = true;
4173 }
4174 }
4175
4176 new_selections.push((selection.map(|_| anchor), 0));
4177 edits.push((selection.start..selection.end, text.clone()));
4178 }
4179
4180 drop(snapshot);
4181
4182 self.transact(window, cx, |this, window, cx| {
4183 if clear_linked_edit_ranges {
4184 this.linked_edit_ranges.clear();
4185 }
4186 let initial_buffer_versions =
4187 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4188
4189 this.buffer.update(cx, |buffer, cx| {
4190 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4191 });
4192 for (buffer, edits) in linked_edits {
4193 buffer.update(cx, |buffer, cx| {
4194 let snapshot = buffer.snapshot();
4195 let edits = edits
4196 .into_iter()
4197 .map(|(range, text)| {
4198 use text::ToPoint as TP;
4199 let end_point = TP::to_point(&range.end, &snapshot);
4200 let start_point = TP::to_point(&range.start, &snapshot);
4201 (start_point..end_point, text)
4202 })
4203 .sorted_by_key(|(range, _)| range.start);
4204 buffer.edit(edits, None, cx);
4205 })
4206 }
4207 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4208 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4209 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4210 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
4211 .zip(new_selection_deltas)
4212 .map(|(selection, delta)| Selection {
4213 id: selection.id,
4214 start: selection.start + delta,
4215 end: selection.end + delta,
4216 reversed: selection.reversed,
4217 goal: SelectionGoal::None,
4218 })
4219 .collect::<Vec<_>>();
4220
4221 let mut i = 0;
4222 for (position, delta, selection_id, pair) in new_autoclose_regions {
4223 let position = position.to_offset(&map.buffer_snapshot) + delta;
4224 let start = map.buffer_snapshot.anchor_before(position);
4225 let end = map.buffer_snapshot.anchor_after(position);
4226 while let Some(existing_state) = this.autoclose_regions.get(i) {
4227 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
4228 Ordering::Less => i += 1,
4229 Ordering::Greater => break,
4230 Ordering::Equal => {
4231 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
4232 Ordering::Less => i += 1,
4233 Ordering::Equal => break,
4234 Ordering::Greater => break,
4235 }
4236 }
4237 }
4238 }
4239 this.autoclose_regions.insert(
4240 i,
4241 AutocloseRegion {
4242 selection_id,
4243 range: start..end,
4244 pair,
4245 },
4246 );
4247 }
4248
4249 let had_active_inline_completion = this.has_active_inline_completion();
4250 this.change_selections(
4251 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4252 window,
4253 cx,
4254 |s| s.select(new_selections),
4255 );
4256
4257 if !bracket_inserted {
4258 if let Some(on_type_format_task) =
4259 this.trigger_on_type_formatting(text.to_string(), window, cx)
4260 {
4261 on_type_format_task.detach_and_log_err(cx);
4262 }
4263 }
4264
4265 let editor_settings = EditorSettings::get_global(cx);
4266 if bracket_inserted
4267 && (editor_settings.auto_signature_help
4268 || editor_settings.show_signature_help_after_edits)
4269 {
4270 this.show_signature_help(&ShowSignatureHelp, window, cx);
4271 }
4272
4273 let trigger_in_words =
4274 this.show_edit_predictions_in_menu() || !had_active_inline_completion;
4275 if this.hard_wrap.is_some() {
4276 let latest: Range<Point> = this.selections.newest(cx).range();
4277 if latest.is_empty()
4278 && this
4279 .buffer()
4280 .read(cx)
4281 .snapshot(cx)
4282 .line_len(MultiBufferRow(latest.start.row))
4283 == latest.start.column
4284 {
4285 this.rewrap_impl(
4286 RewrapOptions {
4287 override_language_settings: true,
4288 preserve_existing_whitespace: true,
4289 },
4290 cx,
4291 )
4292 }
4293 }
4294 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4295 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
4296 this.refresh_inline_completion(true, false, window, cx);
4297 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4298 });
4299 }
4300
4301 fn find_possible_emoji_shortcode_at_position(
4302 snapshot: &MultiBufferSnapshot,
4303 position: Point,
4304 ) -> Option<String> {
4305 let mut chars = Vec::new();
4306 let mut found_colon = false;
4307 for char in snapshot.reversed_chars_at(position).take(100) {
4308 // Found a possible emoji shortcode in the middle of the buffer
4309 if found_colon {
4310 if char.is_whitespace() {
4311 chars.reverse();
4312 return Some(chars.iter().collect());
4313 }
4314 // If the previous character is not a whitespace, we are in the middle of a word
4315 // and we only want to complete the shortcode if the word is made up of other emojis
4316 let mut containing_word = String::new();
4317 for ch in snapshot
4318 .reversed_chars_at(position)
4319 .skip(chars.len() + 1)
4320 .take(100)
4321 {
4322 if ch.is_whitespace() {
4323 break;
4324 }
4325 containing_word.push(ch);
4326 }
4327 let containing_word = containing_word.chars().rev().collect::<String>();
4328 if util::word_consists_of_emojis(containing_word.as_str()) {
4329 chars.reverse();
4330 return Some(chars.iter().collect());
4331 }
4332 }
4333
4334 if char.is_whitespace() || !char.is_ascii() {
4335 return None;
4336 }
4337 if char == ':' {
4338 found_colon = true;
4339 } else {
4340 chars.push(char);
4341 }
4342 }
4343 // Found a possible emoji shortcode at the beginning of the buffer
4344 chars.reverse();
4345 Some(chars.iter().collect())
4346 }
4347
4348 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4349 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4350 self.transact(window, cx, |this, window, cx| {
4351 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4352 let selections = this.selections.all::<usize>(cx);
4353 let multi_buffer = this.buffer.read(cx);
4354 let buffer = multi_buffer.snapshot(cx);
4355 selections
4356 .iter()
4357 .map(|selection| {
4358 let start_point = selection.start.to_point(&buffer);
4359 let mut existing_indent =
4360 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4361 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4362 let start = selection.start;
4363 let end = selection.end;
4364 let selection_is_empty = start == end;
4365 let language_scope = buffer.language_scope_at(start);
4366 let (
4367 comment_delimiter,
4368 doc_delimiter,
4369 insert_extra_newline,
4370 indent_on_newline,
4371 indent_on_extra_newline,
4372 ) = if let Some(language) = &language_scope {
4373 let mut insert_extra_newline =
4374 insert_extra_newline_brackets(&buffer, start..end, language)
4375 || insert_extra_newline_tree_sitter(&buffer, start..end);
4376
4377 // Comment extension on newline is allowed only for cursor selections
4378 let comment_delimiter = maybe!({
4379 if !selection_is_empty {
4380 return None;
4381 }
4382
4383 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4384 return None;
4385 }
4386
4387 let delimiters = language.line_comment_prefixes();
4388 let max_len_of_delimiter =
4389 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4390 let (snapshot, range) =
4391 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4392
4393 let num_of_whitespaces = snapshot
4394 .chars_for_range(range.clone())
4395 .take_while(|c| c.is_whitespace())
4396 .count();
4397 let comment_candidate = snapshot
4398 .chars_for_range(range.clone())
4399 .skip(num_of_whitespaces)
4400 .take(max_len_of_delimiter)
4401 .collect::<String>();
4402 let (delimiter, trimmed_len) = delimiters
4403 .iter()
4404 .filter_map(|delimiter| {
4405 let prefix = delimiter.trim_end();
4406 if comment_candidate.starts_with(prefix) {
4407 Some((delimiter, prefix.len()))
4408 } else {
4409 None
4410 }
4411 })
4412 .max_by_key(|(_, len)| *len)?;
4413
4414 if let Some(BlockCommentConfig {
4415 start: block_start, ..
4416 }) = language.block_comment()
4417 {
4418 let block_start_trimmed = block_start.trim_end();
4419 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4420 let line_content = snapshot
4421 .chars_for_range(range)
4422 .skip(num_of_whitespaces)
4423 .take(block_start_trimmed.len())
4424 .collect::<String>();
4425
4426 if line_content.starts_with(block_start_trimmed) {
4427 return None;
4428 }
4429 }
4430 }
4431
4432 let cursor_is_placed_after_comment_marker =
4433 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4434 if cursor_is_placed_after_comment_marker {
4435 Some(delimiter.clone())
4436 } else {
4437 None
4438 }
4439 });
4440
4441 let mut indent_on_newline = IndentSize::spaces(0);
4442 let mut indent_on_extra_newline = IndentSize::spaces(0);
4443
4444 let doc_delimiter = maybe!({
4445 if !selection_is_empty {
4446 return None;
4447 }
4448
4449 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4450 return None;
4451 }
4452
4453 let BlockCommentConfig {
4454 start: start_tag,
4455 end: end_tag,
4456 prefix: delimiter,
4457 tab_size: len,
4458 } = language.documentation_comment()?;
4459 let is_within_block_comment = buffer
4460 .language_scope_at(start_point)
4461 .is_some_and(|scope| scope.override_name() == Some("comment"));
4462 if !is_within_block_comment {
4463 return None;
4464 }
4465
4466 let (snapshot, range) =
4467 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4468
4469 let num_of_whitespaces = snapshot
4470 .chars_for_range(range.clone())
4471 .take_while(|c| c.is_whitespace())
4472 .count();
4473
4474 // 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.
4475 let column = start_point.column;
4476 let cursor_is_after_start_tag = {
4477 let start_tag_len = start_tag.len();
4478 let start_tag_line = snapshot
4479 .chars_for_range(range.clone())
4480 .skip(num_of_whitespaces)
4481 .take(start_tag_len)
4482 .collect::<String>();
4483 if start_tag_line.starts_with(start_tag.as_ref()) {
4484 num_of_whitespaces + start_tag_len <= column as usize
4485 } else {
4486 false
4487 }
4488 };
4489
4490 let cursor_is_after_delimiter = {
4491 let delimiter_trim = delimiter.trim_end();
4492 let delimiter_line = snapshot
4493 .chars_for_range(range.clone())
4494 .skip(num_of_whitespaces)
4495 .take(delimiter_trim.len())
4496 .collect::<String>();
4497 if delimiter_line.starts_with(delimiter_trim) {
4498 num_of_whitespaces + delimiter_trim.len() <= column as usize
4499 } else {
4500 false
4501 }
4502 };
4503
4504 let cursor_is_before_end_tag_if_exists = {
4505 let mut char_position = 0u32;
4506 let mut end_tag_offset = None;
4507
4508 'outer: for chunk in snapshot.text_for_range(range.clone()) {
4509 if let Some(byte_pos) = chunk.find(&**end_tag) {
4510 let chars_before_match =
4511 chunk[..byte_pos].chars().count() as u32;
4512 end_tag_offset =
4513 Some(char_position + chars_before_match);
4514 break 'outer;
4515 }
4516 char_position += chunk.chars().count() as u32;
4517 }
4518
4519 if let Some(end_tag_offset) = end_tag_offset {
4520 let cursor_is_before_end_tag = column <= end_tag_offset;
4521 if cursor_is_after_start_tag {
4522 if cursor_is_before_end_tag {
4523 insert_extra_newline = true;
4524 }
4525 let cursor_is_at_start_of_end_tag =
4526 column == end_tag_offset;
4527 if cursor_is_at_start_of_end_tag {
4528 indent_on_extra_newline.len = *len;
4529 }
4530 }
4531 cursor_is_before_end_tag
4532 } else {
4533 true
4534 }
4535 };
4536
4537 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4538 && cursor_is_before_end_tag_if_exists
4539 {
4540 if cursor_is_after_start_tag {
4541 indent_on_newline.len = *len;
4542 }
4543 Some(delimiter.clone())
4544 } else {
4545 None
4546 }
4547 });
4548
4549 (
4550 comment_delimiter,
4551 doc_delimiter,
4552 insert_extra_newline,
4553 indent_on_newline,
4554 indent_on_extra_newline,
4555 )
4556 } else {
4557 (
4558 None,
4559 None,
4560 false,
4561 IndentSize::default(),
4562 IndentSize::default(),
4563 )
4564 };
4565
4566 let prevent_auto_indent = doc_delimiter.is_some();
4567 let delimiter = comment_delimiter.or(doc_delimiter);
4568
4569 let capacity_for_delimiter =
4570 delimiter.as_deref().map(str::len).unwrap_or_default();
4571 let mut new_text = String::with_capacity(
4572 1 + capacity_for_delimiter
4573 + existing_indent.len as usize
4574 + indent_on_newline.len as usize
4575 + indent_on_extra_newline.len as usize,
4576 );
4577 new_text.push('\n');
4578 new_text.extend(existing_indent.chars());
4579 new_text.extend(indent_on_newline.chars());
4580
4581 if let Some(delimiter) = &delimiter {
4582 new_text.push_str(delimiter);
4583 }
4584
4585 if insert_extra_newline {
4586 new_text.push('\n');
4587 new_text.extend(existing_indent.chars());
4588 new_text.extend(indent_on_extra_newline.chars());
4589 }
4590
4591 let anchor = buffer.anchor_after(end);
4592 let new_selection = selection.map(|_| anchor);
4593 (
4594 ((start..end, new_text), prevent_auto_indent),
4595 (insert_extra_newline, new_selection),
4596 )
4597 })
4598 .unzip()
4599 };
4600
4601 let mut auto_indent_edits = Vec::new();
4602 let mut edits = Vec::new();
4603 for (edit, prevent_auto_indent) in edits_with_flags {
4604 if prevent_auto_indent {
4605 edits.push(edit);
4606 } else {
4607 auto_indent_edits.push(edit);
4608 }
4609 }
4610 if !edits.is_empty() {
4611 this.edit(edits, cx);
4612 }
4613 if !auto_indent_edits.is_empty() {
4614 this.edit_with_autoindent(auto_indent_edits, cx);
4615 }
4616
4617 let buffer = this.buffer.read(cx).snapshot(cx);
4618 let new_selections = selection_info
4619 .into_iter()
4620 .map(|(extra_newline_inserted, new_selection)| {
4621 let mut cursor = new_selection.end.to_point(&buffer);
4622 if extra_newline_inserted {
4623 cursor.row -= 1;
4624 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4625 }
4626 new_selection.map(|_| cursor)
4627 })
4628 .collect();
4629
4630 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4631 this.refresh_inline_completion(true, false, window, cx);
4632 });
4633 }
4634
4635 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4636 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4637
4638 let buffer = self.buffer.read(cx);
4639 let snapshot = buffer.snapshot(cx);
4640
4641 let mut edits = Vec::new();
4642 let mut rows = Vec::new();
4643
4644 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4645 let cursor = selection.head();
4646 let row = cursor.row;
4647
4648 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4649
4650 let newline = "\n".to_string();
4651 edits.push((start_of_line..start_of_line, newline));
4652
4653 rows.push(row + rows_inserted as u32);
4654 }
4655
4656 self.transact(window, cx, |editor, window, cx| {
4657 editor.edit(edits, cx);
4658
4659 editor.change_selections(Default::default(), window, cx, |s| {
4660 let mut index = 0;
4661 s.move_cursors_with(|map, _, _| {
4662 let row = rows[index];
4663 index += 1;
4664
4665 let point = Point::new(row, 0);
4666 let boundary = map.next_line_boundary(point).1;
4667 let clipped = map.clip_point(boundary, Bias::Left);
4668
4669 (clipped, SelectionGoal::None)
4670 });
4671 });
4672
4673 let mut indent_edits = Vec::new();
4674 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4675 for row in rows {
4676 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4677 for (row, indent) in indents {
4678 if indent.len == 0 {
4679 continue;
4680 }
4681
4682 let text = match indent.kind {
4683 IndentKind::Space => " ".repeat(indent.len as usize),
4684 IndentKind::Tab => "\t".repeat(indent.len as usize),
4685 };
4686 let point = Point::new(row.0, 0);
4687 indent_edits.push((point..point, text));
4688 }
4689 }
4690 editor.edit(indent_edits, cx);
4691 });
4692 }
4693
4694 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4695 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4696
4697 let buffer = self.buffer.read(cx);
4698 let snapshot = buffer.snapshot(cx);
4699
4700 let mut edits = Vec::new();
4701 let mut rows = Vec::new();
4702 let mut rows_inserted = 0;
4703
4704 for selection in self.selections.all_adjusted(cx) {
4705 let cursor = selection.head();
4706 let row = cursor.row;
4707
4708 let point = Point::new(row + 1, 0);
4709 let start_of_line = snapshot.clip_point(point, Bias::Left);
4710
4711 let newline = "\n".to_string();
4712 edits.push((start_of_line..start_of_line, newline));
4713
4714 rows_inserted += 1;
4715 rows.push(row + rows_inserted);
4716 }
4717
4718 self.transact(window, cx, |editor, window, cx| {
4719 editor.edit(edits, cx);
4720
4721 editor.change_selections(Default::default(), window, cx, |s| {
4722 let mut index = 0;
4723 s.move_cursors_with(|map, _, _| {
4724 let row = rows[index];
4725 index += 1;
4726
4727 let point = Point::new(row, 0);
4728 let boundary = map.next_line_boundary(point).1;
4729 let clipped = map.clip_point(boundary, Bias::Left);
4730
4731 (clipped, SelectionGoal::None)
4732 });
4733 });
4734
4735 let mut indent_edits = Vec::new();
4736 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4737 for row in rows {
4738 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4739 for (row, indent) in indents {
4740 if indent.len == 0 {
4741 continue;
4742 }
4743
4744 let text = match indent.kind {
4745 IndentKind::Space => " ".repeat(indent.len as usize),
4746 IndentKind::Tab => "\t".repeat(indent.len as usize),
4747 };
4748 let point = Point::new(row.0, 0);
4749 indent_edits.push((point..point, text));
4750 }
4751 }
4752 editor.edit(indent_edits, cx);
4753 });
4754 }
4755
4756 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4757 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4758 original_indent_columns: Vec::new(),
4759 });
4760 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4761 }
4762
4763 fn insert_with_autoindent_mode(
4764 &mut self,
4765 text: &str,
4766 autoindent_mode: Option<AutoindentMode>,
4767 window: &mut Window,
4768 cx: &mut Context<Self>,
4769 ) {
4770 if self.read_only(cx) {
4771 return;
4772 }
4773
4774 let text: Arc<str> = text.into();
4775 self.transact(window, cx, |this, window, cx| {
4776 let old_selections = this.selections.all_adjusted(cx);
4777 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4778 let anchors = {
4779 let snapshot = buffer.read(cx);
4780 old_selections
4781 .iter()
4782 .map(|s| {
4783 let anchor = snapshot.anchor_after(s.head());
4784 s.map(|_| anchor)
4785 })
4786 .collect::<Vec<_>>()
4787 };
4788 buffer.edit(
4789 old_selections
4790 .iter()
4791 .map(|s| (s.start..s.end, text.clone())),
4792 autoindent_mode,
4793 cx,
4794 );
4795 anchors
4796 });
4797
4798 this.change_selections(Default::default(), window, cx, |s| {
4799 s.select_anchors(selection_anchors);
4800 });
4801
4802 cx.notify();
4803 });
4804 }
4805
4806 fn trigger_completion_on_input(
4807 &mut self,
4808 text: &str,
4809 trigger_in_words: bool,
4810 window: &mut Window,
4811 cx: &mut Context<Self>,
4812 ) {
4813 let completions_source = self
4814 .context_menu
4815 .borrow()
4816 .as_ref()
4817 .and_then(|menu| match menu {
4818 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4819 CodeContextMenu::CodeActions(_) => None,
4820 });
4821
4822 match completions_source {
4823 Some(CompletionsMenuSource::Words) => {
4824 self.show_word_completions(&ShowWordCompletions, window, cx)
4825 }
4826 Some(CompletionsMenuSource::Normal)
4827 | Some(CompletionsMenuSource::SnippetChoices)
4828 | None
4829 if self.is_completion_trigger(
4830 text,
4831 trigger_in_words,
4832 completions_source.is_some(),
4833 cx,
4834 ) =>
4835 {
4836 self.show_completions(
4837 &ShowCompletions {
4838 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4839 },
4840 window,
4841 cx,
4842 )
4843 }
4844 _ => {
4845 self.hide_context_menu(window, cx);
4846 }
4847 }
4848 }
4849
4850 fn is_completion_trigger(
4851 &self,
4852 text: &str,
4853 trigger_in_words: bool,
4854 menu_is_open: bool,
4855 cx: &mut Context<Self>,
4856 ) -> bool {
4857 let position = self.selections.newest_anchor().head();
4858 let multibuffer = self.buffer.read(cx);
4859 let Some(buffer) = position
4860 .buffer_id
4861 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
4862 else {
4863 return false;
4864 };
4865
4866 if let Some(completion_provider) = &self.completion_provider {
4867 completion_provider.is_completion_trigger(
4868 &buffer,
4869 position.text_anchor,
4870 text,
4871 trigger_in_words,
4872 menu_is_open,
4873 cx,
4874 )
4875 } else {
4876 false
4877 }
4878 }
4879
4880 /// If any empty selections is touching the start of its innermost containing autoclose
4881 /// region, expand it to select the brackets.
4882 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4883 let selections = self.selections.all::<usize>(cx);
4884 let buffer = self.buffer.read(cx).read(cx);
4885 let new_selections = self
4886 .selections_with_autoclose_regions(selections, &buffer)
4887 .map(|(mut selection, region)| {
4888 if !selection.is_empty() {
4889 return selection;
4890 }
4891
4892 if let Some(region) = region {
4893 let mut range = region.range.to_offset(&buffer);
4894 if selection.start == range.start && range.start >= region.pair.start.len() {
4895 range.start -= region.pair.start.len();
4896 if buffer.contains_str_at(range.start, ®ion.pair.start)
4897 && buffer.contains_str_at(range.end, ®ion.pair.end)
4898 {
4899 range.end += region.pair.end.len();
4900 selection.start = range.start;
4901 selection.end = range.end;
4902
4903 return selection;
4904 }
4905 }
4906 }
4907
4908 let always_treat_brackets_as_autoclosed = buffer
4909 .language_settings_at(selection.start, cx)
4910 .always_treat_brackets_as_autoclosed;
4911
4912 if !always_treat_brackets_as_autoclosed {
4913 return selection;
4914 }
4915
4916 if let Some(scope) = buffer.language_scope_at(selection.start) {
4917 for (pair, enabled) in scope.brackets() {
4918 if !enabled || !pair.close {
4919 continue;
4920 }
4921
4922 if buffer.contains_str_at(selection.start, &pair.end) {
4923 let pair_start_len = pair.start.len();
4924 if buffer.contains_str_at(
4925 selection.start.saturating_sub(pair_start_len),
4926 &pair.start,
4927 ) {
4928 selection.start -= pair_start_len;
4929 selection.end += pair.end.len();
4930
4931 return selection;
4932 }
4933 }
4934 }
4935 }
4936
4937 selection
4938 })
4939 .collect();
4940
4941 drop(buffer);
4942 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
4943 selections.select(new_selections)
4944 });
4945 }
4946
4947 /// Iterate the given selections, and for each one, find the smallest surrounding
4948 /// autoclose region. This uses the ordering of the selections and the autoclose
4949 /// regions to avoid repeated comparisons.
4950 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
4951 &'a self,
4952 selections: impl IntoIterator<Item = Selection<D>>,
4953 buffer: &'a MultiBufferSnapshot,
4954 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
4955 let mut i = 0;
4956 let mut regions = self.autoclose_regions.as_slice();
4957 selections.into_iter().map(move |selection| {
4958 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
4959
4960 let mut enclosing = None;
4961 while let Some(pair_state) = regions.get(i) {
4962 if pair_state.range.end.to_offset(buffer) < range.start {
4963 regions = ®ions[i + 1..];
4964 i = 0;
4965 } else if pair_state.range.start.to_offset(buffer) > range.end {
4966 break;
4967 } else {
4968 if pair_state.selection_id == selection.id {
4969 enclosing = Some(pair_state);
4970 }
4971 i += 1;
4972 }
4973 }
4974
4975 (selection, enclosing)
4976 })
4977 }
4978
4979 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
4980 fn invalidate_autoclose_regions(
4981 &mut self,
4982 mut selections: &[Selection<Anchor>],
4983 buffer: &MultiBufferSnapshot,
4984 ) {
4985 self.autoclose_regions.retain(|state| {
4986 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
4987 return false;
4988 }
4989
4990 let mut i = 0;
4991 while let Some(selection) = selections.get(i) {
4992 if selection.end.cmp(&state.range.start, buffer).is_lt() {
4993 selections = &selections[1..];
4994 continue;
4995 }
4996 if selection.start.cmp(&state.range.end, buffer).is_gt() {
4997 break;
4998 }
4999 if selection.id == state.selection_id {
5000 return true;
5001 } else {
5002 i += 1;
5003 }
5004 }
5005 false
5006 });
5007 }
5008
5009 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5010 let offset = position.to_offset(buffer);
5011 let (word_range, kind) = buffer.surrounding_word(offset, true);
5012 if offset > word_range.start && kind == Some(CharKind::Word) {
5013 Some(
5014 buffer
5015 .text_for_range(word_range.start..offset)
5016 .collect::<String>(),
5017 )
5018 } else {
5019 None
5020 }
5021 }
5022
5023 pub fn toggle_inline_values(
5024 &mut self,
5025 _: &ToggleInlineValues,
5026 _: &mut Window,
5027 cx: &mut Context<Self>,
5028 ) {
5029 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
5030
5031 self.refresh_inline_values(cx);
5032 }
5033
5034 pub fn toggle_inlay_hints(
5035 &mut self,
5036 _: &ToggleInlayHints,
5037 _: &mut Window,
5038 cx: &mut Context<Self>,
5039 ) {
5040 self.refresh_inlay_hints(
5041 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
5042 cx,
5043 );
5044 }
5045
5046 pub fn inlay_hints_enabled(&self) -> bool {
5047 self.inlay_hint_cache.enabled
5048 }
5049
5050 pub fn inline_values_enabled(&self) -> bool {
5051 self.inline_value_cache.enabled
5052 }
5053
5054 #[cfg(any(test, feature = "test-support"))]
5055 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
5056 self.display_map
5057 .read(cx)
5058 .current_inlays()
5059 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
5060 .cloned()
5061 .collect()
5062 }
5063
5064 #[cfg(any(test, feature = "test-support"))]
5065 pub fn all_inlays(&self, cx: &App) -> Vec<Inlay> {
5066 self.display_map
5067 .read(cx)
5068 .current_inlays()
5069 .cloned()
5070 .collect()
5071 }
5072
5073 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
5074 if self.semantics_provider.is_none() || !self.mode.is_full() {
5075 return;
5076 }
5077
5078 let reason_description = reason.description();
5079 let ignore_debounce = matches!(
5080 reason,
5081 InlayHintRefreshReason::SettingsChange(_)
5082 | InlayHintRefreshReason::Toggle(_)
5083 | InlayHintRefreshReason::ExcerptsRemoved(_)
5084 | InlayHintRefreshReason::ModifiersChanged(_)
5085 );
5086 let (invalidate_cache, required_languages) = match reason {
5087 InlayHintRefreshReason::ModifiersChanged(enabled) => {
5088 match self.inlay_hint_cache.modifiers_override(enabled) {
5089 Some(enabled) => {
5090 if enabled {
5091 (InvalidationStrategy::RefreshRequested, None)
5092 } else {
5093 self.splice_inlays(
5094 &self
5095 .visible_inlay_hints(cx)
5096 .iter()
5097 .map(|inlay| inlay.id)
5098 .collect::<Vec<InlayId>>(),
5099 Vec::new(),
5100 cx,
5101 );
5102 return;
5103 }
5104 }
5105 None => return,
5106 }
5107 }
5108 InlayHintRefreshReason::Toggle(enabled) => {
5109 if self.inlay_hint_cache.toggle(enabled) {
5110 if enabled {
5111 (InvalidationStrategy::RefreshRequested, None)
5112 } else {
5113 self.splice_inlays(
5114 &self
5115 .visible_inlay_hints(cx)
5116 .iter()
5117 .map(|inlay| inlay.id)
5118 .collect::<Vec<InlayId>>(),
5119 Vec::new(),
5120 cx,
5121 );
5122 return;
5123 }
5124 } else {
5125 return;
5126 }
5127 }
5128 InlayHintRefreshReason::SettingsChange(new_settings) => {
5129 match self.inlay_hint_cache.update_settings(
5130 &self.buffer,
5131 new_settings,
5132 self.visible_inlay_hints(cx),
5133 cx,
5134 ) {
5135 ControlFlow::Break(Some(InlaySplice {
5136 to_remove,
5137 to_insert,
5138 })) => {
5139 self.splice_inlays(&to_remove, to_insert, cx);
5140 return;
5141 }
5142 ControlFlow::Break(None) => return,
5143 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
5144 }
5145 }
5146 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
5147 if let Some(InlaySplice {
5148 to_remove,
5149 to_insert,
5150 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
5151 {
5152 self.splice_inlays(&to_remove, to_insert, cx);
5153 }
5154 self.display_map.update(cx, |display_map, _| {
5155 display_map.remove_inlays_for_excerpts(&excerpts_removed)
5156 });
5157 return;
5158 }
5159 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
5160 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
5161 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
5162 }
5163 InlayHintRefreshReason::RefreshRequested => {
5164 (InvalidationStrategy::RefreshRequested, None)
5165 }
5166 };
5167
5168 if let Some(InlaySplice {
5169 to_remove,
5170 to_insert,
5171 }) = self.inlay_hint_cache.spawn_hint_refresh(
5172 reason_description,
5173 self.visible_excerpts(required_languages.as_ref(), cx),
5174 invalidate_cache,
5175 ignore_debounce,
5176 cx,
5177 ) {
5178 self.splice_inlays(&to_remove, to_insert, cx);
5179 }
5180 }
5181
5182 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
5183 self.display_map
5184 .read(cx)
5185 .current_inlays()
5186 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
5187 .cloned()
5188 .collect()
5189 }
5190
5191 pub fn visible_excerpts(
5192 &self,
5193 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
5194 cx: &mut Context<Editor>,
5195 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5196 let Some(project) = self.project.as_ref() else {
5197 return HashMap::default();
5198 };
5199 let project = project.read(cx);
5200 let multi_buffer = self.buffer().read(cx);
5201 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5202 let multi_buffer_visible_start = self
5203 .scroll_manager
5204 .anchor()
5205 .anchor
5206 .to_point(&multi_buffer_snapshot);
5207 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5208 multi_buffer_visible_start
5209 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5210 Bias::Left,
5211 );
5212 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
5213 multi_buffer_snapshot
5214 .range_to_buffer_ranges(multi_buffer_visible_range)
5215 .into_iter()
5216 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5217 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5218 let buffer_file = project::File::from_dyn(buffer.file())?;
5219 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5220 let worktree_entry = buffer_worktree
5221 .read(cx)
5222 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
5223 if worktree_entry.is_ignored {
5224 return None;
5225 }
5226
5227 let language = buffer.language()?;
5228 if let Some(restrict_to_languages) = restrict_to_languages {
5229 if !restrict_to_languages.contains(language) {
5230 return None;
5231 }
5232 }
5233 Some((
5234 excerpt_id,
5235 (
5236 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5237 buffer.version().clone(),
5238 excerpt_visible_range,
5239 ),
5240 ))
5241 })
5242 .collect()
5243 }
5244
5245 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5246 TextLayoutDetails {
5247 text_system: window.text_system().clone(),
5248 editor_style: self.style.clone().unwrap(),
5249 rem_size: window.rem_size(),
5250 scroll_anchor: self.scroll_manager.anchor(),
5251 visible_rows: self.visible_line_count(),
5252 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5253 }
5254 }
5255
5256 pub fn splice_inlays(
5257 &self,
5258 to_remove: &[InlayId],
5259 to_insert: Vec<Inlay>,
5260 cx: &mut Context<Self>,
5261 ) {
5262 self.display_map.update(cx, |display_map, cx| {
5263 display_map.splice_inlays(to_remove, to_insert, cx)
5264 });
5265 cx.notify();
5266 }
5267
5268 fn trigger_on_type_formatting(
5269 &self,
5270 input: String,
5271 window: &mut Window,
5272 cx: &mut Context<Self>,
5273 ) -> Option<Task<Result<()>>> {
5274 if input.len() != 1 {
5275 return None;
5276 }
5277
5278 let project = self.project.as_ref()?;
5279 let position = self.selections.newest_anchor().head();
5280 let (buffer, buffer_position) = self
5281 .buffer
5282 .read(cx)
5283 .text_anchor_for_position(position, cx)?;
5284
5285 let settings = language_settings::language_settings(
5286 buffer
5287 .read(cx)
5288 .language_at(buffer_position)
5289 .map(|l| l.name()),
5290 buffer.read(cx).file(),
5291 cx,
5292 );
5293 if !settings.use_on_type_format {
5294 return None;
5295 }
5296
5297 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5298 // hence we do LSP request & edit on host side only — add formats to host's history.
5299 let push_to_lsp_host_history = true;
5300 // If this is not the host, append its history with new edits.
5301 let push_to_client_history = project.read(cx).is_via_collab();
5302
5303 let on_type_formatting = project.update(cx, |project, cx| {
5304 project.on_type_format(
5305 buffer.clone(),
5306 buffer_position,
5307 input,
5308 push_to_lsp_host_history,
5309 cx,
5310 )
5311 });
5312 Some(cx.spawn_in(window, async move |editor, cx| {
5313 if let Some(transaction) = on_type_formatting.await? {
5314 if push_to_client_history {
5315 buffer
5316 .update(cx, |buffer, _| {
5317 buffer.push_transaction(transaction, Instant::now());
5318 buffer.finalize_last_transaction();
5319 })
5320 .ok();
5321 }
5322 editor.update(cx, |editor, cx| {
5323 editor.refresh_document_highlights(cx);
5324 })?;
5325 }
5326 Ok(())
5327 }))
5328 }
5329
5330 pub fn show_word_completions(
5331 &mut self,
5332 _: &ShowWordCompletions,
5333 window: &mut Window,
5334 cx: &mut Context<Self>,
5335 ) {
5336 self.open_or_update_completions_menu(Some(CompletionsMenuSource::Words), None, window, cx);
5337 }
5338
5339 pub fn show_completions(
5340 &mut self,
5341 options: &ShowCompletions,
5342 window: &mut Window,
5343 cx: &mut Context<Self>,
5344 ) {
5345 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5346 }
5347
5348 fn open_or_update_completions_menu(
5349 &mut self,
5350 requested_source: Option<CompletionsMenuSource>,
5351 trigger: Option<&str>,
5352 window: &mut Window,
5353 cx: &mut Context<Self>,
5354 ) {
5355 if self.pending_rename.is_some() {
5356 return;
5357 }
5358
5359 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5360
5361 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5362 // inserted and selected. To handle that case, the start of the selection is used so that
5363 // the menu starts with all choices.
5364 let position = self
5365 .selections
5366 .newest_anchor()
5367 .start
5368 .bias_right(&multibuffer_snapshot);
5369 if position.diff_base_anchor.is_some() {
5370 return;
5371 }
5372 let (buffer, buffer_position) =
5373 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
5374 output
5375 } else {
5376 return;
5377 };
5378 let buffer_snapshot = buffer.read(cx).snapshot();
5379
5380 let query: Option<Arc<String>> =
5381 Self::completion_query(&multibuffer_snapshot, position).map(|query| query.into());
5382
5383 drop(multibuffer_snapshot);
5384
5385 let provider = match requested_source {
5386 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5387 Some(CompletionsMenuSource::Words) => None,
5388 Some(CompletionsMenuSource::SnippetChoices) => {
5389 log::error!("bug: SnippetChoices requested_source is not handled");
5390 None
5391 }
5392 };
5393
5394 let sort_completions = provider
5395 .as_ref()
5396 .map_or(false, |provider| provider.sort_completions());
5397
5398 let filter_completions = provider
5399 .as_ref()
5400 .map_or(true, |provider| provider.filter_completions());
5401
5402 let trigger_kind = match trigger {
5403 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5404 CompletionTriggerKind::TRIGGER_CHARACTER
5405 }
5406 _ => CompletionTriggerKind::INVOKED,
5407 };
5408 let completion_context = CompletionContext {
5409 trigger_character: trigger.and_then(|trigger| {
5410 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5411 Some(String::from(trigger))
5412 } else {
5413 None
5414 }
5415 }),
5416 trigger_kind,
5417 };
5418
5419 // Hide the current completions menu when a trigger char is typed. Without this, cached
5420 // completions from before the trigger char may be reused (#32774). Snippet choices could
5421 // involve trigger chars, so this is skipped in that case.
5422 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER && self.snippet_stack.is_empty()
5423 {
5424 let menu_is_open = matches!(
5425 self.context_menu.borrow().as_ref(),
5426 Some(CodeContextMenu::Completions(_))
5427 );
5428 if menu_is_open {
5429 self.hide_context_menu(window, cx);
5430 }
5431 }
5432
5433 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5434 if filter_completions {
5435 menu.filter(query.clone(), provider.clone(), window, cx);
5436 }
5437 // When `is_incomplete` is false, no need to re-query completions when the current query
5438 // is a suffix of the initial query.
5439 if !menu.is_incomplete {
5440 // If the new query is a suffix of the old query (typing more characters) and
5441 // the previous result was complete, the existing completions can be filtered.
5442 //
5443 // Note that this is always true for snippet completions.
5444 let query_matches = match (&menu.initial_query, &query) {
5445 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5446 (None, _) => true,
5447 _ => false,
5448 };
5449 if query_matches {
5450 let position_matches = if menu.initial_position == position {
5451 true
5452 } else {
5453 let snapshot = self.buffer.read(cx).read(cx);
5454 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5455 };
5456 if position_matches {
5457 return;
5458 }
5459 }
5460 }
5461 };
5462
5463 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5464 buffer_snapshot.surrounding_word(buffer_position, false)
5465 {
5466 let word_to_exclude = buffer_snapshot
5467 .text_for_range(word_range.clone())
5468 .collect::<String>();
5469 (
5470 buffer_snapshot.anchor_before(word_range.start)
5471 ..buffer_snapshot.anchor_after(buffer_position),
5472 Some(word_to_exclude),
5473 )
5474 } else {
5475 (buffer_position..buffer_position, None)
5476 };
5477
5478 let language = buffer_snapshot
5479 .language_at(buffer_position)
5480 .map(|language| language.name());
5481
5482 let completion_settings =
5483 language_settings(language.clone(), buffer_snapshot.file(), cx).completions;
5484
5485 let show_completion_documentation = buffer_snapshot
5486 .settings_at(buffer_position, cx)
5487 .show_completion_documentation;
5488
5489 // The document can be large, so stay in reasonable bounds when searching for words,
5490 // otherwise completion pop-up might be slow to appear.
5491 const WORD_LOOKUP_ROWS: u32 = 5_000;
5492 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5493 let min_word_search = buffer_snapshot.clip_point(
5494 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5495 Bias::Left,
5496 );
5497 let max_word_search = buffer_snapshot.clip_point(
5498 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5499 Bias::Right,
5500 );
5501 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5502 ..buffer_snapshot.point_to_offset(max_word_search);
5503
5504 let skip_digits = query
5505 .as_ref()
5506 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
5507
5508 let (mut words, provider_responses) = match &provider {
5509 Some(provider) => {
5510 let provider_responses = provider.completions(
5511 position.excerpt_id,
5512 &buffer,
5513 buffer_position,
5514 completion_context,
5515 window,
5516 cx,
5517 );
5518
5519 let words = match completion_settings.words {
5520 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
5521 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
5522 .background_spawn(async move {
5523 buffer_snapshot.words_in_range(WordsQuery {
5524 fuzzy_contents: None,
5525 range: word_search_range,
5526 skip_digits,
5527 })
5528 }),
5529 };
5530
5531 (words, provider_responses)
5532 }
5533 None => (
5534 cx.background_spawn(async move {
5535 buffer_snapshot.words_in_range(WordsQuery {
5536 fuzzy_contents: None,
5537 range: word_search_range,
5538 skip_digits,
5539 })
5540 }),
5541 Task::ready(Ok(Vec::new())),
5542 ),
5543 };
5544
5545 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5546
5547 let id = post_inc(&mut self.next_completion_id);
5548 let task = cx.spawn_in(window, async move |editor, cx| {
5549 let Ok(()) = editor.update(cx, |this, _| {
5550 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5551 }) else {
5552 return;
5553 };
5554
5555 // TODO: Ideally completions from different sources would be selectively re-queried, so
5556 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5557 let mut completions = Vec::new();
5558 let mut is_incomplete = false;
5559 if let Some(provider_responses) = provider_responses.await.log_err() {
5560 if !provider_responses.is_empty() {
5561 for response in provider_responses {
5562 completions.extend(response.completions);
5563 is_incomplete = is_incomplete || response.is_incomplete;
5564 }
5565 if completion_settings.words == WordsCompletionMode::Fallback {
5566 words = Task::ready(BTreeMap::default());
5567 }
5568 }
5569 }
5570
5571 let mut words = words.await;
5572 if let Some(word_to_exclude) = &word_to_exclude {
5573 words.remove(word_to_exclude);
5574 }
5575 for lsp_completion in &completions {
5576 words.remove(&lsp_completion.new_text);
5577 }
5578 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5579 replace_range: word_replace_range.clone(),
5580 new_text: word.clone(),
5581 label: CodeLabel::plain(word, None),
5582 icon_path: None,
5583 documentation: None,
5584 source: CompletionSource::BufferWord {
5585 word_range,
5586 resolved: false,
5587 },
5588 insert_text_mode: Some(InsertTextMode::AS_IS),
5589 confirm: None,
5590 }));
5591
5592 let menu = if completions.is_empty() {
5593 None
5594 } else {
5595 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5596 let languages = editor
5597 .workspace
5598 .as_ref()
5599 .and_then(|(workspace, _)| workspace.upgrade())
5600 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5601 let menu = CompletionsMenu::new(
5602 id,
5603 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5604 sort_completions,
5605 show_completion_documentation,
5606 position,
5607 query.clone(),
5608 is_incomplete,
5609 buffer.clone(),
5610 completions.into(),
5611 snippet_sort_order,
5612 languages,
5613 language,
5614 cx,
5615 );
5616
5617 let query = if filter_completions { query } else { None };
5618 let matches_task = if let Some(query) = query {
5619 menu.do_async_filtering(query, cx)
5620 } else {
5621 Task::ready(menu.unfiltered_matches())
5622 };
5623 (menu, matches_task)
5624 }) else {
5625 return;
5626 };
5627
5628 let matches = matches_task.await;
5629
5630 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5631 // Newer menu already set, so exit.
5632 match editor.context_menu.borrow().as_ref() {
5633 Some(CodeContextMenu::Completions(prev_menu)) => {
5634 if prev_menu.id > id {
5635 return;
5636 }
5637 }
5638 _ => {}
5639 };
5640
5641 // Only valid to take prev_menu because it the new menu is immediately set
5642 // below, or the menu is hidden.
5643 match editor.context_menu.borrow_mut().take() {
5644 Some(CodeContextMenu::Completions(prev_menu)) => {
5645 let position_matches =
5646 if prev_menu.initial_position == menu.initial_position {
5647 true
5648 } else {
5649 let snapshot = editor.buffer.read(cx).read(cx);
5650 prev_menu.initial_position.to_offset(&snapshot)
5651 == menu.initial_position.to_offset(&snapshot)
5652 };
5653 if position_matches {
5654 // Preserve markdown cache before `set_filter_results` because it will
5655 // try to populate the documentation cache.
5656 menu.preserve_markdown_cache(prev_menu);
5657 }
5658 }
5659 _ => {}
5660 };
5661
5662 menu.set_filter_results(matches, provider, window, cx);
5663 }) else {
5664 return;
5665 };
5666
5667 menu.visible().then_some(menu)
5668 };
5669
5670 editor
5671 .update_in(cx, |editor, window, cx| {
5672 if editor.focus_handle.is_focused(window) {
5673 if let Some(menu) = menu {
5674 *editor.context_menu.borrow_mut() =
5675 Some(CodeContextMenu::Completions(menu));
5676
5677 crate::hover_popover::hide_hover(editor, cx);
5678 if editor.show_edit_predictions_in_menu() {
5679 editor.update_visible_inline_completion(window, cx);
5680 } else {
5681 editor.discard_inline_completion(false, cx);
5682 }
5683
5684 cx.notify();
5685 return;
5686 }
5687 }
5688
5689 if editor.completion_tasks.len() <= 1 {
5690 // If there are no more completion tasks and the last menu was empty, we should hide it.
5691 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5692 // If it was already hidden and we don't show inline completions in the menu, we should
5693 // also show the inline-completion when available.
5694 if was_hidden && editor.show_edit_predictions_in_menu() {
5695 editor.update_visible_inline_completion(window, cx);
5696 }
5697 }
5698 })
5699 .ok();
5700 });
5701
5702 self.completion_tasks.push((id, task));
5703 }
5704
5705 #[cfg(feature = "test-support")]
5706 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5707 let menu = self.context_menu.borrow();
5708 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5709 let completions = menu.completions.borrow();
5710 Some(completions.to_vec())
5711 } else {
5712 None
5713 }
5714 }
5715
5716 pub fn with_completions_menu_matching_id<R>(
5717 &self,
5718 id: CompletionId,
5719 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5720 ) -> R {
5721 let mut context_menu = self.context_menu.borrow_mut();
5722 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5723 return f(None);
5724 };
5725 if completions_menu.id != id {
5726 return f(None);
5727 }
5728 f(Some(completions_menu))
5729 }
5730
5731 pub fn confirm_completion(
5732 &mut self,
5733 action: &ConfirmCompletion,
5734 window: &mut Window,
5735 cx: &mut Context<Self>,
5736 ) -> Option<Task<Result<()>>> {
5737 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5738 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5739 }
5740
5741 pub fn confirm_completion_insert(
5742 &mut self,
5743 _: &ConfirmCompletionInsert,
5744 window: &mut Window,
5745 cx: &mut Context<Self>,
5746 ) -> Option<Task<Result<()>>> {
5747 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5748 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5749 }
5750
5751 pub fn confirm_completion_replace(
5752 &mut self,
5753 _: &ConfirmCompletionReplace,
5754 window: &mut Window,
5755 cx: &mut Context<Self>,
5756 ) -> Option<Task<Result<()>>> {
5757 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5758 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5759 }
5760
5761 pub fn compose_completion(
5762 &mut self,
5763 action: &ComposeCompletion,
5764 window: &mut Window,
5765 cx: &mut Context<Self>,
5766 ) -> Option<Task<Result<()>>> {
5767 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5768 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5769 }
5770
5771 fn do_completion(
5772 &mut self,
5773 item_ix: Option<usize>,
5774 intent: CompletionIntent,
5775 window: &mut Window,
5776 cx: &mut Context<Editor>,
5777 ) -> Option<Task<Result<()>>> {
5778 use language::ToOffset as _;
5779
5780 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5781 else {
5782 return None;
5783 };
5784
5785 let candidate_id = {
5786 let entries = completions_menu.entries.borrow();
5787 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5788 if self.show_edit_predictions_in_menu() {
5789 self.discard_inline_completion(true, cx);
5790 }
5791 mat.candidate_id
5792 };
5793
5794 let completion = completions_menu
5795 .completions
5796 .borrow()
5797 .get(candidate_id)?
5798 .clone();
5799 cx.stop_propagation();
5800
5801 let buffer_handle = completions_menu.buffer.clone();
5802
5803 let CompletionEdit {
5804 new_text,
5805 snippet,
5806 replace_range,
5807 } = process_completion_for_edit(
5808 &completion,
5809 intent,
5810 &buffer_handle,
5811 &completions_menu.initial_position.text_anchor,
5812 cx,
5813 );
5814
5815 let buffer = buffer_handle.read(cx);
5816 let snapshot = self.buffer.read(cx).snapshot(cx);
5817 let newest_anchor = self.selections.newest_anchor();
5818 let replace_range_multibuffer = {
5819 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5820 let multibuffer_anchor = snapshot
5821 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5822 .unwrap()
5823 ..snapshot
5824 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5825 .unwrap();
5826 multibuffer_anchor.start.to_offset(&snapshot)
5827 ..multibuffer_anchor.end.to_offset(&snapshot)
5828 };
5829 if newest_anchor.head().buffer_id != Some(buffer.remote_id()) {
5830 return None;
5831 }
5832
5833 let old_text = buffer
5834 .text_for_range(replace_range.clone())
5835 .collect::<String>();
5836 let lookbehind = newest_anchor
5837 .start
5838 .text_anchor
5839 .to_offset(buffer)
5840 .saturating_sub(replace_range.start);
5841 let lookahead = replace_range
5842 .end
5843 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5844 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5845 let suffix = &old_text[lookbehind.min(old_text.len())..];
5846
5847 let selections = self.selections.all::<usize>(cx);
5848 let mut ranges = Vec::new();
5849 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5850
5851 for selection in &selections {
5852 let range = if selection.id == newest_anchor.id {
5853 replace_range_multibuffer.clone()
5854 } else {
5855 let mut range = selection.range();
5856
5857 // if prefix is present, don't duplicate it
5858 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5859 range.start = range.start.saturating_sub(lookbehind);
5860
5861 // if suffix is also present, mimic the newest cursor and replace it
5862 if selection.id != newest_anchor.id
5863 && snapshot.contains_str_at(range.end, suffix)
5864 {
5865 range.end += lookahead;
5866 }
5867 }
5868 range
5869 };
5870
5871 ranges.push(range.clone());
5872
5873 if !self.linked_edit_ranges.is_empty() {
5874 let start_anchor = snapshot.anchor_before(range.start);
5875 let end_anchor = snapshot.anchor_after(range.end);
5876 if let Some(ranges) = self
5877 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5878 {
5879 for (buffer, edits) in ranges {
5880 linked_edits
5881 .entry(buffer.clone())
5882 .or_default()
5883 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5884 }
5885 }
5886 }
5887 }
5888
5889 let common_prefix_len = old_text
5890 .chars()
5891 .zip(new_text.chars())
5892 .take_while(|(a, b)| a == b)
5893 .map(|(a, _)| a.len_utf8())
5894 .sum::<usize>();
5895
5896 cx.emit(EditorEvent::InputHandled {
5897 utf16_range_to_replace: None,
5898 text: new_text[common_prefix_len..].into(),
5899 });
5900
5901 self.transact(window, cx, |editor, window, cx| {
5902 if let Some(mut snippet) = snippet {
5903 snippet.text = new_text.to_string();
5904 editor
5905 .insert_snippet(&ranges, snippet, window, cx)
5906 .log_err();
5907 } else {
5908 editor.buffer.update(cx, |multi_buffer, cx| {
5909 let auto_indent = match completion.insert_text_mode {
5910 Some(InsertTextMode::AS_IS) => None,
5911 _ => editor.autoindent_mode.clone(),
5912 };
5913 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5914 multi_buffer.edit(edits, auto_indent, cx);
5915 });
5916 }
5917 for (buffer, edits) in linked_edits {
5918 buffer.update(cx, |buffer, cx| {
5919 let snapshot = buffer.snapshot();
5920 let edits = edits
5921 .into_iter()
5922 .map(|(range, text)| {
5923 use text::ToPoint as TP;
5924 let end_point = TP::to_point(&range.end, &snapshot);
5925 let start_point = TP::to_point(&range.start, &snapshot);
5926 (start_point..end_point, text)
5927 })
5928 .sorted_by_key(|(range, _)| range.start);
5929 buffer.edit(edits, None, cx);
5930 })
5931 }
5932
5933 editor.refresh_inline_completion(true, false, window, cx);
5934 });
5935 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), &snapshot);
5936
5937 let show_new_completions_on_confirm = completion
5938 .confirm
5939 .as_ref()
5940 .map_or(false, |confirm| confirm(intent, window, cx));
5941 if show_new_completions_on_confirm {
5942 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5943 }
5944
5945 let provider = self.completion_provider.as_ref()?;
5946 drop(completion);
5947 let apply_edits = provider.apply_additional_edits_for_completion(
5948 buffer_handle,
5949 completions_menu.completions.clone(),
5950 candidate_id,
5951 true,
5952 cx,
5953 );
5954
5955 let editor_settings = EditorSettings::get_global(cx);
5956 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5957 // After the code completion is finished, users often want to know what signatures are needed.
5958 // so we should automatically call signature_help
5959 self.show_signature_help(&ShowSignatureHelp, window, cx);
5960 }
5961
5962 Some(cx.foreground_executor().spawn(async move {
5963 apply_edits.await?;
5964 Ok(())
5965 }))
5966 }
5967
5968 pub fn toggle_code_actions(
5969 &mut self,
5970 action: &ToggleCodeActions,
5971 window: &mut Window,
5972 cx: &mut Context<Self>,
5973 ) {
5974 let quick_launch = action.quick_launch;
5975 let mut context_menu = self.context_menu.borrow_mut();
5976 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5977 if code_actions.deployed_from == action.deployed_from {
5978 // Toggle if we're selecting the same one
5979 *context_menu = None;
5980 cx.notify();
5981 return;
5982 } else {
5983 // Otherwise, clear it and start a new one
5984 *context_menu = None;
5985 cx.notify();
5986 }
5987 }
5988 drop(context_menu);
5989 let snapshot = self.snapshot(window, cx);
5990 let deployed_from = action.deployed_from.clone();
5991 let action = action.clone();
5992 self.completion_tasks.clear();
5993 self.discard_inline_completion(false, cx);
5994
5995 let multibuffer_point = match &action.deployed_from {
5996 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
5997 DisplayPoint::new(*row, 0).to_point(&snapshot)
5998 }
5999 _ => self.selections.newest::<Point>(cx).head(),
6000 };
6001 let Some((buffer, buffer_row)) = snapshot
6002 .buffer_snapshot
6003 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6004 .and_then(|(buffer_snapshot, range)| {
6005 self.buffer()
6006 .read(cx)
6007 .buffer(buffer_snapshot.remote_id())
6008 .map(|buffer| (buffer, range.start.row))
6009 })
6010 else {
6011 return;
6012 };
6013 let buffer_id = buffer.read(cx).remote_id();
6014 let tasks = self
6015 .tasks
6016 .get(&(buffer_id, buffer_row))
6017 .map(|t| Arc::new(t.to_owned()));
6018
6019 if !self.focus_handle.is_focused(window) {
6020 return;
6021 }
6022 let project = self.project.clone();
6023
6024 let code_actions_task = match deployed_from {
6025 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6026 _ => self.code_actions(buffer_row, window, cx),
6027 };
6028
6029 let runnable_task = match deployed_from {
6030 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6031 _ => {
6032 let mut task_context_task = Task::ready(None);
6033 if let Some(tasks) = &tasks {
6034 if let Some(project) = project {
6035 task_context_task =
6036 Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
6037 }
6038 }
6039
6040 cx.spawn_in(window, {
6041 let buffer = buffer.clone();
6042 async move |editor, cx| {
6043 let task_context = task_context_task.await;
6044
6045 let resolved_tasks =
6046 tasks
6047 .zip(task_context.clone())
6048 .map(|(tasks, task_context)| ResolvedTasks {
6049 templates: tasks.resolve(&task_context).collect(),
6050 position: snapshot.buffer_snapshot.anchor_before(Point::new(
6051 multibuffer_point.row,
6052 tasks.column,
6053 )),
6054 });
6055 let debug_scenarios = editor
6056 .update(cx, |editor, cx| {
6057 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6058 })?
6059 .await;
6060 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6061 }
6062 })
6063 }
6064 };
6065
6066 cx.spawn_in(window, async move |editor, cx| {
6067 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6068 let code_actions = code_actions_task.await;
6069 let spawn_straight_away = quick_launch
6070 && resolved_tasks
6071 .as_ref()
6072 .map_or(false, |tasks| tasks.templates.len() == 1)
6073 && code_actions
6074 .as_ref()
6075 .map_or(true, |actions| actions.is_empty())
6076 && debug_scenarios.is_empty();
6077
6078 editor.update_in(cx, |editor, window, cx| {
6079 crate::hover_popover::hide_hover(editor, cx);
6080 let actions = CodeActionContents::new(
6081 resolved_tasks,
6082 code_actions,
6083 debug_scenarios,
6084 task_context.unwrap_or_default(),
6085 );
6086
6087 // Don't show the menu if there are no actions available
6088 if actions.is_empty() {
6089 cx.notify();
6090 return Task::ready(Ok(()));
6091 }
6092
6093 *editor.context_menu.borrow_mut() =
6094 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6095 buffer,
6096 actions,
6097 selected_item: Default::default(),
6098 scroll_handle: UniformListScrollHandle::default(),
6099 deployed_from,
6100 }));
6101 cx.notify();
6102 if spawn_straight_away {
6103 if let Some(task) = editor.confirm_code_action(
6104 &ConfirmCodeAction { item_ix: Some(0) },
6105 window,
6106 cx,
6107 ) {
6108 return task;
6109 }
6110 }
6111
6112 Task::ready(Ok(()))
6113 })
6114 })
6115 .detach_and_log_err(cx);
6116 }
6117
6118 fn debug_scenarios(
6119 &mut self,
6120 resolved_tasks: &Option<ResolvedTasks>,
6121 buffer: &Entity<Buffer>,
6122 cx: &mut App,
6123 ) -> Task<Vec<task::DebugScenario>> {
6124 maybe!({
6125 let project = self.project.as_ref()?;
6126 let dap_store = project.read(cx).dap_store();
6127 let mut scenarios = vec![];
6128 let resolved_tasks = resolved_tasks.as_ref()?;
6129 let buffer = buffer.read(cx);
6130 let language = buffer.language()?;
6131 let file = buffer.file();
6132 let debug_adapter = language_settings(language.name().into(), file, cx)
6133 .debuggers
6134 .first()
6135 .map(SharedString::from)
6136 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6137
6138 dap_store.update(cx, |dap_store, cx| {
6139 for (_, task) in &resolved_tasks.templates {
6140 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6141 task.original_task().clone(),
6142 debug_adapter.clone().into(),
6143 task.display_label().to_owned().into(),
6144 cx,
6145 );
6146 scenarios.push(maybe_scenario);
6147 }
6148 });
6149 Some(cx.background_spawn(async move {
6150 let scenarios = futures::future::join_all(scenarios)
6151 .await
6152 .into_iter()
6153 .flatten()
6154 .collect::<Vec<_>>();
6155 scenarios
6156 }))
6157 })
6158 .unwrap_or_else(|| Task::ready(vec![]))
6159 }
6160
6161 fn code_actions(
6162 &mut self,
6163 buffer_row: u32,
6164 window: &mut Window,
6165 cx: &mut Context<Self>,
6166 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6167 let mut task = self.code_actions_task.take();
6168 cx.spawn_in(window, async move |editor, cx| {
6169 while let Some(prev_task) = task {
6170 prev_task.await.log_err();
6171 task = editor
6172 .update(cx, |this, _| this.code_actions_task.take())
6173 .ok()?;
6174 }
6175
6176 editor
6177 .update(cx, |editor, cx| {
6178 editor
6179 .available_code_actions
6180 .clone()
6181 .and_then(|(location, code_actions)| {
6182 let snapshot = location.buffer.read(cx).snapshot();
6183 let point_range = location.range.to_point(&snapshot);
6184 let point_range = point_range.start.row..=point_range.end.row;
6185 if point_range.contains(&buffer_row) {
6186 Some(code_actions)
6187 } else {
6188 None
6189 }
6190 })
6191 })
6192 .ok()
6193 .flatten()
6194 })
6195 }
6196
6197 pub fn confirm_code_action(
6198 &mut self,
6199 action: &ConfirmCodeAction,
6200 window: &mut Window,
6201 cx: &mut Context<Self>,
6202 ) -> Option<Task<Result<()>>> {
6203 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6204
6205 let actions_menu =
6206 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6207 menu
6208 } else {
6209 return None;
6210 };
6211
6212 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6213 let action = actions_menu.actions.get(action_ix)?;
6214 let title = action.label();
6215 let buffer = actions_menu.buffer;
6216 let workspace = self.workspace()?;
6217
6218 match action {
6219 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6220 workspace.update(cx, |workspace, cx| {
6221 workspace.schedule_resolved_task(
6222 task_source_kind,
6223 resolved_task,
6224 false,
6225 window,
6226 cx,
6227 );
6228
6229 Some(Task::ready(Ok(())))
6230 })
6231 }
6232 CodeActionsItem::CodeAction {
6233 excerpt_id,
6234 action,
6235 provider,
6236 } => {
6237 let apply_code_action =
6238 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6239 let workspace = workspace.downgrade();
6240 Some(cx.spawn_in(window, async move |editor, cx| {
6241 let project_transaction = apply_code_action.await?;
6242 Self::open_project_transaction(
6243 &editor,
6244 workspace,
6245 project_transaction,
6246 title,
6247 cx,
6248 )
6249 .await
6250 }))
6251 }
6252 CodeActionsItem::DebugScenario(scenario) => {
6253 let context = actions_menu.actions.context.clone();
6254
6255 workspace.update(cx, |workspace, cx| {
6256 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6257 workspace.start_debug_session(
6258 scenario,
6259 context,
6260 Some(buffer),
6261 None,
6262 window,
6263 cx,
6264 );
6265 });
6266 Some(Task::ready(Ok(())))
6267 }
6268 }
6269 }
6270
6271 pub async fn open_project_transaction(
6272 this: &WeakEntity<Editor>,
6273 workspace: WeakEntity<Workspace>,
6274 transaction: ProjectTransaction,
6275 title: String,
6276 cx: &mut AsyncWindowContext,
6277 ) -> Result<()> {
6278 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6279 cx.update(|_, cx| {
6280 entries.sort_unstable_by_key(|(buffer, _)| {
6281 buffer.read(cx).file().map(|f| f.path().clone())
6282 });
6283 })?;
6284
6285 // If the project transaction's edits are all contained within this editor, then
6286 // avoid opening a new editor to display them.
6287
6288 if let Some((buffer, transaction)) = entries.first() {
6289 if entries.len() == 1 {
6290 let excerpt = this.update(cx, |editor, cx| {
6291 editor
6292 .buffer()
6293 .read(cx)
6294 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6295 })?;
6296 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
6297 if excerpted_buffer == *buffer {
6298 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6299 let excerpt_range = excerpt_range.to_offset(buffer);
6300 buffer
6301 .edited_ranges_for_transaction::<usize>(transaction)
6302 .all(|range| {
6303 excerpt_range.start <= range.start
6304 && excerpt_range.end >= range.end
6305 })
6306 })?;
6307
6308 if all_edits_within_excerpt {
6309 return Ok(());
6310 }
6311 }
6312 }
6313 }
6314 } else {
6315 return Ok(());
6316 }
6317
6318 let mut ranges_to_highlight = Vec::new();
6319 let excerpt_buffer = cx.new(|cx| {
6320 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6321 for (buffer_handle, transaction) in &entries {
6322 let edited_ranges = buffer_handle
6323 .read(cx)
6324 .edited_ranges_for_transaction::<Point>(transaction)
6325 .collect::<Vec<_>>();
6326 let (ranges, _) = multibuffer.set_excerpts_for_path(
6327 PathKey::for_buffer(buffer_handle, cx),
6328 buffer_handle.clone(),
6329 edited_ranges,
6330 DEFAULT_MULTIBUFFER_CONTEXT,
6331 cx,
6332 );
6333
6334 ranges_to_highlight.extend(ranges);
6335 }
6336 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6337 multibuffer
6338 })?;
6339
6340 workspace.update_in(cx, |workspace, window, cx| {
6341 let project = workspace.project().clone();
6342 let editor =
6343 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6344 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6345 editor.update(cx, |editor, cx| {
6346 editor.highlight_background::<Self>(
6347 &ranges_to_highlight,
6348 |theme| theme.colors().editor_highlighted_line_background,
6349 cx,
6350 );
6351 });
6352 })?;
6353
6354 Ok(())
6355 }
6356
6357 pub fn clear_code_action_providers(&mut self) {
6358 self.code_action_providers.clear();
6359 self.available_code_actions.take();
6360 }
6361
6362 pub fn add_code_action_provider(
6363 &mut self,
6364 provider: Rc<dyn CodeActionProvider>,
6365 window: &mut Window,
6366 cx: &mut Context<Self>,
6367 ) {
6368 if self
6369 .code_action_providers
6370 .iter()
6371 .any(|existing_provider| existing_provider.id() == provider.id())
6372 {
6373 return;
6374 }
6375
6376 self.code_action_providers.push(provider);
6377 self.refresh_code_actions(window, cx);
6378 }
6379
6380 pub fn remove_code_action_provider(
6381 &mut self,
6382 id: Arc<str>,
6383 window: &mut Window,
6384 cx: &mut Context<Self>,
6385 ) {
6386 self.code_action_providers
6387 .retain(|provider| provider.id() != id);
6388 self.refresh_code_actions(window, cx);
6389 }
6390
6391 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6392 !self.code_action_providers.is_empty()
6393 && EditorSettings::get_global(cx).toolbar.code_actions
6394 }
6395
6396 pub fn has_available_code_actions(&self) -> bool {
6397 self.available_code_actions
6398 .as_ref()
6399 .is_some_and(|(_, actions)| !actions.is_empty())
6400 }
6401
6402 fn render_inline_code_actions(
6403 &self,
6404 icon_size: ui::IconSize,
6405 display_row: DisplayRow,
6406 is_active: bool,
6407 cx: &mut Context<Self>,
6408 ) -> AnyElement {
6409 let show_tooltip = !self.context_menu_visible();
6410 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6411 .icon_size(icon_size)
6412 .shape(ui::IconButtonShape::Square)
6413 .style(ButtonStyle::Transparent)
6414 .icon_color(ui::Color::Hidden)
6415 .toggle_state(is_active)
6416 .when(show_tooltip, |this| {
6417 this.tooltip({
6418 let focus_handle = self.focus_handle.clone();
6419 move |window, cx| {
6420 Tooltip::for_action_in(
6421 "Toggle Code Actions",
6422 &ToggleCodeActions {
6423 deployed_from: None,
6424 quick_launch: false,
6425 },
6426 &focus_handle,
6427 window,
6428 cx,
6429 )
6430 }
6431 })
6432 })
6433 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6434 window.focus(&editor.focus_handle(cx));
6435 editor.toggle_code_actions(
6436 &crate::actions::ToggleCodeActions {
6437 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6438 display_row,
6439 )),
6440 quick_launch: false,
6441 },
6442 window,
6443 cx,
6444 );
6445 }))
6446 .into_any_element()
6447 }
6448
6449 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6450 &self.context_menu
6451 }
6452
6453 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
6454 let newest_selection = self.selections.newest_anchor().clone();
6455 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
6456 let buffer = self.buffer.read(cx);
6457 if newest_selection.head().diff_base_anchor.is_some() {
6458 return None;
6459 }
6460 let (start_buffer, start) =
6461 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6462 let (end_buffer, end) =
6463 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6464 if start_buffer != end_buffer {
6465 return None;
6466 }
6467
6468 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6469 cx.background_executor()
6470 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6471 .await;
6472
6473 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6474 let providers = this.code_action_providers.clone();
6475 let tasks = this
6476 .code_action_providers
6477 .iter()
6478 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6479 .collect::<Vec<_>>();
6480 (providers, tasks)
6481 })?;
6482
6483 let mut actions = Vec::new();
6484 for (provider, provider_actions) in
6485 providers.into_iter().zip(future::join_all(tasks).await)
6486 {
6487 if let Some(provider_actions) = provider_actions.log_err() {
6488 actions.extend(provider_actions.into_iter().map(|action| {
6489 AvailableCodeAction {
6490 excerpt_id: newest_selection.start.excerpt_id,
6491 action,
6492 provider: provider.clone(),
6493 }
6494 }));
6495 }
6496 }
6497
6498 this.update(cx, |this, cx| {
6499 this.available_code_actions = if actions.is_empty() {
6500 None
6501 } else {
6502 Some((
6503 Location {
6504 buffer: start_buffer,
6505 range: start..end,
6506 },
6507 actions.into(),
6508 ))
6509 };
6510 cx.notify();
6511 })
6512 }));
6513 None
6514 }
6515
6516 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6517 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6518 self.show_git_blame_inline = false;
6519
6520 self.show_git_blame_inline_delay_task =
6521 Some(cx.spawn_in(window, async move |this, cx| {
6522 cx.background_executor().timer(delay).await;
6523
6524 this.update(cx, |this, cx| {
6525 this.show_git_blame_inline = true;
6526 cx.notify();
6527 })
6528 .log_err();
6529 }));
6530 }
6531 }
6532
6533 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6534 let snapshot = self.snapshot(window, cx);
6535 let cursor = self.selections.newest::<Point>(cx).head();
6536 let Some((buffer, point, _)) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)
6537 else {
6538 return;
6539 };
6540
6541 let Some(blame) = self.blame.as_ref() else {
6542 return;
6543 };
6544
6545 let row_info = RowInfo {
6546 buffer_id: Some(buffer.remote_id()),
6547 buffer_row: Some(point.row),
6548 ..Default::default()
6549 };
6550 let Some(blame_entry) = blame
6551 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6552 .flatten()
6553 else {
6554 return;
6555 };
6556
6557 let anchor = self.selections.newest_anchor().head();
6558 let position = self.to_pixel_point(anchor, &snapshot, window);
6559 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6560 self.show_blame_popover(&blame_entry, position + last_bounds.origin, true, cx);
6561 };
6562 }
6563
6564 fn show_blame_popover(
6565 &mut self,
6566 blame_entry: &BlameEntry,
6567 position: gpui::Point<Pixels>,
6568 ignore_timeout: bool,
6569 cx: &mut Context<Self>,
6570 ) {
6571 if let Some(state) = &mut self.inline_blame_popover {
6572 state.hide_task.take();
6573 } else {
6574 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay;
6575 let blame_entry = blame_entry.clone();
6576 let show_task = cx.spawn(async move |editor, cx| {
6577 if !ignore_timeout {
6578 cx.background_executor()
6579 .timer(std::time::Duration::from_millis(blame_popover_delay))
6580 .await;
6581 }
6582 editor
6583 .update(cx, |editor, cx| {
6584 editor.inline_blame_popover_show_task.take();
6585 let Some(blame) = editor.blame.as_ref() else {
6586 return;
6587 };
6588 let blame = blame.read(cx);
6589 let details = blame.details_for_entry(&blame_entry);
6590 let markdown = cx.new(|cx| {
6591 Markdown::new(
6592 details
6593 .as_ref()
6594 .map(|message| message.message.clone())
6595 .unwrap_or_default(),
6596 None,
6597 None,
6598 cx,
6599 )
6600 });
6601 editor.inline_blame_popover = Some(InlineBlamePopover {
6602 position,
6603 hide_task: None,
6604 popover_bounds: None,
6605 popover_state: InlineBlamePopoverState {
6606 scroll_handle: ScrollHandle::new(),
6607 commit_message: details,
6608 markdown,
6609 },
6610 keyboard_grace: ignore_timeout,
6611 });
6612 cx.notify();
6613 })
6614 .ok();
6615 });
6616 self.inline_blame_popover_show_task = Some(show_task);
6617 }
6618 }
6619
6620 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6621 self.inline_blame_popover_show_task.take();
6622 if let Some(state) = &mut self.inline_blame_popover {
6623 let hide_task = cx.spawn(async move |editor, cx| {
6624 cx.background_executor()
6625 .timer(std::time::Duration::from_millis(100))
6626 .await;
6627 editor
6628 .update(cx, |editor, cx| {
6629 editor.inline_blame_popover.take();
6630 cx.notify();
6631 })
6632 .ok();
6633 });
6634 state.hide_task = Some(hide_task);
6635 }
6636 }
6637
6638 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6639 if self.pending_rename.is_some() {
6640 return None;
6641 }
6642
6643 let provider = self.semantics_provider.clone()?;
6644 let buffer = self.buffer.read(cx);
6645 let newest_selection = self.selections.newest_anchor().clone();
6646 let cursor_position = newest_selection.head();
6647 let (cursor_buffer, cursor_buffer_position) =
6648 buffer.text_anchor_for_position(cursor_position, cx)?;
6649 let (tail_buffer, tail_buffer_position) =
6650 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6651 if cursor_buffer != tail_buffer {
6652 return None;
6653 }
6654
6655 let snapshot = cursor_buffer.read(cx).snapshot();
6656 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, false);
6657 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, false);
6658 if start_word_range != end_word_range {
6659 self.document_highlights_task.take();
6660 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6661 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6662 return None;
6663 }
6664
6665 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6666 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6667 cx.background_executor()
6668 .timer(Duration::from_millis(debounce))
6669 .await;
6670
6671 let highlights = if let Some(highlights) = cx
6672 .update(|cx| {
6673 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6674 })
6675 .ok()
6676 .flatten()
6677 {
6678 highlights.await.log_err()
6679 } else {
6680 None
6681 };
6682
6683 if let Some(highlights) = highlights {
6684 this.update(cx, |this, cx| {
6685 if this.pending_rename.is_some() {
6686 return;
6687 }
6688
6689 let buffer_id = cursor_position.buffer_id;
6690 let buffer = this.buffer.read(cx);
6691 if !buffer
6692 .text_anchor_for_position(cursor_position, cx)
6693 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
6694 {
6695 return;
6696 }
6697
6698 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6699 let mut write_ranges = Vec::new();
6700 let mut read_ranges = Vec::new();
6701 for highlight in highlights {
6702 for (excerpt_id, excerpt_range) in
6703 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
6704 {
6705 let start = highlight
6706 .range
6707 .start
6708 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6709 let end = highlight
6710 .range
6711 .end
6712 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6713 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6714 continue;
6715 }
6716
6717 let range = Anchor {
6718 buffer_id,
6719 excerpt_id,
6720 text_anchor: start,
6721 diff_base_anchor: None,
6722 }..Anchor {
6723 buffer_id,
6724 excerpt_id,
6725 text_anchor: end,
6726 diff_base_anchor: None,
6727 };
6728 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6729 write_ranges.push(range);
6730 } else {
6731 read_ranges.push(range);
6732 }
6733 }
6734 }
6735
6736 this.highlight_background::<DocumentHighlightRead>(
6737 &read_ranges,
6738 |theme| theme.colors().editor_document_highlight_read_background,
6739 cx,
6740 );
6741 this.highlight_background::<DocumentHighlightWrite>(
6742 &write_ranges,
6743 |theme| theme.colors().editor_document_highlight_write_background,
6744 cx,
6745 );
6746 cx.notify();
6747 })
6748 .log_err();
6749 }
6750 }));
6751 None
6752 }
6753
6754 fn prepare_highlight_query_from_selection(
6755 &mut self,
6756 cx: &mut Context<Editor>,
6757 ) -> Option<(String, Range<Anchor>)> {
6758 if matches!(self.mode, EditorMode::SingleLine { .. }) {
6759 return None;
6760 }
6761 if !EditorSettings::get_global(cx).selection_highlight {
6762 return None;
6763 }
6764 if self.selections.count() != 1 || self.selections.line_mode {
6765 return None;
6766 }
6767 let selection = self.selections.newest::<Point>(cx);
6768 if selection.is_empty() || selection.start.row != selection.end.row {
6769 return None;
6770 }
6771 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6772 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6773 let query = multi_buffer_snapshot
6774 .text_for_range(selection_anchor_range.clone())
6775 .collect::<String>();
6776 if query.trim().is_empty() {
6777 return None;
6778 }
6779 Some((query, selection_anchor_range))
6780 }
6781
6782 fn update_selection_occurrence_highlights(
6783 &mut self,
6784 query_text: String,
6785 query_range: Range<Anchor>,
6786 multi_buffer_range_to_query: Range<Point>,
6787 use_debounce: bool,
6788 window: &mut Window,
6789 cx: &mut Context<Editor>,
6790 ) -> Task<()> {
6791 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6792 cx.spawn_in(window, async move |editor, cx| {
6793 if use_debounce {
6794 cx.background_executor()
6795 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6796 .await;
6797 }
6798 let match_task = cx.background_spawn(async move {
6799 let buffer_ranges = multi_buffer_snapshot
6800 .range_to_buffer_ranges(multi_buffer_range_to_query)
6801 .into_iter()
6802 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6803 let mut match_ranges = Vec::new();
6804 let Ok(regex) = project::search::SearchQuery::text(
6805 query_text.clone(),
6806 false,
6807 false,
6808 false,
6809 Default::default(),
6810 Default::default(),
6811 false,
6812 None,
6813 ) else {
6814 return Vec::default();
6815 };
6816 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6817 match_ranges.extend(
6818 regex
6819 .search(&buffer_snapshot, Some(search_range.clone()))
6820 .await
6821 .into_iter()
6822 .filter_map(|match_range| {
6823 let match_start = buffer_snapshot
6824 .anchor_after(search_range.start + match_range.start);
6825 let match_end = buffer_snapshot
6826 .anchor_before(search_range.start + match_range.end);
6827 let match_anchor_range = Anchor::range_in_buffer(
6828 excerpt_id,
6829 buffer_snapshot.remote_id(),
6830 match_start..match_end,
6831 );
6832 (match_anchor_range != query_range).then_some(match_anchor_range)
6833 }),
6834 );
6835 }
6836 match_ranges
6837 });
6838 let match_ranges = match_task.await;
6839 editor
6840 .update_in(cx, |editor, _, cx| {
6841 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6842 if !match_ranges.is_empty() {
6843 editor.highlight_background::<SelectedTextHighlight>(
6844 &match_ranges,
6845 |theme| theme.colors().editor_document_highlight_bracket_background,
6846 cx,
6847 )
6848 }
6849 })
6850 .log_err();
6851 })
6852 }
6853
6854 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
6855 struct NewlineFold;
6856 let type_id = std::any::TypeId::of::<NewlineFold>();
6857 if !self.mode.is_single_line() {
6858 return;
6859 }
6860 let snapshot = self.snapshot(window, cx);
6861 if snapshot.buffer_snapshot.max_point().row == 0 {
6862 return;
6863 }
6864 let task = cx.background_spawn(async move {
6865 let new_newlines = snapshot
6866 .buffer_chars_at(0)
6867 .filter_map(|(c, i)| {
6868 if c == '\n' {
6869 Some(
6870 snapshot.buffer_snapshot.anchor_after(i)
6871 ..snapshot.buffer_snapshot.anchor_before(i + 1),
6872 )
6873 } else {
6874 None
6875 }
6876 })
6877 .collect::<Vec<_>>();
6878 let existing_newlines = snapshot
6879 .folds_in_range(0..snapshot.buffer_snapshot.len())
6880 .filter_map(|fold| {
6881 if fold.placeholder.type_tag == Some(type_id) {
6882 Some(fold.range.start..fold.range.end)
6883 } else {
6884 None
6885 }
6886 })
6887 .collect::<Vec<_>>();
6888
6889 (new_newlines, existing_newlines)
6890 });
6891 self.folding_newlines = cx.spawn(async move |this, cx| {
6892 let (new_newlines, existing_newlines) = task.await;
6893 if new_newlines == existing_newlines {
6894 return;
6895 }
6896 let placeholder = FoldPlaceholder {
6897 render: Arc::new(move |_, _, cx| {
6898 div()
6899 .bg(cx.theme().status().hint_background)
6900 .border_b_1()
6901 .size_full()
6902 .font(ThemeSettings::get_global(cx).buffer_font.clone())
6903 .border_color(cx.theme().status().hint)
6904 .child("\\n")
6905 .into_any()
6906 }),
6907 constrain_width: false,
6908 merge_adjacent: false,
6909 type_tag: Some(type_id),
6910 };
6911 let creases = new_newlines
6912 .into_iter()
6913 .map(|range| Crease::simple(range, placeholder.clone()))
6914 .collect();
6915 this.update(cx, |this, cx| {
6916 this.display_map.update(cx, |display_map, cx| {
6917 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
6918 display_map.fold(creases, cx);
6919 });
6920 })
6921 .ok();
6922 });
6923 }
6924
6925 fn refresh_selected_text_highlights(
6926 &mut self,
6927 on_buffer_edit: bool,
6928 window: &mut Window,
6929 cx: &mut Context<Editor>,
6930 ) {
6931 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
6932 else {
6933 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6934 self.quick_selection_highlight_task.take();
6935 self.debounced_selection_highlight_task.take();
6936 return;
6937 };
6938 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6939 if on_buffer_edit
6940 || self
6941 .quick_selection_highlight_task
6942 .as_ref()
6943 .map_or(true, |(prev_anchor_range, _)| {
6944 prev_anchor_range != &query_range
6945 })
6946 {
6947 let multi_buffer_visible_start = self
6948 .scroll_manager
6949 .anchor()
6950 .anchor
6951 .to_point(&multi_buffer_snapshot);
6952 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
6953 multi_buffer_visible_start
6954 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
6955 Bias::Left,
6956 );
6957 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
6958 self.quick_selection_highlight_task = Some((
6959 query_range.clone(),
6960 self.update_selection_occurrence_highlights(
6961 query_text.clone(),
6962 query_range.clone(),
6963 multi_buffer_visible_range,
6964 false,
6965 window,
6966 cx,
6967 ),
6968 ));
6969 }
6970 if on_buffer_edit
6971 || self
6972 .debounced_selection_highlight_task
6973 .as_ref()
6974 .map_or(true, |(prev_anchor_range, _)| {
6975 prev_anchor_range != &query_range
6976 })
6977 {
6978 let multi_buffer_start = multi_buffer_snapshot
6979 .anchor_before(0)
6980 .to_point(&multi_buffer_snapshot);
6981 let multi_buffer_end = multi_buffer_snapshot
6982 .anchor_after(multi_buffer_snapshot.len())
6983 .to_point(&multi_buffer_snapshot);
6984 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
6985 self.debounced_selection_highlight_task = Some((
6986 query_range.clone(),
6987 self.update_selection_occurrence_highlights(
6988 query_text,
6989 query_range,
6990 multi_buffer_full_range,
6991 true,
6992 window,
6993 cx,
6994 ),
6995 ));
6996 }
6997 }
6998
6999 pub fn refresh_inline_completion(
7000 &mut self,
7001 debounce: bool,
7002 user_requested: bool,
7003 window: &mut Window,
7004 cx: &mut Context<Self>,
7005 ) -> Option<()> {
7006 let provider = self.edit_prediction_provider()?;
7007 let cursor = self.selections.newest_anchor().head();
7008 let (buffer, cursor_buffer_position) =
7009 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7010
7011 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7012 self.discard_inline_completion(false, cx);
7013 return None;
7014 }
7015
7016 if !user_requested
7017 && (!self.should_show_edit_predictions()
7018 || !self.is_focused(window)
7019 || buffer.read(cx).is_empty())
7020 {
7021 self.discard_inline_completion(false, cx);
7022 return None;
7023 }
7024
7025 self.update_visible_inline_completion(window, cx);
7026 provider.refresh(
7027 self.project.clone(),
7028 buffer,
7029 cursor_buffer_position,
7030 debounce,
7031 cx,
7032 );
7033 Some(())
7034 }
7035
7036 fn show_edit_predictions_in_menu(&self) -> bool {
7037 match self.edit_prediction_settings {
7038 EditPredictionSettings::Disabled => false,
7039 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7040 }
7041 }
7042
7043 pub fn edit_predictions_enabled(&self) -> bool {
7044 match self.edit_prediction_settings {
7045 EditPredictionSettings::Disabled => false,
7046 EditPredictionSettings::Enabled { .. } => true,
7047 }
7048 }
7049
7050 fn edit_prediction_requires_modifier(&self) -> bool {
7051 match self.edit_prediction_settings {
7052 EditPredictionSettings::Disabled => false,
7053 EditPredictionSettings::Enabled {
7054 preview_requires_modifier,
7055 ..
7056 } => preview_requires_modifier,
7057 }
7058 }
7059
7060 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7061 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7062 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7063 } else {
7064 let selection = self.selections.newest_anchor();
7065 let cursor = selection.head();
7066
7067 if let Some((buffer, cursor_buffer_position)) =
7068 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7069 {
7070 self.edit_prediction_settings =
7071 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7072 }
7073 }
7074 }
7075
7076 fn edit_prediction_settings_at_position(
7077 &self,
7078 buffer: &Entity<Buffer>,
7079 buffer_position: language::Anchor,
7080 cx: &App,
7081 ) -> EditPredictionSettings {
7082 if !self.mode.is_full()
7083 || !self.show_inline_completions_override.unwrap_or(true)
7084 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
7085 {
7086 return EditPredictionSettings::Disabled;
7087 }
7088
7089 let buffer = buffer.read(cx);
7090
7091 let file = buffer.file();
7092
7093 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7094 return EditPredictionSettings::Disabled;
7095 };
7096
7097 let by_provider = matches!(
7098 self.menu_inline_completions_policy,
7099 MenuInlineCompletionsPolicy::ByProvider
7100 );
7101
7102 let show_in_menu = by_provider
7103 && self
7104 .edit_prediction_provider
7105 .as_ref()
7106 .map_or(false, |provider| {
7107 provider.provider.show_completions_in_menu()
7108 });
7109
7110 let preview_requires_modifier =
7111 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7112
7113 EditPredictionSettings::Enabled {
7114 show_in_menu,
7115 preview_requires_modifier,
7116 }
7117 }
7118
7119 fn should_show_edit_predictions(&self) -> bool {
7120 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7121 }
7122
7123 pub fn edit_prediction_preview_is_active(&self) -> bool {
7124 matches!(
7125 self.edit_prediction_preview,
7126 EditPredictionPreview::Active { .. }
7127 )
7128 }
7129
7130 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7131 let cursor = self.selections.newest_anchor().head();
7132 if let Some((buffer, cursor_position)) =
7133 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7134 {
7135 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7136 } else {
7137 false
7138 }
7139 }
7140
7141 pub fn supports_minimap(&self, cx: &App) -> bool {
7142 !self.minimap_visibility.disabled() && self.is_singleton(cx)
7143 }
7144
7145 fn edit_predictions_enabled_in_buffer(
7146 &self,
7147 buffer: &Entity<Buffer>,
7148 buffer_position: language::Anchor,
7149 cx: &App,
7150 ) -> bool {
7151 maybe!({
7152 if self.read_only(cx) {
7153 return Some(false);
7154 }
7155 let provider = self.edit_prediction_provider()?;
7156 if !provider.is_enabled(&buffer, buffer_position, cx) {
7157 return Some(false);
7158 }
7159 let buffer = buffer.read(cx);
7160 let Some(file) = buffer.file() else {
7161 return Some(true);
7162 };
7163 let settings = all_language_settings(Some(file), cx);
7164 Some(settings.edit_predictions_enabled_for_file(file, cx))
7165 })
7166 .unwrap_or(false)
7167 }
7168
7169 fn cycle_inline_completion(
7170 &mut self,
7171 direction: Direction,
7172 window: &mut Window,
7173 cx: &mut Context<Self>,
7174 ) -> Option<()> {
7175 let provider = self.edit_prediction_provider()?;
7176 let cursor = self.selections.newest_anchor().head();
7177 let (buffer, cursor_buffer_position) =
7178 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7179 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7180 return None;
7181 }
7182
7183 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7184 self.update_visible_inline_completion(window, cx);
7185
7186 Some(())
7187 }
7188
7189 pub fn show_inline_completion(
7190 &mut self,
7191 _: &ShowEditPrediction,
7192 window: &mut Window,
7193 cx: &mut Context<Self>,
7194 ) {
7195 if !self.has_active_inline_completion() {
7196 self.refresh_inline_completion(false, true, window, cx);
7197 return;
7198 }
7199
7200 self.update_visible_inline_completion(window, cx);
7201 }
7202
7203 pub fn display_cursor_names(
7204 &mut self,
7205 _: &DisplayCursorNames,
7206 window: &mut Window,
7207 cx: &mut Context<Self>,
7208 ) {
7209 self.show_cursor_names(window, cx);
7210 }
7211
7212 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7213 self.show_cursor_names = true;
7214 cx.notify();
7215 cx.spawn_in(window, async move |this, cx| {
7216 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7217 this.update(cx, |this, cx| {
7218 this.show_cursor_names = false;
7219 cx.notify()
7220 })
7221 .ok()
7222 })
7223 .detach();
7224 }
7225
7226 pub fn next_edit_prediction(
7227 &mut self,
7228 _: &NextEditPrediction,
7229 window: &mut Window,
7230 cx: &mut Context<Self>,
7231 ) {
7232 if self.has_active_inline_completion() {
7233 self.cycle_inline_completion(Direction::Next, window, cx);
7234 } else {
7235 let is_copilot_disabled = self
7236 .refresh_inline_completion(false, true, window, cx)
7237 .is_none();
7238 if is_copilot_disabled {
7239 cx.propagate();
7240 }
7241 }
7242 }
7243
7244 pub fn previous_edit_prediction(
7245 &mut self,
7246 _: &PreviousEditPrediction,
7247 window: &mut Window,
7248 cx: &mut Context<Self>,
7249 ) {
7250 if self.has_active_inline_completion() {
7251 self.cycle_inline_completion(Direction::Prev, window, cx);
7252 } else {
7253 let is_copilot_disabled = self
7254 .refresh_inline_completion(false, true, window, cx)
7255 .is_none();
7256 if is_copilot_disabled {
7257 cx.propagate();
7258 }
7259 }
7260 }
7261
7262 pub fn accept_edit_prediction(
7263 &mut self,
7264 _: &AcceptEditPrediction,
7265 window: &mut Window,
7266 cx: &mut Context<Self>,
7267 ) {
7268 if self.show_edit_predictions_in_menu() {
7269 self.hide_context_menu(window, cx);
7270 }
7271
7272 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
7273 return;
7274 };
7275
7276 self.report_inline_completion_event(
7277 active_inline_completion.completion_id.clone(),
7278 true,
7279 cx,
7280 );
7281
7282 match &active_inline_completion.completion {
7283 InlineCompletion::Move { target, .. } => {
7284 let target = *target;
7285
7286 if let Some(position_map) = &self.last_position_map {
7287 if position_map
7288 .visible_row_range
7289 .contains(&target.to_display_point(&position_map.snapshot).row())
7290 || !self.edit_prediction_requires_modifier()
7291 {
7292 self.unfold_ranges(&[target..target], true, false, cx);
7293 // Note that this is also done in vim's handler of the Tab action.
7294 self.change_selections(
7295 SelectionEffects::scroll(Autoscroll::newest()),
7296 window,
7297 cx,
7298 |selections| {
7299 selections.select_anchor_ranges([target..target]);
7300 },
7301 );
7302 self.clear_row_highlights::<EditPredictionPreview>();
7303
7304 self.edit_prediction_preview
7305 .set_previous_scroll_position(None);
7306 } else {
7307 self.edit_prediction_preview
7308 .set_previous_scroll_position(Some(
7309 position_map.snapshot.scroll_anchor,
7310 ));
7311
7312 self.highlight_rows::<EditPredictionPreview>(
7313 target..target,
7314 cx.theme().colors().editor_highlighted_line_background,
7315 RowHighlightOptions {
7316 autoscroll: true,
7317 ..Default::default()
7318 },
7319 cx,
7320 );
7321 self.request_autoscroll(Autoscroll::fit(), cx);
7322 }
7323 }
7324 }
7325 InlineCompletion::Edit { edits, .. } => {
7326 if let Some(provider) = self.edit_prediction_provider() {
7327 provider.accept(cx);
7328 }
7329
7330 // Store the transaction ID and selections before applying the edit
7331 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7332
7333 let snapshot = self.buffer.read(cx).snapshot(cx);
7334 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7335
7336 self.buffer.update(cx, |buffer, cx| {
7337 buffer.edit(edits.iter().cloned(), None, cx)
7338 });
7339
7340 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7341 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7342 });
7343
7344 let selections = self.selections.disjoint_anchors();
7345 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7346 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7347 if has_new_transaction {
7348 self.selection_history
7349 .insert_transaction(transaction_id_now, selections);
7350 }
7351 }
7352
7353 self.update_visible_inline_completion(window, cx);
7354 if self.active_inline_completion.is_none() {
7355 self.refresh_inline_completion(true, true, window, cx);
7356 }
7357
7358 cx.notify();
7359 }
7360 }
7361
7362 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7363 }
7364
7365 pub fn accept_partial_inline_completion(
7366 &mut self,
7367 _: &AcceptPartialEditPrediction,
7368 window: &mut Window,
7369 cx: &mut Context<Self>,
7370 ) {
7371 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
7372 return;
7373 };
7374 if self.selections.count() != 1 {
7375 return;
7376 }
7377
7378 self.report_inline_completion_event(
7379 active_inline_completion.completion_id.clone(),
7380 true,
7381 cx,
7382 );
7383
7384 match &active_inline_completion.completion {
7385 InlineCompletion::Move { target, .. } => {
7386 let target = *target;
7387 self.change_selections(
7388 SelectionEffects::scroll(Autoscroll::newest()),
7389 window,
7390 cx,
7391 |selections| {
7392 selections.select_anchor_ranges([target..target]);
7393 },
7394 );
7395 }
7396 InlineCompletion::Edit { edits, .. } => {
7397 // Find an insertion that starts at the cursor position.
7398 let snapshot = self.buffer.read(cx).snapshot(cx);
7399 let cursor_offset = self.selections.newest::<usize>(cx).head();
7400 let insertion = edits.iter().find_map(|(range, text)| {
7401 let range = range.to_offset(&snapshot);
7402 if range.is_empty() && range.start == cursor_offset {
7403 Some(text)
7404 } else {
7405 None
7406 }
7407 });
7408
7409 if let Some(text) = insertion {
7410 let mut partial_completion = text
7411 .chars()
7412 .by_ref()
7413 .take_while(|c| c.is_alphabetic())
7414 .collect::<String>();
7415 if partial_completion.is_empty() {
7416 partial_completion = text
7417 .chars()
7418 .by_ref()
7419 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7420 .collect::<String>();
7421 }
7422
7423 cx.emit(EditorEvent::InputHandled {
7424 utf16_range_to_replace: None,
7425 text: partial_completion.clone().into(),
7426 });
7427
7428 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7429
7430 self.refresh_inline_completion(true, true, window, cx);
7431 cx.notify();
7432 } else {
7433 self.accept_edit_prediction(&Default::default(), window, cx);
7434 }
7435 }
7436 }
7437 }
7438
7439 fn discard_inline_completion(
7440 &mut self,
7441 should_report_inline_completion_event: bool,
7442 cx: &mut Context<Self>,
7443 ) -> bool {
7444 if should_report_inline_completion_event {
7445 let completion_id = self
7446 .active_inline_completion
7447 .as_ref()
7448 .and_then(|active_completion| active_completion.completion_id.clone());
7449
7450 self.report_inline_completion_event(completion_id, false, cx);
7451 }
7452
7453 if let Some(provider) = self.edit_prediction_provider() {
7454 provider.discard(cx);
7455 }
7456
7457 self.take_active_inline_completion(cx)
7458 }
7459
7460 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7461 let Some(provider) = self.edit_prediction_provider() else {
7462 return;
7463 };
7464
7465 let Some((_, buffer, _)) = self
7466 .buffer
7467 .read(cx)
7468 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7469 else {
7470 return;
7471 };
7472
7473 let extension = buffer
7474 .read(cx)
7475 .file()
7476 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
7477
7478 let event_type = match accepted {
7479 true => "Edit Prediction Accepted",
7480 false => "Edit Prediction Discarded",
7481 };
7482 telemetry::event!(
7483 event_type,
7484 provider = provider.name(),
7485 prediction_id = id,
7486 suggestion_accepted = accepted,
7487 file_extension = extension,
7488 );
7489 }
7490
7491 pub fn has_active_inline_completion(&self) -> bool {
7492 self.active_inline_completion.is_some()
7493 }
7494
7495 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
7496 let Some(active_inline_completion) = self.active_inline_completion.take() else {
7497 return false;
7498 };
7499
7500 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
7501 self.clear_highlights::<InlineCompletionHighlight>(cx);
7502 self.stale_inline_completion_in_menu = Some(active_inline_completion);
7503 true
7504 }
7505
7506 /// Returns true when we're displaying the edit prediction popover below the cursor
7507 /// like we are not previewing and the LSP autocomplete menu is visible
7508 /// or we are in `when_holding_modifier` mode.
7509 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7510 if self.edit_prediction_preview_is_active()
7511 || !self.show_edit_predictions_in_menu()
7512 || !self.edit_predictions_enabled()
7513 {
7514 return false;
7515 }
7516
7517 if self.has_visible_completions_menu() {
7518 return true;
7519 }
7520
7521 has_completion && self.edit_prediction_requires_modifier()
7522 }
7523
7524 fn handle_modifiers_changed(
7525 &mut self,
7526 modifiers: Modifiers,
7527 position_map: &PositionMap,
7528 window: &mut Window,
7529 cx: &mut Context<Self>,
7530 ) {
7531 if self.show_edit_predictions_in_menu() {
7532 self.update_edit_prediction_preview(&modifiers, window, cx);
7533 }
7534
7535 self.update_selection_mode(&modifiers, position_map, window, cx);
7536
7537 let mouse_position = window.mouse_position();
7538 if !position_map.text_hitbox.is_hovered(window) {
7539 return;
7540 }
7541
7542 self.update_hovered_link(
7543 position_map.point_for_position(mouse_position),
7544 &position_map.snapshot,
7545 modifiers,
7546 window,
7547 cx,
7548 )
7549 }
7550
7551 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7552 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7553 if invert {
7554 match multi_cursor_setting {
7555 MultiCursorModifier::Alt => modifiers.alt,
7556 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7557 }
7558 } else {
7559 match multi_cursor_setting {
7560 MultiCursorModifier::Alt => modifiers.secondary(),
7561 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7562 }
7563 }
7564 }
7565
7566 fn columnar_selection_mode(
7567 modifiers: &Modifiers,
7568 cx: &mut Context<Self>,
7569 ) -> Option<ColumnarMode> {
7570 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7571 if Self::multi_cursor_modifier(false, modifiers, cx) {
7572 Some(ColumnarMode::FromMouse)
7573 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7574 Some(ColumnarMode::FromSelection)
7575 } else {
7576 None
7577 }
7578 } else {
7579 None
7580 }
7581 }
7582
7583 fn update_selection_mode(
7584 &mut self,
7585 modifiers: &Modifiers,
7586 position_map: &PositionMap,
7587 window: &mut Window,
7588 cx: &mut Context<Self>,
7589 ) {
7590 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7591 return;
7592 };
7593 if self.selections.pending.is_none() {
7594 return;
7595 }
7596
7597 let mouse_position = window.mouse_position();
7598 let point_for_position = position_map.point_for_position(mouse_position);
7599 let position = point_for_position.previous_valid;
7600
7601 self.select(
7602 SelectPhase::BeginColumnar {
7603 position,
7604 reset: false,
7605 mode,
7606 goal_column: point_for_position.exact_unclipped.column(),
7607 },
7608 window,
7609 cx,
7610 );
7611 }
7612
7613 fn update_edit_prediction_preview(
7614 &mut self,
7615 modifiers: &Modifiers,
7616 window: &mut Window,
7617 cx: &mut Context<Self>,
7618 ) {
7619 let mut modifiers_held = false;
7620 if let Some(accept_keystroke) = self
7621 .accept_edit_prediction_keybind(false, window, cx)
7622 .keystroke()
7623 {
7624 modifiers_held = modifiers_held
7625 || (&accept_keystroke.modifiers == modifiers
7626 && accept_keystroke.modifiers.modified());
7627 };
7628 if let Some(accept_partial_keystroke) = self
7629 .accept_edit_prediction_keybind(true, window, cx)
7630 .keystroke()
7631 {
7632 modifiers_held = modifiers_held
7633 || (&accept_partial_keystroke.modifiers == modifiers
7634 && accept_partial_keystroke.modifiers.modified());
7635 }
7636
7637 if modifiers_held {
7638 if matches!(
7639 self.edit_prediction_preview,
7640 EditPredictionPreview::Inactive { .. }
7641 ) {
7642 self.edit_prediction_preview = EditPredictionPreview::Active {
7643 previous_scroll_position: None,
7644 since: Instant::now(),
7645 };
7646
7647 self.update_visible_inline_completion(window, cx);
7648 cx.notify();
7649 }
7650 } else if let EditPredictionPreview::Active {
7651 previous_scroll_position,
7652 since,
7653 } = self.edit_prediction_preview
7654 {
7655 if let (Some(previous_scroll_position), Some(position_map)) =
7656 (previous_scroll_position, self.last_position_map.as_ref())
7657 {
7658 self.set_scroll_position(
7659 previous_scroll_position
7660 .scroll_position(&position_map.snapshot.display_snapshot),
7661 window,
7662 cx,
7663 );
7664 }
7665
7666 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7667 released_too_fast: since.elapsed() < Duration::from_millis(200),
7668 };
7669 self.clear_row_highlights::<EditPredictionPreview>();
7670 self.update_visible_inline_completion(window, cx);
7671 cx.notify();
7672 }
7673 }
7674
7675 fn update_visible_inline_completion(
7676 &mut self,
7677 _window: &mut Window,
7678 cx: &mut Context<Self>,
7679 ) -> Option<()> {
7680 let selection = self.selections.newest_anchor();
7681 let cursor = selection.head();
7682 let multibuffer = self.buffer.read(cx).snapshot(cx);
7683 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7684 let excerpt_id = cursor.excerpt_id;
7685
7686 let show_in_menu = self.show_edit_predictions_in_menu();
7687 let completions_menu_has_precedence = !show_in_menu
7688 && (self.context_menu.borrow().is_some()
7689 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
7690
7691 if completions_menu_has_precedence
7692 || !offset_selection.is_empty()
7693 || self
7694 .active_inline_completion
7695 .as_ref()
7696 .map_or(false, |completion| {
7697 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
7698 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7699 !invalidation_range.contains(&offset_selection.head())
7700 })
7701 {
7702 self.discard_inline_completion(false, cx);
7703 return None;
7704 }
7705
7706 self.take_active_inline_completion(cx);
7707 let Some(provider) = self.edit_prediction_provider() else {
7708 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7709 return None;
7710 };
7711
7712 let (buffer, cursor_buffer_position) =
7713 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7714
7715 self.edit_prediction_settings =
7716 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7717
7718 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7719
7720 if self.edit_prediction_indent_conflict {
7721 let cursor_point = cursor.to_point(&multibuffer);
7722
7723 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7724
7725 if let Some((_, indent)) = indents.iter().next() {
7726 if indent.len == cursor_point.column {
7727 self.edit_prediction_indent_conflict = false;
7728 }
7729 }
7730 }
7731
7732 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7733 let edits = inline_completion
7734 .edits
7735 .into_iter()
7736 .flat_map(|(range, new_text)| {
7737 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7738 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7739 Some((start..end, new_text))
7740 })
7741 .collect::<Vec<_>>();
7742 if edits.is_empty() {
7743 return None;
7744 }
7745
7746 let first_edit_start = edits.first().unwrap().0.start;
7747 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7748 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7749
7750 let last_edit_end = edits.last().unwrap().0.end;
7751 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7752 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7753
7754 let cursor_row = cursor.to_point(&multibuffer).row;
7755
7756 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7757
7758 let mut inlay_ids = Vec::new();
7759 let invalidation_row_range;
7760 let move_invalidation_row_range = if cursor_row < edit_start_row {
7761 Some(cursor_row..edit_end_row)
7762 } else if cursor_row > edit_end_row {
7763 Some(edit_start_row..cursor_row)
7764 } else {
7765 None
7766 };
7767 let is_move =
7768 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
7769 let completion = if is_move {
7770 invalidation_row_range =
7771 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7772 let target = first_edit_start;
7773 InlineCompletion::Move { target, snapshot }
7774 } else {
7775 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7776 && !self.inline_completions_hidden_for_vim_mode;
7777
7778 if show_completions_in_buffer {
7779 if edits
7780 .iter()
7781 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7782 {
7783 let mut inlays = Vec::new();
7784 for (range, new_text) in &edits {
7785 let inlay = Inlay::inline_completion(
7786 post_inc(&mut self.next_inlay_id),
7787 range.start,
7788 new_text.as_str(),
7789 );
7790 inlay_ids.push(inlay.id);
7791 inlays.push(inlay);
7792 }
7793
7794 self.splice_inlays(&[], inlays, cx);
7795 } else {
7796 let background_color = cx.theme().status().deleted_background;
7797 self.highlight_text::<InlineCompletionHighlight>(
7798 edits.iter().map(|(range, _)| range.clone()).collect(),
7799 HighlightStyle {
7800 background_color: Some(background_color),
7801 ..Default::default()
7802 },
7803 cx,
7804 );
7805 }
7806 }
7807
7808 invalidation_row_range = edit_start_row..edit_end_row;
7809
7810 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7811 if provider.show_tab_accept_marker() {
7812 EditDisplayMode::TabAccept
7813 } else {
7814 EditDisplayMode::Inline
7815 }
7816 } else {
7817 EditDisplayMode::DiffPopover
7818 };
7819
7820 InlineCompletion::Edit {
7821 edits,
7822 edit_preview: inline_completion.edit_preview,
7823 display_mode,
7824 snapshot,
7825 }
7826 };
7827
7828 let invalidation_range = multibuffer
7829 .anchor_before(Point::new(invalidation_row_range.start, 0))
7830 ..multibuffer.anchor_after(Point::new(
7831 invalidation_row_range.end,
7832 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7833 ));
7834
7835 self.stale_inline_completion_in_menu = None;
7836 self.active_inline_completion = Some(InlineCompletionState {
7837 inlay_ids,
7838 completion,
7839 completion_id: inline_completion.id,
7840 invalidation_range,
7841 });
7842
7843 cx.notify();
7844
7845 Some(())
7846 }
7847
7848 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
7849 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7850 }
7851
7852 fn clear_tasks(&mut self) {
7853 self.tasks.clear()
7854 }
7855
7856 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7857 if self.tasks.insert(key, value).is_some() {
7858 // This case should hopefully be rare, but just in case...
7859 log::error!(
7860 "multiple different run targets found on a single line, only the last target will be rendered"
7861 )
7862 }
7863 }
7864
7865 /// Get all display points of breakpoints that will be rendered within editor
7866 ///
7867 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7868 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7869 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7870 fn active_breakpoints(
7871 &self,
7872 range: Range<DisplayRow>,
7873 window: &mut Window,
7874 cx: &mut Context<Self>,
7875 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7876 let mut breakpoint_display_points = HashMap::default();
7877
7878 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7879 return breakpoint_display_points;
7880 };
7881
7882 let snapshot = self.snapshot(window, cx);
7883
7884 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
7885 let Some(project) = self.project.as_ref() else {
7886 return breakpoint_display_points;
7887 };
7888
7889 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7890 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7891
7892 for (buffer_snapshot, range, excerpt_id) in
7893 multi_buffer_snapshot.range_to_buffer_ranges(range)
7894 {
7895 let Some(buffer) = project
7896 .read(cx)
7897 .buffer_for_id(buffer_snapshot.remote_id(), cx)
7898 else {
7899 continue;
7900 };
7901 let breakpoints = breakpoint_store.read(cx).breakpoints(
7902 &buffer,
7903 Some(
7904 buffer_snapshot.anchor_before(range.start)
7905 ..buffer_snapshot.anchor_after(range.end),
7906 ),
7907 buffer_snapshot,
7908 cx,
7909 );
7910 for (breakpoint, state) in breakpoints {
7911 let multi_buffer_anchor =
7912 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
7913 let position = multi_buffer_anchor
7914 .to_point(&multi_buffer_snapshot)
7915 .to_display_point(&snapshot);
7916
7917 breakpoint_display_points.insert(
7918 position.row(),
7919 (multi_buffer_anchor, breakpoint.bp.clone(), state),
7920 );
7921 }
7922 }
7923
7924 breakpoint_display_points
7925 }
7926
7927 fn breakpoint_context_menu(
7928 &self,
7929 anchor: Anchor,
7930 window: &mut Window,
7931 cx: &mut Context<Self>,
7932 ) -> Entity<ui::ContextMenu> {
7933 let weak_editor = cx.weak_entity();
7934 let focus_handle = self.focus_handle(cx);
7935
7936 let row = self
7937 .buffer
7938 .read(cx)
7939 .snapshot(cx)
7940 .summary_for_anchor::<Point>(&anchor)
7941 .row;
7942
7943 let breakpoint = self
7944 .breakpoint_at_row(row, window, cx)
7945 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
7946
7947 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
7948 "Edit Log Breakpoint"
7949 } else {
7950 "Set Log Breakpoint"
7951 };
7952
7953 let condition_breakpoint_msg = if breakpoint
7954 .as_ref()
7955 .is_some_and(|bp| bp.1.condition.is_some())
7956 {
7957 "Edit Condition Breakpoint"
7958 } else {
7959 "Set Condition Breakpoint"
7960 };
7961
7962 let hit_condition_breakpoint_msg = if breakpoint
7963 .as_ref()
7964 .is_some_and(|bp| bp.1.hit_condition.is_some())
7965 {
7966 "Edit Hit Condition Breakpoint"
7967 } else {
7968 "Set Hit Condition Breakpoint"
7969 };
7970
7971 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
7972 "Unset Breakpoint"
7973 } else {
7974 "Set Breakpoint"
7975 };
7976
7977 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
7978
7979 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
7980 BreakpointState::Enabled => Some("Disable"),
7981 BreakpointState::Disabled => Some("Enable"),
7982 });
7983
7984 let (anchor, breakpoint) =
7985 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
7986
7987 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
7988 menu.on_blur_subscription(Subscription::new(|| {}))
7989 .context(focus_handle)
7990 .when(run_to_cursor, |this| {
7991 let weak_editor = weak_editor.clone();
7992 this.entry("Run to cursor", None, move |window, cx| {
7993 weak_editor
7994 .update(cx, |editor, cx| {
7995 editor.change_selections(
7996 SelectionEffects::no_scroll(),
7997 window,
7998 cx,
7999 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8000 );
8001 })
8002 .ok();
8003
8004 window.dispatch_action(Box::new(RunToCursor), cx);
8005 })
8006 .separator()
8007 })
8008 .when_some(toggle_state_msg, |this, msg| {
8009 this.entry(msg, None, {
8010 let weak_editor = weak_editor.clone();
8011 let breakpoint = breakpoint.clone();
8012 move |_window, cx| {
8013 weak_editor
8014 .update(cx, |this, cx| {
8015 this.edit_breakpoint_at_anchor(
8016 anchor,
8017 breakpoint.as_ref().clone(),
8018 BreakpointEditAction::InvertState,
8019 cx,
8020 );
8021 })
8022 .log_err();
8023 }
8024 })
8025 })
8026 .entry(set_breakpoint_msg, None, {
8027 let weak_editor = weak_editor.clone();
8028 let breakpoint = breakpoint.clone();
8029 move |_window, cx| {
8030 weak_editor
8031 .update(cx, |this, cx| {
8032 this.edit_breakpoint_at_anchor(
8033 anchor,
8034 breakpoint.as_ref().clone(),
8035 BreakpointEditAction::Toggle,
8036 cx,
8037 );
8038 })
8039 .log_err();
8040 }
8041 })
8042 .entry(log_breakpoint_msg, None, {
8043 let breakpoint = breakpoint.clone();
8044 let weak_editor = weak_editor.clone();
8045 move |window, cx| {
8046 weak_editor
8047 .update(cx, |this, cx| {
8048 this.add_edit_breakpoint_block(
8049 anchor,
8050 breakpoint.as_ref(),
8051 BreakpointPromptEditAction::Log,
8052 window,
8053 cx,
8054 );
8055 })
8056 .log_err();
8057 }
8058 })
8059 .entry(condition_breakpoint_msg, None, {
8060 let breakpoint = breakpoint.clone();
8061 let weak_editor = weak_editor.clone();
8062 move |window, cx| {
8063 weak_editor
8064 .update(cx, |this, cx| {
8065 this.add_edit_breakpoint_block(
8066 anchor,
8067 breakpoint.as_ref(),
8068 BreakpointPromptEditAction::Condition,
8069 window,
8070 cx,
8071 );
8072 })
8073 .log_err();
8074 }
8075 })
8076 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8077 weak_editor
8078 .update(cx, |this, cx| {
8079 this.add_edit_breakpoint_block(
8080 anchor,
8081 breakpoint.as_ref(),
8082 BreakpointPromptEditAction::HitCondition,
8083 window,
8084 cx,
8085 );
8086 })
8087 .log_err();
8088 })
8089 })
8090 }
8091
8092 fn render_breakpoint(
8093 &self,
8094 position: Anchor,
8095 row: DisplayRow,
8096 breakpoint: &Breakpoint,
8097 state: Option<BreakpointSessionState>,
8098 cx: &mut Context<Self>,
8099 ) -> IconButton {
8100 let is_rejected = state.is_some_and(|s| !s.verified);
8101 // Is it a breakpoint that shows up when hovering over gutter?
8102 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8103 (false, false),
8104 |PhantomBreakpointIndicator {
8105 is_active,
8106 display_row,
8107 collides_with_existing_breakpoint,
8108 }| {
8109 (
8110 is_active && display_row == row,
8111 collides_with_existing_breakpoint,
8112 )
8113 },
8114 );
8115
8116 let (color, icon) = {
8117 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8118 (false, false) => ui::IconName::DebugBreakpoint,
8119 (true, false) => ui::IconName::DebugLogBreakpoint,
8120 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8121 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8122 };
8123
8124 let color = if is_phantom {
8125 Color::Hint
8126 } else if is_rejected {
8127 Color::Disabled
8128 } else {
8129 Color::Debugger
8130 };
8131
8132 (color, icon)
8133 };
8134
8135 let breakpoint = Arc::from(breakpoint.clone());
8136
8137 let alt_as_text = gpui::Keystroke {
8138 modifiers: Modifiers::secondary_key(),
8139 ..Default::default()
8140 };
8141 let primary_action_text = if breakpoint.is_disabled() {
8142 "Enable breakpoint"
8143 } else if is_phantom && !collides_with_existing {
8144 "Set breakpoint"
8145 } else {
8146 "Unset breakpoint"
8147 };
8148 let focus_handle = self.focus_handle.clone();
8149
8150 let meta = if is_rejected {
8151 SharedString::from("No executable code is associated with this line.")
8152 } else if collides_with_existing && !breakpoint.is_disabled() {
8153 SharedString::from(format!(
8154 "{alt_as_text}-click to disable,\nright-click for more options."
8155 ))
8156 } else {
8157 SharedString::from("Right-click for more options.")
8158 };
8159 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8160 .icon_size(IconSize::XSmall)
8161 .size(ui::ButtonSize::None)
8162 .when(is_rejected, |this| {
8163 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8164 })
8165 .icon_color(color)
8166 .style(ButtonStyle::Transparent)
8167 .on_click(cx.listener({
8168 let breakpoint = breakpoint.clone();
8169
8170 move |editor, event: &ClickEvent, window, cx| {
8171 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8172 BreakpointEditAction::InvertState
8173 } else {
8174 BreakpointEditAction::Toggle
8175 };
8176
8177 window.focus(&editor.focus_handle(cx));
8178 editor.edit_breakpoint_at_anchor(
8179 position,
8180 breakpoint.as_ref().clone(),
8181 edit_action,
8182 cx,
8183 );
8184 }
8185 }))
8186 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8187 editor.set_breakpoint_context_menu(
8188 row,
8189 Some(position),
8190 event.down.position,
8191 window,
8192 cx,
8193 );
8194 }))
8195 .tooltip(move |window, cx| {
8196 Tooltip::with_meta_in(
8197 primary_action_text,
8198 Some(&ToggleBreakpoint),
8199 meta.clone(),
8200 &focus_handle,
8201 window,
8202 cx,
8203 )
8204 })
8205 }
8206
8207 fn build_tasks_context(
8208 project: &Entity<Project>,
8209 buffer: &Entity<Buffer>,
8210 buffer_row: u32,
8211 tasks: &Arc<RunnableTasks>,
8212 cx: &mut Context<Self>,
8213 ) -> Task<Option<task::TaskContext>> {
8214 let position = Point::new(buffer_row, tasks.column);
8215 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8216 let location = Location {
8217 buffer: buffer.clone(),
8218 range: range_start..range_start,
8219 };
8220 // Fill in the environmental variables from the tree-sitter captures
8221 let mut captured_task_variables = TaskVariables::default();
8222 for (capture_name, value) in tasks.extra_variables.clone() {
8223 captured_task_variables.insert(
8224 task::VariableName::Custom(capture_name.into()),
8225 value.clone(),
8226 );
8227 }
8228 project.update(cx, |project, cx| {
8229 project.task_store().update(cx, |task_store, cx| {
8230 task_store.task_context_for_location(captured_task_variables, location, cx)
8231 })
8232 })
8233 }
8234
8235 pub fn spawn_nearest_task(
8236 &mut self,
8237 action: &SpawnNearestTask,
8238 window: &mut Window,
8239 cx: &mut Context<Self>,
8240 ) {
8241 let Some((workspace, _)) = self.workspace.clone() else {
8242 return;
8243 };
8244 let Some(project) = self.project.clone() else {
8245 return;
8246 };
8247
8248 // Try to find a closest, enclosing node using tree-sitter that has a task
8249 let Some((buffer, buffer_row, tasks)) = self
8250 .find_enclosing_node_task(cx)
8251 // Or find the task that's closest in row-distance.
8252 .or_else(|| self.find_closest_task(cx))
8253 else {
8254 return;
8255 };
8256
8257 let reveal_strategy = action.reveal;
8258 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8259 cx.spawn_in(window, async move |_, cx| {
8260 let context = task_context.await?;
8261 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8262
8263 let resolved = &mut resolved_task.resolved;
8264 resolved.reveal = reveal_strategy;
8265
8266 workspace
8267 .update_in(cx, |workspace, window, cx| {
8268 workspace.schedule_resolved_task(
8269 task_source_kind,
8270 resolved_task,
8271 false,
8272 window,
8273 cx,
8274 );
8275 })
8276 .ok()
8277 })
8278 .detach();
8279 }
8280
8281 fn find_closest_task(
8282 &mut self,
8283 cx: &mut Context<Self>,
8284 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8285 let cursor_row = self.selections.newest_adjusted(cx).head().row;
8286
8287 let ((buffer_id, row), tasks) = self
8288 .tasks
8289 .iter()
8290 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8291
8292 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8293 let tasks = Arc::new(tasks.to_owned());
8294 Some((buffer, *row, tasks))
8295 }
8296
8297 fn find_enclosing_node_task(
8298 &mut self,
8299 cx: &mut Context<Self>,
8300 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8301 let snapshot = self.buffer.read(cx).snapshot(cx);
8302 let offset = self.selections.newest::<usize>(cx).head();
8303 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8304 let buffer_id = excerpt.buffer().remote_id();
8305
8306 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8307 let mut cursor = layer.node().walk();
8308
8309 while cursor.goto_first_child_for_byte(offset).is_some() {
8310 if cursor.node().end_byte() == offset {
8311 cursor.goto_next_sibling();
8312 }
8313 }
8314
8315 // Ascend to the smallest ancestor that contains the range and has a task.
8316 loop {
8317 let node = cursor.node();
8318 let node_range = node.byte_range();
8319 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8320
8321 // Check if this node contains our offset
8322 if node_range.start <= offset && node_range.end >= offset {
8323 // If it contains offset, check for task
8324 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8325 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8326 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8327 }
8328 }
8329
8330 if !cursor.goto_parent() {
8331 break;
8332 }
8333 }
8334 None
8335 }
8336
8337 fn render_run_indicator(
8338 &self,
8339 _style: &EditorStyle,
8340 is_active: bool,
8341 row: DisplayRow,
8342 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8343 cx: &mut Context<Self>,
8344 ) -> IconButton {
8345 let color = Color::Muted;
8346 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8347
8348 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
8349 .shape(ui::IconButtonShape::Square)
8350 .icon_size(IconSize::XSmall)
8351 .icon_color(color)
8352 .toggle_state(is_active)
8353 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8354 let quick_launch = e.down.button == MouseButton::Left;
8355 window.focus(&editor.focus_handle(cx));
8356 editor.toggle_code_actions(
8357 &ToggleCodeActions {
8358 deployed_from: Some(CodeActionSource::RunMenu(row)),
8359 quick_launch,
8360 },
8361 window,
8362 cx,
8363 );
8364 }))
8365 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8366 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
8367 }))
8368 }
8369
8370 pub fn context_menu_visible(&self) -> bool {
8371 !self.edit_prediction_preview_is_active()
8372 && self
8373 .context_menu
8374 .borrow()
8375 .as_ref()
8376 .map_or(false, |menu| menu.visible())
8377 }
8378
8379 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8380 self.context_menu
8381 .borrow()
8382 .as_ref()
8383 .map(|menu| menu.origin())
8384 }
8385
8386 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8387 self.context_menu_options = Some(options);
8388 }
8389
8390 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
8391 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
8392
8393 fn render_edit_prediction_popover(
8394 &mut self,
8395 text_bounds: &Bounds<Pixels>,
8396 content_origin: gpui::Point<Pixels>,
8397 right_margin: Pixels,
8398 editor_snapshot: &EditorSnapshot,
8399 visible_row_range: Range<DisplayRow>,
8400 scroll_top: f32,
8401 scroll_bottom: f32,
8402 line_layouts: &[LineWithInvisibles],
8403 line_height: Pixels,
8404 scroll_pixel_position: gpui::Point<Pixels>,
8405 newest_selection_head: Option<DisplayPoint>,
8406 editor_width: Pixels,
8407 style: &EditorStyle,
8408 window: &mut Window,
8409 cx: &mut App,
8410 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8411 if self.mode().is_minimap() {
8412 return None;
8413 }
8414 let active_inline_completion = self.active_inline_completion.as_ref()?;
8415
8416 if self.edit_prediction_visible_in_cursor_popover(true) {
8417 return None;
8418 }
8419
8420 match &active_inline_completion.completion {
8421 InlineCompletion::Move { target, .. } => {
8422 let target_display_point = target.to_display_point(editor_snapshot);
8423
8424 if self.edit_prediction_requires_modifier() {
8425 if !self.edit_prediction_preview_is_active() {
8426 return None;
8427 }
8428
8429 self.render_edit_prediction_modifier_jump_popover(
8430 text_bounds,
8431 content_origin,
8432 visible_row_range,
8433 line_layouts,
8434 line_height,
8435 scroll_pixel_position,
8436 newest_selection_head,
8437 target_display_point,
8438 window,
8439 cx,
8440 )
8441 } else {
8442 self.render_edit_prediction_eager_jump_popover(
8443 text_bounds,
8444 content_origin,
8445 editor_snapshot,
8446 visible_row_range,
8447 scroll_top,
8448 scroll_bottom,
8449 line_height,
8450 scroll_pixel_position,
8451 target_display_point,
8452 editor_width,
8453 window,
8454 cx,
8455 )
8456 }
8457 }
8458 InlineCompletion::Edit {
8459 display_mode: EditDisplayMode::Inline,
8460 ..
8461 } => None,
8462 InlineCompletion::Edit {
8463 display_mode: EditDisplayMode::TabAccept,
8464 edits,
8465 ..
8466 } => {
8467 let range = &edits.first()?.0;
8468 let target_display_point = range.end.to_display_point(editor_snapshot);
8469
8470 self.render_edit_prediction_end_of_line_popover(
8471 "Accept",
8472 editor_snapshot,
8473 visible_row_range,
8474 target_display_point,
8475 line_height,
8476 scroll_pixel_position,
8477 content_origin,
8478 editor_width,
8479 window,
8480 cx,
8481 )
8482 }
8483 InlineCompletion::Edit {
8484 edits,
8485 edit_preview,
8486 display_mode: EditDisplayMode::DiffPopover,
8487 snapshot,
8488 } => self.render_edit_prediction_diff_popover(
8489 text_bounds,
8490 content_origin,
8491 right_margin,
8492 editor_snapshot,
8493 visible_row_range,
8494 line_layouts,
8495 line_height,
8496 scroll_pixel_position,
8497 newest_selection_head,
8498 editor_width,
8499 style,
8500 edits,
8501 edit_preview,
8502 snapshot,
8503 window,
8504 cx,
8505 ),
8506 }
8507 }
8508
8509 fn render_edit_prediction_modifier_jump_popover(
8510 &mut self,
8511 text_bounds: &Bounds<Pixels>,
8512 content_origin: gpui::Point<Pixels>,
8513 visible_row_range: Range<DisplayRow>,
8514 line_layouts: &[LineWithInvisibles],
8515 line_height: Pixels,
8516 scroll_pixel_position: gpui::Point<Pixels>,
8517 newest_selection_head: Option<DisplayPoint>,
8518 target_display_point: DisplayPoint,
8519 window: &mut Window,
8520 cx: &mut App,
8521 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8522 let scrolled_content_origin =
8523 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
8524
8525 const SCROLL_PADDING_Y: Pixels = px(12.);
8526
8527 if target_display_point.row() < visible_row_range.start {
8528 return self.render_edit_prediction_scroll_popover(
8529 |_| SCROLL_PADDING_Y,
8530 IconName::ArrowUp,
8531 visible_row_range,
8532 line_layouts,
8533 newest_selection_head,
8534 scrolled_content_origin,
8535 window,
8536 cx,
8537 );
8538 } else if target_display_point.row() >= visible_row_range.end {
8539 return self.render_edit_prediction_scroll_popover(
8540 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8541 IconName::ArrowDown,
8542 visible_row_range,
8543 line_layouts,
8544 newest_selection_head,
8545 scrolled_content_origin,
8546 window,
8547 cx,
8548 );
8549 }
8550
8551 const POLE_WIDTH: Pixels = px(2.);
8552
8553 let line_layout =
8554 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8555 let target_column = target_display_point.column() as usize;
8556
8557 let target_x = line_layout.x_for_index(target_column);
8558 let target_y =
8559 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
8560
8561 let flag_on_right = target_x < text_bounds.size.width / 2.;
8562
8563 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8564 border_color.l += 0.001;
8565
8566 let mut element = v_flex()
8567 .items_end()
8568 .when(flag_on_right, |el| el.items_start())
8569 .child(if flag_on_right {
8570 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8571 .rounded_bl(px(0.))
8572 .rounded_tl(px(0.))
8573 .border_l_2()
8574 .border_color(border_color)
8575 } else {
8576 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8577 .rounded_br(px(0.))
8578 .rounded_tr(px(0.))
8579 .border_r_2()
8580 .border_color(border_color)
8581 })
8582 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8583 .into_any();
8584
8585 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8586
8587 let mut origin = scrolled_content_origin + point(target_x, target_y)
8588 - point(
8589 if flag_on_right {
8590 POLE_WIDTH
8591 } else {
8592 size.width - POLE_WIDTH
8593 },
8594 size.height - line_height,
8595 );
8596
8597 origin.x = origin.x.max(content_origin.x);
8598
8599 element.prepaint_at(origin, window, cx);
8600
8601 Some((element, origin))
8602 }
8603
8604 fn render_edit_prediction_scroll_popover(
8605 &mut self,
8606 to_y: impl Fn(Size<Pixels>) -> Pixels,
8607 scroll_icon: IconName,
8608 visible_row_range: Range<DisplayRow>,
8609 line_layouts: &[LineWithInvisibles],
8610 newest_selection_head: Option<DisplayPoint>,
8611 scrolled_content_origin: gpui::Point<Pixels>,
8612 window: &mut Window,
8613 cx: &mut App,
8614 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8615 let mut element = self
8616 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
8617 .into_any();
8618
8619 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8620
8621 let cursor = newest_selection_head?;
8622 let cursor_row_layout =
8623 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8624 let cursor_column = cursor.column() as usize;
8625
8626 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8627
8628 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8629
8630 element.prepaint_at(origin, window, cx);
8631 Some((element, origin))
8632 }
8633
8634 fn render_edit_prediction_eager_jump_popover(
8635 &mut self,
8636 text_bounds: &Bounds<Pixels>,
8637 content_origin: gpui::Point<Pixels>,
8638 editor_snapshot: &EditorSnapshot,
8639 visible_row_range: Range<DisplayRow>,
8640 scroll_top: f32,
8641 scroll_bottom: f32,
8642 line_height: Pixels,
8643 scroll_pixel_position: gpui::Point<Pixels>,
8644 target_display_point: DisplayPoint,
8645 editor_width: Pixels,
8646 window: &mut Window,
8647 cx: &mut App,
8648 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8649 if target_display_point.row().as_f32() < scroll_top {
8650 let mut element = self
8651 .render_edit_prediction_line_popover(
8652 "Jump to Edit",
8653 Some(IconName::ArrowUp),
8654 window,
8655 cx,
8656 )?
8657 .into_any();
8658
8659 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8660 let offset = point(
8661 (text_bounds.size.width - size.width) / 2.,
8662 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8663 );
8664
8665 let origin = text_bounds.origin + offset;
8666 element.prepaint_at(origin, window, cx);
8667 Some((element, origin))
8668 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
8669 let mut element = self
8670 .render_edit_prediction_line_popover(
8671 "Jump to Edit",
8672 Some(IconName::ArrowDown),
8673 window,
8674 cx,
8675 )?
8676 .into_any();
8677
8678 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8679 let offset = point(
8680 (text_bounds.size.width - size.width) / 2.,
8681 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8682 );
8683
8684 let origin = text_bounds.origin + offset;
8685 element.prepaint_at(origin, window, cx);
8686 Some((element, origin))
8687 } else {
8688 self.render_edit_prediction_end_of_line_popover(
8689 "Jump to Edit",
8690 editor_snapshot,
8691 visible_row_range,
8692 target_display_point,
8693 line_height,
8694 scroll_pixel_position,
8695 content_origin,
8696 editor_width,
8697 window,
8698 cx,
8699 )
8700 }
8701 }
8702
8703 fn render_edit_prediction_end_of_line_popover(
8704 self: &mut Editor,
8705 label: &'static str,
8706 editor_snapshot: &EditorSnapshot,
8707 visible_row_range: Range<DisplayRow>,
8708 target_display_point: DisplayPoint,
8709 line_height: Pixels,
8710 scroll_pixel_position: gpui::Point<Pixels>,
8711 content_origin: gpui::Point<Pixels>,
8712 editor_width: Pixels,
8713 window: &mut Window,
8714 cx: &mut App,
8715 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8716 let target_line_end = DisplayPoint::new(
8717 target_display_point.row(),
8718 editor_snapshot.line_len(target_display_point.row()),
8719 );
8720
8721 let mut element = self
8722 .render_edit_prediction_line_popover(label, None, window, cx)?
8723 .into_any();
8724
8725 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8726
8727 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8728
8729 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
8730 let mut origin = start_point
8731 + line_origin
8732 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8733 origin.x = origin.x.max(content_origin.x);
8734
8735 let max_x = content_origin.x + editor_width - size.width;
8736
8737 if origin.x > max_x {
8738 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8739
8740 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8741 origin.y += offset;
8742 IconName::ArrowUp
8743 } else {
8744 origin.y -= offset;
8745 IconName::ArrowDown
8746 };
8747
8748 element = self
8749 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
8750 .into_any();
8751
8752 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8753
8754 origin.x = content_origin.x + editor_width - size.width - px(2.);
8755 }
8756
8757 element.prepaint_at(origin, window, cx);
8758 Some((element, origin))
8759 }
8760
8761 fn render_edit_prediction_diff_popover(
8762 self: &Editor,
8763 text_bounds: &Bounds<Pixels>,
8764 content_origin: gpui::Point<Pixels>,
8765 right_margin: Pixels,
8766 editor_snapshot: &EditorSnapshot,
8767 visible_row_range: Range<DisplayRow>,
8768 line_layouts: &[LineWithInvisibles],
8769 line_height: Pixels,
8770 scroll_pixel_position: gpui::Point<Pixels>,
8771 newest_selection_head: Option<DisplayPoint>,
8772 editor_width: Pixels,
8773 style: &EditorStyle,
8774 edits: &Vec<(Range<Anchor>, String)>,
8775 edit_preview: &Option<language::EditPreview>,
8776 snapshot: &language::BufferSnapshot,
8777 window: &mut Window,
8778 cx: &mut App,
8779 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8780 let edit_start = edits
8781 .first()
8782 .unwrap()
8783 .0
8784 .start
8785 .to_display_point(editor_snapshot);
8786 let edit_end = edits
8787 .last()
8788 .unwrap()
8789 .0
8790 .end
8791 .to_display_point(editor_snapshot);
8792
8793 let is_visible = visible_row_range.contains(&edit_start.row())
8794 || visible_row_range.contains(&edit_end.row());
8795 if !is_visible {
8796 return None;
8797 }
8798
8799 let highlighted_edits =
8800 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
8801
8802 let styled_text = highlighted_edits.to_styled_text(&style.text);
8803 let line_count = highlighted_edits.text.lines().count();
8804
8805 const BORDER_WIDTH: Pixels = px(1.);
8806
8807 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8808 let has_keybind = keybind.is_some();
8809
8810 let mut element = h_flex()
8811 .items_start()
8812 .child(
8813 h_flex()
8814 .bg(cx.theme().colors().editor_background)
8815 .border(BORDER_WIDTH)
8816 .shadow_xs()
8817 .border_color(cx.theme().colors().border)
8818 .rounded_l_lg()
8819 .when(line_count > 1, |el| el.rounded_br_lg())
8820 .pr_1()
8821 .child(styled_text),
8822 )
8823 .child(
8824 h_flex()
8825 .h(line_height + BORDER_WIDTH * 2.)
8826 .px_1p5()
8827 .gap_1()
8828 // Workaround: For some reason, there's a gap if we don't do this
8829 .ml(-BORDER_WIDTH)
8830 .shadow(vec![gpui::BoxShadow {
8831 color: gpui::black().opacity(0.05),
8832 offset: point(px(1.), px(1.)),
8833 blur_radius: px(2.),
8834 spread_radius: px(0.),
8835 }])
8836 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8837 .border(BORDER_WIDTH)
8838 .border_color(cx.theme().colors().border)
8839 .rounded_r_lg()
8840 .id("edit_prediction_diff_popover_keybind")
8841 .when(!has_keybind, |el| {
8842 let status_colors = cx.theme().status();
8843
8844 el.bg(status_colors.error_background)
8845 .border_color(status_colors.error.opacity(0.6))
8846 .child(Icon::new(IconName::Info).color(Color::Error))
8847 .cursor_default()
8848 .hoverable_tooltip(move |_window, cx| {
8849 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8850 })
8851 })
8852 .children(keybind),
8853 )
8854 .into_any();
8855
8856 let longest_row =
8857 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8858 let longest_line_width = if visible_row_range.contains(&longest_row) {
8859 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8860 } else {
8861 layout_line(
8862 longest_row,
8863 editor_snapshot,
8864 style,
8865 editor_width,
8866 |_| false,
8867 window,
8868 cx,
8869 )
8870 .width
8871 };
8872
8873 let viewport_bounds =
8874 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8875 right: -right_margin,
8876 ..Default::default()
8877 });
8878
8879 let x_after_longest =
8880 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
8881 - scroll_pixel_position.x;
8882
8883 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8884
8885 // Fully visible if it can be displayed within the window (allow overlapping other
8886 // panes). However, this is only allowed if the popover starts within text_bounds.
8887 let can_position_to_the_right = x_after_longest < text_bounds.right()
8888 && x_after_longest + element_bounds.width < viewport_bounds.right();
8889
8890 let mut origin = if can_position_to_the_right {
8891 point(
8892 x_after_longest,
8893 text_bounds.origin.y + edit_start.row().as_f32() * line_height
8894 - scroll_pixel_position.y,
8895 )
8896 } else {
8897 let cursor_row = newest_selection_head.map(|head| head.row());
8898 let above_edit = edit_start
8899 .row()
8900 .0
8901 .checked_sub(line_count as u32)
8902 .map(DisplayRow);
8903 let below_edit = Some(edit_end.row() + 1);
8904 let above_cursor =
8905 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
8906 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
8907
8908 // Place the edit popover adjacent to the edit if there is a location
8909 // available that is onscreen and does not obscure the cursor. Otherwise,
8910 // place it adjacent to the cursor.
8911 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
8912 .into_iter()
8913 .flatten()
8914 .find(|&start_row| {
8915 let end_row = start_row + line_count as u32;
8916 visible_row_range.contains(&start_row)
8917 && visible_row_range.contains(&end_row)
8918 && cursor_row.map_or(true, |cursor_row| {
8919 !((start_row..end_row).contains(&cursor_row))
8920 })
8921 })?;
8922
8923 content_origin
8924 + point(
8925 -scroll_pixel_position.x,
8926 row_target.as_f32() * line_height - scroll_pixel_position.y,
8927 )
8928 };
8929
8930 origin.x -= BORDER_WIDTH;
8931
8932 window.defer_draw(element, origin, 1);
8933
8934 // Do not return an element, since it will already be drawn due to defer_draw.
8935 None
8936 }
8937
8938 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
8939 px(30.)
8940 }
8941
8942 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
8943 if self.read_only(cx) {
8944 cx.theme().players().read_only()
8945 } else {
8946 self.style.as_ref().unwrap().local_player
8947 }
8948 }
8949
8950 fn render_edit_prediction_accept_keybind(
8951 &self,
8952 window: &mut Window,
8953 cx: &App,
8954 ) -> Option<AnyElement> {
8955 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
8956 let accept_keystroke = accept_binding.keystroke()?;
8957
8958 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8959
8960 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
8961 Color::Accent
8962 } else {
8963 Color::Muted
8964 };
8965
8966 h_flex()
8967 .px_0p5()
8968 .when(is_platform_style_mac, |parent| parent.gap_0p5())
8969 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8970 .text_size(TextSize::XSmall.rems(cx))
8971 .child(h_flex().children(ui::render_modifiers(
8972 &accept_keystroke.modifiers,
8973 PlatformStyle::platform(),
8974 Some(modifiers_color),
8975 Some(IconSize::XSmall.rems().into()),
8976 true,
8977 )))
8978 .when(is_platform_style_mac, |parent| {
8979 parent.child(accept_keystroke.key.clone())
8980 })
8981 .when(!is_platform_style_mac, |parent| {
8982 parent.child(
8983 Key::new(
8984 util::capitalize(&accept_keystroke.key),
8985 Some(Color::Default),
8986 )
8987 .size(Some(IconSize::XSmall.rems().into())),
8988 )
8989 })
8990 .into_any()
8991 .into()
8992 }
8993
8994 fn render_edit_prediction_line_popover(
8995 &self,
8996 label: impl Into<SharedString>,
8997 icon: Option<IconName>,
8998 window: &mut Window,
8999 cx: &App,
9000 ) -> Option<Stateful<Div>> {
9001 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9002
9003 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9004 let has_keybind = keybind.is_some();
9005
9006 let result = h_flex()
9007 .id("ep-line-popover")
9008 .py_0p5()
9009 .pl_1()
9010 .pr(padding_right)
9011 .gap_1()
9012 .rounded_md()
9013 .border_1()
9014 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9015 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9016 .shadow_xs()
9017 .when(!has_keybind, |el| {
9018 let status_colors = cx.theme().status();
9019
9020 el.bg(status_colors.error_background)
9021 .border_color(status_colors.error.opacity(0.6))
9022 .pl_2()
9023 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9024 .cursor_default()
9025 .hoverable_tooltip(move |_window, cx| {
9026 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9027 })
9028 })
9029 .children(keybind)
9030 .child(
9031 Label::new(label)
9032 .size(LabelSize::Small)
9033 .when(!has_keybind, |el| {
9034 el.color(cx.theme().status().error.into()).strikethrough()
9035 }),
9036 )
9037 .when(!has_keybind, |el| {
9038 el.child(
9039 h_flex().ml_1().child(
9040 Icon::new(IconName::Info)
9041 .size(IconSize::Small)
9042 .color(cx.theme().status().error.into()),
9043 ),
9044 )
9045 })
9046 .when_some(icon, |element, icon| {
9047 element.child(
9048 div()
9049 .mt(px(1.5))
9050 .child(Icon::new(icon).size(IconSize::Small)),
9051 )
9052 });
9053
9054 Some(result)
9055 }
9056
9057 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9058 let accent_color = cx.theme().colors().text_accent;
9059 let editor_bg_color = cx.theme().colors().editor_background;
9060 editor_bg_color.blend(accent_color.opacity(0.1))
9061 }
9062
9063 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9064 let accent_color = cx.theme().colors().text_accent;
9065 let editor_bg_color = cx.theme().colors().editor_background;
9066 editor_bg_color.blend(accent_color.opacity(0.6))
9067 }
9068
9069 fn render_edit_prediction_cursor_popover(
9070 &self,
9071 min_width: Pixels,
9072 max_width: Pixels,
9073 cursor_point: Point,
9074 style: &EditorStyle,
9075 accept_keystroke: Option<&gpui::Keystroke>,
9076 _window: &Window,
9077 cx: &mut Context<Editor>,
9078 ) -> Option<AnyElement> {
9079 let provider = self.edit_prediction_provider.as_ref()?;
9080
9081 if provider.provider.needs_terms_acceptance(cx) {
9082 return Some(
9083 h_flex()
9084 .min_w(min_width)
9085 .flex_1()
9086 .px_2()
9087 .py_1()
9088 .gap_3()
9089 .elevation_2(cx)
9090 .hover(|style| style.bg(cx.theme().colors().element_hover))
9091 .id("accept-terms")
9092 .cursor_pointer()
9093 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
9094 .on_click(cx.listener(|this, _event, window, cx| {
9095 cx.stop_propagation();
9096 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
9097 window.dispatch_action(
9098 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
9099 cx,
9100 );
9101 }))
9102 .child(
9103 h_flex()
9104 .flex_1()
9105 .gap_2()
9106 .child(Icon::new(IconName::ZedPredict))
9107 .child(Label::new("Accept Terms of Service"))
9108 .child(div().w_full())
9109 .child(
9110 Icon::new(IconName::ArrowUpRight)
9111 .color(Color::Muted)
9112 .size(IconSize::Small),
9113 )
9114 .into_any_element(),
9115 )
9116 .into_any(),
9117 );
9118 }
9119
9120 let is_refreshing = provider.provider.is_refreshing(cx);
9121
9122 fn pending_completion_container() -> Div {
9123 h_flex()
9124 .h_full()
9125 .flex_1()
9126 .gap_2()
9127 .child(Icon::new(IconName::ZedPredict))
9128 }
9129
9130 let completion = match &self.active_inline_completion {
9131 Some(prediction) => {
9132 if !self.has_visible_completions_menu() {
9133 const RADIUS: Pixels = px(6.);
9134 const BORDER_WIDTH: Pixels = px(1.);
9135
9136 return Some(
9137 h_flex()
9138 .elevation_2(cx)
9139 .border(BORDER_WIDTH)
9140 .border_color(cx.theme().colors().border)
9141 .when(accept_keystroke.is_none(), |el| {
9142 el.border_color(cx.theme().status().error)
9143 })
9144 .rounded(RADIUS)
9145 .rounded_tl(px(0.))
9146 .overflow_hidden()
9147 .child(div().px_1p5().child(match &prediction.completion {
9148 InlineCompletion::Move { target, snapshot } => {
9149 use text::ToPoint as _;
9150 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
9151 {
9152 Icon::new(IconName::ZedPredictDown)
9153 } else {
9154 Icon::new(IconName::ZedPredictUp)
9155 }
9156 }
9157 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
9158 }))
9159 .child(
9160 h_flex()
9161 .gap_1()
9162 .py_1()
9163 .px_2()
9164 .rounded_r(RADIUS - BORDER_WIDTH)
9165 .border_l_1()
9166 .border_color(cx.theme().colors().border)
9167 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9168 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9169 el.child(
9170 Label::new("Hold")
9171 .size(LabelSize::Small)
9172 .when(accept_keystroke.is_none(), |el| {
9173 el.strikethrough()
9174 })
9175 .line_height_style(LineHeightStyle::UiLabel),
9176 )
9177 })
9178 .id("edit_prediction_cursor_popover_keybind")
9179 .when(accept_keystroke.is_none(), |el| {
9180 let status_colors = cx.theme().status();
9181
9182 el.bg(status_colors.error_background)
9183 .border_color(status_colors.error.opacity(0.6))
9184 .child(Icon::new(IconName::Info).color(Color::Error))
9185 .cursor_default()
9186 .hoverable_tooltip(move |_window, cx| {
9187 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9188 .into()
9189 })
9190 })
9191 .when_some(
9192 accept_keystroke.as_ref(),
9193 |el, accept_keystroke| {
9194 el.child(h_flex().children(ui::render_modifiers(
9195 &accept_keystroke.modifiers,
9196 PlatformStyle::platform(),
9197 Some(Color::Default),
9198 Some(IconSize::XSmall.rems().into()),
9199 false,
9200 )))
9201 },
9202 ),
9203 )
9204 .into_any(),
9205 );
9206 }
9207
9208 self.render_edit_prediction_cursor_popover_preview(
9209 prediction,
9210 cursor_point,
9211 style,
9212 cx,
9213 )?
9214 }
9215
9216 None if is_refreshing => match &self.stale_inline_completion_in_menu {
9217 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9218 stale_completion,
9219 cursor_point,
9220 style,
9221 cx,
9222 )?,
9223
9224 None => {
9225 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
9226 }
9227 },
9228
9229 None => pending_completion_container().child(Label::new("No Prediction")),
9230 };
9231
9232 let completion = if is_refreshing {
9233 completion
9234 .with_animation(
9235 "loading-completion",
9236 Animation::new(Duration::from_secs(2))
9237 .repeat()
9238 .with_easing(pulsating_between(0.4, 0.8)),
9239 |label, delta| label.opacity(delta),
9240 )
9241 .into_any_element()
9242 } else {
9243 completion.into_any_element()
9244 };
9245
9246 let has_completion = self.active_inline_completion.is_some();
9247
9248 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9249 Some(
9250 h_flex()
9251 .min_w(min_width)
9252 .max_w(max_width)
9253 .flex_1()
9254 .elevation_2(cx)
9255 .border_color(cx.theme().colors().border)
9256 .child(
9257 div()
9258 .flex_1()
9259 .py_1()
9260 .px_2()
9261 .overflow_hidden()
9262 .child(completion),
9263 )
9264 .when_some(accept_keystroke, |el, accept_keystroke| {
9265 if !accept_keystroke.modifiers.modified() {
9266 return el;
9267 }
9268
9269 el.child(
9270 h_flex()
9271 .h_full()
9272 .border_l_1()
9273 .rounded_r_lg()
9274 .border_color(cx.theme().colors().border)
9275 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9276 .gap_1()
9277 .py_1()
9278 .px_2()
9279 .child(
9280 h_flex()
9281 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9282 .when(is_platform_style_mac, |parent| parent.gap_1())
9283 .child(h_flex().children(ui::render_modifiers(
9284 &accept_keystroke.modifiers,
9285 PlatformStyle::platform(),
9286 Some(if !has_completion {
9287 Color::Muted
9288 } else {
9289 Color::Default
9290 }),
9291 None,
9292 false,
9293 ))),
9294 )
9295 .child(Label::new("Preview").into_any_element())
9296 .opacity(if has_completion { 1.0 } else { 0.4 }),
9297 )
9298 })
9299 .into_any(),
9300 )
9301 }
9302
9303 fn render_edit_prediction_cursor_popover_preview(
9304 &self,
9305 completion: &InlineCompletionState,
9306 cursor_point: Point,
9307 style: &EditorStyle,
9308 cx: &mut Context<Editor>,
9309 ) -> Option<Div> {
9310 use text::ToPoint as _;
9311
9312 fn render_relative_row_jump(
9313 prefix: impl Into<String>,
9314 current_row: u32,
9315 target_row: u32,
9316 ) -> Div {
9317 let (row_diff, arrow) = if target_row < current_row {
9318 (current_row - target_row, IconName::ArrowUp)
9319 } else {
9320 (target_row - current_row, IconName::ArrowDown)
9321 };
9322
9323 h_flex()
9324 .child(
9325 Label::new(format!("{}{}", prefix.into(), row_diff))
9326 .color(Color::Muted)
9327 .size(LabelSize::Small),
9328 )
9329 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9330 }
9331
9332 match &completion.completion {
9333 InlineCompletion::Move {
9334 target, snapshot, ..
9335 } => Some(
9336 h_flex()
9337 .px_2()
9338 .gap_2()
9339 .flex_1()
9340 .child(
9341 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
9342 Icon::new(IconName::ZedPredictDown)
9343 } else {
9344 Icon::new(IconName::ZedPredictUp)
9345 },
9346 )
9347 .child(Label::new("Jump to Edit")),
9348 ),
9349
9350 InlineCompletion::Edit {
9351 edits,
9352 edit_preview,
9353 snapshot,
9354 display_mode: _,
9355 } => {
9356 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
9357
9358 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
9359 &snapshot,
9360 &edits,
9361 edit_preview.as_ref()?,
9362 true,
9363 cx,
9364 )
9365 .first_line_preview();
9366
9367 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9368 .with_default_highlights(&style.text, highlighted_edits.highlights);
9369
9370 let preview = h_flex()
9371 .gap_1()
9372 .min_w_16()
9373 .child(styled_text)
9374 .when(has_more_lines, |parent| parent.child("…"));
9375
9376 let left = if first_edit_row != cursor_point.row {
9377 render_relative_row_jump("", cursor_point.row, first_edit_row)
9378 .into_any_element()
9379 } else {
9380 Icon::new(IconName::ZedPredict).into_any_element()
9381 };
9382
9383 Some(
9384 h_flex()
9385 .h_full()
9386 .flex_1()
9387 .gap_2()
9388 .pr_1()
9389 .overflow_x_hidden()
9390 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9391 .child(left)
9392 .child(preview),
9393 )
9394 }
9395 }
9396 }
9397
9398 pub fn render_context_menu(
9399 &self,
9400 style: &EditorStyle,
9401 max_height_in_lines: u32,
9402 window: &mut Window,
9403 cx: &mut Context<Editor>,
9404 ) -> Option<AnyElement> {
9405 let menu = self.context_menu.borrow();
9406 let menu = menu.as_ref()?;
9407 if !menu.visible() {
9408 return None;
9409 };
9410 Some(menu.render(style, max_height_in_lines, window, cx))
9411 }
9412
9413 fn render_context_menu_aside(
9414 &mut self,
9415 max_size: Size<Pixels>,
9416 window: &mut Window,
9417 cx: &mut Context<Editor>,
9418 ) -> Option<AnyElement> {
9419 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9420 if menu.visible() {
9421 menu.render_aside(max_size, window, cx)
9422 } else {
9423 None
9424 }
9425 })
9426 }
9427
9428 fn hide_context_menu(
9429 &mut self,
9430 window: &mut Window,
9431 cx: &mut Context<Self>,
9432 ) -> Option<CodeContextMenu> {
9433 cx.notify();
9434 self.completion_tasks.clear();
9435 let context_menu = self.context_menu.borrow_mut().take();
9436 self.stale_inline_completion_in_menu.take();
9437 self.update_visible_inline_completion(window, cx);
9438 if let Some(CodeContextMenu::Completions(_)) = &context_menu {
9439 if let Some(completion_provider) = &self.completion_provider {
9440 completion_provider.selection_changed(None, window, cx);
9441 }
9442 }
9443 context_menu
9444 }
9445
9446 fn show_snippet_choices(
9447 &mut self,
9448 choices: &Vec<String>,
9449 selection: Range<Anchor>,
9450 cx: &mut Context<Self>,
9451 ) {
9452 let buffer_id = match (&selection.start.buffer_id, &selection.end.buffer_id) {
9453 (Some(a), Some(b)) if a == b => a,
9454 _ => {
9455 log::error!("expected anchor range to have matching buffer IDs");
9456 return;
9457 }
9458 };
9459 let multi_buffer = self.buffer().read(cx);
9460 let Some(buffer) = multi_buffer.buffer(*buffer_id) else {
9461 return;
9462 };
9463
9464 let id = post_inc(&mut self.next_completion_id);
9465 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9466 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9467 CompletionsMenu::new_snippet_choices(
9468 id,
9469 true,
9470 choices,
9471 selection,
9472 buffer,
9473 snippet_sort_order,
9474 ),
9475 ));
9476 }
9477
9478 pub fn insert_snippet(
9479 &mut self,
9480 insertion_ranges: &[Range<usize>],
9481 snippet: Snippet,
9482 window: &mut Window,
9483 cx: &mut Context<Self>,
9484 ) -> Result<()> {
9485 struct Tabstop<T> {
9486 is_end_tabstop: bool,
9487 ranges: Vec<Range<T>>,
9488 choices: Option<Vec<String>>,
9489 }
9490
9491 let tabstops = self.buffer.update(cx, |buffer, cx| {
9492 let snippet_text: Arc<str> = snippet.text.clone().into();
9493 let edits = insertion_ranges
9494 .iter()
9495 .cloned()
9496 .map(|range| (range, snippet_text.clone()));
9497 let autoindent_mode = AutoindentMode::Block {
9498 original_indent_columns: Vec::new(),
9499 };
9500 buffer.edit(edits, Some(autoindent_mode), cx);
9501
9502 let snapshot = &*buffer.read(cx);
9503 let snippet = &snippet;
9504 snippet
9505 .tabstops
9506 .iter()
9507 .map(|tabstop| {
9508 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
9509 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9510 });
9511 let mut tabstop_ranges = tabstop
9512 .ranges
9513 .iter()
9514 .flat_map(|tabstop_range| {
9515 let mut delta = 0_isize;
9516 insertion_ranges.iter().map(move |insertion_range| {
9517 let insertion_start = insertion_range.start as isize + delta;
9518 delta +=
9519 snippet.text.len() as isize - insertion_range.len() as isize;
9520
9521 let start = ((insertion_start + tabstop_range.start) as usize)
9522 .min(snapshot.len());
9523 let end = ((insertion_start + tabstop_range.end) as usize)
9524 .min(snapshot.len());
9525 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9526 })
9527 })
9528 .collect::<Vec<_>>();
9529 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9530
9531 Tabstop {
9532 is_end_tabstop,
9533 ranges: tabstop_ranges,
9534 choices: tabstop.choices.clone(),
9535 }
9536 })
9537 .collect::<Vec<_>>()
9538 });
9539 if let Some(tabstop) = tabstops.first() {
9540 self.change_selections(Default::default(), window, cx, |s| {
9541 // Reverse order so that the first range is the newest created selection.
9542 // Completions will use it and autoscroll will prioritize it.
9543 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9544 });
9545
9546 if let Some(choices) = &tabstop.choices {
9547 if let Some(selection) = tabstop.ranges.first() {
9548 self.show_snippet_choices(choices, selection.clone(), cx)
9549 }
9550 }
9551
9552 // If we're already at the last tabstop and it's at the end of the snippet,
9553 // we're done, we don't need to keep the state around.
9554 if !tabstop.is_end_tabstop {
9555 let choices = tabstops
9556 .iter()
9557 .map(|tabstop| tabstop.choices.clone())
9558 .collect();
9559
9560 let ranges = tabstops
9561 .into_iter()
9562 .map(|tabstop| tabstop.ranges)
9563 .collect::<Vec<_>>();
9564
9565 self.snippet_stack.push(SnippetState {
9566 active_index: 0,
9567 ranges,
9568 choices,
9569 });
9570 }
9571
9572 // Check whether the just-entered snippet ends with an auto-closable bracket.
9573 if self.autoclose_regions.is_empty() {
9574 let snapshot = self.buffer.read(cx).snapshot(cx);
9575 let mut all_selections = self.selections.all::<Point>(cx);
9576 for selection in &mut all_selections {
9577 let selection_head = selection.head();
9578 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9579 continue;
9580 };
9581
9582 let mut bracket_pair = None;
9583 let max_lookup_length = scope
9584 .brackets()
9585 .map(|(pair, _)| {
9586 pair.start
9587 .as_str()
9588 .chars()
9589 .count()
9590 .max(pair.end.as_str().chars().count())
9591 })
9592 .max();
9593 if let Some(max_lookup_length) = max_lookup_length {
9594 let next_text = snapshot
9595 .chars_at(selection_head)
9596 .take(max_lookup_length)
9597 .collect::<String>();
9598 let prev_text = snapshot
9599 .reversed_chars_at(selection_head)
9600 .take(max_lookup_length)
9601 .collect::<String>();
9602
9603 for (pair, enabled) in scope.brackets() {
9604 if enabled
9605 && pair.close
9606 && prev_text.starts_with(pair.start.as_str())
9607 && next_text.starts_with(pair.end.as_str())
9608 {
9609 bracket_pair = Some(pair.clone());
9610 break;
9611 }
9612 }
9613 }
9614
9615 if let Some(pair) = bracket_pair {
9616 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9617 let autoclose_enabled =
9618 self.use_autoclose && snapshot_settings.use_autoclose;
9619 if autoclose_enabled {
9620 let start = snapshot.anchor_after(selection_head);
9621 let end = snapshot.anchor_after(selection_head);
9622 self.autoclose_regions.push(AutocloseRegion {
9623 selection_id: selection.id,
9624 range: start..end,
9625 pair,
9626 });
9627 }
9628 }
9629 }
9630 }
9631 }
9632 Ok(())
9633 }
9634
9635 pub fn move_to_next_snippet_tabstop(
9636 &mut self,
9637 window: &mut Window,
9638 cx: &mut Context<Self>,
9639 ) -> bool {
9640 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9641 }
9642
9643 pub fn move_to_prev_snippet_tabstop(
9644 &mut self,
9645 window: &mut Window,
9646 cx: &mut Context<Self>,
9647 ) -> bool {
9648 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9649 }
9650
9651 pub fn move_to_snippet_tabstop(
9652 &mut self,
9653 bias: Bias,
9654 window: &mut Window,
9655 cx: &mut Context<Self>,
9656 ) -> bool {
9657 if let Some(mut snippet) = self.snippet_stack.pop() {
9658 match bias {
9659 Bias::Left => {
9660 if snippet.active_index > 0 {
9661 snippet.active_index -= 1;
9662 } else {
9663 self.snippet_stack.push(snippet);
9664 return false;
9665 }
9666 }
9667 Bias::Right => {
9668 if snippet.active_index + 1 < snippet.ranges.len() {
9669 snippet.active_index += 1;
9670 } else {
9671 self.snippet_stack.push(snippet);
9672 return false;
9673 }
9674 }
9675 }
9676 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9677 self.change_selections(Default::default(), window, cx, |s| {
9678 // Reverse order so that the first range is the newest created selection.
9679 // Completions will use it and autoscroll will prioritize it.
9680 s.select_ranges(current_ranges.iter().rev().cloned())
9681 });
9682
9683 if let Some(choices) = &snippet.choices[snippet.active_index] {
9684 if let Some(selection) = current_ranges.first() {
9685 self.show_snippet_choices(&choices, selection.clone(), cx);
9686 }
9687 }
9688
9689 // If snippet state is not at the last tabstop, push it back on the stack
9690 if snippet.active_index + 1 < snippet.ranges.len() {
9691 self.snippet_stack.push(snippet);
9692 }
9693 return true;
9694 }
9695 }
9696
9697 false
9698 }
9699
9700 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9701 self.transact(window, cx, |this, window, cx| {
9702 this.select_all(&SelectAll, window, cx);
9703 this.insert("", window, cx);
9704 });
9705 }
9706
9707 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9708 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9709 self.transact(window, cx, |this, window, cx| {
9710 this.select_autoclose_pair(window, cx);
9711 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9712 if !this.linked_edit_ranges.is_empty() {
9713 let selections = this.selections.all::<MultiBufferPoint>(cx);
9714 let snapshot = this.buffer.read(cx).snapshot(cx);
9715
9716 for selection in selections.iter() {
9717 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9718 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9719 if selection_start.buffer_id != selection_end.buffer_id {
9720 continue;
9721 }
9722 if let Some(ranges) =
9723 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9724 {
9725 for (buffer, entries) in ranges {
9726 linked_ranges.entry(buffer).or_default().extend(entries);
9727 }
9728 }
9729 }
9730 }
9731
9732 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
9733 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9734 for selection in &mut selections {
9735 if selection.is_empty() {
9736 let old_head = selection.head();
9737 let mut new_head =
9738 movement::left(&display_map, old_head.to_display_point(&display_map))
9739 .to_point(&display_map);
9740 if let Some((buffer, line_buffer_range)) = display_map
9741 .buffer_snapshot
9742 .buffer_line_for_row(MultiBufferRow(old_head.row))
9743 {
9744 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9745 let indent_len = match indent_size.kind {
9746 IndentKind::Space => {
9747 buffer.settings_at(line_buffer_range.start, cx).tab_size
9748 }
9749 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9750 };
9751 if old_head.column <= indent_size.len && old_head.column > 0 {
9752 let indent_len = indent_len.get();
9753 new_head = cmp::min(
9754 new_head,
9755 MultiBufferPoint::new(
9756 old_head.row,
9757 ((old_head.column - 1) / indent_len) * indent_len,
9758 ),
9759 );
9760 }
9761 }
9762
9763 selection.set_head(new_head, SelectionGoal::None);
9764 }
9765 }
9766
9767 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9768 this.insert("", window, cx);
9769 let empty_str: Arc<str> = Arc::from("");
9770 for (buffer, edits) in linked_ranges {
9771 let snapshot = buffer.read(cx).snapshot();
9772 use text::ToPoint as TP;
9773
9774 let edits = edits
9775 .into_iter()
9776 .map(|range| {
9777 let end_point = TP::to_point(&range.end, &snapshot);
9778 let mut start_point = TP::to_point(&range.start, &snapshot);
9779
9780 if end_point == start_point {
9781 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9782 .saturating_sub(1);
9783 start_point =
9784 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9785 };
9786
9787 (start_point..end_point, empty_str.clone())
9788 })
9789 .sorted_by_key(|(range, _)| range.start)
9790 .collect::<Vec<_>>();
9791 buffer.update(cx, |this, cx| {
9792 this.edit(edits, None, cx);
9793 })
9794 }
9795 this.refresh_inline_completion(true, false, window, cx);
9796 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
9797 });
9798 }
9799
9800 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9801 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9802 self.transact(window, cx, |this, window, cx| {
9803 this.change_selections(Default::default(), window, cx, |s| {
9804 s.move_with(|map, selection| {
9805 if selection.is_empty() {
9806 let cursor = movement::right(map, selection.head());
9807 selection.end = cursor;
9808 selection.reversed = true;
9809 selection.goal = SelectionGoal::None;
9810 }
9811 })
9812 });
9813 this.insert("", window, cx);
9814 this.refresh_inline_completion(true, false, window, cx);
9815 });
9816 }
9817
9818 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9819 if self.mode.is_single_line() {
9820 cx.propagate();
9821 return;
9822 }
9823
9824 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9825 if self.move_to_prev_snippet_tabstop(window, cx) {
9826 return;
9827 }
9828 self.outdent(&Outdent, window, cx);
9829 }
9830
9831 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9832 if self.mode.is_single_line() {
9833 cx.propagate();
9834 return;
9835 }
9836
9837 if self.move_to_next_snippet_tabstop(window, cx) {
9838 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9839 return;
9840 }
9841 if self.read_only(cx) {
9842 return;
9843 }
9844 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9845 let mut selections = self.selections.all_adjusted(cx);
9846 let buffer = self.buffer.read(cx);
9847 let snapshot = buffer.snapshot(cx);
9848 let rows_iter = selections.iter().map(|s| s.head().row);
9849 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9850
9851 let has_some_cursor_in_whitespace = selections
9852 .iter()
9853 .filter(|selection| selection.is_empty())
9854 .any(|selection| {
9855 let cursor = selection.head();
9856 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9857 cursor.column < current_indent.len
9858 });
9859
9860 let mut edits = Vec::new();
9861 let mut prev_edited_row = 0;
9862 let mut row_delta = 0;
9863 for selection in &mut selections {
9864 if selection.start.row != prev_edited_row {
9865 row_delta = 0;
9866 }
9867 prev_edited_row = selection.end.row;
9868
9869 // If the selection is non-empty, then increase the indentation of the selected lines.
9870 if !selection.is_empty() {
9871 row_delta =
9872 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9873 continue;
9874 }
9875
9876 let cursor = selection.head();
9877 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9878 if let Some(suggested_indent) =
9879 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
9880 {
9881 // Don't do anything if already at suggested indent
9882 // and there is any other cursor which is not
9883 if has_some_cursor_in_whitespace
9884 && cursor.column == current_indent.len
9885 && current_indent.len == suggested_indent.len
9886 {
9887 continue;
9888 }
9889
9890 // Adjust line and move cursor to suggested indent
9891 // if cursor is not at suggested indent
9892 if cursor.column < suggested_indent.len
9893 && cursor.column <= current_indent.len
9894 && current_indent.len <= suggested_indent.len
9895 {
9896 selection.start = Point::new(cursor.row, suggested_indent.len);
9897 selection.end = selection.start;
9898 if row_delta == 0 {
9899 edits.extend(Buffer::edit_for_indent_size_adjustment(
9900 cursor.row,
9901 current_indent,
9902 suggested_indent,
9903 ));
9904 row_delta = suggested_indent.len - current_indent.len;
9905 }
9906 continue;
9907 }
9908
9909 // If current indent is more than suggested indent
9910 // only move cursor to current indent and skip indent
9911 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
9912 selection.start = Point::new(cursor.row, current_indent.len);
9913 selection.end = selection.start;
9914 continue;
9915 }
9916 }
9917
9918 // Otherwise, insert a hard or soft tab.
9919 let settings = buffer.language_settings_at(cursor, cx);
9920 let tab_size = if settings.hard_tabs {
9921 IndentSize::tab()
9922 } else {
9923 let tab_size = settings.tab_size.get();
9924 let indent_remainder = snapshot
9925 .text_for_range(Point::new(cursor.row, 0)..cursor)
9926 .flat_map(str::chars)
9927 .fold(row_delta % tab_size, |counter: u32, c| {
9928 if c == '\t' {
9929 0
9930 } else {
9931 (counter + 1) % tab_size
9932 }
9933 });
9934
9935 let chars_to_next_tab_stop = tab_size - indent_remainder;
9936 IndentSize::spaces(chars_to_next_tab_stop)
9937 };
9938 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
9939 selection.end = selection.start;
9940 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
9941 row_delta += tab_size.len;
9942 }
9943
9944 self.transact(window, cx, |this, window, cx| {
9945 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9946 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9947 this.refresh_inline_completion(true, false, window, cx);
9948 });
9949 }
9950
9951 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
9952 if self.read_only(cx) {
9953 return;
9954 }
9955 if self.mode.is_single_line() {
9956 cx.propagate();
9957 return;
9958 }
9959
9960 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9961 let mut selections = self.selections.all::<Point>(cx);
9962 let mut prev_edited_row = 0;
9963 let mut row_delta = 0;
9964 let mut edits = Vec::new();
9965 let buffer = self.buffer.read(cx);
9966 let snapshot = buffer.snapshot(cx);
9967 for selection in &mut selections {
9968 if selection.start.row != prev_edited_row {
9969 row_delta = 0;
9970 }
9971 prev_edited_row = selection.end.row;
9972
9973 row_delta =
9974 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9975 }
9976
9977 self.transact(window, cx, |this, window, cx| {
9978 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9979 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9980 });
9981 }
9982
9983 fn indent_selection(
9984 buffer: &MultiBuffer,
9985 snapshot: &MultiBufferSnapshot,
9986 selection: &mut Selection<Point>,
9987 edits: &mut Vec<(Range<Point>, String)>,
9988 delta_for_start_row: u32,
9989 cx: &App,
9990 ) -> u32 {
9991 let settings = buffer.language_settings_at(selection.start, cx);
9992 let tab_size = settings.tab_size.get();
9993 let indent_kind = if settings.hard_tabs {
9994 IndentKind::Tab
9995 } else {
9996 IndentKind::Space
9997 };
9998 let mut start_row = selection.start.row;
9999 let mut end_row = selection.end.row + 1;
10000
10001 // If a selection ends at the beginning of a line, don't indent
10002 // that last line.
10003 if selection.end.column == 0 && selection.end.row > selection.start.row {
10004 end_row -= 1;
10005 }
10006
10007 // Avoid re-indenting a row that has already been indented by a
10008 // previous selection, but still update this selection's column
10009 // to reflect that indentation.
10010 if delta_for_start_row > 0 {
10011 start_row += 1;
10012 selection.start.column += delta_for_start_row;
10013 if selection.end.row == selection.start.row {
10014 selection.end.column += delta_for_start_row;
10015 }
10016 }
10017
10018 let mut delta_for_end_row = 0;
10019 let has_multiple_rows = start_row + 1 != end_row;
10020 for row in start_row..end_row {
10021 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10022 let indent_delta = match (current_indent.kind, indent_kind) {
10023 (IndentKind::Space, IndentKind::Space) => {
10024 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10025 IndentSize::spaces(columns_to_next_tab_stop)
10026 }
10027 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10028 (_, IndentKind::Tab) => IndentSize::tab(),
10029 };
10030
10031 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10032 0
10033 } else {
10034 selection.start.column
10035 };
10036 let row_start = Point::new(row, start);
10037 edits.push((
10038 row_start..row_start,
10039 indent_delta.chars().collect::<String>(),
10040 ));
10041
10042 // Update this selection's endpoints to reflect the indentation.
10043 if row == selection.start.row {
10044 selection.start.column += indent_delta.len;
10045 }
10046 if row == selection.end.row {
10047 selection.end.column += indent_delta.len;
10048 delta_for_end_row = indent_delta.len;
10049 }
10050 }
10051
10052 if selection.start.row == selection.end.row {
10053 delta_for_start_row + delta_for_end_row
10054 } else {
10055 delta_for_end_row
10056 }
10057 }
10058
10059 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10060 if self.read_only(cx) {
10061 return;
10062 }
10063 if self.mode.is_single_line() {
10064 cx.propagate();
10065 return;
10066 }
10067
10068 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10069 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10070 let selections = self.selections.all::<Point>(cx);
10071 let mut deletion_ranges = Vec::new();
10072 let mut last_outdent = None;
10073 {
10074 let buffer = self.buffer.read(cx);
10075 let snapshot = buffer.snapshot(cx);
10076 for selection in &selections {
10077 let settings = buffer.language_settings_at(selection.start, cx);
10078 let tab_size = settings.tab_size.get();
10079 let mut rows = selection.spanned_rows(false, &display_map);
10080
10081 // Avoid re-outdenting a row that has already been outdented by a
10082 // previous selection.
10083 if let Some(last_row) = last_outdent {
10084 if last_row == rows.start {
10085 rows.start = rows.start.next_row();
10086 }
10087 }
10088 let has_multiple_rows = rows.len() > 1;
10089 for row in rows.iter_rows() {
10090 let indent_size = snapshot.indent_size_for_line(row);
10091 if indent_size.len > 0 {
10092 let deletion_len = match indent_size.kind {
10093 IndentKind::Space => {
10094 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10095 if columns_to_prev_tab_stop == 0 {
10096 tab_size
10097 } else {
10098 columns_to_prev_tab_stop
10099 }
10100 }
10101 IndentKind::Tab => 1,
10102 };
10103 let start = if has_multiple_rows
10104 || deletion_len > selection.start.column
10105 || indent_size.len < selection.start.column
10106 {
10107 0
10108 } else {
10109 selection.start.column - deletion_len
10110 };
10111 deletion_ranges.push(
10112 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10113 );
10114 last_outdent = Some(row);
10115 }
10116 }
10117 }
10118 }
10119
10120 self.transact(window, cx, |this, window, cx| {
10121 this.buffer.update(cx, |buffer, cx| {
10122 let empty_str: Arc<str> = Arc::default();
10123 buffer.edit(
10124 deletion_ranges
10125 .into_iter()
10126 .map(|range| (range, empty_str.clone())),
10127 None,
10128 cx,
10129 );
10130 });
10131 let selections = this.selections.all::<usize>(cx);
10132 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10133 });
10134 }
10135
10136 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10137 if self.read_only(cx) {
10138 return;
10139 }
10140 if self.mode.is_single_line() {
10141 cx.propagate();
10142 return;
10143 }
10144
10145 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10146 let selections = self
10147 .selections
10148 .all::<usize>(cx)
10149 .into_iter()
10150 .map(|s| s.range());
10151
10152 self.transact(window, cx, |this, window, cx| {
10153 this.buffer.update(cx, |buffer, cx| {
10154 buffer.autoindent_ranges(selections, cx);
10155 });
10156 let selections = this.selections.all::<usize>(cx);
10157 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10158 });
10159 }
10160
10161 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10162 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10163 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10164 let selections = self.selections.all::<Point>(cx);
10165
10166 let mut new_cursors = Vec::new();
10167 let mut edit_ranges = Vec::new();
10168 let mut selections = selections.iter().peekable();
10169 while let Some(selection) = selections.next() {
10170 let mut rows = selection.spanned_rows(false, &display_map);
10171 let goal_display_column = selection.head().to_display_point(&display_map).column();
10172
10173 // Accumulate contiguous regions of rows that we want to delete.
10174 while let Some(next_selection) = selections.peek() {
10175 let next_rows = next_selection.spanned_rows(false, &display_map);
10176 if next_rows.start <= rows.end {
10177 rows.end = next_rows.end;
10178 selections.next().unwrap();
10179 } else {
10180 break;
10181 }
10182 }
10183
10184 let buffer = &display_map.buffer_snapshot;
10185 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
10186 let edit_end;
10187 let cursor_buffer_row;
10188 if buffer.max_point().row >= rows.end.0 {
10189 // If there's a line after the range, delete the \n from the end of the row range
10190 // and position the cursor on the next line.
10191 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
10192 cursor_buffer_row = rows.end;
10193 } else {
10194 // If there isn't a line after the range, delete the \n from the line before the
10195 // start of the row range and position the cursor there.
10196 edit_start = edit_start.saturating_sub(1);
10197 edit_end = buffer.len();
10198 cursor_buffer_row = rows.start.previous_row();
10199 }
10200
10201 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
10202 *cursor.column_mut() =
10203 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
10204
10205 new_cursors.push((
10206 selection.id,
10207 buffer.anchor_after(cursor.to_point(&display_map)),
10208 ));
10209 edit_ranges.push(edit_start..edit_end);
10210 }
10211
10212 self.transact(window, cx, |this, window, cx| {
10213 let buffer = this.buffer.update(cx, |buffer, cx| {
10214 let empty_str: Arc<str> = Arc::default();
10215 buffer.edit(
10216 edit_ranges
10217 .into_iter()
10218 .map(|range| (range, empty_str.clone())),
10219 None,
10220 cx,
10221 );
10222 buffer.snapshot(cx)
10223 });
10224 let new_selections = new_cursors
10225 .into_iter()
10226 .map(|(id, cursor)| {
10227 let cursor = cursor.to_point(&buffer);
10228 Selection {
10229 id,
10230 start: cursor,
10231 end: cursor,
10232 reversed: false,
10233 goal: SelectionGoal::None,
10234 }
10235 })
10236 .collect();
10237
10238 this.change_selections(Default::default(), window, cx, |s| {
10239 s.select(new_selections);
10240 });
10241 });
10242 }
10243
10244 pub fn join_lines_impl(
10245 &mut self,
10246 insert_whitespace: bool,
10247 window: &mut Window,
10248 cx: &mut Context<Self>,
10249 ) {
10250 if self.read_only(cx) {
10251 return;
10252 }
10253 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10254 for selection in self.selections.all::<Point>(cx) {
10255 let start = MultiBufferRow(selection.start.row);
10256 // Treat single line selections as if they include the next line. Otherwise this action
10257 // would do nothing for single line selections individual cursors.
10258 let end = if selection.start.row == selection.end.row {
10259 MultiBufferRow(selection.start.row + 1)
10260 } else {
10261 MultiBufferRow(selection.end.row)
10262 };
10263
10264 if let Some(last_row_range) = row_ranges.last_mut() {
10265 if start <= last_row_range.end {
10266 last_row_range.end = end;
10267 continue;
10268 }
10269 }
10270 row_ranges.push(start..end);
10271 }
10272
10273 let snapshot = self.buffer.read(cx).snapshot(cx);
10274 let mut cursor_positions = Vec::new();
10275 for row_range in &row_ranges {
10276 let anchor = snapshot.anchor_before(Point::new(
10277 row_range.end.previous_row().0,
10278 snapshot.line_len(row_range.end.previous_row()),
10279 ));
10280 cursor_positions.push(anchor..anchor);
10281 }
10282
10283 self.transact(window, cx, |this, window, cx| {
10284 for row_range in row_ranges.into_iter().rev() {
10285 for row in row_range.iter_rows().rev() {
10286 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10287 let next_line_row = row.next_row();
10288 let indent = snapshot.indent_size_for_line(next_line_row);
10289 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10290
10291 let replace =
10292 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10293 " "
10294 } else {
10295 ""
10296 };
10297
10298 this.buffer.update(cx, |buffer, cx| {
10299 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10300 });
10301 }
10302 }
10303
10304 this.change_selections(Default::default(), window, cx, |s| {
10305 s.select_anchor_ranges(cursor_positions)
10306 });
10307 });
10308 }
10309
10310 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10311 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10312 self.join_lines_impl(true, window, cx);
10313 }
10314
10315 pub fn sort_lines_case_sensitive(
10316 &mut self,
10317 _: &SortLinesCaseSensitive,
10318 window: &mut Window,
10319 cx: &mut Context<Self>,
10320 ) {
10321 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10322 }
10323
10324 pub fn sort_lines_by_length(
10325 &mut self,
10326 _: &SortLinesByLength,
10327 window: &mut Window,
10328 cx: &mut Context<Self>,
10329 ) {
10330 self.manipulate_immutable_lines(window, cx, |lines| {
10331 lines.sort_by_key(|&line| line.chars().count())
10332 })
10333 }
10334
10335 pub fn sort_lines_case_insensitive(
10336 &mut self,
10337 _: &SortLinesCaseInsensitive,
10338 window: &mut Window,
10339 cx: &mut Context<Self>,
10340 ) {
10341 self.manipulate_immutable_lines(window, cx, |lines| {
10342 lines.sort_by_key(|line| line.to_lowercase())
10343 })
10344 }
10345
10346 pub fn unique_lines_case_insensitive(
10347 &mut self,
10348 _: &UniqueLinesCaseInsensitive,
10349 window: &mut Window,
10350 cx: &mut Context<Self>,
10351 ) {
10352 self.manipulate_immutable_lines(window, cx, |lines| {
10353 let mut seen = HashSet::default();
10354 lines.retain(|line| seen.insert(line.to_lowercase()));
10355 })
10356 }
10357
10358 pub fn unique_lines_case_sensitive(
10359 &mut self,
10360 _: &UniqueLinesCaseSensitive,
10361 window: &mut Window,
10362 cx: &mut Context<Self>,
10363 ) {
10364 self.manipulate_immutable_lines(window, cx, |lines| {
10365 let mut seen = HashSet::default();
10366 lines.retain(|line| seen.insert(*line));
10367 })
10368 }
10369
10370 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10371 let Some(project) = self.project.clone() else {
10372 return;
10373 };
10374 self.reload(project, window, cx)
10375 .detach_and_notify_err(window, cx);
10376 }
10377
10378 pub fn restore_file(
10379 &mut self,
10380 _: &::git::RestoreFile,
10381 window: &mut Window,
10382 cx: &mut Context<Self>,
10383 ) {
10384 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10385 let mut buffer_ids = HashSet::default();
10386 let snapshot = self.buffer().read(cx).snapshot(cx);
10387 for selection in self.selections.all::<usize>(cx) {
10388 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10389 }
10390
10391 let buffer = self.buffer().read(cx);
10392 let ranges = buffer_ids
10393 .into_iter()
10394 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10395 .collect::<Vec<_>>();
10396
10397 self.restore_hunks_in_ranges(ranges, window, cx);
10398 }
10399
10400 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10401 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10402 let selections = self
10403 .selections
10404 .all(cx)
10405 .into_iter()
10406 .map(|s| s.range())
10407 .collect();
10408 self.restore_hunks_in_ranges(selections, window, cx);
10409 }
10410
10411 pub fn restore_hunks_in_ranges(
10412 &mut self,
10413 ranges: Vec<Range<Point>>,
10414 window: &mut Window,
10415 cx: &mut Context<Editor>,
10416 ) {
10417 let mut revert_changes = HashMap::default();
10418 let chunk_by = self
10419 .snapshot(window, cx)
10420 .hunks_for_ranges(ranges)
10421 .into_iter()
10422 .chunk_by(|hunk| hunk.buffer_id);
10423 for (buffer_id, hunks) in &chunk_by {
10424 let hunks = hunks.collect::<Vec<_>>();
10425 for hunk in &hunks {
10426 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10427 }
10428 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10429 }
10430 drop(chunk_by);
10431 if !revert_changes.is_empty() {
10432 self.transact(window, cx, |editor, window, cx| {
10433 editor.restore(revert_changes, window, cx);
10434 });
10435 }
10436 }
10437
10438 pub fn open_active_item_in_terminal(
10439 &mut self,
10440 _: &OpenInTerminal,
10441 window: &mut Window,
10442 cx: &mut Context<Self>,
10443 ) {
10444 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10445 let project_path = buffer.read(cx).project_path(cx)?;
10446 let project = self.project.as_ref()?.read(cx);
10447 let entry = project.entry_for_path(&project_path, cx)?;
10448 let parent = match &entry.canonical_path {
10449 Some(canonical_path) => canonical_path.to_path_buf(),
10450 None => project.absolute_path(&project_path, cx)?,
10451 }
10452 .parent()?
10453 .to_path_buf();
10454 Some(parent)
10455 }) {
10456 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10457 }
10458 }
10459
10460 fn set_breakpoint_context_menu(
10461 &mut self,
10462 display_row: DisplayRow,
10463 position: Option<Anchor>,
10464 clicked_point: gpui::Point<Pixels>,
10465 window: &mut Window,
10466 cx: &mut Context<Self>,
10467 ) {
10468 let source = self
10469 .buffer
10470 .read(cx)
10471 .snapshot(cx)
10472 .anchor_before(Point::new(display_row.0, 0u32));
10473
10474 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10475
10476 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10477 self,
10478 source,
10479 clicked_point,
10480 context_menu,
10481 window,
10482 cx,
10483 );
10484 }
10485
10486 fn add_edit_breakpoint_block(
10487 &mut self,
10488 anchor: Anchor,
10489 breakpoint: &Breakpoint,
10490 edit_action: BreakpointPromptEditAction,
10491 window: &mut Window,
10492 cx: &mut Context<Self>,
10493 ) {
10494 let weak_editor = cx.weak_entity();
10495 let bp_prompt = cx.new(|cx| {
10496 BreakpointPromptEditor::new(
10497 weak_editor,
10498 anchor,
10499 breakpoint.clone(),
10500 edit_action,
10501 window,
10502 cx,
10503 )
10504 });
10505
10506 let height = bp_prompt.update(cx, |this, cx| {
10507 this.prompt
10508 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10509 });
10510 let cloned_prompt = bp_prompt.clone();
10511 let blocks = vec![BlockProperties {
10512 style: BlockStyle::Sticky,
10513 placement: BlockPlacement::Above(anchor),
10514 height: Some(height),
10515 render: Arc::new(move |cx| {
10516 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10517 cloned_prompt.clone().into_any_element()
10518 }),
10519 priority: 0,
10520 }];
10521
10522 let focus_handle = bp_prompt.focus_handle(cx);
10523 window.focus(&focus_handle);
10524
10525 let block_ids = self.insert_blocks(blocks, None, cx);
10526 bp_prompt.update(cx, |prompt, _| {
10527 prompt.add_block_ids(block_ids);
10528 });
10529 }
10530
10531 pub(crate) fn breakpoint_at_row(
10532 &self,
10533 row: u32,
10534 window: &mut Window,
10535 cx: &mut Context<Self>,
10536 ) -> Option<(Anchor, Breakpoint)> {
10537 let snapshot = self.snapshot(window, cx);
10538 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
10539
10540 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10541 }
10542
10543 pub(crate) fn breakpoint_at_anchor(
10544 &self,
10545 breakpoint_position: Anchor,
10546 snapshot: &EditorSnapshot,
10547 cx: &mut Context<Self>,
10548 ) -> Option<(Anchor, Breakpoint)> {
10549 let project = self.project.clone()?;
10550
10551 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
10552 snapshot
10553 .buffer_snapshot
10554 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
10555 })?;
10556
10557 let enclosing_excerpt = breakpoint_position.excerpt_id;
10558 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
10559 let buffer_snapshot = buffer.read(cx).snapshot();
10560
10561 let row = buffer_snapshot
10562 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10563 .row;
10564
10565 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
10566 let anchor_end = snapshot
10567 .buffer_snapshot
10568 .anchor_after(Point::new(row, line_len));
10569
10570 let bp = self
10571 .breakpoint_store
10572 .as_ref()?
10573 .read_with(cx, |breakpoint_store, cx| {
10574 breakpoint_store
10575 .breakpoints(
10576 &buffer,
10577 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10578 &buffer_snapshot,
10579 cx,
10580 )
10581 .next()
10582 .and_then(|(bp, _)| {
10583 let breakpoint_row = buffer_snapshot
10584 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10585 .row;
10586
10587 if breakpoint_row == row {
10588 snapshot
10589 .buffer_snapshot
10590 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10591 .map(|position| (position, bp.bp.clone()))
10592 } else {
10593 None
10594 }
10595 })
10596 });
10597 bp
10598 }
10599
10600 pub fn edit_log_breakpoint(
10601 &mut self,
10602 _: &EditLogBreakpoint,
10603 window: &mut Window,
10604 cx: &mut Context<Self>,
10605 ) {
10606 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10607 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10608 message: None,
10609 state: BreakpointState::Enabled,
10610 condition: None,
10611 hit_condition: None,
10612 });
10613
10614 self.add_edit_breakpoint_block(
10615 anchor,
10616 &breakpoint,
10617 BreakpointPromptEditAction::Log,
10618 window,
10619 cx,
10620 );
10621 }
10622 }
10623
10624 fn breakpoints_at_cursors(
10625 &self,
10626 window: &mut Window,
10627 cx: &mut Context<Self>,
10628 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10629 let snapshot = self.snapshot(window, cx);
10630 let cursors = self
10631 .selections
10632 .disjoint_anchors()
10633 .into_iter()
10634 .map(|selection| {
10635 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
10636
10637 let breakpoint_position = self
10638 .breakpoint_at_row(cursor_position.row, window, cx)
10639 .map(|bp| bp.0)
10640 .unwrap_or_else(|| {
10641 snapshot
10642 .display_snapshot
10643 .buffer_snapshot
10644 .anchor_after(Point::new(cursor_position.row, 0))
10645 });
10646
10647 let breakpoint = self
10648 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10649 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10650
10651 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10652 })
10653 // 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.
10654 .collect::<HashMap<Anchor, _>>();
10655
10656 cursors.into_iter().collect()
10657 }
10658
10659 pub fn enable_breakpoint(
10660 &mut self,
10661 _: &crate::actions::EnableBreakpoint,
10662 window: &mut Window,
10663 cx: &mut Context<Self>,
10664 ) {
10665 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10666 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10667 continue;
10668 };
10669 self.edit_breakpoint_at_anchor(
10670 anchor,
10671 breakpoint,
10672 BreakpointEditAction::InvertState,
10673 cx,
10674 );
10675 }
10676 }
10677
10678 pub fn disable_breakpoint(
10679 &mut self,
10680 _: &crate::actions::DisableBreakpoint,
10681 window: &mut Window,
10682 cx: &mut Context<Self>,
10683 ) {
10684 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10685 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10686 continue;
10687 };
10688 self.edit_breakpoint_at_anchor(
10689 anchor,
10690 breakpoint,
10691 BreakpointEditAction::InvertState,
10692 cx,
10693 );
10694 }
10695 }
10696
10697 pub fn toggle_breakpoint(
10698 &mut self,
10699 _: &crate::actions::ToggleBreakpoint,
10700 window: &mut Window,
10701 cx: &mut Context<Self>,
10702 ) {
10703 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10704 if let Some(breakpoint) = breakpoint {
10705 self.edit_breakpoint_at_anchor(
10706 anchor,
10707 breakpoint,
10708 BreakpointEditAction::Toggle,
10709 cx,
10710 );
10711 } else {
10712 self.edit_breakpoint_at_anchor(
10713 anchor,
10714 Breakpoint::new_standard(),
10715 BreakpointEditAction::Toggle,
10716 cx,
10717 );
10718 }
10719 }
10720 }
10721
10722 pub fn edit_breakpoint_at_anchor(
10723 &mut self,
10724 breakpoint_position: Anchor,
10725 breakpoint: Breakpoint,
10726 edit_action: BreakpointEditAction,
10727 cx: &mut Context<Self>,
10728 ) {
10729 let Some(breakpoint_store) = &self.breakpoint_store else {
10730 return;
10731 };
10732
10733 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
10734 if breakpoint_position == Anchor::min() {
10735 self.buffer()
10736 .read(cx)
10737 .excerpt_buffer_ids()
10738 .into_iter()
10739 .next()
10740 } else {
10741 None
10742 }
10743 }) else {
10744 return;
10745 };
10746
10747 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
10748 return;
10749 };
10750
10751 breakpoint_store.update(cx, |breakpoint_store, cx| {
10752 breakpoint_store.toggle_breakpoint(
10753 buffer,
10754 BreakpointWithPosition {
10755 position: breakpoint_position.text_anchor,
10756 bp: breakpoint,
10757 },
10758 edit_action,
10759 cx,
10760 );
10761 });
10762
10763 cx.notify();
10764 }
10765
10766 #[cfg(any(test, feature = "test-support"))]
10767 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10768 self.breakpoint_store.clone()
10769 }
10770
10771 pub fn prepare_restore_change(
10772 &self,
10773 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10774 hunk: &MultiBufferDiffHunk,
10775 cx: &mut App,
10776 ) -> Option<()> {
10777 if hunk.is_created_file() {
10778 return None;
10779 }
10780 let buffer = self.buffer.read(cx);
10781 let diff = buffer.diff_for(hunk.buffer_id)?;
10782 let buffer = buffer.buffer(hunk.buffer_id)?;
10783 let buffer = buffer.read(cx);
10784 let original_text = diff
10785 .read(cx)
10786 .base_text()
10787 .as_rope()
10788 .slice(hunk.diff_base_byte_range.clone());
10789 let buffer_snapshot = buffer.snapshot();
10790 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
10791 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
10792 probe
10793 .0
10794 .start
10795 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
10796 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
10797 }) {
10798 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
10799 Some(())
10800 } else {
10801 None
10802 }
10803 }
10804
10805 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
10806 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
10807 }
10808
10809 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
10810 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
10811 }
10812
10813 fn manipulate_lines<M>(
10814 &mut self,
10815 window: &mut Window,
10816 cx: &mut Context<Self>,
10817 mut manipulate: M,
10818 ) where
10819 M: FnMut(&str) -> LineManipulationResult,
10820 {
10821 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10822
10823 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10824 let buffer = self.buffer.read(cx).snapshot(cx);
10825
10826 let mut edits = Vec::new();
10827
10828 let selections = self.selections.all::<Point>(cx);
10829 let mut selections = selections.iter().peekable();
10830 let mut contiguous_row_selections = Vec::new();
10831 let mut new_selections = Vec::new();
10832 let mut added_lines = 0;
10833 let mut removed_lines = 0;
10834
10835 while let Some(selection) = selections.next() {
10836 let (start_row, end_row) = consume_contiguous_rows(
10837 &mut contiguous_row_selections,
10838 selection,
10839 &display_map,
10840 &mut selections,
10841 );
10842
10843 let start_point = Point::new(start_row.0, 0);
10844 let end_point = Point::new(
10845 end_row.previous_row().0,
10846 buffer.line_len(end_row.previous_row()),
10847 );
10848 let text = buffer
10849 .text_for_range(start_point..end_point)
10850 .collect::<String>();
10851
10852 let LineManipulationResult {
10853 new_text,
10854 line_count_before,
10855 line_count_after,
10856 } = manipulate(&text);
10857
10858 edits.push((start_point..end_point, new_text));
10859
10860 // Selections must change based on added and removed line count
10861 let start_row =
10862 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
10863 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
10864 new_selections.push(Selection {
10865 id: selection.id,
10866 start: start_row,
10867 end: end_row,
10868 goal: SelectionGoal::None,
10869 reversed: selection.reversed,
10870 });
10871
10872 if line_count_after > line_count_before {
10873 added_lines += line_count_after - line_count_before;
10874 } else if line_count_before > line_count_after {
10875 removed_lines += line_count_before - line_count_after;
10876 }
10877 }
10878
10879 self.transact(window, cx, |this, window, cx| {
10880 let buffer = this.buffer.update(cx, |buffer, cx| {
10881 buffer.edit(edits, None, cx);
10882 buffer.snapshot(cx)
10883 });
10884
10885 // Recalculate offsets on newly edited buffer
10886 let new_selections = new_selections
10887 .iter()
10888 .map(|s| {
10889 let start_point = Point::new(s.start.0, 0);
10890 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
10891 Selection {
10892 id: s.id,
10893 start: buffer.point_to_offset(start_point),
10894 end: buffer.point_to_offset(end_point),
10895 goal: s.goal,
10896 reversed: s.reversed,
10897 }
10898 })
10899 .collect();
10900
10901 this.change_selections(Default::default(), window, cx, |s| {
10902 s.select(new_selections);
10903 });
10904
10905 this.request_autoscroll(Autoscroll::fit(), cx);
10906 });
10907 }
10908
10909 fn manipulate_immutable_lines<Fn>(
10910 &mut self,
10911 window: &mut Window,
10912 cx: &mut Context<Self>,
10913 mut callback: Fn,
10914 ) where
10915 Fn: FnMut(&mut Vec<&str>),
10916 {
10917 self.manipulate_lines(window, cx, |text| {
10918 let mut lines: Vec<&str> = text.split('\n').collect();
10919 let line_count_before = lines.len();
10920
10921 callback(&mut lines);
10922
10923 LineManipulationResult {
10924 new_text: lines.join("\n"),
10925 line_count_before,
10926 line_count_after: lines.len(),
10927 }
10928 });
10929 }
10930
10931 fn manipulate_mutable_lines<Fn>(
10932 &mut self,
10933 window: &mut Window,
10934 cx: &mut Context<Self>,
10935 mut callback: Fn,
10936 ) where
10937 Fn: FnMut(&mut Vec<Cow<'_, str>>),
10938 {
10939 self.manipulate_lines(window, cx, |text| {
10940 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
10941 let line_count_before = lines.len();
10942
10943 callback(&mut lines);
10944
10945 LineManipulationResult {
10946 new_text: lines.join("\n"),
10947 line_count_before,
10948 line_count_after: lines.len(),
10949 }
10950 });
10951 }
10952
10953 pub fn convert_indentation_to_spaces(
10954 &mut self,
10955 _: &ConvertIndentationToSpaces,
10956 window: &mut Window,
10957 cx: &mut Context<Self>,
10958 ) {
10959 let settings = self.buffer.read(cx).language_settings(cx);
10960 let tab_size = settings.tab_size.get() as usize;
10961
10962 self.manipulate_mutable_lines(window, cx, |lines| {
10963 // Allocates a reasonably sized scratch buffer once for the whole loop
10964 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
10965 // Avoids recomputing spaces that could be inserted many times
10966 let space_cache: Vec<Vec<char>> = (1..=tab_size)
10967 .map(|n| IndentSize::spaces(n as u32).chars().collect())
10968 .collect();
10969
10970 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
10971 let mut chars = line.as_ref().chars();
10972 let mut col = 0;
10973 let mut changed = false;
10974
10975 while let Some(ch) = chars.next() {
10976 match ch {
10977 ' ' => {
10978 reindented_line.push(' ');
10979 col += 1;
10980 }
10981 '\t' => {
10982 // \t are converted to spaces depending on the current column
10983 let spaces_len = tab_size - (col % tab_size);
10984 reindented_line.extend(&space_cache[spaces_len - 1]);
10985 col += spaces_len;
10986 changed = true;
10987 }
10988 _ => {
10989 // If we dont append before break, the character is consumed
10990 reindented_line.push(ch);
10991 break;
10992 }
10993 }
10994 }
10995
10996 if !changed {
10997 reindented_line.clear();
10998 continue;
10999 }
11000 // Append the rest of the line and replace old reference with new one
11001 reindented_line.extend(chars);
11002 *line = Cow::Owned(reindented_line.clone());
11003 reindented_line.clear();
11004 }
11005 });
11006 }
11007
11008 pub fn convert_indentation_to_tabs(
11009 &mut self,
11010 _: &ConvertIndentationToTabs,
11011 window: &mut Window,
11012 cx: &mut Context<Self>,
11013 ) {
11014 let settings = self.buffer.read(cx).language_settings(cx);
11015 let tab_size = settings.tab_size.get() as usize;
11016
11017 self.manipulate_mutable_lines(window, cx, |lines| {
11018 // Allocates a reasonably sized buffer once for the whole loop
11019 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11020 // Avoids recomputing spaces that could be inserted many times
11021 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11022 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11023 .collect();
11024
11025 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11026 let mut chars = line.chars();
11027 let mut spaces_count = 0;
11028 let mut first_non_indent_char = None;
11029 let mut changed = false;
11030
11031 while let Some(ch) = chars.next() {
11032 match ch {
11033 ' ' => {
11034 // Keep track of spaces. Append \t when we reach tab_size
11035 spaces_count += 1;
11036 changed = true;
11037 if spaces_count == tab_size {
11038 reindented_line.push('\t');
11039 spaces_count = 0;
11040 }
11041 }
11042 '\t' => {
11043 reindented_line.push('\t');
11044 spaces_count = 0;
11045 }
11046 _ => {
11047 // Dont append it yet, we might have remaining spaces
11048 first_non_indent_char = Some(ch);
11049 break;
11050 }
11051 }
11052 }
11053
11054 if !changed {
11055 reindented_line.clear();
11056 continue;
11057 }
11058 // Remaining spaces that didn't make a full tab stop
11059 if spaces_count > 0 {
11060 reindented_line.extend(&space_cache[spaces_count - 1]);
11061 }
11062 // If we consume an extra character that was not indentation, add it back
11063 if let Some(extra_char) = first_non_indent_char {
11064 reindented_line.push(extra_char);
11065 }
11066 // Append the rest of the line and replace old reference with new one
11067 reindented_line.extend(chars);
11068 *line = Cow::Owned(reindented_line.clone());
11069 reindented_line.clear();
11070 }
11071 });
11072 }
11073
11074 pub fn convert_to_upper_case(
11075 &mut self,
11076 _: &ConvertToUpperCase,
11077 window: &mut Window,
11078 cx: &mut Context<Self>,
11079 ) {
11080 self.manipulate_text(window, cx, |text| text.to_uppercase())
11081 }
11082
11083 pub fn convert_to_lower_case(
11084 &mut self,
11085 _: &ConvertToLowerCase,
11086 window: &mut Window,
11087 cx: &mut Context<Self>,
11088 ) {
11089 self.manipulate_text(window, cx, |text| text.to_lowercase())
11090 }
11091
11092 pub fn convert_to_title_case(
11093 &mut self,
11094 _: &ConvertToTitleCase,
11095 window: &mut Window,
11096 cx: &mut Context<Self>,
11097 ) {
11098 self.manipulate_text(window, cx, |text| {
11099 text.split('\n')
11100 .map(|line| line.to_case(Case::Title))
11101 .join("\n")
11102 })
11103 }
11104
11105 pub fn convert_to_snake_case(
11106 &mut self,
11107 _: &ConvertToSnakeCase,
11108 window: &mut Window,
11109 cx: &mut Context<Self>,
11110 ) {
11111 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11112 }
11113
11114 pub fn convert_to_kebab_case(
11115 &mut self,
11116 _: &ConvertToKebabCase,
11117 window: &mut Window,
11118 cx: &mut Context<Self>,
11119 ) {
11120 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11121 }
11122
11123 pub fn convert_to_upper_camel_case(
11124 &mut self,
11125 _: &ConvertToUpperCamelCase,
11126 window: &mut Window,
11127 cx: &mut Context<Self>,
11128 ) {
11129 self.manipulate_text(window, cx, |text| {
11130 text.split('\n')
11131 .map(|line| line.to_case(Case::UpperCamel))
11132 .join("\n")
11133 })
11134 }
11135
11136 pub fn convert_to_lower_camel_case(
11137 &mut self,
11138 _: &ConvertToLowerCamelCase,
11139 window: &mut Window,
11140 cx: &mut Context<Self>,
11141 ) {
11142 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11143 }
11144
11145 pub fn convert_to_opposite_case(
11146 &mut self,
11147 _: &ConvertToOppositeCase,
11148 window: &mut Window,
11149 cx: &mut Context<Self>,
11150 ) {
11151 self.manipulate_text(window, cx, |text| {
11152 text.chars()
11153 .fold(String::with_capacity(text.len()), |mut t, c| {
11154 if c.is_uppercase() {
11155 t.extend(c.to_lowercase());
11156 } else {
11157 t.extend(c.to_uppercase());
11158 }
11159 t
11160 })
11161 })
11162 }
11163
11164 pub fn convert_to_sentence_case(
11165 &mut self,
11166 _: &ConvertToSentenceCase,
11167 window: &mut Window,
11168 cx: &mut Context<Self>,
11169 ) {
11170 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11171 }
11172
11173 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11174 self.manipulate_text(window, cx, |text| {
11175 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11176 if has_upper_case_characters {
11177 text.to_lowercase()
11178 } else {
11179 text.to_uppercase()
11180 }
11181 })
11182 }
11183
11184 pub fn convert_to_rot13(
11185 &mut self,
11186 _: &ConvertToRot13,
11187 window: &mut Window,
11188 cx: &mut Context<Self>,
11189 ) {
11190 self.manipulate_text(window, cx, |text| {
11191 text.chars()
11192 .map(|c| match c {
11193 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11194 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11195 _ => c,
11196 })
11197 .collect()
11198 })
11199 }
11200
11201 pub fn convert_to_rot47(
11202 &mut self,
11203 _: &ConvertToRot47,
11204 window: &mut Window,
11205 cx: &mut Context<Self>,
11206 ) {
11207 self.manipulate_text(window, cx, |text| {
11208 text.chars()
11209 .map(|c| {
11210 let code_point = c as u32;
11211 if code_point >= 33 && code_point <= 126 {
11212 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11213 }
11214 c
11215 })
11216 .collect()
11217 })
11218 }
11219
11220 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11221 where
11222 Fn: FnMut(&str) -> String,
11223 {
11224 let buffer = self.buffer.read(cx).snapshot(cx);
11225
11226 let mut new_selections = Vec::new();
11227 let mut edits = Vec::new();
11228 let mut selection_adjustment = 0i32;
11229
11230 for selection in self.selections.all::<usize>(cx) {
11231 let selection_is_empty = selection.is_empty();
11232
11233 let (start, end) = if selection_is_empty {
11234 let (word_range, _) = buffer.surrounding_word(selection.start, false);
11235 (word_range.start, word_range.end)
11236 } else {
11237 (selection.start, selection.end)
11238 };
11239
11240 let text = buffer.text_for_range(start..end).collect::<String>();
11241 let old_length = text.len() as i32;
11242 let text = callback(&text);
11243
11244 new_selections.push(Selection {
11245 start: (start as i32 - selection_adjustment) as usize,
11246 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11247 goal: SelectionGoal::None,
11248 ..selection
11249 });
11250
11251 selection_adjustment += old_length - text.len() as i32;
11252
11253 edits.push((start..end, text));
11254 }
11255
11256 self.transact(window, cx, |this, window, cx| {
11257 this.buffer.update(cx, |buffer, cx| {
11258 buffer.edit(edits, None, cx);
11259 });
11260
11261 this.change_selections(Default::default(), window, cx, |s| {
11262 s.select(new_selections);
11263 });
11264
11265 this.request_autoscroll(Autoscroll::fit(), cx);
11266 });
11267 }
11268
11269 pub fn move_selection_on_drop(
11270 &mut self,
11271 selection: &Selection<Anchor>,
11272 target: DisplayPoint,
11273 is_cut: bool,
11274 window: &mut Window,
11275 cx: &mut Context<Self>,
11276 ) {
11277 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11278 let buffer = &display_map.buffer_snapshot;
11279 let mut edits = Vec::new();
11280 let insert_point = display_map
11281 .clip_point(target, Bias::Left)
11282 .to_point(&display_map);
11283 let text = buffer
11284 .text_for_range(selection.start..selection.end)
11285 .collect::<String>();
11286 if is_cut {
11287 edits.push(((selection.start..selection.end), String::new()));
11288 }
11289 let insert_anchor = buffer.anchor_before(insert_point);
11290 edits.push(((insert_anchor..insert_anchor), text));
11291 let last_edit_start = insert_anchor.bias_left(buffer);
11292 let last_edit_end = insert_anchor.bias_right(buffer);
11293 self.transact(window, cx, |this, window, cx| {
11294 this.buffer.update(cx, |buffer, cx| {
11295 buffer.edit(edits, None, cx);
11296 });
11297 this.change_selections(Default::default(), window, cx, |s| {
11298 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11299 });
11300 });
11301 }
11302
11303 pub fn clear_selection_drag_state(&mut self) {
11304 self.selection_drag_state = SelectionDragState::None;
11305 }
11306
11307 pub fn duplicate(
11308 &mut self,
11309 upwards: bool,
11310 whole_lines: bool,
11311 window: &mut Window,
11312 cx: &mut Context<Self>,
11313 ) {
11314 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11315
11316 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11317 let buffer = &display_map.buffer_snapshot;
11318 let selections = self.selections.all::<Point>(cx);
11319
11320 let mut edits = Vec::new();
11321 let mut selections_iter = selections.iter().peekable();
11322 while let Some(selection) = selections_iter.next() {
11323 let mut rows = selection.spanned_rows(false, &display_map);
11324 // duplicate line-wise
11325 if whole_lines || selection.start == selection.end {
11326 // Avoid duplicating the same lines twice.
11327 while let Some(next_selection) = selections_iter.peek() {
11328 let next_rows = next_selection.spanned_rows(false, &display_map);
11329 if next_rows.start < rows.end {
11330 rows.end = next_rows.end;
11331 selections_iter.next().unwrap();
11332 } else {
11333 break;
11334 }
11335 }
11336
11337 // Copy the text from the selected row region and splice it either at the start
11338 // or end of the region.
11339 let start = Point::new(rows.start.0, 0);
11340 let end = Point::new(
11341 rows.end.previous_row().0,
11342 buffer.line_len(rows.end.previous_row()),
11343 );
11344 let text = buffer
11345 .text_for_range(start..end)
11346 .chain(Some("\n"))
11347 .collect::<String>();
11348 let insert_location = if upwards {
11349 Point::new(rows.end.0, 0)
11350 } else {
11351 start
11352 };
11353 edits.push((insert_location..insert_location, text));
11354 } else {
11355 // duplicate character-wise
11356 let start = selection.start;
11357 let end = selection.end;
11358 let text = buffer.text_for_range(start..end).collect::<String>();
11359 edits.push((selection.end..selection.end, text));
11360 }
11361 }
11362
11363 self.transact(window, cx, |this, _, cx| {
11364 this.buffer.update(cx, |buffer, cx| {
11365 buffer.edit(edits, None, cx);
11366 });
11367
11368 this.request_autoscroll(Autoscroll::fit(), cx);
11369 });
11370 }
11371
11372 pub fn duplicate_line_up(
11373 &mut self,
11374 _: &DuplicateLineUp,
11375 window: &mut Window,
11376 cx: &mut Context<Self>,
11377 ) {
11378 self.duplicate(true, true, window, cx);
11379 }
11380
11381 pub fn duplicate_line_down(
11382 &mut self,
11383 _: &DuplicateLineDown,
11384 window: &mut Window,
11385 cx: &mut Context<Self>,
11386 ) {
11387 self.duplicate(false, true, window, cx);
11388 }
11389
11390 pub fn duplicate_selection(
11391 &mut self,
11392 _: &DuplicateSelection,
11393 window: &mut Window,
11394 cx: &mut Context<Self>,
11395 ) {
11396 self.duplicate(false, false, window, cx);
11397 }
11398
11399 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11400 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11401 if self.mode.is_single_line() {
11402 cx.propagate();
11403 return;
11404 }
11405
11406 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11407 let buffer = self.buffer.read(cx).snapshot(cx);
11408
11409 let mut edits = Vec::new();
11410 let mut unfold_ranges = Vec::new();
11411 let mut refold_creases = Vec::new();
11412
11413 let selections = self.selections.all::<Point>(cx);
11414 let mut selections = selections.iter().peekable();
11415 let mut contiguous_row_selections = Vec::new();
11416 let mut new_selections = Vec::new();
11417
11418 while let Some(selection) = selections.next() {
11419 // Find all the selections that span a contiguous row range
11420 let (start_row, end_row) = consume_contiguous_rows(
11421 &mut contiguous_row_selections,
11422 selection,
11423 &display_map,
11424 &mut selections,
11425 );
11426
11427 // Move the text spanned by the row range to be before the line preceding the row range
11428 if start_row.0 > 0 {
11429 let range_to_move = Point::new(
11430 start_row.previous_row().0,
11431 buffer.line_len(start_row.previous_row()),
11432 )
11433 ..Point::new(
11434 end_row.previous_row().0,
11435 buffer.line_len(end_row.previous_row()),
11436 );
11437 let insertion_point = display_map
11438 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11439 .0;
11440
11441 // Don't move lines across excerpts
11442 if buffer
11443 .excerpt_containing(insertion_point..range_to_move.end)
11444 .is_some()
11445 {
11446 let text = buffer
11447 .text_for_range(range_to_move.clone())
11448 .flat_map(|s| s.chars())
11449 .skip(1)
11450 .chain(['\n'])
11451 .collect::<String>();
11452
11453 edits.push((
11454 buffer.anchor_after(range_to_move.start)
11455 ..buffer.anchor_before(range_to_move.end),
11456 String::new(),
11457 ));
11458 let insertion_anchor = buffer.anchor_after(insertion_point);
11459 edits.push((insertion_anchor..insertion_anchor, text));
11460
11461 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11462
11463 // Move selections up
11464 new_selections.extend(contiguous_row_selections.drain(..).map(
11465 |mut selection| {
11466 selection.start.row -= row_delta;
11467 selection.end.row -= row_delta;
11468 selection
11469 },
11470 ));
11471
11472 // Move folds up
11473 unfold_ranges.push(range_to_move.clone());
11474 for fold in display_map.folds_in_range(
11475 buffer.anchor_before(range_to_move.start)
11476 ..buffer.anchor_after(range_to_move.end),
11477 ) {
11478 let mut start = fold.range.start.to_point(&buffer);
11479 let mut end = fold.range.end.to_point(&buffer);
11480 start.row -= row_delta;
11481 end.row -= row_delta;
11482 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11483 }
11484 }
11485 }
11486
11487 // If we didn't move line(s), preserve the existing selections
11488 new_selections.append(&mut contiguous_row_selections);
11489 }
11490
11491 self.transact(window, cx, |this, window, cx| {
11492 this.unfold_ranges(&unfold_ranges, true, true, cx);
11493 this.buffer.update(cx, |buffer, cx| {
11494 for (range, text) in edits {
11495 buffer.edit([(range, text)], None, cx);
11496 }
11497 });
11498 this.fold_creases(refold_creases, true, window, cx);
11499 this.change_selections(Default::default(), window, cx, |s| {
11500 s.select(new_selections);
11501 })
11502 });
11503 }
11504
11505 pub fn move_line_down(
11506 &mut self,
11507 _: &MoveLineDown,
11508 window: &mut Window,
11509 cx: &mut Context<Self>,
11510 ) {
11511 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11512 if self.mode.is_single_line() {
11513 cx.propagate();
11514 return;
11515 }
11516
11517 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11518 let buffer = self.buffer.read(cx).snapshot(cx);
11519
11520 let mut edits = Vec::new();
11521 let mut unfold_ranges = Vec::new();
11522 let mut refold_creases = Vec::new();
11523
11524 let selections = self.selections.all::<Point>(cx);
11525 let mut selections = selections.iter().peekable();
11526 let mut contiguous_row_selections = Vec::new();
11527 let mut new_selections = Vec::new();
11528
11529 while let Some(selection) = selections.next() {
11530 // Find all the selections that span a contiguous row range
11531 let (start_row, end_row) = consume_contiguous_rows(
11532 &mut contiguous_row_selections,
11533 selection,
11534 &display_map,
11535 &mut selections,
11536 );
11537
11538 // Move the text spanned by the row range to be after the last line of the row range
11539 if end_row.0 <= buffer.max_point().row {
11540 let range_to_move =
11541 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11542 let insertion_point = display_map
11543 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11544 .0;
11545
11546 // Don't move lines across excerpt boundaries
11547 if buffer
11548 .excerpt_containing(range_to_move.start..insertion_point)
11549 .is_some()
11550 {
11551 let mut text = String::from("\n");
11552 text.extend(buffer.text_for_range(range_to_move.clone()));
11553 text.pop(); // Drop trailing newline
11554 edits.push((
11555 buffer.anchor_after(range_to_move.start)
11556 ..buffer.anchor_before(range_to_move.end),
11557 String::new(),
11558 ));
11559 let insertion_anchor = buffer.anchor_after(insertion_point);
11560 edits.push((insertion_anchor..insertion_anchor, text));
11561
11562 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11563
11564 // Move selections down
11565 new_selections.extend(contiguous_row_selections.drain(..).map(
11566 |mut selection| {
11567 selection.start.row += row_delta;
11568 selection.end.row += row_delta;
11569 selection
11570 },
11571 ));
11572
11573 // Move folds down
11574 unfold_ranges.push(range_to_move.clone());
11575 for fold in display_map.folds_in_range(
11576 buffer.anchor_before(range_to_move.start)
11577 ..buffer.anchor_after(range_to_move.end),
11578 ) {
11579 let mut start = fold.range.start.to_point(&buffer);
11580 let mut end = fold.range.end.to_point(&buffer);
11581 start.row += row_delta;
11582 end.row += row_delta;
11583 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11584 }
11585 }
11586 }
11587
11588 // If we didn't move line(s), preserve the existing selections
11589 new_selections.append(&mut contiguous_row_selections);
11590 }
11591
11592 self.transact(window, cx, |this, window, cx| {
11593 this.unfold_ranges(&unfold_ranges, true, true, cx);
11594 this.buffer.update(cx, |buffer, cx| {
11595 for (range, text) in edits {
11596 buffer.edit([(range, text)], None, cx);
11597 }
11598 });
11599 this.fold_creases(refold_creases, true, window, cx);
11600 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
11601 });
11602 }
11603
11604 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11605 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11606 let text_layout_details = &self.text_layout_details(window);
11607 self.transact(window, cx, |this, window, cx| {
11608 let edits = this.change_selections(Default::default(), window, cx, |s| {
11609 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11610 s.move_with(|display_map, selection| {
11611 if !selection.is_empty() {
11612 return;
11613 }
11614
11615 let mut head = selection.head();
11616 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11617 if head.column() == display_map.line_len(head.row()) {
11618 transpose_offset = display_map
11619 .buffer_snapshot
11620 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11621 }
11622
11623 if transpose_offset == 0 {
11624 return;
11625 }
11626
11627 *head.column_mut() += 1;
11628 head = display_map.clip_point(head, Bias::Right);
11629 let goal = SelectionGoal::HorizontalPosition(
11630 display_map
11631 .x_for_display_point(head, text_layout_details)
11632 .into(),
11633 );
11634 selection.collapse_to(head, goal);
11635
11636 let transpose_start = display_map
11637 .buffer_snapshot
11638 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11639 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
11640 let transpose_end = display_map
11641 .buffer_snapshot
11642 .clip_offset(transpose_offset + 1, Bias::Right);
11643 if let Some(ch) =
11644 display_map.buffer_snapshot.chars_at(transpose_start).next()
11645 {
11646 edits.push((transpose_start..transpose_offset, String::new()));
11647 edits.push((transpose_end..transpose_end, ch.to_string()));
11648 }
11649 }
11650 });
11651 edits
11652 });
11653 this.buffer
11654 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11655 let selections = this.selections.all::<usize>(cx);
11656 this.change_selections(Default::default(), window, cx, |s| {
11657 s.select(selections);
11658 });
11659 });
11660 }
11661
11662 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
11663 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11664 if self.mode.is_single_line() {
11665 cx.propagate();
11666 return;
11667 }
11668
11669 self.rewrap_impl(RewrapOptions::default(), cx)
11670 }
11671
11672 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
11673 let buffer = self.buffer.read(cx).snapshot(cx);
11674 let selections = self.selections.all::<Point>(cx);
11675
11676 // Split selections to respect paragraph, indent, and comment prefix boundaries.
11677 let wrap_ranges = selections.into_iter().flat_map(|selection| {
11678 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
11679 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11680 .peekable();
11681
11682 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
11683 row
11684 } else {
11685 return Vec::new();
11686 };
11687
11688 let language_settings = buffer.language_settings_at(selection.head(), cx);
11689 let language_scope = buffer.language_scope_at(selection.head());
11690
11691 let indent_and_prefix_for_row =
11692 |row: u32| -> (IndentSize, Option<String>, Option<String>) {
11693 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
11694 let (comment_prefix, rewrap_prefix) =
11695 if let Some(language_scope) = &language_scope {
11696 let indent_end = Point::new(row, indent.len);
11697 let comment_prefix = language_scope
11698 .line_comment_prefixes()
11699 .iter()
11700 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11701 .map(|prefix| prefix.to_string());
11702 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11703 let line_text_after_indent = buffer
11704 .text_for_range(indent_end..line_end)
11705 .collect::<String>();
11706 let rewrap_prefix = language_scope
11707 .rewrap_prefixes()
11708 .iter()
11709 .find_map(|prefix_regex| {
11710 prefix_regex.find(&line_text_after_indent).map(|mat| {
11711 if mat.start() == 0 {
11712 Some(mat.as_str().to_string())
11713 } else {
11714 None
11715 }
11716 })
11717 })
11718 .flatten();
11719 (comment_prefix, rewrap_prefix)
11720 } else {
11721 (None, None)
11722 };
11723 (indent, comment_prefix, rewrap_prefix)
11724 };
11725
11726 let mut ranges = Vec::new();
11727 let from_empty_selection = selection.is_empty();
11728
11729 let mut current_range_start = first_row;
11730 let mut prev_row = first_row;
11731 let (
11732 mut current_range_indent,
11733 mut current_range_comment_prefix,
11734 mut current_range_rewrap_prefix,
11735 ) = indent_and_prefix_for_row(first_row);
11736
11737 for row in non_blank_rows_iter.skip(1) {
11738 let has_paragraph_break = row > prev_row + 1;
11739
11740 let (row_indent, row_comment_prefix, row_rewrap_prefix) =
11741 indent_and_prefix_for_row(row);
11742
11743 let has_indent_change = row_indent != current_range_indent;
11744 let has_comment_change = row_comment_prefix != current_range_comment_prefix;
11745
11746 let has_boundary_change = has_comment_change
11747 || row_rewrap_prefix.is_some()
11748 || (has_indent_change && current_range_comment_prefix.is_some());
11749
11750 if has_paragraph_break || has_boundary_change {
11751 ranges.push((
11752 language_settings.clone(),
11753 Point::new(current_range_start, 0)
11754 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
11755 current_range_indent,
11756 current_range_comment_prefix.clone(),
11757 current_range_rewrap_prefix.clone(),
11758 from_empty_selection,
11759 ));
11760 current_range_start = row;
11761 current_range_indent = row_indent;
11762 current_range_comment_prefix = row_comment_prefix;
11763 current_range_rewrap_prefix = row_rewrap_prefix;
11764 }
11765 prev_row = row;
11766 }
11767
11768 ranges.push((
11769 language_settings.clone(),
11770 Point::new(current_range_start, 0)
11771 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
11772 current_range_indent,
11773 current_range_comment_prefix,
11774 current_range_rewrap_prefix,
11775 from_empty_selection,
11776 ));
11777
11778 ranges
11779 });
11780
11781 let mut edits = Vec::new();
11782 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
11783
11784 for (
11785 language_settings,
11786 wrap_range,
11787 indent_size,
11788 comment_prefix,
11789 rewrap_prefix,
11790 from_empty_selection,
11791 ) in wrap_ranges
11792 {
11793 let mut start_row = wrap_range.start.row;
11794 let mut end_row = wrap_range.end.row;
11795
11796 // Skip selections that overlap with a range that has already been rewrapped.
11797 let selection_range = start_row..end_row;
11798 if rewrapped_row_ranges
11799 .iter()
11800 .any(|range| range.overlaps(&selection_range))
11801 {
11802 continue;
11803 }
11804
11805 let tab_size = language_settings.tab_size;
11806
11807 let indent_prefix = indent_size.chars().collect::<String>();
11808 let mut line_prefix = indent_prefix.clone();
11809 let mut inside_comment = false;
11810 if let Some(prefix) = &comment_prefix {
11811 line_prefix.push_str(prefix);
11812 inside_comment = true;
11813 }
11814 if let Some(prefix) = &rewrap_prefix {
11815 line_prefix.push_str(prefix);
11816 }
11817
11818 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
11819 RewrapBehavior::InComments => inside_comment,
11820 RewrapBehavior::InSelections => !wrap_range.is_empty(),
11821 RewrapBehavior::Anywhere => true,
11822 };
11823
11824 let should_rewrap = options.override_language_settings
11825 || allow_rewrap_based_on_language
11826 || self.hard_wrap.is_some();
11827 if !should_rewrap {
11828 continue;
11829 }
11830
11831 if from_empty_selection {
11832 'expand_upwards: while start_row > 0 {
11833 let prev_row = start_row - 1;
11834 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
11835 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
11836 && !buffer.is_line_blank(MultiBufferRow(prev_row))
11837 {
11838 start_row = prev_row;
11839 } else {
11840 break 'expand_upwards;
11841 }
11842 }
11843
11844 'expand_downwards: while end_row < buffer.max_point().row {
11845 let next_row = end_row + 1;
11846 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
11847 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
11848 && !buffer.is_line_blank(MultiBufferRow(next_row))
11849 {
11850 end_row = next_row;
11851 } else {
11852 break 'expand_downwards;
11853 }
11854 }
11855 }
11856
11857 let start = Point::new(start_row, 0);
11858 let start_offset = start.to_offset(&buffer);
11859 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
11860 let selection_text = buffer.text_for_range(start..end).collect::<String>();
11861 let Some(lines_without_prefixes) = selection_text
11862 .lines()
11863 .enumerate()
11864 .map(|(ix, line)| {
11865 let line_trimmed = line.trim_start();
11866 if rewrap_prefix.is_some() && ix > 0 {
11867 Ok(line_trimmed)
11868 } else {
11869 line_trimmed
11870 .strip_prefix(&line_prefix.trim_start())
11871 .with_context(|| {
11872 format!("line did not start with prefix {line_prefix:?}: {line:?}")
11873 })
11874 }
11875 })
11876 .collect::<Result<Vec<_>, _>>()
11877 .log_err()
11878 else {
11879 continue;
11880 };
11881
11882 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
11883 buffer
11884 .language_settings_at(Point::new(start_row, 0), cx)
11885 .preferred_line_length as usize
11886 });
11887
11888 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
11889 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
11890 } else {
11891 line_prefix.clone()
11892 };
11893
11894 let wrapped_text = wrap_with_prefix(
11895 line_prefix,
11896 subsequent_lines_prefix,
11897 lines_without_prefixes.join("\n"),
11898 wrap_column,
11899 tab_size,
11900 options.preserve_existing_whitespace,
11901 );
11902
11903 // TODO: should always use char-based diff while still supporting cursor behavior that
11904 // matches vim.
11905 let mut diff_options = DiffOptions::default();
11906 if options.override_language_settings {
11907 diff_options.max_word_diff_len = 0;
11908 diff_options.max_word_diff_line_count = 0;
11909 } else {
11910 diff_options.max_word_diff_len = usize::MAX;
11911 diff_options.max_word_diff_line_count = usize::MAX;
11912 }
11913
11914 for (old_range, new_text) in
11915 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
11916 {
11917 let edit_start = buffer.anchor_after(start_offset + old_range.start);
11918 let edit_end = buffer.anchor_after(start_offset + old_range.end);
11919 edits.push((edit_start..edit_end, new_text));
11920 }
11921
11922 rewrapped_row_ranges.push(start_row..=end_row);
11923 }
11924
11925 self.buffer
11926 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11927 }
11928
11929 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
11930 let mut text = String::new();
11931 let buffer = self.buffer.read(cx).snapshot(cx);
11932 let mut selections = self.selections.all::<Point>(cx);
11933 let mut clipboard_selections = Vec::with_capacity(selections.len());
11934 {
11935 let max_point = buffer.max_point();
11936 let mut is_first = true;
11937 for selection in &mut selections {
11938 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11939 if is_entire_line {
11940 selection.start = Point::new(selection.start.row, 0);
11941 if !selection.is_empty() && selection.end.column == 0 {
11942 selection.end = cmp::min(max_point, selection.end);
11943 } else {
11944 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
11945 }
11946 selection.goal = SelectionGoal::None;
11947 }
11948 if is_first {
11949 is_first = false;
11950 } else {
11951 text += "\n";
11952 }
11953 let mut len = 0;
11954 for chunk in buffer.text_for_range(selection.start..selection.end) {
11955 text.push_str(chunk);
11956 len += chunk.len();
11957 }
11958 clipboard_selections.push(ClipboardSelection {
11959 len,
11960 is_entire_line,
11961 first_line_indent: buffer
11962 .indent_size_for_line(MultiBufferRow(selection.start.row))
11963 .len,
11964 });
11965 }
11966 }
11967
11968 self.transact(window, cx, |this, window, cx| {
11969 this.change_selections(Default::default(), window, cx, |s| {
11970 s.select(selections);
11971 });
11972 this.insert("", window, cx);
11973 });
11974 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
11975 }
11976
11977 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
11978 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11979 let item = self.cut_common(window, cx);
11980 cx.write_to_clipboard(item);
11981 }
11982
11983 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
11984 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11985 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11986 s.move_with(|snapshot, sel| {
11987 if sel.is_empty() {
11988 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
11989 }
11990 });
11991 });
11992 let item = self.cut_common(window, cx);
11993 cx.set_global(KillRing(item))
11994 }
11995
11996 pub fn kill_ring_yank(
11997 &mut self,
11998 _: &KillRingYank,
11999 window: &mut Window,
12000 cx: &mut Context<Self>,
12001 ) {
12002 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12003 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12004 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12005 (kill_ring.text().to_string(), kill_ring.metadata_json())
12006 } else {
12007 return;
12008 }
12009 } else {
12010 return;
12011 };
12012 self.do_paste(&text, metadata, false, window, cx);
12013 }
12014
12015 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12016 self.do_copy(true, cx);
12017 }
12018
12019 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12020 self.do_copy(false, cx);
12021 }
12022
12023 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12024 let selections = self.selections.all::<Point>(cx);
12025 let buffer = self.buffer.read(cx).read(cx);
12026 let mut text = String::new();
12027
12028 let mut clipboard_selections = Vec::with_capacity(selections.len());
12029 {
12030 let max_point = buffer.max_point();
12031 let mut is_first = true;
12032 for selection in &selections {
12033 let mut start = selection.start;
12034 let mut end = selection.end;
12035 let is_entire_line = selection.is_empty() || self.selections.line_mode;
12036 if is_entire_line {
12037 start = Point::new(start.row, 0);
12038 end = cmp::min(max_point, Point::new(end.row + 1, 0));
12039 }
12040
12041 let mut trimmed_selections = Vec::new();
12042 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12043 let row = MultiBufferRow(start.row);
12044 let first_indent = buffer.indent_size_for_line(row);
12045 if first_indent.len == 0 || start.column > first_indent.len {
12046 trimmed_selections.push(start..end);
12047 } else {
12048 trimmed_selections.push(
12049 Point::new(row.0, first_indent.len)
12050 ..Point::new(row.0, buffer.line_len(row)),
12051 );
12052 for row in start.row + 1..=end.row {
12053 let mut line_len = buffer.line_len(MultiBufferRow(row));
12054 if row == end.row {
12055 line_len = end.column;
12056 }
12057 if line_len == 0 {
12058 trimmed_selections
12059 .push(Point::new(row, 0)..Point::new(row, line_len));
12060 continue;
12061 }
12062 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12063 if row_indent_size.len >= first_indent.len {
12064 trimmed_selections.push(
12065 Point::new(row, first_indent.len)..Point::new(row, line_len),
12066 );
12067 } else {
12068 trimmed_selections.clear();
12069 trimmed_selections.push(start..end);
12070 break;
12071 }
12072 }
12073 }
12074 } else {
12075 trimmed_selections.push(start..end);
12076 }
12077
12078 for trimmed_range in trimmed_selections {
12079 if is_first {
12080 is_first = false;
12081 } else {
12082 text += "\n";
12083 }
12084 let mut len = 0;
12085 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12086 text.push_str(chunk);
12087 len += chunk.len();
12088 }
12089 clipboard_selections.push(ClipboardSelection {
12090 len,
12091 is_entire_line,
12092 first_line_indent: buffer
12093 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12094 .len,
12095 });
12096 }
12097 }
12098 }
12099
12100 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12101 text,
12102 clipboard_selections,
12103 ));
12104 }
12105
12106 pub fn do_paste(
12107 &mut self,
12108 text: &String,
12109 clipboard_selections: Option<Vec<ClipboardSelection>>,
12110 handle_entire_lines: bool,
12111 window: &mut Window,
12112 cx: &mut Context<Self>,
12113 ) {
12114 if self.read_only(cx) {
12115 return;
12116 }
12117
12118 let clipboard_text = Cow::Borrowed(text);
12119
12120 self.transact(window, cx, |this, window, cx| {
12121 if let Some(mut clipboard_selections) = clipboard_selections {
12122 let old_selections = this.selections.all::<usize>(cx);
12123 let all_selections_were_entire_line =
12124 clipboard_selections.iter().all(|s| s.is_entire_line);
12125 let first_selection_indent_column =
12126 clipboard_selections.first().map(|s| s.first_line_indent);
12127 if clipboard_selections.len() != old_selections.len() {
12128 clipboard_selections.drain(..);
12129 }
12130 let cursor_offset = this.selections.last::<usize>(cx).head();
12131 let mut auto_indent_on_paste = true;
12132
12133 this.buffer.update(cx, |buffer, cx| {
12134 let snapshot = buffer.read(cx);
12135 auto_indent_on_paste = snapshot
12136 .language_settings_at(cursor_offset, cx)
12137 .auto_indent_on_paste;
12138
12139 let mut start_offset = 0;
12140 let mut edits = Vec::new();
12141 let mut original_indent_columns = Vec::new();
12142 for (ix, selection) in old_selections.iter().enumerate() {
12143 let to_insert;
12144 let entire_line;
12145 let original_indent_column;
12146 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12147 let end_offset = start_offset + clipboard_selection.len;
12148 to_insert = &clipboard_text[start_offset..end_offset];
12149 entire_line = clipboard_selection.is_entire_line;
12150 start_offset = end_offset + 1;
12151 original_indent_column = Some(clipboard_selection.first_line_indent);
12152 } else {
12153 to_insert = clipboard_text.as_str();
12154 entire_line = all_selections_were_entire_line;
12155 original_indent_column = first_selection_indent_column
12156 }
12157
12158 // If the corresponding selection was empty when this slice of the
12159 // clipboard text was written, then the entire line containing the
12160 // selection was copied. If this selection is also currently empty,
12161 // then paste the line before the current line of the buffer.
12162 let range = if selection.is_empty() && handle_entire_lines && entire_line {
12163 let column = selection.start.to_point(&snapshot).column as usize;
12164 let line_start = selection.start - column;
12165 line_start..line_start
12166 } else {
12167 selection.range()
12168 };
12169
12170 edits.push((range, to_insert));
12171 original_indent_columns.push(original_indent_column);
12172 }
12173 drop(snapshot);
12174
12175 buffer.edit(
12176 edits,
12177 if auto_indent_on_paste {
12178 Some(AutoindentMode::Block {
12179 original_indent_columns,
12180 })
12181 } else {
12182 None
12183 },
12184 cx,
12185 );
12186 });
12187
12188 let selections = this.selections.all::<usize>(cx);
12189 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12190 } else {
12191 this.insert(&clipboard_text, window, cx);
12192 }
12193 });
12194 }
12195
12196 pub fn diff_clipboard_with_selection(
12197 &mut self,
12198 _: &DiffClipboardWithSelection,
12199 window: &mut Window,
12200 cx: &mut Context<Self>,
12201 ) {
12202 let selections = self.selections.all::<usize>(cx);
12203
12204 if selections.is_empty() {
12205 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12206 return;
12207 };
12208
12209 let clipboard_text = match cx.read_from_clipboard() {
12210 Some(item) => match item.entries().first() {
12211 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
12212 _ => None,
12213 },
12214 None => None,
12215 };
12216
12217 let Some(clipboard_text) = clipboard_text else {
12218 log::warn!("Clipboard doesn't contain text.");
12219 return;
12220 };
12221
12222 window.dispatch_action(
12223 Box::new(DiffClipboardWithSelectionData {
12224 clipboard_text,
12225 editor: cx.entity(),
12226 }),
12227 cx,
12228 );
12229 }
12230
12231 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12232 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12233 if let Some(item) = cx.read_from_clipboard() {
12234 let entries = item.entries();
12235
12236 match entries.first() {
12237 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12238 // of all the pasted entries.
12239 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12240 .do_paste(
12241 clipboard_string.text(),
12242 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12243 true,
12244 window,
12245 cx,
12246 ),
12247 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12248 }
12249 }
12250 }
12251
12252 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12253 if self.read_only(cx) {
12254 return;
12255 }
12256
12257 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12258
12259 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12260 if let Some((selections, _)) =
12261 self.selection_history.transaction(transaction_id).cloned()
12262 {
12263 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12264 s.select_anchors(selections.to_vec());
12265 });
12266 } else {
12267 log::error!(
12268 "No entry in selection_history found for undo. \
12269 This may correspond to a bug where undo does not update the selection. \
12270 If this is occurring, please add details to \
12271 https://github.com/zed-industries/zed/issues/22692"
12272 );
12273 }
12274 self.request_autoscroll(Autoscroll::fit(), cx);
12275 self.unmark_text(window, cx);
12276 self.refresh_inline_completion(true, false, window, cx);
12277 cx.emit(EditorEvent::Edited { transaction_id });
12278 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12279 }
12280 }
12281
12282 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12283 if self.read_only(cx) {
12284 return;
12285 }
12286
12287 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12288
12289 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12290 if let Some((_, Some(selections))) =
12291 self.selection_history.transaction(transaction_id).cloned()
12292 {
12293 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12294 s.select_anchors(selections.to_vec());
12295 });
12296 } else {
12297 log::error!(
12298 "No entry in selection_history found for redo. \
12299 This may correspond to a bug where undo does not update the selection. \
12300 If this is occurring, please add details to \
12301 https://github.com/zed-industries/zed/issues/22692"
12302 );
12303 }
12304 self.request_autoscroll(Autoscroll::fit(), cx);
12305 self.unmark_text(window, cx);
12306 self.refresh_inline_completion(true, false, window, cx);
12307 cx.emit(EditorEvent::Edited { transaction_id });
12308 }
12309 }
12310
12311 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12312 self.buffer
12313 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12314 }
12315
12316 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12317 self.buffer
12318 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12319 }
12320
12321 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
12322 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12323 self.change_selections(Default::default(), window, cx, |s| {
12324 s.move_with(|map, selection| {
12325 let cursor = if selection.is_empty() {
12326 movement::left(map, selection.start)
12327 } else {
12328 selection.start
12329 };
12330 selection.collapse_to(cursor, SelectionGoal::None);
12331 });
12332 })
12333 }
12334
12335 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12336 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12337 self.change_selections(Default::default(), window, cx, |s| {
12338 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12339 })
12340 }
12341
12342 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
12343 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12344 self.change_selections(Default::default(), window, cx, |s| {
12345 s.move_with(|map, selection| {
12346 let cursor = if selection.is_empty() {
12347 movement::right(map, selection.end)
12348 } else {
12349 selection.end
12350 };
12351 selection.collapse_to(cursor, SelectionGoal::None)
12352 });
12353 })
12354 }
12355
12356 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12357 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12358 self.change_selections(Default::default(), window, cx, |s| {
12359 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
12360 })
12361 }
12362
12363 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
12364 if self.take_rename(true, window, cx).is_some() {
12365 return;
12366 }
12367
12368 if self.mode.is_single_line() {
12369 cx.propagate();
12370 return;
12371 }
12372
12373 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12374
12375 let text_layout_details = &self.text_layout_details(window);
12376 let selection_count = self.selections.count();
12377 let first_selection = self.selections.first_anchor();
12378
12379 self.change_selections(Default::default(), window, cx, |s| {
12380 s.move_with(|map, selection| {
12381 if !selection.is_empty() {
12382 selection.goal = SelectionGoal::None;
12383 }
12384 let (cursor, goal) = movement::up(
12385 map,
12386 selection.start,
12387 selection.goal,
12388 false,
12389 text_layout_details,
12390 );
12391 selection.collapse_to(cursor, goal);
12392 });
12393 });
12394
12395 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12396 {
12397 cx.propagate();
12398 }
12399 }
12400
12401 pub fn move_up_by_lines(
12402 &mut self,
12403 action: &MoveUpByLines,
12404 window: &mut Window,
12405 cx: &mut Context<Self>,
12406 ) {
12407 if self.take_rename(true, window, cx).is_some() {
12408 return;
12409 }
12410
12411 if self.mode.is_single_line() {
12412 cx.propagate();
12413 return;
12414 }
12415
12416 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12417
12418 let text_layout_details = &self.text_layout_details(window);
12419
12420 self.change_selections(Default::default(), window, cx, |s| {
12421 s.move_with(|map, selection| {
12422 if !selection.is_empty() {
12423 selection.goal = SelectionGoal::None;
12424 }
12425 let (cursor, goal) = movement::up_by_rows(
12426 map,
12427 selection.start,
12428 action.lines,
12429 selection.goal,
12430 false,
12431 text_layout_details,
12432 );
12433 selection.collapse_to(cursor, goal);
12434 });
12435 })
12436 }
12437
12438 pub fn move_down_by_lines(
12439 &mut self,
12440 action: &MoveDownByLines,
12441 window: &mut Window,
12442 cx: &mut Context<Self>,
12443 ) {
12444 if self.take_rename(true, window, cx).is_some() {
12445 return;
12446 }
12447
12448 if self.mode.is_single_line() {
12449 cx.propagate();
12450 return;
12451 }
12452
12453 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12454
12455 let text_layout_details = &self.text_layout_details(window);
12456
12457 self.change_selections(Default::default(), window, cx, |s| {
12458 s.move_with(|map, selection| {
12459 if !selection.is_empty() {
12460 selection.goal = SelectionGoal::None;
12461 }
12462 let (cursor, goal) = movement::down_by_rows(
12463 map,
12464 selection.start,
12465 action.lines,
12466 selection.goal,
12467 false,
12468 text_layout_details,
12469 );
12470 selection.collapse_to(cursor, goal);
12471 });
12472 })
12473 }
12474
12475 pub fn select_down_by_lines(
12476 &mut self,
12477 action: &SelectDownByLines,
12478 window: &mut Window,
12479 cx: &mut Context<Self>,
12480 ) {
12481 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12482 let text_layout_details = &self.text_layout_details(window);
12483 self.change_selections(Default::default(), window, cx, |s| {
12484 s.move_heads_with(|map, head, goal| {
12485 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
12486 })
12487 })
12488 }
12489
12490 pub fn select_up_by_lines(
12491 &mut self,
12492 action: &SelectUpByLines,
12493 window: &mut Window,
12494 cx: &mut Context<Self>,
12495 ) {
12496 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12497 let text_layout_details = &self.text_layout_details(window);
12498 self.change_selections(Default::default(), window, cx, |s| {
12499 s.move_heads_with(|map, head, goal| {
12500 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
12501 })
12502 })
12503 }
12504
12505 pub fn select_page_up(
12506 &mut self,
12507 _: &SelectPageUp,
12508 window: &mut Window,
12509 cx: &mut Context<Self>,
12510 ) {
12511 let Some(row_count) = self.visible_row_count() else {
12512 return;
12513 };
12514
12515 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12516
12517 let text_layout_details = &self.text_layout_details(window);
12518
12519 self.change_selections(Default::default(), window, cx, |s| {
12520 s.move_heads_with(|map, head, goal| {
12521 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
12522 })
12523 })
12524 }
12525
12526 pub fn move_page_up(
12527 &mut self,
12528 action: &MovePageUp,
12529 window: &mut Window,
12530 cx: &mut Context<Self>,
12531 ) {
12532 if self.take_rename(true, window, cx).is_some() {
12533 return;
12534 }
12535
12536 if self
12537 .context_menu
12538 .borrow_mut()
12539 .as_mut()
12540 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
12541 .unwrap_or(false)
12542 {
12543 return;
12544 }
12545
12546 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12547 cx.propagate();
12548 return;
12549 }
12550
12551 let Some(row_count) = self.visible_row_count() else {
12552 return;
12553 };
12554
12555 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12556
12557 let effects = if action.center_cursor {
12558 SelectionEffects::scroll(Autoscroll::center())
12559 } else {
12560 SelectionEffects::default()
12561 };
12562
12563 let text_layout_details = &self.text_layout_details(window);
12564
12565 self.change_selections(effects, window, cx, |s| {
12566 s.move_with(|map, selection| {
12567 if !selection.is_empty() {
12568 selection.goal = SelectionGoal::None;
12569 }
12570 let (cursor, goal) = movement::up_by_rows(
12571 map,
12572 selection.end,
12573 row_count,
12574 selection.goal,
12575 false,
12576 text_layout_details,
12577 );
12578 selection.collapse_to(cursor, goal);
12579 });
12580 });
12581 }
12582
12583 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
12584 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12585 let text_layout_details = &self.text_layout_details(window);
12586 self.change_selections(Default::default(), window, cx, |s| {
12587 s.move_heads_with(|map, head, goal| {
12588 movement::up(map, head, goal, false, text_layout_details)
12589 })
12590 })
12591 }
12592
12593 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
12594 self.take_rename(true, window, cx);
12595
12596 if self.mode.is_single_line() {
12597 cx.propagate();
12598 return;
12599 }
12600
12601 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12602
12603 let text_layout_details = &self.text_layout_details(window);
12604 let selection_count = self.selections.count();
12605 let first_selection = self.selections.first_anchor();
12606
12607 self.change_selections(Default::default(), window, cx, |s| {
12608 s.move_with(|map, selection| {
12609 if !selection.is_empty() {
12610 selection.goal = SelectionGoal::None;
12611 }
12612 let (cursor, goal) = movement::down(
12613 map,
12614 selection.end,
12615 selection.goal,
12616 false,
12617 text_layout_details,
12618 );
12619 selection.collapse_to(cursor, goal);
12620 });
12621 });
12622
12623 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12624 {
12625 cx.propagate();
12626 }
12627 }
12628
12629 pub fn select_page_down(
12630 &mut self,
12631 _: &SelectPageDown,
12632 window: &mut Window,
12633 cx: &mut Context<Self>,
12634 ) {
12635 let Some(row_count) = self.visible_row_count() else {
12636 return;
12637 };
12638
12639 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12640
12641 let text_layout_details = &self.text_layout_details(window);
12642
12643 self.change_selections(Default::default(), window, cx, |s| {
12644 s.move_heads_with(|map, head, goal| {
12645 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
12646 })
12647 })
12648 }
12649
12650 pub fn move_page_down(
12651 &mut self,
12652 action: &MovePageDown,
12653 window: &mut Window,
12654 cx: &mut Context<Self>,
12655 ) {
12656 if self.take_rename(true, window, cx).is_some() {
12657 return;
12658 }
12659
12660 if self
12661 .context_menu
12662 .borrow_mut()
12663 .as_mut()
12664 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
12665 .unwrap_or(false)
12666 {
12667 return;
12668 }
12669
12670 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12671 cx.propagate();
12672 return;
12673 }
12674
12675 let Some(row_count) = self.visible_row_count() else {
12676 return;
12677 };
12678
12679 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12680
12681 let effects = if action.center_cursor {
12682 SelectionEffects::scroll(Autoscroll::center())
12683 } else {
12684 SelectionEffects::default()
12685 };
12686
12687 let text_layout_details = &self.text_layout_details(window);
12688 self.change_selections(effects, window, cx, |s| {
12689 s.move_with(|map, selection| {
12690 if !selection.is_empty() {
12691 selection.goal = SelectionGoal::None;
12692 }
12693 let (cursor, goal) = movement::down_by_rows(
12694 map,
12695 selection.end,
12696 row_count,
12697 selection.goal,
12698 false,
12699 text_layout_details,
12700 );
12701 selection.collapse_to(cursor, goal);
12702 });
12703 });
12704 }
12705
12706 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
12707 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12708 let text_layout_details = &self.text_layout_details(window);
12709 self.change_selections(Default::default(), window, cx, |s| {
12710 s.move_heads_with(|map, head, goal| {
12711 movement::down(map, head, goal, false, text_layout_details)
12712 })
12713 });
12714 }
12715
12716 pub fn context_menu_first(
12717 &mut self,
12718 _: &ContextMenuFirst,
12719 window: &mut Window,
12720 cx: &mut Context<Self>,
12721 ) {
12722 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12723 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
12724 }
12725 }
12726
12727 pub fn context_menu_prev(
12728 &mut self,
12729 _: &ContextMenuPrevious,
12730 window: &mut Window,
12731 cx: &mut Context<Self>,
12732 ) {
12733 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12734 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
12735 }
12736 }
12737
12738 pub fn context_menu_next(
12739 &mut self,
12740 _: &ContextMenuNext,
12741 window: &mut Window,
12742 cx: &mut Context<Self>,
12743 ) {
12744 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12745 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
12746 }
12747 }
12748
12749 pub fn context_menu_last(
12750 &mut self,
12751 _: &ContextMenuLast,
12752 window: &mut Window,
12753 cx: &mut Context<Self>,
12754 ) {
12755 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12756 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
12757 }
12758 }
12759
12760 pub fn signature_help_prev(
12761 &mut self,
12762 _: &SignatureHelpPrevious,
12763 _: &mut Window,
12764 cx: &mut Context<Self>,
12765 ) {
12766 if let Some(popover) = self.signature_help_state.popover_mut() {
12767 if popover.current_signature == 0 {
12768 popover.current_signature = popover.signatures.len() - 1;
12769 } else {
12770 popover.current_signature -= 1;
12771 }
12772 cx.notify();
12773 }
12774 }
12775
12776 pub fn signature_help_next(
12777 &mut self,
12778 _: &SignatureHelpNext,
12779 _: &mut Window,
12780 cx: &mut Context<Self>,
12781 ) {
12782 if let Some(popover) = self.signature_help_state.popover_mut() {
12783 if popover.current_signature + 1 == popover.signatures.len() {
12784 popover.current_signature = 0;
12785 } else {
12786 popover.current_signature += 1;
12787 }
12788 cx.notify();
12789 }
12790 }
12791
12792 pub fn move_to_previous_word_start(
12793 &mut self,
12794 _: &MoveToPreviousWordStart,
12795 window: &mut Window,
12796 cx: &mut Context<Self>,
12797 ) {
12798 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12799 self.change_selections(Default::default(), window, cx, |s| {
12800 s.move_cursors_with(|map, head, _| {
12801 (
12802 movement::previous_word_start(map, head),
12803 SelectionGoal::None,
12804 )
12805 });
12806 })
12807 }
12808
12809 pub fn move_to_previous_subword_start(
12810 &mut self,
12811 _: &MoveToPreviousSubwordStart,
12812 window: &mut Window,
12813 cx: &mut Context<Self>,
12814 ) {
12815 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12816 self.change_selections(Default::default(), window, cx, |s| {
12817 s.move_cursors_with(|map, head, _| {
12818 (
12819 movement::previous_subword_start(map, head),
12820 SelectionGoal::None,
12821 )
12822 });
12823 })
12824 }
12825
12826 pub fn select_to_previous_word_start(
12827 &mut self,
12828 _: &SelectToPreviousWordStart,
12829 window: &mut Window,
12830 cx: &mut Context<Self>,
12831 ) {
12832 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12833 self.change_selections(Default::default(), window, cx, |s| {
12834 s.move_heads_with(|map, head, _| {
12835 (
12836 movement::previous_word_start(map, head),
12837 SelectionGoal::None,
12838 )
12839 });
12840 })
12841 }
12842
12843 pub fn select_to_previous_subword_start(
12844 &mut self,
12845 _: &SelectToPreviousSubwordStart,
12846 window: &mut Window,
12847 cx: &mut Context<Self>,
12848 ) {
12849 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12850 self.change_selections(Default::default(), window, cx, |s| {
12851 s.move_heads_with(|map, head, _| {
12852 (
12853 movement::previous_subword_start(map, head),
12854 SelectionGoal::None,
12855 )
12856 });
12857 })
12858 }
12859
12860 pub fn delete_to_previous_word_start(
12861 &mut self,
12862 action: &DeleteToPreviousWordStart,
12863 window: &mut Window,
12864 cx: &mut Context<Self>,
12865 ) {
12866 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12867 self.transact(window, cx, |this, window, cx| {
12868 this.select_autoclose_pair(window, cx);
12869 this.change_selections(Default::default(), window, cx, |s| {
12870 s.move_with(|map, selection| {
12871 if selection.is_empty() {
12872 let cursor = if action.ignore_newlines {
12873 movement::previous_word_start(map, selection.head())
12874 } else {
12875 movement::previous_word_start_or_newline(map, selection.head())
12876 };
12877 selection.set_head(cursor, SelectionGoal::None);
12878 }
12879 });
12880 });
12881 this.insert("", window, cx);
12882 });
12883 }
12884
12885 pub fn delete_to_previous_subword_start(
12886 &mut self,
12887 _: &DeleteToPreviousSubwordStart,
12888 window: &mut Window,
12889 cx: &mut Context<Self>,
12890 ) {
12891 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12892 self.transact(window, cx, |this, window, cx| {
12893 this.select_autoclose_pair(window, cx);
12894 this.change_selections(Default::default(), window, cx, |s| {
12895 s.move_with(|map, selection| {
12896 if selection.is_empty() {
12897 let cursor = movement::previous_subword_start(map, selection.head());
12898 selection.set_head(cursor, SelectionGoal::None);
12899 }
12900 });
12901 });
12902 this.insert("", window, cx);
12903 });
12904 }
12905
12906 pub fn move_to_next_word_end(
12907 &mut self,
12908 _: &MoveToNextWordEnd,
12909 window: &mut Window,
12910 cx: &mut Context<Self>,
12911 ) {
12912 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12913 self.change_selections(Default::default(), window, cx, |s| {
12914 s.move_cursors_with(|map, head, _| {
12915 (movement::next_word_end(map, head), SelectionGoal::None)
12916 });
12917 })
12918 }
12919
12920 pub fn move_to_next_subword_end(
12921 &mut self,
12922 _: &MoveToNextSubwordEnd,
12923 window: &mut Window,
12924 cx: &mut Context<Self>,
12925 ) {
12926 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12927 self.change_selections(Default::default(), window, cx, |s| {
12928 s.move_cursors_with(|map, head, _| {
12929 (movement::next_subword_end(map, head), SelectionGoal::None)
12930 });
12931 })
12932 }
12933
12934 pub fn select_to_next_word_end(
12935 &mut self,
12936 _: &SelectToNextWordEnd,
12937 window: &mut Window,
12938 cx: &mut Context<Self>,
12939 ) {
12940 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12941 self.change_selections(Default::default(), window, cx, |s| {
12942 s.move_heads_with(|map, head, _| {
12943 (movement::next_word_end(map, head), SelectionGoal::None)
12944 });
12945 })
12946 }
12947
12948 pub fn select_to_next_subword_end(
12949 &mut self,
12950 _: &SelectToNextSubwordEnd,
12951 window: &mut Window,
12952 cx: &mut Context<Self>,
12953 ) {
12954 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12955 self.change_selections(Default::default(), window, cx, |s| {
12956 s.move_heads_with(|map, head, _| {
12957 (movement::next_subword_end(map, head), SelectionGoal::None)
12958 });
12959 })
12960 }
12961
12962 pub fn delete_to_next_word_end(
12963 &mut self,
12964 action: &DeleteToNextWordEnd,
12965 window: &mut Window,
12966 cx: &mut Context<Self>,
12967 ) {
12968 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12969 self.transact(window, cx, |this, window, cx| {
12970 this.change_selections(Default::default(), window, cx, |s| {
12971 s.move_with(|map, selection| {
12972 if selection.is_empty() {
12973 let cursor = if action.ignore_newlines {
12974 movement::next_word_end(map, selection.head())
12975 } else {
12976 movement::next_word_end_or_newline(map, selection.head())
12977 };
12978 selection.set_head(cursor, SelectionGoal::None);
12979 }
12980 });
12981 });
12982 this.insert("", window, cx);
12983 });
12984 }
12985
12986 pub fn delete_to_next_subword_end(
12987 &mut self,
12988 _: &DeleteToNextSubwordEnd,
12989 window: &mut Window,
12990 cx: &mut Context<Self>,
12991 ) {
12992 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12993 self.transact(window, cx, |this, window, cx| {
12994 this.change_selections(Default::default(), window, cx, |s| {
12995 s.move_with(|map, selection| {
12996 if selection.is_empty() {
12997 let cursor = movement::next_subword_end(map, selection.head());
12998 selection.set_head(cursor, SelectionGoal::None);
12999 }
13000 });
13001 });
13002 this.insert("", window, cx);
13003 });
13004 }
13005
13006 pub fn move_to_beginning_of_line(
13007 &mut self,
13008 action: &MoveToBeginningOfLine,
13009 window: &mut Window,
13010 cx: &mut Context<Self>,
13011 ) {
13012 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13013 self.change_selections(Default::default(), window, cx, |s| {
13014 s.move_cursors_with(|map, head, _| {
13015 (
13016 movement::indented_line_beginning(
13017 map,
13018 head,
13019 action.stop_at_soft_wraps,
13020 action.stop_at_indent,
13021 ),
13022 SelectionGoal::None,
13023 )
13024 });
13025 })
13026 }
13027
13028 pub fn select_to_beginning_of_line(
13029 &mut self,
13030 action: &SelectToBeginningOfLine,
13031 window: &mut Window,
13032 cx: &mut Context<Self>,
13033 ) {
13034 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13035 self.change_selections(Default::default(), window, cx, |s| {
13036 s.move_heads_with(|map, head, _| {
13037 (
13038 movement::indented_line_beginning(
13039 map,
13040 head,
13041 action.stop_at_soft_wraps,
13042 action.stop_at_indent,
13043 ),
13044 SelectionGoal::None,
13045 )
13046 });
13047 });
13048 }
13049
13050 pub fn delete_to_beginning_of_line(
13051 &mut self,
13052 action: &DeleteToBeginningOfLine,
13053 window: &mut Window,
13054 cx: &mut Context<Self>,
13055 ) {
13056 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13057 self.transact(window, cx, |this, window, cx| {
13058 this.change_selections(Default::default(), window, cx, |s| {
13059 s.move_with(|_, selection| {
13060 selection.reversed = true;
13061 });
13062 });
13063
13064 this.select_to_beginning_of_line(
13065 &SelectToBeginningOfLine {
13066 stop_at_soft_wraps: false,
13067 stop_at_indent: action.stop_at_indent,
13068 },
13069 window,
13070 cx,
13071 );
13072 this.backspace(&Backspace, window, cx);
13073 });
13074 }
13075
13076 pub fn move_to_end_of_line(
13077 &mut self,
13078 action: &MoveToEndOfLine,
13079 window: &mut Window,
13080 cx: &mut Context<Self>,
13081 ) {
13082 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13083 self.change_selections(Default::default(), window, cx, |s| {
13084 s.move_cursors_with(|map, head, _| {
13085 (
13086 movement::line_end(map, head, action.stop_at_soft_wraps),
13087 SelectionGoal::None,
13088 )
13089 });
13090 })
13091 }
13092
13093 pub fn select_to_end_of_line(
13094 &mut self,
13095 action: &SelectToEndOfLine,
13096 window: &mut Window,
13097 cx: &mut Context<Self>,
13098 ) {
13099 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13100 self.change_selections(Default::default(), window, cx, |s| {
13101 s.move_heads_with(|map, head, _| {
13102 (
13103 movement::line_end(map, head, action.stop_at_soft_wraps),
13104 SelectionGoal::None,
13105 )
13106 });
13107 })
13108 }
13109
13110 pub fn delete_to_end_of_line(
13111 &mut self,
13112 _: &DeleteToEndOfLine,
13113 window: &mut Window,
13114 cx: &mut Context<Self>,
13115 ) {
13116 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13117 self.transact(window, cx, |this, window, cx| {
13118 this.select_to_end_of_line(
13119 &SelectToEndOfLine {
13120 stop_at_soft_wraps: false,
13121 },
13122 window,
13123 cx,
13124 );
13125 this.delete(&Delete, window, cx);
13126 });
13127 }
13128
13129 pub fn cut_to_end_of_line(
13130 &mut self,
13131 _: &CutToEndOfLine,
13132 window: &mut Window,
13133 cx: &mut Context<Self>,
13134 ) {
13135 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13136 self.transact(window, cx, |this, window, cx| {
13137 this.select_to_end_of_line(
13138 &SelectToEndOfLine {
13139 stop_at_soft_wraps: false,
13140 },
13141 window,
13142 cx,
13143 );
13144 this.cut(&Cut, window, cx);
13145 });
13146 }
13147
13148 pub fn move_to_start_of_paragraph(
13149 &mut self,
13150 _: &MoveToStartOfParagraph,
13151 window: &mut Window,
13152 cx: &mut Context<Self>,
13153 ) {
13154 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13155 cx.propagate();
13156 return;
13157 }
13158 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13159 self.change_selections(Default::default(), window, cx, |s| {
13160 s.move_with(|map, selection| {
13161 selection.collapse_to(
13162 movement::start_of_paragraph(map, selection.head(), 1),
13163 SelectionGoal::None,
13164 )
13165 });
13166 })
13167 }
13168
13169 pub fn move_to_end_of_paragraph(
13170 &mut self,
13171 _: &MoveToEndOfParagraph,
13172 window: &mut Window,
13173 cx: &mut Context<Self>,
13174 ) {
13175 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13176 cx.propagate();
13177 return;
13178 }
13179 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13180 self.change_selections(Default::default(), window, cx, |s| {
13181 s.move_with(|map, selection| {
13182 selection.collapse_to(
13183 movement::end_of_paragraph(map, selection.head(), 1),
13184 SelectionGoal::None,
13185 )
13186 });
13187 })
13188 }
13189
13190 pub fn select_to_start_of_paragraph(
13191 &mut self,
13192 _: &SelectToStartOfParagraph,
13193 window: &mut Window,
13194 cx: &mut Context<Self>,
13195 ) {
13196 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13197 cx.propagate();
13198 return;
13199 }
13200 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13201 self.change_selections(Default::default(), window, cx, |s| {
13202 s.move_heads_with(|map, head, _| {
13203 (
13204 movement::start_of_paragraph(map, head, 1),
13205 SelectionGoal::None,
13206 )
13207 });
13208 })
13209 }
13210
13211 pub fn select_to_end_of_paragraph(
13212 &mut self,
13213 _: &SelectToEndOfParagraph,
13214 window: &mut Window,
13215 cx: &mut Context<Self>,
13216 ) {
13217 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13218 cx.propagate();
13219 return;
13220 }
13221 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13222 self.change_selections(Default::default(), window, cx, |s| {
13223 s.move_heads_with(|map, head, _| {
13224 (
13225 movement::end_of_paragraph(map, head, 1),
13226 SelectionGoal::None,
13227 )
13228 });
13229 })
13230 }
13231
13232 pub fn move_to_start_of_excerpt(
13233 &mut self,
13234 _: &MoveToStartOfExcerpt,
13235 window: &mut Window,
13236 cx: &mut Context<Self>,
13237 ) {
13238 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13239 cx.propagate();
13240 return;
13241 }
13242 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13243 self.change_selections(Default::default(), window, cx, |s| {
13244 s.move_with(|map, selection| {
13245 selection.collapse_to(
13246 movement::start_of_excerpt(
13247 map,
13248 selection.head(),
13249 workspace::searchable::Direction::Prev,
13250 ),
13251 SelectionGoal::None,
13252 )
13253 });
13254 })
13255 }
13256
13257 pub fn move_to_start_of_next_excerpt(
13258 &mut self,
13259 _: &MoveToStartOfNextExcerpt,
13260 window: &mut Window,
13261 cx: &mut Context<Self>,
13262 ) {
13263 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13264 cx.propagate();
13265 return;
13266 }
13267
13268 self.change_selections(Default::default(), window, cx, |s| {
13269 s.move_with(|map, selection| {
13270 selection.collapse_to(
13271 movement::start_of_excerpt(
13272 map,
13273 selection.head(),
13274 workspace::searchable::Direction::Next,
13275 ),
13276 SelectionGoal::None,
13277 )
13278 });
13279 })
13280 }
13281
13282 pub fn move_to_end_of_excerpt(
13283 &mut self,
13284 _: &MoveToEndOfExcerpt,
13285 window: &mut Window,
13286 cx: &mut Context<Self>,
13287 ) {
13288 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13289 cx.propagate();
13290 return;
13291 }
13292 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13293 self.change_selections(Default::default(), window, cx, |s| {
13294 s.move_with(|map, selection| {
13295 selection.collapse_to(
13296 movement::end_of_excerpt(
13297 map,
13298 selection.head(),
13299 workspace::searchable::Direction::Next,
13300 ),
13301 SelectionGoal::None,
13302 )
13303 });
13304 })
13305 }
13306
13307 pub fn move_to_end_of_previous_excerpt(
13308 &mut self,
13309 _: &MoveToEndOfPreviousExcerpt,
13310 window: &mut Window,
13311 cx: &mut Context<Self>,
13312 ) {
13313 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13314 cx.propagate();
13315 return;
13316 }
13317 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13318 self.change_selections(Default::default(), window, cx, |s| {
13319 s.move_with(|map, selection| {
13320 selection.collapse_to(
13321 movement::end_of_excerpt(
13322 map,
13323 selection.head(),
13324 workspace::searchable::Direction::Prev,
13325 ),
13326 SelectionGoal::None,
13327 )
13328 });
13329 })
13330 }
13331
13332 pub fn select_to_start_of_excerpt(
13333 &mut self,
13334 _: &SelectToStartOfExcerpt,
13335 window: &mut Window,
13336 cx: &mut Context<Self>,
13337 ) {
13338 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13339 cx.propagate();
13340 return;
13341 }
13342 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13343 self.change_selections(Default::default(), window, cx, |s| {
13344 s.move_heads_with(|map, head, _| {
13345 (
13346 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13347 SelectionGoal::None,
13348 )
13349 });
13350 })
13351 }
13352
13353 pub fn select_to_start_of_next_excerpt(
13354 &mut self,
13355 _: &SelectToStartOfNextExcerpt,
13356 window: &mut Window,
13357 cx: &mut Context<Self>,
13358 ) {
13359 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13360 cx.propagate();
13361 return;
13362 }
13363 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13364 self.change_selections(Default::default(), window, cx, |s| {
13365 s.move_heads_with(|map, head, _| {
13366 (
13367 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
13368 SelectionGoal::None,
13369 )
13370 });
13371 })
13372 }
13373
13374 pub fn select_to_end_of_excerpt(
13375 &mut self,
13376 _: &SelectToEndOfExcerpt,
13377 window: &mut Window,
13378 cx: &mut Context<Self>,
13379 ) {
13380 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13381 cx.propagate();
13382 return;
13383 }
13384 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13385 self.change_selections(Default::default(), window, cx, |s| {
13386 s.move_heads_with(|map, head, _| {
13387 (
13388 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
13389 SelectionGoal::None,
13390 )
13391 });
13392 })
13393 }
13394
13395 pub fn select_to_end_of_previous_excerpt(
13396 &mut self,
13397 _: &SelectToEndOfPreviousExcerpt,
13398 window: &mut Window,
13399 cx: &mut Context<Self>,
13400 ) {
13401 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13402 cx.propagate();
13403 return;
13404 }
13405 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13406 self.change_selections(Default::default(), window, cx, |s| {
13407 s.move_heads_with(|map, head, _| {
13408 (
13409 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13410 SelectionGoal::None,
13411 )
13412 });
13413 })
13414 }
13415
13416 pub fn move_to_beginning(
13417 &mut self,
13418 _: &MoveToBeginning,
13419 window: &mut Window,
13420 cx: &mut Context<Self>,
13421 ) {
13422 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13423 cx.propagate();
13424 return;
13425 }
13426 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13427 self.change_selections(Default::default(), window, cx, |s| {
13428 s.select_ranges(vec![0..0]);
13429 });
13430 }
13431
13432 pub fn select_to_beginning(
13433 &mut self,
13434 _: &SelectToBeginning,
13435 window: &mut Window,
13436 cx: &mut Context<Self>,
13437 ) {
13438 let mut selection = self.selections.last::<Point>(cx);
13439 selection.set_head(Point::zero(), SelectionGoal::None);
13440 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13441 self.change_selections(Default::default(), window, cx, |s| {
13442 s.select(vec![selection]);
13443 });
13444 }
13445
13446 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
13447 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13448 cx.propagate();
13449 return;
13450 }
13451 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13452 let cursor = self.buffer.read(cx).read(cx).len();
13453 self.change_selections(Default::default(), window, cx, |s| {
13454 s.select_ranges(vec![cursor..cursor])
13455 });
13456 }
13457
13458 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
13459 self.nav_history = nav_history;
13460 }
13461
13462 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
13463 self.nav_history.as_ref()
13464 }
13465
13466 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
13467 self.push_to_nav_history(
13468 self.selections.newest_anchor().head(),
13469 None,
13470 false,
13471 true,
13472 cx,
13473 );
13474 }
13475
13476 fn push_to_nav_history(
13477 &mut self,
13478 cursor_anchor: Anchor,
13479 new_position: Option<Point>,
13480 is_deactivate: bool,
13481 always: bool,
13482 cx: &mut Context<Self>,
13483 ) {
13484 if let Some(nav_history) = self.nav_history.as_mut() {
13485 let buffer = self.buffer.read(cx).read(cx);
13486 let cursor_position = cursor_anchor.to_point(&buffer);
13487 let scroll_state = self.scroll_manager.anchor();
13488 let scroll_top_row = scroll_state.top_row(&buffer);
13489 drop(buffer);
13490
13491 if let Some(new_position) = new_position {
13492 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
13493 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
13494 return;
13495 }
13496 }
13497
13498 nav_history.push(
13499 Some(NavigationData {
13500 cursor_anchor,
13501 cursor_position,
13502 scroll_anchor: scroll_state,
13503 scroll_top_row,
13504 }),
13505 cx,
13506 );
13507 cx.emit(EditorEvent::PushedToNavHistory {
13508 anchor: cursor_anchor,
13509 is_deactivate,
13510 })
13511 }
13512 }
13513
13514 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
13515 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13516 let buffer = self.buffer.read(cx).snapshot(cx);
13517 let mut selection = self.selections.first::<usize>(cx);
13518 selection.set_head(buffer.len(), SelectionGoal::None);
13519 self.change_selections(Default::default(), window, cx, |s| {
13520 s.select(vec![selection]);
13521 });
13522 }
13523
13524 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
13525 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13526 let end = self.buffer.read(cx).read(cx).len();
13527 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13528 s.select_ranges(vec![0..end]);
13529 });
13530 }
13531
13532 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
13533 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13534 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13535 let mut selections = self.selections.all::<Point>(cx);
13536 let max_point = display_map.buffer_snapshot.max_point();
13537 for selection in &mut selections {
13538 let rows = selection.spanned_rows(true, &display_map);
13539 selection.start = Point::new(rows.start.0, 0);
13540 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
13541 selection.reversed = false;
13542 }
13543 self.change_selections(Default::default(), window, cx, |s| {
13544 s.select(selections);
13545 });
13546 }
13547
13548 pub fn split_selection_into_lines(
13549 &mut self,
13550 _: &SplitSelectionIntoLines,
13551 window: &mut Window,
13552 cx: &mut Context<Self>,
13553 ) {
13554 let selections = self
13555 .selections
13556 .all::<Point>(cx)
13557 .into_iter()
13558 .map(|selection| selection.start..selection.end)
13559 .collect::<Vec<_>>();
13560 self.unfold_ranges(&selections, true, true, cx);
13561
13562 let mut new_selection_ranges = Vec::new();
13563 {
13564 let buffer = self.buffer.read(cx).read(cx);
13565 for selection in selections {
13566 for row in selection.start.row..selection.end.row {
13567 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
13568 new_selection_ranges.push(cursor..cursor);
13569 }
13570
13571 let is_multiline_selection = selection.start.row != selection.end.row;
13572 // Don't insert last one if it's a multi-line selection ending at the start of a line,
13573 // so this action feels more ergonomic when paired with other selection operations
13574 let should_skip_last = is_multiline_selection && selection.end.column == 0;
13575 if !should_skip_last {
13576 new_selection_ranges.push(selection.end..selection.end);
13577 }
13578 }
13579 }
13580 self.change_selections(Default::default(), window, cx, |s| {
13581 s.select_ranges(new_selection_ranges);
13582 });
13583 }
13584
13585 pub fn add_selection_above(
13586 &mut self,
13587 _: &AddSelectionAbove,
13588 window: &mut Window,
13589 cx: &mut Context<Self>,
13590 ) {
13591 self.add_selection(true, window, cx);
13592 }
13593
13594 pub fn add_selection_below(
13595 &mut self,
13596 _: &AddSelectionBelow,
13597 window: &mut Window,
13598 cx: &mut Context<Self>,
13599 ) {
13600 self.add_selection(false, window, cx);
13601 }
13602
13603 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
13604 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13605
13606 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13607 let all_selections = self.selections.all::<Point>(cx);
13608 let text_layout_details = self.text_layout_details(window);
13609
13610 let (mut columnar_selections, new_selections_to_columnarize) = {
13611 if let Some(state) = self.add_selections_state.as_ref() {
13612 let columnar_selection_ids: HashSet<_> = state
13613 .groups
13614 .iter()
13615 .flat_map(|group| group.stack.iter())
13616 .copied()
13617 .collect();
13618
13619 all_selections
13620 .into_iter()
13621 .partition(|s| columnar_selection_ids.contains(&s.id))
13622 } else {
13623 (Vec::new(), all_selections)
13624 }
13625 };
13626
13627 let mut state = self
13628 .add_selections_state
13629 .take()
13630 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
13631
13632 for selection in new_selections_to_columnarize {
13633 let range = selection.display_range(&display_map).sorted();
13634 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
13635 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
13636 let positions = start_x.min(end_x)..start_x.max(end_x);
13637 let mut stack = Vec::new();
13638 for row in range.start.row().0..=range.end.row().0 {
13639 if let Some(selection) = self.selections.build_columnar_selection(
13640 &display_map,
13641 DisplayRow(row),
13642 &positions,
13643 selection.reversed,
13644 &text_layout_details,
13645 ) {
13646 stack.push(selection.id);
13647 columnar_selections.push(selection);
13648 }
13649 }
13650 if !stack.is_empty() {
13651 if above {
13652 stack.reverse();
13653 }
13654 state.groups.push(AddSelectionsGroup { above, stack });
13655 }
13656 }
13657
13658 let mut final_selections = Vec::new();
13659 let end_row = if above {
13660 DisplayRow(0)
13661 } else {
13662 display_map.max_point().row()
13663 };
13664
13665 let mut last_added_item_per_group = HashMap::default();
13666 for group in state.groups.iter_mut() {
13667 if let Some(last_id) = group.stack.last() {
13668 last_added_item_per_group.insert(*last_id, group);
13669 }
13670 }
13671
13672 for selection in columnar_selections {
13673 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
13674 if above == group.above {
13675 let range = selection.display_range(&display_map).sorted();
13676 debug_assert_eq!(range.start.row(), range.end.row());
13677 let mut row = range.start.row();
13678 let positions =
13679 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
13680 px(start)..px(end)
13681 } else {
13682 let start_x =
13683 display_map.x_for_display_point(range.start, &text_layout_details);
13684 let end_x =
13685 display_map.x_for_display_point(range.end, &text_layout_details);
13686 start_x.min(end_x)..start_x.max(end_x)
13687 };
13688
13689 let mut maybe_new_selection = None;
13690 while row != end_row {
13691 if above {
13692 row.0 -= 1;
13693 } else {
13694 row.0 += 1;
13695 }
13696 if let Some(new_selection) = self.selections.build_columnar_selection(
13697 &display_map,
13698 row,
13699 &positions,
13700 selection.reversed,
13701 &text_layout_details,
13702 ) {
13703 maybe_new_selection = Some(new_selection);
13704 break;
13705 }
13706 }
13707
13708 if let Some(new_selection) = maybe_new_selection {
13709 group.stack.push(new_selection.id);
13710 if above {
13711 final_selections.push(new_selection);
13712 final_selections.push(selection);
13713 } else {
13714 final_selections.push(selection);
13715 final_selections.push(new_selection);
13716 }
13717 } else {
13718 final_selections.push(selection);
13719 }
13720 } else {
13721 group.stack.pop();
13722 }
13723 } else {
13724 final_selections.push(selection);
13725 }
13726 }
13727
13728 self.change_selections(Default::default(), window, cx, |s| {
13729 s.select(final_selections);
13730 });
13731
13732 let final_selection_ids: HashSet<_> = self
13733 .selections
13734 .all::<Point>(cx)
13735 .iter()
13736 .map(|s| s.id)
13737 .collect();
13738 state.groups.retain_mut(|group| {
13739 // selections might get merged above so we remove invalid items from stacks
13740 group.stack.retain(|id| final_selection_ids.contains(id));
13741
13742 // single selection in stack can be treated as initial state
13743 group.stack.len() > 1
13744 });
13745
13746 if !state.groups.is_empty() {
13747 self.add_selections_state = Some(state);
13748 }
13749 }
13750
13751 fn select_match_ranges(
13752 &mut self,
13753 range: Range<usize>,
13754 reversed: bool,
13755 replace_newest: bool,
13756 auto_scroll: Option<Autoscroll>,
13757 window: &mut Window,
13758 cx: &mut Context<Editor>,
13759 ) {
13760 self.unfold_ranges(
13761 std::slice::from_ref(&range),
13762 false,
13763 auto_scroll.is_some(),
13764 cx,
13765 );
13766 let effects = if let Some(scroll) = auto_scroll {
13767 SelectionEffects::scroll(scroll)
13768 } else {
13769 SelectionEffects::no_scroll()
13770 };
13771 self.change_selections(effects, window, cx, |s| {
13772 if replace_newest {
13773 s.delete(s.newest_anchor().id);
13774 }
13775 if reversed {
13776 s.insert_range(range.end..range.start);
13777 } else {
13778 s.insert_range(range);
13779 }
13780 });
13781 }
13782
13783 pub fn select_next_match_internal(
13784 &mut self,
13785 display_map: &DisplaySnapshot,
13786 replace_newest: bool,
13787 autoscroll: Option<Autoscroll>,
13788 window: &mut Window,
13789 cx: &mut Context<Self>,
13790 ) -> Result<()> {
13791 let buffer = &display_map.buffer_snapshot;
13792 let mut selections = self.selections.all::<usize>(cx);
13793 if let Some(mut select_next_state) = self.select_next_state.take() {
13794 let query = &select_next_state.query;
13795 if !select_next_state.done {
13796 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13797 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13798 let mut next_selected_range = None;
13799
13800 let bytes_after_last_selection =
13801 buffer.bytes_in_range(last_selection.end..buffer.len());
13802 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
13803 let query_matches = query
13804 .stream_find_iter(bytes_after_last_selection)
13805 .map(|result| (last_selection.end, result))
13806 .chain(
13807 query
13808 .stream_find_iter(bytes_before_first_selection)
13809 .map(|result| (0, result)),
13810 );
13811
13812 for (start_offset, query_match) in query_matches {
13813 let query_match = query_match.unwrap(); // can only fail due to I/O
13814 let offset_range =
13815 start_offset + query_match.start()..start_offset + query_match.end();
13816
13817 if !select_next_state.wordwise
13818 || (!buffer.is_inside_word(offset_range.start, false)
13819 && !buffer.is_inside_word(offset_range.end, false))
13820 {
13821 // TODO: This is n^2, because we might check all the selections
13822 if !selections
13823 .iter()
13824 .any(|selection| selection.range().overlaps(&offset_range))
13825 {
13826 next_selected_range = Some(offset_range);
13827 break;
13828 }
13829 }
13830 }
13831
13832 if let Some(next_selected_range) = next_selected_range {
13833 self.select_match_ranges(
13834 next_selected_range,
13835 last_selection.reversed,
13836 replace_newest,
13837 autoscroll,
13838 window,
13839 cx,
13840 );
13841 } else {
13842 select_next_state.done = true;
13843 }
13844 }
13845
13846 self.select_next_state = Some(select_next_state);
13847 } else {
13848 let mut only_carets = true;
13849 let mut same_text_selected = true;
13850 let mut selected_text = None;
13851
13852 let mut selections_iter = selections.iter().peekable();
13853 while let Some(selection) = selections_iter.next() {
13854 if selection.start != selection.end {
13855 only_carets = false;
13856 }
13857
13858 if same_text_selected {
13859 if selected_text.is_none() {
13860 selected_text =
13861 Some(buffer.text_for_range(selection.range()).collect::<String>());
13862 }
13863
13864 if let Some(next_selection) = selections_iter.peek() {
13865 if next_selection.range().len() == selection.range().len() {
13866 let next_selected_text = buffer
13867 .text_for_range(next_selection.range())
13868 .collect::<String>();
13869 if Some(next_selected_text) != selected_text {
13870 same_text_selected = false;
13871 selected_text = None;
13872 }
13873 } else {
13874 same_text_selected = false;
13875 selected_text = None;
13876 }
13877 }
13878 }
13879 }
13880
13881 if only_carets {
13882 for selection in &mut selections {
13883 let (word_range, _) = buffer.surrounding_word(selection.start, false);
13884 selection.start = word_range.start;
13885 selection.end = word_range.end;
13886 selection.goal = SelectionGoal::None;
13887 selection.reversed = false;
13888 self.select_match_ranges(
13889 selection.start..selection.end,
13890 selection.reversed,
13891 replace_newest,
13892 autoscroll,
13893 window,
13894 cx,
13895 );
13896 }
13897
13898 if selections.len() == 1 {
13899 let selection = selections
13900 .last()
13901 .expect("ensured that there's only one selection");
13902 let query = buffer
13903 .text_for_range(selection.start..selection.end)
13904 .collect::<String>();
13905 let is_empty = query.is_empty();
13906 let select_state = SelectNextState {
13907 query: AhoCorasick::new(&[query])?,
13908 wordwise: true,
13909 done: is_empty,
13910 };
13911 self.select_next_state = Some(select_state);
13912 } else {
13913 self.select_next_state = None;
13914 }
13915 } else if let Some(selected_text) = selected_text {
13916 self.select_next_state = Some(SelectNextState {
13917 query: AhoCorasick::new(&[selected_text])?,
13918 wordwise: false,
13919 done: false,
13920 });
13921 self.select_next_match_internal(
13922 display_map,
13923 replace_newest,
13924 autoscroll,
13925 window,
13926 cx,
13927 )?;
13928 }
13929 }
13930 Ok(())
13931 }
13932
13933 pub fn select_all_matches(
13934 &mut self,
13935 _action: &SelectAllMatches,
13936 window: &mut Window,
13937 cx: &mut Context<Self>,
13938 ) -> Result<()> {
13939 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13940
13941 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13942
13943 self.select_next_match_internal(&display_map, false, None, window, cx)?;
13944 let Some(select_next_state) = self.select_next_state.as_mut() else {
13945 return Ok(());
13946 };
13947 if select_next_state.done {
13948 return Ok(());
13949 }
13950
13951 let mut new_selections = Vec::new();
13952
13953 let reversed = self.selections.oldest::<usize>(cx).reversed;
13954 let buffer = &display_map.buffer_snapshot;
13955 let query_matches = select_next_state
13956 .query
13957 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
13958
13959 for query_match in query_matches.into_iter() {
13960 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
13961 let offset_range = if reversed {
13962 query_match.end()..query_match.start()
13963 } else {
13964 query_match.start()..query_match.end()
13965 };
13966
13967 if !select_next_state.wordwise
13968 || (!buffer.is_inside_word(offset_range.start, false)
13969 && !buffer.is_inside_word(offset_range.end, false))
13970 {
13971 new_selections.push(offset_range.start..offset_range.end);
13972 }
13973 }
13974
13975 select_next_state.done = true;
13976
13977 if new_selections.is_empty() {
13978 log::error!("bug: new_selections is empty in select_all_matches");
13979 return Ok(());
13980 }
13981
13982 self.unfold_ranges(&new_selections.clone(), false, false, cx);
13983 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
13984 selections.select_ranges(new_selections)
13985 });
13986
13987 Ok(())
13988 }
13989
13990 pub fn select_next(
13991 &mut self,
13992 action: &SelectNext,
13993 window: &mut Window,
13994 cx: &mut Context<Self>,
13995 ) -> Result<()> {
13996 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13997 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13998 self.select_next_match_internal(
13999 &display_map,
14000 action.replace_newest,
14001 Some(Autoscroll::newest()),
14002 window,
14003 cx,
14004 )?;
14005 Ok(())
14006 }
14007
14008 pub fn select_previous(
14009 &mut self,
14010 action: &SelectPrevious,
14011 window: &mut Window,
14012 cx: &mut Context<Self>,
14013 ) -> Result<()> {
14014 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14015 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14016 let buffer = &display_map.buffer_snapshot;
14017 let mut selections = self.selections.all::<usize>(cx);
14018 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14019 let query = &select_prev_state.query;
14020 if !select_prev_state.done {
14021 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14022 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14023 let mut next_selected_range = None;
14024 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14025 let bytes_before_last_selection =
14026 buffer.reversed_bytes_in_range(0..last_selection.start);
14027 let bytes_after_first_selection =
14028 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14029 let query_matches = query
14030 .stream_find_iter(bytes_before_last_selection)
14031 .map(|result| (last_selection.start, result))
14032 .chain(
14033 query
14034 .stream_find_iter(bytes_after_first_selection)
14035 .map(|result| (buffer.len(), result)),
14036 );
14037 for (end_offset, query_match) in query_matches {
14038 let query_match = query_match.unwrap(); // can only fail due to I/O
14039 let offset_range =
14040 end_offset - query_match.end()..end_offset - query_match.start();
14041
14042 if !select_prev_state.wordwise
14043 || (!buffer.is_inside_word(offset_range.start, false)
14044 && !buffer.is_inside_word(offset_range.end, false))
14045 {
14046 next_selected_range = Some(offset_range);
14047 break;
14048 }
14049 }
14050
14051 if let Some(next_selected_range) = next_selected_range {
14052 self.select_match_ranges(
14053 next_selected_range,
14054 last_selection.reversed,
14055 action.replace_newest,
14056 Some(Autoscroll::newest()),
14057 window,
14058 cx,
14059 );
14060 } else {
14061 select_prev_state.done = true;
14062 }
14063 }
14064
14065 self.select_prev_state = Some(select_prev_state);
14066 } else {
14067 let mut only_carets = true;
14068 let mut same_text_selected = true;
14069 let mut selected_text = None;
14070
14071 let mut selections_iter = selections.iter().peekable();
14072 while let Some(selection) = selections_iter.next() {
14073 if selection.start != selection.end {
14074 only_carets = false;
14075 }
14076
14077 if same_text_selected {
14078 if selected_text.is_none() {
14079 selected_text =
14080 Some(buffer.text_for_range(selection.range()).collect::<String>());
14081 }
14082
14083 if let Some(next_selection) = selections_iter.peek() {
14084 if next_selection.range().len() == selection.range().len() {
14085 let next_selected_text = buffer
14086 .text_for_range(next_selection.range())
14087 .collect::<String>();
14088 if Some(next_selected_text) != selected_text {
14089 same_text_selected = false;
14090 selected_text = None;
14091 }
14092 } else {
14093 same_text_selected = false;
14094 selected_text = None;
14095 }
14096 }
14097 }
14098 }
14099
14100 if only_carets {
14101 for selection in &mut selections {
14102 let (word_range, _) = buffer.surrounding_word(selection.start, false);
14103 selection.start = word_range.start;
14104 selection.end = word_range.end;
14105 selection.goal = SelectionGoal::None;
14106 selection.reversed = false;
14107 self.select_match_ranges(
14108 selection.start..selection.end,
14109 selection.reversed,
14110 action.replace_newest,
14111 Some(Autoscroll::newest()),
14112 window,
14113 cx,
14114 );
14115 }
14116 if selections.len() == 1 {
14117 let selection = selections
14118 .last()
14119 .expect("ensured that there's only one selection");
14120 let query = buffer
14121 .text_for_range(selection.start..selection.end)
14122 .collect::<String>();
14123 let is_empty = query.is_empty();
14124 let select_state = SelectNextState {
14125 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
14126 wordwise: true,
14127 done: is_empty,
14128 };
14129 self.select_prev_state = Some(select_state);
14130 } else {
14131 self.select_prev_state = None;
14132 }
14133 } else if let Some(selected_text) = selected_text {
14134 self.select_prev_state = Some(SelectNextState {
14135 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
14136 wordwise: false,
14137 done: false,
14138 });
14139 self.select_previous(action, window, cx)?;
14140 }
14141 }
14142 Ok(())
14143 }
14144
14145 pub fn find_next_match(
14146 &mut self,
14147 _: &FindNextMatch,
14148 window: &mut Window,
14149 cx: &mut Context<Self>,
14150 ) -> Result<()> {
14151 let selections = self.selections.disjoint_anchors();
14152 match selections.first() {
14153 Some(first) if selections.len() >= 2 => {
14154 self.change_selections(Default::default(), window, cx, |s| {
14155 s.select_ranges([first.range()]);
14156 });
14157 }
14158 _ => self.select_next(
14159 &SelectNext {
14160 replace_newest: true,
14161 },
14162 window,
14163 cx,
14164 )?,
14165 }
14166 Ok(())
14167 }
14168
14169 pub fn find_previous_match(
14170 &mut self,
14171 _: &FindPreviousMatch,
14172 window: &mut Window,
14173 cx: &mut Context<Self>,
14174 ) -> Result<()> {
14175 let selections = self.selections.disjoint_anchors();
14176 match selections.last() {
14177 Some(last) if selections.len() >= 2 => {
14178 self.change_selections(Default::default(), window, cx, |s| {
14179 s.select_ranges([last.range()]);
14180 });
14181 }
14182 _ => self.select_previous(
14183 &SelectPrevious {
14184 replace_newest: true,
14185 },
14186 window,
14187 cx,
14188 )?,
14189 }
14190 Ok(())
14191 }
14192
14193 pub fn toggle_comments(
14194 &mut self,
14195 action: &ToggleComments,
14196 window: &mut Window,
14197 cx: &mut Context<Self>,
14198 ) {
14199 if self.read_only(cx) {
14200 return;
14201 }
14202 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14203 let text_layout_details = &self.text_layout_details(window);
14204 self.transact(window, cx, |this, window, cx| {
14205 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
14206 let mut edits = Vec::new();
14207 let mut selection_edit_ranges = Vec::new();
14208 let mut last_toggled_row = None;
14209 let snapshot = this.buffer.read(cx).read(cx);
14210 let empty_str: Arc<str> = Arc::default();
14211 let mut suffixes_inserted = Vec::new();
14212 let ignore_indent = action.ignore_indent;
14213
14214 fn comment_prefix_range(
14215 snapshot: &MultiBufferSnapshot,
14216 row: MultiBufferRow,
14217 comment_prefix: &str,
14218 comment_prefix_whitespace: &str,
14219 ignore_indent: bool,
14220 ) -> Range<Point> {
14221 let indent_size = if ignore_indent {
14222 0
14223 } else {
14224 snapshot.indent_size_for_line(row).len
14225 };
14226
14227 let start = Point::new(row.0, indent_size);
14228
14229 let mut line_bytes = snapshot
14230 .bytes_in_range(start..snapshot.max_point())
14231 .flatten()
14232 .copied();
14233
14234 // If this line currently begins with the line comment prefix, then record
14235 // the range containing the prefix.
14236 if line_bytes
14237 .by_ref()
14238 .take(comment_prefix.len())
14239 .eq(comment_prefix.bytes())
14240 {
14241 // Include any whitespace that matches the comment prefix.
14242 let matching_whitespace_len = line_bytes
14243 .zip(comment_prefix_whitespace.bytes())
14244 .take_while(|(a, b)| a == b)
14245 .count() as u32;
14246 let end = Point::new(
14247 start.row,
14248 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14249 );
14250 start..end
14251 } else {
14252 start..start
14253 }
14254 }
14255
14256 fn comment_suffix_range(
14257 snapshot: &MultiBufferSnapshot,
14258 row: MultiBufferRow,
14259 comment_suffix: &str,
14260 comment_suffix_has_leading_space: bool,
14261 ) -> Range<Point> {
14262 let end = Point::new(row.0, snapshot.line_len(row));
14263 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14264
14265 let mut line_end_bytes = snapshot
14266 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14267 .flatten()
14268 .copied();
14269
14270 let leading_space_len = if suffix_start_column > 0
14271 && line_end_bytes.next() == Some(b' ')
14272 && comment_suffix_has_leading_space
14273 {
14274 1
14275 } else {
14276 0
14277 };
14278
14279 // If this line currently begins with the line comment prefix, then record
14280 // the range containing the prefix.
14281 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
14282 let start = Point::new(end.row, suffix_start_column - leading_space_len);
14283 start..end
14284 } else {
14285 end..end
14286 }
14287 }
14288
14289 // TODO: Handle selections that cross excerpts
14290 for selection in &mut selections {
14291 let start_column = snapshot
14292 .indent_size_for_line(MultiBufferRow(selection.start.row))
14293 .len;
14294 let language = if let Some(language) =
14295 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
14296 {
14297 language
14298 } else {
14299 continue;
14300 };
14301
14302 selection_edit_ranges.clear();
14303
14304 // If multiple selections contain a given row, avoid processing that
14305 // row more than once.
14306 let mut start_row = MultiBufferRow(selection.start.row);
14307 if last_toggled_row == Some(start_row) {
14308 start_row = start_row.next_row();
14309 }
14310 let end_row =
14311 if selection.end.row > selection.start.row && selection.end.column == 0 {
14312 MultiBufferRow(selection.end.row - 1)
14313 } else {
14314 MultiBufferRow(selection.end.row)
14315 };
14316 last_toggled_row = Some(end_row);
14317
14318 if start_row > end_row {
14319 continue;
14320 }
14321
14322 // If the language has line comments, toggle those.
14323 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
14324
14325 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
14326 if ignore_indent {
14327 full_comment_prefixes = full_comment_prefixes
14328 .into_iter()
14329 .map(|s| Arc::from(s.trim_end()))
14330 .collect();
14331 }
14332
14333 if !full_comment_prefixes.is_empty() {
14334 let first_prefix = full_comment_prefixes
14335 .first()
14336 .expect("prefixes is non-empty");
14337 let prefix_trimmed_lengths = full_comment_prefixes
14338 .iter()
14339 .map(|p| p.trim_end_matches(' ').len())
14340 .collect::<SmallVec<[usize; 4]>>();
14341
14342 let mut all_selection_lines_are_comments = true;
14343
14344 for row in start_row.0..=end_row.0 {
14345 let row = MultiBufferRow(row);
14346 if start_row < end_row && snapshot.is_line_blank(row) {
14347 continue;
14348 }
14349
14350 let prefix_range = full_comment_prefixes
14351 .iter()
14352 .zip(prefix_trimmed_lengths.iter().copied())
14353 .map(|(prefix, trimmed_prefix_len)| {
14354 comment_prefix_range(
14355 snapshot.deref(),
14356 row,
14357 &prefix[..trimmed_prefix_len],
14358 &prefix[trimmed_prefix_len..],
14359 ignore_indent,
14360 )
14361 })
14362 .max_by_key(|range| range.end.column - range.start.column)
14363 .expect("prefixes is non-empty");
14364
14365 if prefix_range.is_empty() {
14366 all_selection_lines_are_comments = false;
14367 }
14368
14369 selection_edit_ranges.push(prefix_range);
14370 }
14371
14372 if all_selection_lines_are_comments {
14373 edits.extend(
14374 selection_edit_ranges
14375 .iter()
14376 .cloned()
14377 .map(|range| (range, empty_str.clone())),
14378 );
14379 } else {
14380 let min_column = selection_edit_ranges
14381 .iter()
14382 .map(|range| range.start.column)
14383 .min()
14384 .unwrap_or(0);
14385 edits.extend(selection_edit_ranges.iter().map(|range| {
14386 let position = Point::new(range.start.row, min_column);
14387 (position..position, first_prefix.clone())
14388 }));
14389 }
14390 } else if let Some(BlockCommentConfig {
14391 start: full_comment_prefix,
14392 end: comment_suffix,
14393 ..
14394 }) = language.block_comment()
14395 {
14396 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
14397 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
14398 let prefix_range = comment_prefix_range(
14399 snapshot.deref(),
14400 start_row,
14401 comment_prefix,
14402 comment_prefix_whitespace,
14403 ignore_indent,
14404 );
14405 let suffix_range = comment_suffix_range(
14406 snapshot.deref(),
14407 end_row,
14408 comment_suffix.trim_start_matches(' '),
14409 comment_suffix.starts_with(' '),
14410 );
14411
14412 if prefix_range.is_empty() || suffix_range.is_empty() {
14413 edits.push((
14414 prefix_range.start..prefix_range.start,
14415 full_comment_prefix.clone(),
14416 ));
14417 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
14418 suffixes_inserted.push((end_row, comment_suffix.len()));
14419 } else {
14420 edits.push((prefix_range, empty_str.clone()));
14421 edits.push((suffix_range, empty_str.clone()));
14422 }
14423 } else {
14424 continue;
14425 }
14426 }
14427
14428 drop(snapshot);
14429 this.buffer.update(cx, |buffer, cx| {
14430 buffer.edit(edits, None, cx);
14431 });
14432
14433 // Adjust selections so that they end before any comment suffixes that
14434 // were inserted.
14435 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
14436 let mut selections = this.selections.all::<Point>(cx);
14437 let snapshot = this.buffer.read(cx).read(cx);
14438 for selection in &mut selections {
14439 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
14440 match row.cmp(&MultiBufferRow(selection.end.row)) {
14441 Ordering::Less => {
14442 suffixes_inserted.next();
14443 continue;
14444 }
14445 Ordering::Greater => break,
14446 Ordering::Equal => {
14447 if selection.end.column == snapshot.line_len(row) {
14448 if selection.is_empty() {
14449 selection.start.column -= suffix_len as u32;
14450 }
14451 selection.end.column -= suffix_len as u32;
14452 }
14453 break;
14454 }
14455 }
14456 }
14457 }
14458
14459 drop(snapshot);
14460 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
14461
14462 let selections = this.selections.all::<Point>(cx);
14463 let selections_on_single_row = selections.windows(2).all(|selections| {
14464 selections[0].start.row == selections[1].start.row
14465 && selections[0].end.row == selections[1].end.row
14466 && selections[0].start.row == selections[0].end.row
14467 });
14468 let selections_selecting = selections
14469 .iter()
14470 .any(|selection| selection.start != selection.end);
14471 let advance_downwards = action.advance_downwards
14472 && selections_on_single_row
14473 && !selections_selecting
14474 && !matches!(this.mode, EditorMode::SingleLine { .. });
14475
14476 if advance_downwards {
14477 let snapshot = this.buffer.read(cx).snapshot(cx);
14478
14479 this.change_selections(Default::default(), window, cx, |s| {
14480 s.move_cursors_with(|display_snapshot, display_point, _| {
14481 let mut point = display_point.to_point(display_snapshot);
14482 point.row += 1;
14483 point = snapshot.clip_point(point, Bias::Left);
14484 let display_point = point.to_display_point(display_snapshot);
14485 let goal = SelectionGoal::HorizontalPosition(
14486 display_snapshot
14487 .x_for_display_point(display_point, text_layout_details)
14488 .into(),
14489 );
14490 (display_point, goal)
14491 })
14492 });
14493 }
14494 });
14495 }
14496
14497 pub fn select_enclosing_symbol(
14498 &mut self,
14499 _: &SelectEnclosingSymbol,
14500 window: &mut Window,
14501 cx: &mut Context<Self>,
14502 ) {
14503 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14504
14505 let buffer = self.buffer.read(cx).snapshot(cx);
14506 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
14507
14508 fn update_selection(
14509 selection: &Selection<usize>,
14510 buffer_snap: &MultiBufferSnapshot,
14511 ) -> Option<Selection<usize>> {
14512 let cursor = selection.head();
14513 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
14514 for symbol in symbols.iter().rev() {
14515 let start = symbol.range.start.to_offset(buffer_snap);
14516 let end = symbol.range.end.to_offset(buffer_snap);
14517 let new_range = start..end;
14518 if start < selection.start || end > selection.end {
14519 return Some(Selection {
14520 id: selection.id,
14521 start: new_range.start,
14522 end: new_range.end,
14523 goal: SelectionGoal::None,
14524 reversed: selection.reversed,
14525 });
14526 }
14527 }
14528 None
14529 }
14530
14531 let mut selected_larger_symbol = false;
14532 let new_selections = old_selections
14533 .iter()
14534 .map(|selection| match update_selection(selection, &buffer) {
14535 Some(new_selection) => {
14536 if new_selection.range() != selection.range() {
14537 selected_larger_symbol = true;
14538 }
14539 new_selection
14540 }
14541 None => selection.clone(),
14542 })
14543 .collect::<Vec<_>>();
14544
14545 if selected_larger_symbol {
14546 self.change_selections(Default::default(), window, cx, |s| {
14547 s.select(new_selections);
14548 });
14549 }
14550 }
14551
14552 pub fn select_larger_syntax_node(
14553 &mut self,
14554 _: &SelectLargerSyntaxNode,
14555 window: &mut Window,
14556 cx: &mut Context<Self>,
14557 ) {
14558 let Some(visible_row_count) = self.visible_row_count() else {
14559 return;
14560 };
14561 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
14562 if old_selections.is_empty() {
14563 return;
14564 }
14565
14566 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14567
14568 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14569 let buffer = self.buffer.read(cx).snapshot(cx);
14570
14571 let mut selected_larger_node = false;
14572 let mut new_selections = old_selections
14573 .iter()
14574 .map(|selection| {
14575 let old_range = selection.start..selection.end;
14576
14577 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
14578 // manually select word at selection
14579 if ["string_content", "inline"].contains(&node.kind()) {
14580 let (word_range, _) = buffer.surrounding_word(old_range.start, false);
14581 // ignore if word is already selected
14582 if !word_range.is_empty() && old_range != word_range {
14583 let (last_word_range, _) =
14584 buffer.surrounding_word(old_range.end, false);
14585 // only select word if start and end point belongs to same word
14586 if word_range == last_word_range {
14587 selected_larger_node = true;
14588 return Selection {
14589 id: selection.id,
14590 start: word_range.start,
14591 end: word_range.end,
14592 goal: SelectionGoal::None,
14593 reversed: selection.reversed,
14594 };
14595 }
14596 }
14597 }
14598 }
14599
14600 let mut new_range = old_range.clone();
14601 while let Some((_node, containing_range)) =
14602 buffer.syntax_ancestor(new_range.clone())
14603 {
14604 new_range = match containing_range {
14605 MultiOrSingleBufferOffsetRange::Single(_) => break,
14606 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14607 };
14608 if !display_map.intersects_fold(new_range.start)
14609 && !display_map.intersects_fold(new_range.end)
14610 {
14611 break;
14612 }
14613 }
14614
14615 selected_larger_node |= new_range != old_range;
14616 Selection {
14617 id: selection.id,
14618 start: new_range.start,
14619 end: new_range.end,
14620 goal: SelectionGoal::None,
14621 reversed: selection.reversed,
14622 }
14623 })
14624 .collect::<Vec<_>>();
14625
14626 if !selected_larger_node {
14627 return; // don't put this call in the history
14628 }
14629
14630 // scroll based on transformation done to the last selection created by the user
14631 let (last_old, last_new) = old_selections
14632 .last()
14633 .zip(new_selections.last().cloned())
14634 .expect("old_selections isn't empty");
14635
14636 // revert selection
14637 let is_selection_reversed = {
14638 let should_newest_selection_be_reversed = last_old.start != last_new.start;
14639 new_selections.last_mut().expect("checked above").reversed =
14640 should_newest_selection_be_reversed;
14641 should_newest_selection_be_reversed
14642 };
14643
14644 if selected_larger_node {
14645 self.select_syntax_node_history.disable_clearing = true;
14646 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14647 s.select(new_selections.clone());
14648 });
14649 self.select_syntax_node_history.disable_clearing = false;
14650 }
14651
14652 let start_row = last_new.start.to_display_point(&display_map).row().0;
14653 let end_row = last_new.end.to_display_point(&display_map).row().0;
14654 let selection_height = end_row - start_row + 1;
14655 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
14656
14657 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
14658 let scroll_behavior = if fits_on_the_screen {
14659 self.request_autoscroll(Autoscroll::fit(), cx);
14660 SelectSyntaxNodeScrollBehavior::FitSelection
14661 } else if is_selection_reversed {
14662 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14663 SelectSyntaxNodeScrollBehavior::CursorTop
14664 } else {
14665 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14666 SelectSyntaxNodeScrollBehavior::CursorBottom
14667 };
14668
14669 self.select_syntax_node_history.push((
14670 old_selections,
14671 scroll_behavior,
14672 is_selection_reversed,
14673 ));
14674 }
14675
14676 pub fn select_smaller_syntax_node(
14677 &mut self,
14678 _: &SelectSmallerSyntaxNode,
14679 window: &mut Window,
14680 cx: &mut Context<Self>,
14681 ) {
14682 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14683
14684 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
14685 self.select_syntax_node_history.pop()
14686 {
14687 if let Some(selection) = selections.last_mut() {
14688 selection.reversed = is_selection_reversed;
14689 }
14690
14691 self.select_syntax_node_history.disable_clearing = true;
14692 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14693 s.select(selections.to_vec());
14694 });
14695 self.select_syntax_node_history.disable_clearing = false;
14696
14697 match scroll_behavior {
14698 SelectSyntaxNodeScrollBehavior::CursorTop => {
14699 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14700 }
14701 SelectSyntaxNodeScrollBehavior::FitSelection => {
14702 self.request_autoscroll(Autoscroll::fit(), cx);
14703 }
14704 SelectSyntaxNodeScrollBehavior::CursorBottom => {
14705 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14706 }
14707 }
14708 }
14709 }
14710
14711 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
14712 if !EditorSettings::get_global(cx).gutter.runnables {
14713 self.clear_tasks();
14714 return Task::ready(());
14715 }
14716 let project = self.project.as_ref().map(Entity::downgrade);
14717 let task_sources = self.lsp_task_sources(cx);
14718 let multi_buffer = self.buffer.downgrade();
14719 cx.spawn_in(window, async move |editor, cx| {
14720 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
14721 let Some(project) = project.and_then(|p| p.upgrade()) else {
14722 return;
14723 };
14724 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
14725 this.display_map.update(cx, |map, cx| map.snapshot(cx))
14726 }) else {
14727 return;
14728 };
14729
14730 let hide_runnables = project
14731 .update(cx, |project, cx| {
14732 // Do not display any test indicators in non-dev server remote projects.
14733 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
14734 })
14735 .unwrap_or(true);
14736 if hide_runnables {
14737 return;
14738 }
14739 let new_rows =
14740 cx.background_spawn({
14741 let snapshot = display_snapshot.clone();
14742 async move {
14743 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
14744 }
14745 })
14746 .await;
14747 let Ok(lsp_tasks) =
14748 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
14749 else {
14750 return;
14751 };
14752 let lsp_tasks = lsp_tasks.await;
14753
14754 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
14755 lsp_tasks
14756 .into_iter()
14757 .flat_map(|(kind, tasks)| {
14758 tasks.into_iter().filter_map(move |(location, task)| {
14759 Some((kind.clone(), location?, task))
14760 })
14761 })
14762 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
14763 let buffer = location.target.buffer;
14764 let buffer_snapshot = buffer.read(cx).snapshot();
14765 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
14766 |(excerpt_id, snapshot, _)| {
14767 if snapshot.remote_id() == buffer_snapshot.remote_id() {
14768 display_snapshot
14769 .buffer_snapshot
14770 .anchor_in_excerpt(excerpt_id, location.target.range.start)
14771 } else {
14772 None
14773 }
14774 },
14775 );
14776 if let Some(offset) = offset {
14777 let task_buffer_range =
14778 location.target.range.to_point(&buffer_snapshot);
14779 let context_buffer_range =
14780 task_buffer_range.to_offset(&buffer_snapshot);
14781 let context_range = BufferOffset(context_buffer_range.start)
14782 ..BufferOffset(context_buffer_range.end);
14783
14784 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
14785 .or_insert_with(|| RunnableTasks {
14786 templates: Vec::new(),
14787 offset,
14788 column: task_buffer_range.start.column,
14789 extra_variables: HashMap::default(),
14790 context_range,
14791 })
14792 .templates
14793 .push((kind, task.original_task().clone()));
14794 }
14795
14796 acc
14797 })
14798 }) else {
14799 return;
14800 };
14801
14802 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
14803 buffer.language_settings(cx).tasks.prefer_lsp
14804 }) else {
14805 return;
14806 };
14807
14808 let rows = Self::runnable_rows(
14809 project,
14810 display_snapshot,
14811 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
14812 new_rows,
14813 cx.clone(),
14814 )
14815 .await;
14816 editor
14817 .update(cx, |editor, _| {
14818 editor.clear_tasks();
14819 for (key, mut value) in rows {
14820 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
14821 value.templates.extend(lsp_tasks.templates);
14822 }
14823
14824 editor.insert_tasks(key, value);
14825 }
14826 for (key, value) in lsp_tasks_by_rows {
14827 editor.insert_tasks(key, value);
14828 }
14829 })
14830 .ok();
14831 })
14832 }
14833 fn fetch_runnable_ranges(
14834 snapshot: &DisplaySnapshot,
14835 range: Range<Anchor>,
14836 ) -> Vec<language::RunnableRange> {
14837 snapshot.buffer_snapshot.runnable_ranges(range).collect()
14838 }
14839
14840 fn runnable_rows(
14841 project: Entity<Project>,
14842 snapshot: DisplaySnapshot,
14843 prefer_lsp: bool,
14844 runnable_ranges: Vec<RunnableRange>,
14845 cx: AsyncWindowContext,
14846 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
14847 cx.spawn(async move |cx| {
14848 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
14849 for mut runnable in runnable_ranges {
14850 let Some(tasks) = cx
14851 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
14852 .ok()
14853 else {
14854 continue;
14855 };
14856 let mut tasks = tasks.await;
14857
14858 if prefer_lsp {
14859 tasks.retain(|(task_kind, _)| {
14860 !matches!(task_kind, TaskSourceKind::Language { .. })
14861 });
14862 }
14863 if tasks.is_empty() {
14864 continue;
14865 }
14866
14867 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
14868 let Some(row) = snapshot
14869 .buffer_snapshot
14870 .buffer_line_for_row(MultiBufferRow(point.row))
14871 .map(|(_, range)| range.start.row)
14872 else {
14873 continue;
14874 };
14875
14876 let context_range =
14877 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
14878 runnable_rows.push((
14879 (runnable.buffer_id, row),
14880 RunnableTasks {
14881 templates: tasks,
14882 offset: snapshot
14883 .buffer_snapshot
14884 .anchor_before(runnable.run_range.start),
14885 context_range,
14886 column: point.column,
14887 extra_variables: runnable.extra_captures,
14888 },
14889 ));
14890 }
14891 runnable_rows
14892 })
14893 }
14894
14895 fn templates_with_tags(
14896 project: &Entity<Project>,
14897 runnable: &mut Runnable,
14898 cx: &mut App,
14899 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
14900 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
14901 let (worktree_id, file) = project
14902 .buffer_for_id(runnable.buffer, cx)
14903 .and_then(|buffer| buffer.read(cx).file())
14904 .map(|file| (file.worktree_id(cx), file.clone()))
14905 .unzip();
14906
14907 (
14908 project.task_store().read(cx).task_inventory().cloned(),
14909 worktree_id,
14910 file,
14911 )
14912 });
14913
14914 let tags = mem::take(&mut runnable.tags);
14915 let language = runnable.language.clone();
14916 cx.spawn(async move |cx| {
14917 let mut templates_with_tags = Vec::new();
14918 if let Some(inventory) = inventory {
14919 for RunnableTag(tag) in tags {
14920 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
14921 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
14922 }) else {
14923 return templates_with_tags;
14924 };
14925 templates_with_tags.extend(new_tasks.await.into_iter().filter(
14926 move |(_, template)| {
14927 template.tags.iter().any(|source_tag| source_tag == &tag)
14928 },
14929 ));
14930 }
14931 }
14932 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
14933
14934 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
14935 // Strongest source wins; if we have worktree tag binding, prefer that to
14936 // global and language bindings;
14937 // if we have a global binding, prefer that to language binding.
14938 let first_mismatch = templates_with_tags
14939 .iter()
14940 .position(|(tag_source, _)| tag_source != leading_tag_source);
14941 if let Some(index) = first_mismatch {
14942 templates_with_tags.truncate(index);
14943 }
14944 }
14945
14946 templates_with_tags
14947 })
14948 }
14949
14950 pub fn move_to_enclosing_bracket(
14951 &mut self,
14952 _: &MoveToEnclosingBracket,
14953 window: &mut Window,
14954 cx: &mut Context<Self>,
14955 ) {
14956 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14957 self.change_selections(Default::default(), window, cx, |s| {
14958 s.move_offsets_with(|snapshot, selection| {
14959 let Some(enclosing_bracket_ranges) =
14960 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
14961 else {
14962 return;
14963 };
14964
14965 let mut best_length = usize::MAX;
14966 let mut best_inside = false;
14967 let mut best_in_bracket_range = false;
14968 let mut best_destination = None;
14969 for (open, close) in enclosing_bracket_ranges {
14970 let close = close.to_inclusive();
14971 let length = close.end() - open.start;
14972 let inside = selection.start >= open.end && selection.end <= *close.start();
14973 let in_bracket_range = open.to_inclusive().contains(&selection.head())
14974 || close.contains(&selection.head());
14975
14976 // If best is next to a bracket and current isn't, skip
14977 if !in_bracket_range && best_in_bracket_range {
14978 continue;
14979 }
14980
14981 // Prefer smaller lengths unless best is inside and current isn't
14982 if length > best_length && (best_inside || !inside) {
14983 continue;
14984 }
14985
14986 best_length = length;
14987 best_inside = inside;
14988 best_in_bracket_range = in_bracket_range;
14989 best_destination = Some(
14990 if close.contains(&selection.start) && close.contains(&selection.end) {
14991 if inside { open.end } else { open.start }
14992 } else if inside {
14993 *close.start()
14994 } else {
14995 *close.end()
14996 },
14997 );
14998 }
14999
15000 if let Some(destination) = best_destination {
15001 selection.collapse_to(destination, SelectionGoal::None);
15002 }
15003 })
15004 });
15005 }
15006
15007 pub fn undo_selection(
15008 &mut self,
15009 _: &UndoSelection,
15010 window: &mut Window,
15011 cx: &mut Context<Self>,
15012 ) {
15013 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15014 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
15015 self.selection_history.mode = SelectionHistoryMode::Undoing;
15016 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15017 this.end_selection(window, cx);
15018 this.change_selections(
15019 SelectionEffects::scroll(Autoscroll::newest()),
15020 window,
15021 cx,
15022 |s| s.select_anchors(entry.selections.to_vec()),
15023 );
15024 });
15025 self.selection_history.mode = SelectionHistoryMode::Normal;
15026
15027 self.select_next_state = entry.select_next_state;
15028 self.select_prev_state = entry.select_prev_state;
15029 self.add_selections_state = entry.add_selections_state;
15030 }
15031 }
15032
15033 pub fn redo_selection(
15034 &mut self,
15035 _: &RedoSelection,
15036 window: &mut Window,
15037 cx: &mut Context<Self>,
15038 ) {
15039 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15040 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
15041 self.selection_history.mode = SelectionHistoryMode::Redoing;
15042 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15043 this.end_selection(window, cx);
15044 this.change_selections(
15045 SelectionEffects::scroll(Autoscroll::newest()),
15046 window,
15047 cx,
15048 |s| s.select_anchors(entry.selections.to_vec()),
15049 );
15050 });
15051 self.selection_history.mode = SelectionHistoryMode::Normal;
15052
15053 self.select_next_state = entry.select_next_state;
15054 self.select_prev_state = entry.select_prev_state;
15055 self.add_selections_state = entry.add_selections_state;
15056 }
15057 }
15058
15059 pub fn expand_excerpts(
15060 &mut self,
15061 action: &ExpandExcerpts,
15062 _: &mut Window,
15063 cx: &mut Context<Self>,
15064 ) {
15065 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
15066 }
15067
15068 pub fn expand_excerpts_down(
15069 &mut self,
15070 action: &ExpandExcerptsDown,
15071 _: &mut Window,
15072 cx: &mut Context<Self>,
15073 ) {
15074 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
15075 }
15076
15077 pub fn expand_excerpts_up(
15078 &mut self,
15079 action: &ExpandExcerptsUp,
15080 _: &mut Window,
15081 cx: &mut Context<Self>,
15082 ) {
15083 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
15084 }
15085
15086 pub fn expand_excerpts_for_direction(
15087 &mut self,
15088 lines: u32,
15089 direction: ExpandExcerptDirection,
15090
15091 cx: &mut Context<Self>,
15092 ) {
15093 let selections = self.selections.disjoint_anchors();
15094
15095 let lines = if lines == 0 {
15096 EditorSettings::get_global(cx).expand_excerpt_lines
15097 } else {
15098 lines
15099 };
15100
15101 self.buffer.update(cx, |buffer, cx| {
15102 let snapshot = buffer.snapshot(cx);
15103 let mut excerpt_ids = selections
15104 .iter()
15105 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
15106 .collect::<Vec<_>>();
15107 excerpt_ids.sort();
15108 excerpt_ids.dedup();
15109 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
15110 })
15111 }
15112
15113 pub fn expand_excerpt(
15114 &mut self,
15115 excerpt: ExcerptId,
15116 direction: ExpandExcerptDirection,
15117 window: &mut Window,
15118 cx: &mut Context<Self>,
15119 ) {
15120 let current_scroll_position = self.scroll_position(cx);
15121 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
15122 let mut should_scroll_up = false;
15123
15124 if direction == ExpandExcerptDirection::Down {
15125 let multi_buffer = self.buffer.read(cx);
15126 let snapshot = multi_buffer.snapshot(cx);
15127 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
15128 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
15129 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
15130 let buffer_snapshot = buffer.read(cx).snapshot();
15131 let excerpt_end_row =
15132 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
15133 let last_row = buffer_snapshot.max_point().row;
15134 let lines_below = last_row.saturating_sub(excerpt_end_row);
15135 should_scroll_up = lines_below >= lines_to_expand;
15136 }
15137 }
15138 }
15139 }
15140
15141 self.buffer.update(cx, |buffer, cx| {
15142 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
15143 });
15144
15145 if should_scroll_up {
15146 let new_scroll_position =
15147 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
15148 self.set_scroll_position(new_scroll_position, window, cx);
15149 }
15150 }
15151
15152 pub fn go_to_singleton_buffer_point(
15153 &mut self,
15154 point: Point,
15155 window: &mut Window,
15156 cx: &mut Context<Self>,
15157 ) {
15158 self.go_to_singleton_buffer_range(point..point, window, cx);
15159 }
15160
15161 pub fn go_to_singleton_buffer_range(
15162 &mut self,
15163 range: Range<Point>,
15164 window: &mut Window,
15165 cx: &mut Context<Self>,
15166 ) {
15167 let multibuffer = self.buffer().read(cx);
15168 let Some(buffer) = multibuffer.as_singleton() else {
15169 return;
15170 };
15171 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
15172 return;
15173 };
15174 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
15175 return;
15176 };
15177 self.change_selections(
15178 SelectionEffects::default().nav_history(true),
15179 window,
15180 cx,
15181 |s| s.select_anchor_ranges([start..end]),
15182 );
15183 }
15184
15185 pub fn go_to_diagnostic(
15186 &mut self,
15187 action: &GoToDiagnostic,
15188 window: &mut Window,
15189 cx: &mut Context<Self>,
15190 ) {
15191 if !self.diagnostics_enabled() {
15192 return;
15193 }
15194 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15195 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
15196 }
15197
15198 pub fn go_to_prev_diagnostic(
15199 &mut self,
15200 action: &GoToPreviousDiagnostic,
15201 window: &mut Window,
15202 cx: &mut Context<Self>,
15203 ) {
15204 if !self.diagnostics_enabled() {
15205 return;
15206 }
15207 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15208 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
15209 }
15210
15211 pub fn go_to_diagnostic_impl(
15212 &mut self,
15213 direction: Direction,
15214 severity: GoToDiagnosticSeverityFilter,
15215 window: &mut Window,
15216 cx: &mut Context<Self>,
15217 ) {
15218 let buffer = self.buffer.read(cx).snapshot(cx);
15219 let selection = self.selections.newest::<usize>(cx);
15220
15221 let mut active_group_id = None;
15222 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
15223 if active_group.active_range.start.to_offset(&buffer) == selection.start {
15224 active_group_id = Some(active_group.group_id);
15225 }
15226 }
15227
15228 fn filtered(
15229 snapshot: EditorSnapshot,
15230 severity: GoToDiagnosticSeverityFilter,
15231 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
15232 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
15233 diagnostics
15234 .filter(move |entry| severity.matches(entry.diagnostic.severity))
15235 .filter(|entry| entry.range.start != entry.range.end)
15236 .filter(|entry| !entry.diagnostic.is_unnecessary)
15237 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
15238 }
15239
15240 let snapshot = self.snapshot(window, cx);
15241 let before = filtered(
15242 snapshot.clone(),
15243 severity,
15244 buffer
15245 .diagnostics_in_range(0..selection.start)
15246 .filter(|entry| entry.range.start <= selection.start),
15247 );
15248 let after = filtered(
15249 snapshot,
15250 severity,
15251 buffer
15252 .diagnostics_in_range(selection.start..buffer.len())
15253 .filter(|entry| entry.range.start >= selection.start),
15254 );
15255
15256 let mut found: Option<DiagnosticEntry<usize>> = None;
15257 if direction == Direction::Prev {
15258 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
15259 {
15260 for diagnostic in prev_diagnostics.into_iter().rev() {
15261 if diagnostic.range.start != selection.start
15262 || active_group_id
15263 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
15264 {
15265 found = Some(diagnostic);
15266 break 'outer;
15267 }
15268 }
15269 }
15270 } else {
15271 for diagnostic in after.chain(before) {
15272 if diagnostic.range.start != selection.start
15273 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
15274 {
15275 found = Some(diagnostic);
15276 break;
15277 }
15278 }
15279 }
15280 let Some(next_diagnostic) = found else {
15281 return;
15282 };
15283
15284 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
15285 return;
15286 };
15287 self.change_selections(Default::default(), window, cx, |s| {
15288 s.select_ranges(vec![
15289 next_diagnostic.range.start..next_diagnostic.range.start,
15290 ])
15291 });
15292 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
15293 self.refresh_inline_completion(false, true, window, cx);
15294 }
15295
15296 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
15297 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15298 let snapshot = self.snapshot(window, cx);
15299 let selection = self.selections.newest::<Point>(cx);
15300 self.go_to_hunk_before_or_after_position(
15301 &snapshot,
15302 selection.head(),
15303 Direction::Next,
15304 window,
15305 cx,
15306 );
15307 }
15308
15309 pub fn go_to_hunk_before_or_after_position(
15310 &mut self,
15311 snapshot: &EditorSnapshot,
15312 position: Point,
15313 direction: Direction,
15314 window: &mut Window,
15315 cx: &mut Context<Editor>,
15316 ) {
15317 let row = if direction == Direction::Next {
15318 self.hunk_after_position(snapshot, position)
15319 .map(|hunk| hunk.row_range.start)
15320 } else {
15321 self.hunk_before_position(snapshot, position)
15322 };
15323
15324 if let Some(row) = row {
15325 let destination = Point::new(row.0, 0);
15326 let autoscroll = Autoscroll::center();
15327
15328 self.unfold_ranges(&[destination..destination], false, false, cx);
15329 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
15330 s.select_ranges([destination..destination]);
15331 });
15332 }
15333 }
15334
15335 fn hunk_after_position(
15336 &mut self,
15337 snapshot: &EditorSnapshot,
15338 position: Point,
15339 ) -> Option<MultiBufferDiffHunk> {
15340 snapshot
15341 .buffer_snapshot
15342 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
15343 .find(|hunk| hunk.row_range.start.0 > position.row)
15344 .or_else(|| {
15345 snapshot
15346 .buffer_snapshot
15347 .diff_hunks_in_range(Point::zero()..position)
15348 .find(|hunk| hunk.row_range.end.0 < position.row)
15349 })
15350 }
15351
15352 fn go_to_prev_hunk(
15353 &mut self,
15354 _: &GoToPreviousHunk,
15355 window: &mut Window,
15356 cx: &mut Context<Self>,
15357 ) {
15358 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15359 let snapshot = self.snapshot(window, cx);
15360 let selection = self.selections.newest::<Point>(cx);
15361 self.go_to_hunk_before_or_after_position(
15362 &snapshot,
15363 selection.head(),
15364 Direction::Prev,
15365 window,
15366 cx,
15367 );
15368 }
15369
15370 fn hunk_before_position(
15371 &mut self,
15372 snapshot: &EditorSnapshot,
15373 position: Point,
15374 ) -> Option<MultiBufferRow> {
15375 snapshot
15376 .buffer_snapshot
15377 .diff_hunk_before(position)
15378 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
15379 }
15380
15381 fn go_to_next_change(
15382 &mut self,
15383 _: &GoToNextChange,
15384 window: &mut Window,
15385 cx: &mut Context<Self>,
15386 ) {
15387 if let Some(selections) = self
15388 .change_list
15389 .next_change(1, Direction::Next)
15390 .map(|s| s.to_vec())
15391 {
15392 self.change_selections(Default::default(), window, cx, |s| {
15393 let map = s.display_map();
15394 s.select_display_ranges(selections.iter().map(|a| {
15395 let point = a.to_display_point(&map);
15396 point..point
15397 }))
15398 })
15399 }
15400 }
15401
15402 fn go_to_previous_change(
15403 &mut self,
15404 _: &GoToPreviousChange,
15405 window: &mut Window,
15406 cx: &mut Context<Self>,
15407 ) {
15408 if let Some(selections) = self
15409 .change_list
15410 .next_change(1, Direction::Prev)
15411 .map(|s| s.to_vec())
15412 {
15413 self.change_selections(Default::default(), window, cx, |s| {
15414 let map = s.display_map();
15415 s.select_display_ranges(selections.iter().map(|a| {
15416 let point = a.to_display_point(&map);
15417 point..point
15418 }))
15419 })
15420 }
15421 }
15422
15423 fn go_to_line<T: 'static>(
15424 &mut self,
15425 position: Anchor,
15426 highlight_color: Option<Hsla>,
15427 window: &mut Window,
15428 cx: &mut Context<Self>,
15429 ) {
15430 let snapshot = self.snapshot(window, cx).display_snapshot;
15431 let position = position.to_point(&snapshot.buffer_snapshot);
15432 let start = snapshot
15433 .buffer_snapshot
15434 .clip_point(Point::new(position.row, 0), Bias::Left);
15435 let end = start + Point::new(1, 0);
15436 let start = snapshot.buffer_snapshot.anchor_before(start);
15437 let end = snapshot.buffer_snapshot.anchor_before(end);
15438
15439 self.highlight_rows::<T>(
15440 start..end,
15441 highlight_color
15442 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
15443 Default::default(),
15444 cx,
15445 );
15446
15447 if self.buffer.read(cx).is_singleton() {
15448 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
15449 }
15450 }
15451
15452 pub fn go_to_definition(
15453 &mut self,
15454 _: &GoToDefinition,
15455 window: &mut Window,
15456 cx: &mut Context<Self>,
15457 ) -> Task<Result<Navigated>> {
15458 let definition =
15459 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
15460 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
15461 cx.spawn_in(window, async move |editor, cx| {
15462 if definition.await? == Navigated::Yes {
15463 return Ok(Navigated::Yes);
15464 }
15465 match fallback_strategy {
15466 GoToDefinitionFallback::None => Ok(Navigated::No),
15467 GoToDefinitionFallback::FindAllReferences => {
15468 match editor.update_in(cx, |editor, window, cx| {
15469 editor.find_all_references(&FindAllReferences, window, cx)
15470 })? {
15471 Some(references) => references.await,
15472 None => Ok(Navigated::No),
15473 }
15474 }
15475 }
15476 })
15477 }
15478
15479 pub fn go_to_declaration(
15480 &mut self,
15481 _: &GoToDeclaration,
15482 window: &mut Window,
15483 cx: &mut Context<Self>,
15484 ) -> Task<Result<Navigated>> {
15485 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
15486 }
15487
15488 pub fn go_to_declaration_split(
15489 &mut self,
15490 _: &GoToDeclaration,
15491 window: &mut Window,
15492 cx: &mut Context<Self>,
15493 ) -> Task<Result<Navigated>> {
15494 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
15495 }
15496
15497 pub fn go_to_implementation(
15498 &mut self,
15499 _: &GoToImplementation,
15500 window: &mut Window,
15501 cx: &mut Context<Self>,
15502 ) -> Task<Result<Navigated>> {
15503 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
15504 }
15505
15506 pub fn go_to_implementation_split(
15507 &mut self,
15508 _: &GoToImplementationSplit,
15509 window: &mut Window,
15510 cx: &mut Context<Self>,
15511 ) -> Task<Result<Navigated>> {
15512 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
15513 }
15514
15515 pub fn go_to_type_definition(
15516 &mut self,
15517 _: &GoToTypeDefinition,
15518 window: &mut Window,
15519 cx: &mut Context<Self>,
15520 ) -> Task<Result<Navigated>> {
15521 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
15522 }
15523
15524 pub fn go_to_definition_split(
15525 &mut self,
15526 _: &GoToDefinitionSplit,
15527 window: &mut Window,
15528 cx: &mut Context<Self>,
15529 ) -> Task<Result<Navigated>> {
15530 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
15531 }
15532
15533 pub fn go_to_type_definition_split(
15534 &mut self,
15535 _: &GoToTypeDefinitionSplit,
15536 window: &mut Window,
15537 cx: &mut Context<Self>,
15538 ) -> Task<Result<Navigated>> {
15539 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
15540 }
15541
15542 fn go_to_definition_of_kind(
15543 &mut self,
15544 kind: GotoDefinitionKind,
15545 split: bool,
15546 window: &mut Window,
15547 cx: &mut Context<Self>,
15548 ) -> Task<Result<Navigated>> {
15549 let Some(provider) = self.semantics_provider.clone() else {
15550 return Task::ready(Ok(Navigated::No));
15551 };
15552 let head = self.selections.newest::<usize>(cx).head();
15553 let buffer = self.buffer.read(cx);
15554 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
15555 text_anchor
15556 } else {
15557 return Task::ready(Ok(Navigated::No));
15558 };
15559
15560 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
15561 return Task::ready(Ok(Navigated::No));
15562 };
15563
15564 cx.spawn_in(window, async move |editor, cx| {
15565 let definitions = definitions.await?;
15566 let navigated = editor
15567 .update_in(cx, |editor, window, cx| {
15568 editor.navigate_to_hover_links(
15569 Some(kind),
15570 definitions
15571 .into_iter()
15572 .filter(|location| {
15573 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
15574 })
15575 .map(HoverLink::Text)
15576 .collect::<Vec<_>>(),
15577 split,
15578 window,
15579 cx,
15580 )
15581 })?
15582 .await?;
15583 anyhow::Ok(navigated)
15584 })
15585 }
15586
15587 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
15588 let selection = self.selections.newest_anchor();
15589 let head = selection.head();
15590 let tail = selection.tail();
15591
15592 let Some((buffer, start_position)) =
15593 self.buffer.read(cx).text_anchor_for_position(head, cx)
15594 else {
15595 return;
15596 };
15597
15598 let end_position = if head != tail {
15599 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
15600 return;
15601 };
15602 Some(pos)
15603 } else {
15604 None
15605 };
15606
15607 let url_finder = cx.spawn_in(window, async move |editor, cx| {
15608 let url = if let Some(end_pos) = end_position {
15609 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
15610 } else {
15611 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
15612 };
15613
15614 if let Some(url) = url {
15615 editor.update(cx, |_, cx| {
15616 cx.open_url(&url);
15617 })
15618 } else {
15619 Ok(())
15620 }
15621 });
15622
15623 url_finder.detach();
15624 }
15625
15626 pub fn open_selected_filename(
15627 &mut self,
15628 _: &OpenSelectedFilename,
15629 window: &mut Window,
15630 cx: &mut Context<Self>,
15631 ) {
15632 let Some(workspace) = self.workspace() else {
15633 return;
15634 };
15635
15636 let position = self.selections.newest_anchor().head();
15637
15638 let Some((buffer, buffer_position)) =
15639 self.buffer.read(cx).text_anchor_for_position(position, cx)
15640 else {
15641 return;
15642 };
15643
15644 let project = self.project.clone();
15645
15646 cx.spawn_in(window, async move |_, cx| {
15647 let result = find_file(&buffer, project, buffer_position, cx).await;
15648
15649 if let Some((_, path)) = result {
15650 workspace
15651 .update_in(cx, |workspace, window, cx| {
15652 workspace.open_resolved_path(path, window, cx)
15653 })?
15654 .await?;
15655 }
15656 anyhow::Ok(())
15657 })
15658 .detach();
15659 }
15660
15661 pub(crate) fn navigate_to_hover_links(
15662 &mut self,
15663 kind: Option<GotoDefinitionKind>,
15664 mut definitions: Vec<HoverLink>,
15665 split: bool,
15666 window: &mut Window,
15667 cx: &mut Context<Editor>,
15668 ) -> Task<Result<Navigated>> {
15669 // If there is one definition, just open it directly
15670 if definitions.len() == 1 {
15671 let definition = definitions.pop().unwrap();
15672
15673 enum TargetTaskResult {
15674 Location(Option<Location>),
15675 AlreadyNavigated,
15676 }
15677
15678 let target_task = match definition {
15679 HoverLink::Text(link) => {
15680 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
15681 }
15682 HoverLink::InlayHint(lsp_location, server_id) => {
15683 let computation =
15684 self.compute_target_location(lsp_location, server_id, window, cx);
15685 cx.background_spawn(async move {
15686 let location = computation.await?;
15687 Ok(TargetTaskResult::Location(location))
15688 })
15689 }
15690 HoverLink::Url(url) => {
15691 cx.open_url(&url);
15692 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
15693 }
15694 HoverLink::File(path) => {
15695 if let Some(workspace) = self.workspace() {
15696 cx.spawn_in(window, async move |_, cx| {
15697 workspace
15698 .update_in(cx, |workspace, window, cx| {
15699 workspace.open_resolved_path(path, window, cx)
15700 })?
15701 .await
15702 .map(|_| TargetTaskResult::AlreadyNavigated)
15703 })
15704 } else {
15705 Task::ready(Ok(TargetTaskResult::Location(None)))
15706 }
15707 }
15708 };
15709 cx.spawn_in(window, async move |editor, cx| {
15710 let target = match target_task.await.context("target resolution task")? {
15711 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
15712 TargetTaskResult::Location(None) => return Ok(Navigated::No),
15713 TargetTaskResult::Location(Some(target)) => target,
15714 };
15715
15716 editor.update_in(cx, |editor, window, cx| {
15717 let Some(workspace) = editor.workspace() else {
15718 return Navigated::No;
15719 };
15720 let pane = workspace.read(cx).active_pane().clone();
15721
15722 let range = target.range.to_point(target.buffer.read(cx));
15723 let range = editor.range_for_match(&range);
15724 let range = collapse_multiline_range(range);
15725
15726 if !split
15727 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
15728 {
15729 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
15730 } else {
15731 window.defer(cx, move |window, cx| {
15732 let target_editor: Entity<Self> =
15733 workspace.update(cx, |workspace, cx| {
15734 let pane = if split {
15735 workspace.adjacent_pane(window, cx)
15736 } else {
15737 workspace.active_pane().clone()
15738 };
15739
15740 workspace.open_project_item(
15741 pane,
15742 target.buffer.clone(),
15743 true,
15744 true,
15745 window,
15746 cx,
15747 )
15748 });
15749 target_editor.update(cx, |target_editor, cx| {
15750 // When selecting a definition in a different buffer, disable the nav history
15751 // to avoid creating a history entry at the previous cursor location.
15752 pane.update(cx, |pane, _| pane.disable_history());
15753 target_editor.go_to_singleton_buffer_range(range, window, cx);
15754 pane.update(cx, |pane, _| pane.enable_history());
15755 });
15756 });
15757 }
15758 Navigated::Yes
15759 })
15760 })
15761 } else if !definitions.is_empty() {
15762 cx.spawn_in(window, async move |editor, cx| {
15763 let (title, location_tasks, workspace) = editor
15764 .update_in(cx, |editor, window, cx| {
15765 let tab_kind = match kind {
15766 Some(GotoDefinitionKind::Implementation) => "Implementations",
15767 _ => "Definitions",
15768 };
15769 let title = definitions
15770 .iter()
15771 .find_map(|definition| match definition {
15772 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
15773 let buffer = origin.buffer.read(cx);
15774 format!(
15775 "{} for {}",
15776 tab_kind,
15777 buffer
15778 .text_for_range(origin.range.clone())
15779 .collect::<String>()
15780 )
15781 }),
15782 HoverLink::InlayHint(_, _) => None,
15783 HoverLink::Url(_) => None,
15784 HoverLink::File(_) => None,
15785 })
15786 .unwrap_or(tab_kind.to_string());
15787 let location_tasks = definitions
15788 .into_iter()
15789 .map(|definition| match definition {
15790 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
15791 HoverLink::InlayHint(lsp_location, server_id) => editor
15792 .compute_target_location(lsp_location, server_id, window, cx),
15793 HoverLink::Url(_) => Task::ready(Ok(None)),
15794 HoverLink::File(_) => Task::ready(Ok(None)),
15795 })
15796 .collect::<Vec<_>>();
15797 (title, location_tasks, editor.workspace().clone())
15798 })
15799 .context("location tasks preparation")?;
15800
15801 let locations: Vec<Location> = future::join_all(location_tasks)
15802 .await
15803 .into_iter()
15804 .filter_map(|location| location.transpose())
15805 .collect::<Result<_>>()
15806 .context("location tasks")?;
15807
15808 if locations.is_empty() {
15809 return Ok(Navigated::No);
15810 }
15811
15812 let Some(workspace) = workspace else {
15813 return Ok(Navigated::No);
15814 };
15815
15816 let opened = workspace
15817 .update_in(cx, |workspace, window, cx| {
15818 Self::open_locations_in_multibuffer(
15819 workspace,
15820 locations,
15821 title,
15822 split,
15823 MultibufferSelectionMode::First,
15824 window,
15825 cx,
15826 )
15827 })
15828 .ok();
15829
15830 anyhow::Ok(Navigated::from_bool(opened.is_some()))
15831 })
15832 } else {
15833 Task::ready(Ok(Navigated::No))
15834 }
15835 }
15836
15837 fn compute_target_location(
15838 &self,
15839 lsp_location: lsp::Location,
15840 server_id: LanguageServerId,
15841 window: &mut Window,
15842 cx: &mut Context<Self>,
15843 ) -> Task<anyhow::Result<Option<Location>>> {
15844 let Some(project) = self.project.clone() else {
15845 return Task::ready(Ok(None));
15846 };
15847
15848 cx.spawn_in(window, async move |editor, cx| {
15849 let location_task = editor.update(cx, |_, cx| {
15850 project.update(cx, |project, cx| {
15851 let language_server_name = project
15852 .language_server_statuses(cx)
15853 .find(|(id, _)| server_id == *id)
15854 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
15855 language_server_name.map(|language_server_name| {
15856 project.open_local_buffer_via_lsp(
15857 lsp_location.uri.clone(),
15858 server_id,
15859 language_server_name,
15860 cx,
15861 )
15862 })
15863 })
15864 })?;
15865 let location = match location_task {
15866 Some(task) => Some({
15867 let target_buffer_handle = task.await.context("open local buffer")?;
15868 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
15869 let target_start = target_buffer
15870 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
15871 let target_end = target_buffer
15872 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
15873 target_buffer.anchor_after(target_start)
15874 ..target_buffer.anchor_before(target_end)
15875 })?;
15876 Location {
15877 buffer: target_buffer_handle,
15878 range,
15879 }
15880 }),
15881 None => None,
15882 };
15883 Ok(location)
15884 })
15885 }
15886
15887 pub fn find_all_references(
15888 &mut self,
15889 _: &FindAllReferences,
15890 window: &mut Window,
15891 cx: &mut Context<Self>,
15892 ) -> Option<Task<Result<Navigated>>> {
15893 let selection = self.selections.newest::<usize>(cx);
15894 let multi_buffer = self.buffer.read(cx);
15895 let head = selection.head();
15896
15897 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
15898 let head_anchor = multi_buffer_snapshot.anchor_at(
15899 head,
15900 if head < selection.tail() {
15901 Bias::Right
15902 } else {
15903 Bias::Left
15904 },
15905 );
15906
15907 match self
15908 .find_all_references_task_sources
15909 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15910 {
15911 Ok(_) => {
15912 log::info!(
15913 "Ignoring repeated FindAllReferences invocation with the position of already running task"
15914 );
15915 return None;
15916 }
15917 Err(i) => {
15918 self.find_all_references_task_sources.insert(i, head_anchor);
15919 }
15920 }
15921
15922 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
15923 let workspace = self.workspace()?;
15924 let project = workspace.read(cx).project().clone();
15925 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
15926 Some(cx.spawn_in(window, async move |editor, cx| {
15927 let _cleanup = cx.on_drop(&editor, move |editor, _| {
15928 if let Ok(i) = editor
15929 .find_all_references_task_sources
15930 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15931 {
15932 editor.find_all_references_task_sources.remove(i);
15933 }
15934 });
15935
15936 let locations = references.await?;
15937 if locations.is_empty() {
15938 return anyhow::Ok(Navigated::No);
15939 }
15940
15941 workspace.update_in(cx, |workspace, window, cx| {
15942 let title = locations
15943 .first()
15944 .as_ref()
15945 .map(|location| {
15946 let buffer = location.buffer.read(cx);
15947 format!(
15948 "References to `{}`",
15949 buffer
15950 .text_for_range(location.range.clone())
15951 .collect::<String>()
15952 )
15953 })
15954 .unwrap();
15955 Self::open_locations_in_multibuffer(
15956 workspace,
15957 locations,
15958 title,
15959 false,
15960 MultibufferSelectionMode::First,
15961 window,
15962 cx,
15963 );
15964 Navigated::Yes
15965 })
15966 }))
15967 }
15968
15969 /// Opens a multibuffer with the given project locations in it
15970 pub fn open_locations_in_multibuffer(
15971 workspace: &mut Workspace,
15972 mut locations: Vec<Location>,
15973 title: String,
15974 split: bool,
15975 multibuffer_selection_mode: MultibufferSelectionMode,
15976 window: &mut Window,
15977 cx: &mut Context<Workspace>,
15978 ) {
15979 if locations.is_empty() {
15980 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
15981 return;
15982 }
15983
15984 // If there are multiple definitions, open them in a multibuffer
15985 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
15986 let mut locations = locations.into_iter().peekable();
15987 let mut ranges: Vec<Range<Anchor>> = Vec::new();
15988 let capability = workspace.project().read(cx).capability();
15989
15990 let excerpt_buffer = cx.new(|cx| {
15991 let mut multibuffer = MultiBuffer::new(capability);
15992 while let Some(location) = locations.next() {
15993 let buffer = location.buffer.read(cx);
15994 let mut ranges_for_buffer = Vec::new();
15995 let range = location.range.to_point(buffer);
15996 ranges_for_buffer.push(range.clone());
15997
15998 while let Some(next_location) = locations.peek() {
15999 if next_location.buffer == location.buffer {
16000 ranges_for_buffer.push(next_location.range.to_point(buffer));
16001 locations.next();
16002 } else {
16003 break;
16004 }
16005 }
16006
16007 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
16008 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
16009 PathKey::for_buffer(&location.buffer, cx),
16010 location.buffer.clone(),
16011 ranges_for_buffer,
16012 DEFAULT_MULTIBUFFER_CONTEXT,
16013 cx,
16014 );
16015 ranges.extend(new_ranges)
16016 }
16017
16018 multibuffer.with_title(title)
16019 });
16020
16021 let editor = cx.new(|cx| {
16022 Editor::for_multibuffer(
16023 excerpt_buffer,
16024 Some(workspace.project().clone()),
16025 window,
16026 cx,
16027 )
16028 });
16029 editor.update(cx, |editor, cx| {
16030 match multibuffer_selection_mode {
16031 MultibufferSelectionMode::First => {
16032 if let Some(first_range) = ranges.first() {
16033 editor.change_selections(
16034 SelectionEffects::no_scroll(),
16035 window,
16036 cx,
16037 |selections| {
16038 selections.clear_disjoint();
16039 selections
16040 .select_anchor_ranges(std::iter::once(first_range.clone()));
16041 },
16042 );
16043 }
16044 editor.highlight_background::<Self>(
16045 &ranges,
16046 |theme| theme.colors().editor_highlighted_line_background,
16047 cx,
16048 );
16049 }
16050 MultibufferSelectionMode::All => {
16051 editor.change_selections(
16052 SelectionEffects::no_scroll(),
16053 window,
16054 cx,
16055 |selections| {
16056 selections.clear_disjoint();
16057 selections.select_anchor_ranges(ranges);
16058 },
16059 );
16060 }
16061 }
16062 editor.register_buffers_with_language_servers(cx);
16063 });
16064
16065 let item = Box::new(editor);
16066 let item_id = item.item_id();
16067
16068 if split {
16069 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
16070 } else {
16071 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
16072 let (preview_item_id, preview_item_idx) =
16073 workspace.active_pane().read_with(cx, |pane, _| {
16074 (pane.preview_item_id(), pane.preview_item_idx())
16075 });
16076
16077 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
16078
16079 if let Some(preview_item_id) = preview_item_id {
16080 workspace.active_pane().update(cx, |pane, cx| {
16081 pane.remove_item(preview_item_id, false, false, window, cx);
16082 });
16083 }
16084 } else {
16085 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
16086 }
16087 }
16088 workspace.active_pane().update(cx, |pane, cx| {
16089 pane.set_preview_item_id(Some(item_id), cx);
16090 });
16091 }
16092
16093 pub fn rename(
16094 &mut self,
16095 _: &Rename,
16096 window: &mut Window,
16097 cx: &mut Context<Self>,
16098 ) -> Option<Task<Result<()>>> {
16099 use language::ToOffset as _;
16100
16101 let provider = self.semantics_provider.clone()?;
16102 let selection = self.selections.newest_anchor().clone();
16103 let (cursor_buffer, cursor_buffer_position) = self
16104 .buffer
16105 .read(cx)
16106 .text_anchor_for_position(selection.head(), cx)?;
16107 let (tail_buffer, cursor_buffer_position_end) = self
16108 .buffer
16109 .read(cx)
16110 .text_anchor_for_position(selection.tail(), cx)?;
16111 if tail_buffer != cursor_buffer {
16112 return None;
16113 }
16114
16115 let snapshot = cursor_buffer.read(cx).snapshot();
16116 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
16117 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
16118 let prepare_rename = provider
16119 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
16120 .unwrap_or_else(|| Task::ready(Ok(None)));
16121 drop(snapshot);
16122
16123 Some(cx.spawn_in(window, async move |this, cx| {
16124 let rename_range = if let Some(range) = prepare_rename.await? {
16125 Some(range)
16126 } else {
16127 this.update(cx, |this, cx| {
16128 let buffer = this.buffer.read(cx).snapshot(cx);
16129 let mut buffer_highlights = this
16130 .document_highlights_for_position(selection.head(), &buffer)
16131 .filter(|highlight| {
16132 highlight.start.excerpt_id == selection.head().excerpt_id
16133 && highlight.end.excerpt_id == selection.head().excerpt_id
16134 });
16135 buffer_highlights
16136 .next()
16137 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
16138 })?
16139 };
16140 if let Some(rename_range) = rename_range {
16141 this.update_in(cx, |this, window, cx| {
16142 let snapshot = cursor_buffer.read(cx).snapshot();
16143 let rename_buffer_range = rename_range.to_offset(&snapshot);
16144 let cursor_offset_in_rename_range =
16145 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
16146 let cursor_offset_in_rename_range_end =
16147 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
16148
16149 this.take_rename(false, window, cx);
16150 let buffer = this.buffer.read(cx).read(cx);
16151 let cursor_offset = selection.head().to_offset(&buffer);
16152 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
16153 let rename_end = rename_start + rename_buffer_range.len();
16154 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
16155 let mut old_highlight_id = None;
16156 let old_name: Arc<str> = buffer
16157 .chunks(rename_start..rename_end, true)
16158 .map(|chunk| {
16159 if old_highlight_id.is_none() {
16160 old_highlight_id = chunk.syntax_highlight_id;
16161 }
16162 chunk.text
16163 })
16164 .collect::<String>()
16165 .into();
16166
16167 drop(buffer);
16168
16169 // Position the selection in the rename editor so that it matches the current selection.
16170 this.show_local_selections = false;
16171 let rename_editor = cx.new(|cx| {
16172 let mut editor = Editor::single_line(window, cx);
16173 editor.buffer.update(cx, |buffer, cx| {
16174 buffer.edit([(0..0, old_name.clone())], None, cx)
16175 });
16176 let rename_selection_range = match cursor_offset_in_rename_range
16177 .cmp(&cursor_offset_in_rename_range_end)
16178 {
16179 Ordering::Equal => {
16180 editor.select_all(&SelectAll, window, cx);
16181 return editor;
16182 }
16183 Ordering::Less => {
16184 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
16185 }
16186 Ordering::Greater => {
16187 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
16188 }
16189 };
16190 if rename_selection_range.end > old_name.len() {
16191 editor.select_all(&SelectAll, window, cx);
16192 } else {
16193 editor.change_selections(Default::default(), window, cx, |s| {
16194 s.select_ranges([rename_selection_range]);
16195 });
16196 }
16197 editor
16198 });
16199 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
16200 if e == &EditorEvent::Focused {
16201 cx.emit(EditorEvent::FocusedIn)
16202 }
16203 })
16204 .detach();
16205
16206 let write_highlights =
16207 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
16208 let read_highlights =
16209 this.clear_background_highlights::<DocumentHighlightRead>(cx);
16210 let ranges = write_highlights
16211 .iter()
16212 .flat_map(|(_, ranges)| ranges.iter())
16213 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
16214 .cloned()
16215 .collect();
16216
16217 this.highlight_text::<Rename>(
16218 ranges,
16219 HighlightStyle {
16220 fade_out: Some(0.6),
16221 ..Default::default()
16222 },
16223 cx,
16224 );
16225 let rename_focus_handle = rename_editor.focus_handle(cx);
16226 window.focus(&rename_focus_handle);
16227 let block_id = this.insert_blocks(
16228 [BlockProperties {
16229 style: BlockStyle::Flex,
16230 placement: BlockPlacement::Below(range.start),
16231 height: Some(1),
16232 render: Arc::new({
16233 let rename_editor = rename_editor.clone();
16234 move |cx: &mut BlockContext| {
16235 let mut text_style = cx.editor_style.text.clone();
16236 if let Some(highlight_style) = old_highlight_id
16237 .and_then(|h| h.style(&cx.editor_style.syntax))
16238 {
16239 text_style = text_style.highlight(highlight_style);
16240 }
16241 div()
16242 .block_mouse_except_scroll()
16243 .pl(cx.anchor_x)
16244 .child(EditorElement::new(
16245 &rename_editor,
16246 EditorStyle {
16247 background: cx.theme().system().transparent,
16248 local_player: cx.editor_style.local_player,
16249 text: text_style,
16250 scrollbar_width: cx.editor_style.scrollbar_width,
16251 syntax: cx.editor_style.syntax.clone(),
16252 status: cx.editor_style.status.clone(),
16253 inlay_hints_style: HighlightStyle {
16254 font_weight: Some(FontWeight::BOLD),
16255 ..make_inlay_hints_style(cx.app)
16256 },
16257 inline_completion_styles: make_suggestion_styles(
16258 cx.app,
16259 ),
16260 ..EditorStyle::default()
16261 },
16262 ))
16263 .into_any_element()
16264 }
16265 }),
16266 priority: 0,
16267 }],
16268 Some(Autoscroll::fit()),
16269 cx,
16270 )[0];
16271 this.pending_rename = Some(RenameState {
16272 range,
16273 old_name,
16274 editor: rename_editor,
16275 block_id,
16276 });
16277 })?;
16278 }
16279
16280 Ok(())
16281 }))
16282 }
16283
16284 pub fn confirm_rename(
16285 &mut self,
16286 _: &ConfirmRename,
16287 window: &mut Window,
16288 cx: &mut Context<Self>,
16289 ) -> Option<Task<Result<()>>> {
16290 let rename = self.take_rename(false, window, cx)?;
16291 let workspace = self.workspace()?.downgrade();
16292 let (buffer, start) = self
16293 .buffer
16294 .read(cx)
16295 .text_anchor_for_position(rename.range.start, cx)?;
16296 let (end_buffer, _) = self
16297 .buffer
16298 .read(cx)
16299 .text_anchor_for_position(rename.range.end, cx)?;
16300 if buffer != end_buffer {
16301 return None;
16302 }
16303
16304 let old_name = rename.old_name;
16305 let new_name = rename.editor.read(cx).text(cx);
16306
16307 let rename = self.semantics_provider.as_ref()?.perform_rename(
16308 &buffer,
16309 start,
16310 new_name.clone(),
16311 cx,
16312 )?;
16313
16314 Some(cx.spawn_in(window, async move |editor, cx| {
16315 let project_transaction = rename.await?;
16316 Self::open_project_transaction(
16317 &editor,
16318 workspace,
16319 project_transaction,
16320 format!("Rename: {} → {}", old_name, new_name),
16321 cx,
16322 )
16323 .await?;
16324
16325 editor.update(cx, |editor, cx| {
16326 editor.refresh_document_highlights(cx);
16327 })?;
16328 Ok(())
16329 }))
16330 }
16331
16332 fn take_rename(
16333 &mut self,
16334 moving_cursor: bool,
16335 window: &mut Window,
16336 cx: &mut Context<Self>,
16337 ) -> Option<RenameState> {
16338 let rename = self.pending_rename.take()?;
16339 if rename.editor.focus_handle(cx).is_focused(window) {
16340 window.focus(&self.focus_handle);
16341 }
16342
16343 self.remove_blocks(
16344 [rename.block_id].into_iter().collect(),
16345 Some(Autoscroll::fit()),
16346 cx,
16347 );
16348 self.clear_highlights::<Rename>(cx);
16349 self.show_local_selections = true;
16350
16351 if moving_cursor {
16352 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
16353 editor.selections.newest::<usize>(cx).head()
16354 });
16355
16356 // Update the selection to match the position of the selection inside
16357 // the rename editor.
16358 let snapshot = self.buffer.read(cx).read(cx);
16359 let rename_range = rename.range.to_offset(&snapshot);
16360 let cursor_in_editor = snapshot
16361 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
16362 .min(rename_range.end);
16363 drop(snapshot);
16364
16365 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16366 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
16367 });
16368 } else {
16369 self.refresh_document_highlights(cx);
16370 }
16371
16372 Some(rename)
16373 }
16374
16375 pub fn pending_rename(&self) -> Option<&RenameState> {
16376 self.pending_rename.as_ref()
16377 }
16378
16379 fn format(
16380 &mut self,
16381 _: &Format,
16382 window: &mut Window,
16383 cx: &mut Context<Self>,
16384 ) -> Option<Task<Result<()>>> {
16385 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16386
16387 let project = match &self.project {
16388 Some(project) => project.clone(),
16389 None => return None,
16390 };
16391
16392 Some(self.perform_format(
16393 project,
16394 FormatTrigger::Manual,
16395 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
16396 window,
16397 cx,
16398 ))
16399 }
16400
16401 fn format_selections(
16402 &mut self,
16403 _: &FormatSelections,
16404 window: &mut Window,
16405 cx: &mut Context<Self>,
16406 ) -> Option<Task<Result<()>>> {
16407 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16408
16409 let project = match &self.project {
16410 Some(project) => project.clone(),
16411 None => return None,
16412 };
16413
16414 let ranges = self
16415 .selections
16416 .all_adjusted(cx)
16417 .into_iter()
16418 .map(|selection| selection.range())
16419 .collect_vec();
16420
16421 Some(self.perform_format(
16422 project,
16423 FormatTrigger::Manual,
16424 FormatTarget::Ranges(ranges),
16425 window,
16426 cx,
16427 ))
16428 }
16429
16430 fn perform_format(
16431 &mut self,
16432 project: Entity<Project>,
16433 trigger: FormatTrigger,
16434 target: FormatTarget,
16435 window: &mut Window,
16436 cx: &mut Context<Self>,
16437 ) -> Task<Result<()>> {
16438 let buffer = self.buffer.clone();
16439 let (buffers, target) = match target {
16440 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
16441 FormatTarget::Ranges(selection_ranges) => {
16442 let multi_buffer = buffer.read(cx);
16443 let snapshot = multi_buffer.read(cx);
16444 let mut buffers = HashSet::default();
16445 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
16446 BTreeMap::new();
16447 for selection_range in selection_ranges {
16448 for (buffer, buffer_range, _) in
16449 snapshot.range_to_buffer_ranges(selection_range)
16450 {
16451 let buffer_id = buffer.remote_id();
16452 let start = buffer.anchor_before(buffer_range.start);
16453 let end = buffer.anchor_after(buffer_range.end);
16454 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
16455 buffer_id_to_ranges
16456 .entry(buffer_id)
16457 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
16458 .or_insert_with(|| vec![start..end]);
16459 }
16460 }
16461 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
16462 }
16463 };
16464
16465 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
16466 let selections_prev = transaction_id_prev
16467 .and_then(|transaction_id_prev| {
16468 // default to selections as they were after the last edit, if we have them,
16469 // instead of how they are now.
16470 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
16471 // will take you back to where you made the last edit, instead of staying where you scrolled
16472 self.selection_history
16473 .transaction(transaction_id_prev)
16474 .map(|t| t.0.clone())
16475 })
16476 .unwrap_or_else(|| {
16477 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
16478 self.selections.disjoint_anchors()
16479 });
16480
16481 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
16482 let format = project.update(cx, |project, cx| {
16483 project.format(buffers, target, true, trigger, cx)
16484 });
16485
16486 cx.spawn_in(window, async move |editor, cx| {
16487 let transaction = futures::select_biased! {
16488 transaction = format.log_err().fuse() => transaction,
16489 () = timeout => {
16490 log::warn!("timed out waiting for formatting");
16491 None
16492 }
16493 };
16494
16495 buffer
16496 .update(cx, |buffer, cx| {
16497 if let Some(transaction) = transaction {
16498 if !buffer.is_singleton() {
16499 buffer.push_transaction(&transaction.0, cx);
16500 }
16501 }
16502 cx.notify();
16503 })
16504 .ok();
16505
16506 if let Some(transaction_id_now) =
16507 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
16508 {
16509 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
16510 if has_new_transaction {
16511 _ = editor.update(cx, |editor, _| {
16512 editor
16513 .selection_history
16514 .insert_transaction(transaction_id_now, selections_prev);
16515 });
16516 }
16517 }
16518
16519 Ok(())
16520 })
16521 }
16522
16523 fn organize_imports(
16524 &mut self,
16525 _: &OrganizeImports,
16526 window: &mut Window,
16527 cx: &mut Context<Self>,
16528 ) -> Option<Task<Result<()>>> {
16529 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16530 let project = match &self.project {
16531 Some(project) => project.clone(),
16532 None => return None,
16533 };
16534 Some(self.perform_code_action_kind(
16535 project,
16536 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
16537 window,
16538 cx,
16539 ))
16540 }
16541
16542 fn perform_code_action_kind(
16543 &mut self,
16544 project: Entity<Project>,
16545 kind: CodeActionKind,
16546 window: &mut Window,
16547 cx: &mut Context<Self>,
16548 ) -> Task<Result<()>> {
16549 let buffer = self.buffer.clone();
16550 let buffers = buffer.read(cx).all_buffers();
16551 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
16552 let apply_action = project.update(cx, |project, cx| {
16553 project.apply_code_action_kind(buffers, kind, true, cx)
16554 });
16555 cx.spawn_in(window, async move |_, cx| {
16556 let transaction = futures::select_biased! {
16557 () = timeout => {
16558 log::warn!("timed out waiting for executing code action");
16559 None
16560 }
16561 transaction = apply_action.log_err().fuse() => transaction,
16562 };
16563 buffer
16564 .update(cx, |buffer, cx| {
16565 // check if we need this
16566 if let Some(transaction) = transaction {
16567 if !buffer.is_singleton() {
16568 buffer.push_transaction(&transaction.0, cx);
16569 }
16570 }
16571 cx.notify();
16572 })
16573 .ok();
16574 Ok(())
16575 })
16576 }
16577
16578 pub fn restart_language_server(
16579 &mut self,
16580 _: &RestartLanguageServer,
16581 _: &mut Window,
16582 cx: &mut Context<Self>,
16583 ) {
16584 if let Some(project) = self.project.clone() {
16585 self.buffer.update(cx, |multi_buffer, cx| {
16586 project.update(cx, |project, cx| {
16587 project.restart_language_servers_for_buffers(
16588 multi_buffer.all_buffers().into_iter().collect(),
16589 HashSet::default(),
16590 cx,
16591 );
16592 });
16593 })
16594 }
16595 }
16596
16597 pub fn stop_language_server(
16598 &mut self,
16599 _: &StopLanguageServer,
16600 _: &mut Window,
16601 cx: &mut Context<Self>,
16602 ) {
16603 if let Some(project) = self.project.clone() {
16604 self.buffer.update(cx, |multi_buffer, cx| {
16605 project.update(cx, |project, cx| {
16606 project.stop_language_servers_for_buffers(
16607 multi_buffer.all_buffers().into_iter().collect(),
16608 HashSet::default(),
16609 cx,
16610 );
16611 cx.emit(project::Event::RefreshInlayHints);
16612 });
16613 });
16614 }
16615 }
16616
16617 fn cancel_language_server_work(
16618 workspace: &mut Workspace,
16619 _: &actions::CancelLanguageServerWork,
16620 _: &mut Window,
16621 cx: &mut Context<Workspace>,
16622 ) {
16623 let project = workspace.project();
16624 let buffers = workspace
16625 .active_item(cx)
16626 .and_then(|item| item.act_as::<Editor>(cx))
16627 .map_or(HashSet::default(), |editor| {
16628 editor.read(cx).buffer.read(cx).all_buffers()
16629 });
16630 project.update(cx, |project, cx| {
16631 project.cancel_language_server_work_for_buffers(buffers, cx);
16632 });
16633 }
16634
16635 fn show_character_palette(
16636 &mut self,
16637 _: &ShowCharacterPalette,
16638 window: &mut Window,
16639 _: &mut Context<Self>,
16640 ) {
16641 window.show_character_palette();
16642 }
16643
16644 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
16645 if !self.diagnostics_enabled() {
16646 return;
16647 }
16648
16649 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
16650 let buffer = self.buffer.read(cx).snapshot(cx);
16651 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
16652 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
16653 let is_valid = buffer
16654 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
16655 .any(|entry| {
16656 entry.diagnostic.is_primary
16657 && !entry.range.is_empty()
16658 && entry.range.start == primary_range_start
16659 && entry.diagnostic.message == active_diagnostics.active_message
16660 });
16661
16662 if !is_valid {
16663 self.dismiss_diagnostics(cx);
16664 }
16665 }
16666 }
16667
16668 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
16669 match &self.active_diagnostics {
16670 ActiveDiagnostic::Group(group) => Some(group),
16671 _ => None,
16672 }
16673 }
16674
16675 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
16676 if !self.diagnostics_enabled() {
16677 return;
16678 }
16679 self.dismiss_diagnostics(cx);
16680 self.active_diagnostics = ActiveDiagnostic::All;
16681 }
16682
16683 fn activate_diagnostics(
16684 &mut self,
16685 buffer_id: BufferId,
16686 diagnostic: DiagnosticEntry<usize>,
16687 window: &mut Window,
16688 cx: &mut Context<Self>,
16689 ) {
16690 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16691 return;
16692 }
16693 self.dismiss_diagnostics(cx);
16694 let snapshot = self.snapshot(window, cx);
16695 let buffer = self.buffer.read(cx).snapshot(cx);
16696 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
16697 return;
16698 };
16699
16700 let diagnostic_group = buffer
16701 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
16702 .collect::<Vec<_>>();
16703
16704 let blocks =
16705 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
16706
16707 let blocks = self.display_map.update(cx, |display_map, cx| {
16708 display_map.insert_blocks(blocks, cx).into_iter().collect()
16709 });
16710 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
16711 active_range: buffer.anchor_before(diagnostic.range.start)
16712 ..buffer.anchor_after(diagnostic.range.end),
16713 active_message: diagnostic.diagnostic.message.clone(),
16714 group_id: diagnostic.diagnostic.group_id,
16715 blocks,
16716 });
16717 cx.notify();
16718 }
16719
16720 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
16721 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16722 return;
16723 };
16724
16725 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
16726 if let ActiveDiagnostic::Group(group) = prev {
16727 self.display_map.update(cx, |display_map, cx| {
16728 display_map.remove_blocks(group.blocks, cx);
16729 });
16730 cx.notify();
16731 }
16732 }
16733
16734 /// Disable inline diagnostics rendering for this editor.
16735 pub fn disable_inline_diagnostics(&mut self) {
16736 self.inline_diagnostics_enabled = false;
16737 self.inline_diagnostics_update = Task::ready(());
16738 self.inline_diagnostics.clear();
16739 }
16740
16741 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
16742 self.diagnostics_enabled = false;
16743 self.dismiss_diagnostics(cx);
16744 self.inline_diagnostics_update = Task::ready(());
16745 self.inline_diagnostics.clear();
16746 }
16747
16748 pub fn diagnostics_enabled(&self) -> bool {
16749 self.diagnostics_enabled && self.mode.is_full()
16750 }
16751
16752 pub fn inline_diagnostics_enabled(&self) -> bool {
16753 self.inline_diagnostics_enabled && self.diagnostics_enabled()
16754 }
16755
16756 pub fn show_inline_diagnostics(&self) -> bool {
16757 self.show_inline_diagnostics
16758 }
16759
16760 pub fn toggle_inline_diagnostics(
16761 &mut self,
16762 _: &ToggleInlineDiagnostics,
16763 window: &mut Window,
16764 cx: &mut Context<Editor>,
16765 ) {
16766 self.show_inline_diagnostics = !self.show_inline_diagnostics;
16767 self.refresh_inline_diagnostics(false, window, cx);
16768 }
16769
16770 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
16771 self.diagnostics_max_severity = severity;
16772 self.display_map.update(cx, |display_map, _| {
16773 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
16774 });
16775 }
16776
16777 pub fn toggle_diagnostics(
16778 &mut self,
16779 _: &ToggleDiagnostics,
16780 window: &mut Window,
16781 cx: &mut Context<Editor>,
16782 ) {
16783 if !self.diagnostics_enabled() {
16784 return;
16785 }
16786
16787 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16788 EditorSettings::get_global(cx)
16789 .diagnostics_max_severity
16790 .filter(|severity| severity != &DiagnosticSeverity::Off)
16791 .unwrap_or(DiagnosticSeverity::Hint)
16792 } else {
16793 DiagnosticSeverity::Off
16794 };
16795 self.set_max_diagnostics_severity(new_severity, cx);
16796 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16797 self.active_diagnostics = ActiveDiagnostic::None;
16798 self.inline_diagnostics_update = Task::ready(());
16799 self.inline_diagnostics.clear();
16800 } else {
16801 self.refresh_inline_diagnostics(false, window, cx);
16802 }
16803
16804 cx.notify();
16805 }
16806
16807 pub fn toggle_minimap(
16808 &mut self,
16809 _: &ToggleMinimap,
16810 window: &mut Window,
16811 cx: &mut Context<Editor>,
16812 ) {
16813 if self.supports_minimap(cx) {
16814 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
16815 }
16816 }
16817
16818 fn refresh_inline_diagnostics(
16819 &mut self,
16820 debounce: bool,
16821 window: &mut Window,
16822 cx: &mut Context<Self>,
16823 ) {
16824 let max_severity = ProjectSettings::get_global(cx)
16825 .diagnostics
16826 .inline
16827 .max_severity
16828 .unwrap_or(self.diagnostics_max_severity);
16829
16830 if !self.inline_diagnostics_enabled()
16831 || !self.show_inline_diagnostics
16832 || max_severity == DiagnosticSeverity::Off
16833 {
16834 self.inline_diagnostics_update = Task::ready(());
16835 self.inline_diagnostics.clear();
16836 return;
16837 }
16838
16839 let debounce_ms = ProjectSettings::get_global(cx)
16840 .diagnostics
16841 .inline
16842 .update_debounce_ms;
16843 let debounce = if debounce && debounce_ms > 0 {
16844 Some(Duration::from_millis(debounce_ms))
16845 } else {
16846 None
16847 };
16848 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
16849 if let Some(debounce) = debounce {
16850 cx.background_executor().timer(debounce).await;
16851 }
16852 let Some(snapshot) = editor.upgrade().and_then(|editor| {
16853 editor
16854 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
16855 .ok()
16856 }) else {
16857 return;
16858 };
16859
16860 let new_inline_diagnostics = cx
16861 .background_spawn(async move {
16862 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
16863 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
16864 let message = diagnostic_entry
16865 .diagnostic
16866 .message
16867 .split_once('\n')
16868 .map(|(line, _)| line)
16869 .map(SharedString::new)
16870 .unwrap_or_else(|| {
16871 SharedString::from(diagnostic_entry.diagnostic.message)
16872 });
16873 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
16874 let (Ok(i) | Err(i)) = inline_diagnostics
16875 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
16876 inline_diagnostics.insert(
16877 i,
16878 (
16879 start_anchor,
16880 InlineDiagnostic {
16881 message,
16882 group_id: diagnostic_entry.diagnostic.group_id,
16883 start: diagnostic_entry.range.start.to_point(&snapshot),
16884 is_primary: diagnostic_entry.diagnostic.is_primary,
16885 severity: diagnostic_entry.diagnostic.severity,
16886 },
16887 ),
16888 );
16889 }
16890 inline_diagnostics
16891 })
16892 .await;
16893
16894 editor
16895 .update(cx, |editor, cx| {
16896 editor.inline_diagnostics = new_inline_diagnostics;
16897 cx.notify();
16898 })
16899 .ok();
16900 });
16901 }
16902
16903 fn pull_diagnostics(
16904 &mut self,
16905 buffer_id: Option<BufferId>,
16906 window: &Window,
16907 cx: &mut Context<Self>,
16908 ) -> Option<()> {
16909 if !self.mode().is_full() {
16910 return None;
16911 }
16912 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
16913 .diagnostics
16914 .lsp_pull_diagnostics;
16915 if !pull_diagnostics_settings.enabled {
16916 return None;
16917 }
16918 let project = self.project.as_ref()?.downgrade();
16919 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
16920 let mut buffers = self.buffer.read(cx).all_buffers();
16921 if let Some(buffer_id) = buffer_id {
16922 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
16923 }
16924
16925 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
16926 cx.background_executor().timer(debounce).await;
16927
16928 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
16929 buffers
16930 .into_iter()
16931 .filter_map(|buffer| {
16932 project
16933 .update(cx, |project, cx| {
16934 project.lsp_store().update(cx, |lsp_store, cx| {
16935 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
16936 })
16937 })
16938 .ok()
16939 })
16940 .collect::<FuturesUnordered<_>>()
16941 }) else {
16942 return;
16943 };
16944
16945 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
16946 match pull_task {
16947 Ok(()) => {
16948 if editor
16949 .update_in(cx, |editor, window, cx| {
16950 editor.update_diagnostics_state(window, cx);
16951 })
16952 .is_err()
16953 {
16954 return;
16955 }
16956 }
16957 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
16958 }
16959 }
16960 });
16961
16962 Some(())
16963 }
16964
16965 pub fn set_selections_from_remote(
16966 &mut self,
16967 selections: Vec<Selection<Anchor>>,
16968 pending_selection: Option<Selection<Anchor>>,
16969 window: &mut Window,
16970 cx: &mut Context<Self>,
16971 ) {
16972 let old_cursor_position = self.selections.newest_anchor().head();
16973 self.selections.change_with(cx, |s| {
16974 s.select_anchors(selections);
16975 if let Some(pending_selection) = pending_selection {
16976 s.set_pending(pending_selection, SelectMode::Character);
16977 } else {
16978 s.clear_pending();
16979 }
16980 });
16981 self.selections_did_change(
16982 false,
16983 &old_cursor_position,
16984 SelectionEffects::default(),
16985 window,
16986 cx,
16987 );
16988 }
16989
16990 pub fn transact(
16991 &mut self,
16992 window: &mut Window,
16993 cx: &mut Context<Self>,
16994 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
16995 ) -> Option<TransactionId> {
16996 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16997 this.start_transaction_at(Instant::now(), window, cx);
16998 update(this, window, cx);
16999 this.end_transaction_at(Instant::now(), cx)
17000 })
17001 }
17002
17003 pub fn start_transaction_at(
17004 &mut self,
17005 now: Instant,
17006 window: &mut Window,
17007 cx: &mut Context<Self>,
17008 ) -> Option<TransactionId> {
17009 self.end_selection(window, cx);
17010 if let Some(tx_id) = self
17011 .buffer
17012 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
17013 {
17014 self.selection_history
17015 .insert_transaction(tx_id, self.selections.disjoint_anchors());
17016 cx.emit(EditorEvent::TransactionBegun {
17017 transaction_id: tx_id,
17018 });
17019 Some(tx_id)
17020 } else {
17021 None
17022 }
17023 }
17024
17025 pub fn end_transaction_at(
17026 &mut self,
17027 now: Instant,
17028 cx: &mut Context<Self>,
17029 ) -> Option<TransactionId> {
17030 if let Some(transaction_id) = self
17031 .buffer
17032 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
17033 {
17034 if let Some((_, end_selections)) =
17035 self.selection_history.transaction_mut(transaction_id)
17036 {
17037 *end_selections = Some(self.selections.disjoint_anchors());
17038 } else {
17039 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
17040 }
17041
17042 cx.emit(EditorEvent::Edited { transaction_id });
17043 Some(transaction_id)
17044 } else {
17045 None
17046 }
17047 }
17048
17049 pub fn modify_transaction_selection_history(
17050 &mut self,
17051 transaction_id: TransactionId,
17052 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
17053 ) -> bool {
17054 self.selection_history
17055 .transaction_mut(transaction_id)
17056 .map(modify)
17057 .is_some()
17058 }
17059
17060 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
17061 if self.selection_mark_mode {
17062 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17063 s.move_with(|_, sel| {
17064 sel.collapse_to(sel.head(), SelectionGoal::None);
17065 });
17066 })
17067 }
17068 self.selection_mark_mode = true;
17069 cx.notify();
17070 }
17071
17072 pub fn swap_selection_ends(
17073 &mut self,
17074 _: &actions::SwapSelectionEnds,
17075 window: &mut Window,
17076 cx: &mut Context<Self>,
17077 ) {
17078 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17079 s.move_with(|_, sel| {
17080 if sel.start != sel.end {
17081 sel.reversed = !sel.reversed
17082 }
17083 });
17084 });
17085 self.request_autoscroll(Autoscroll::newest(), cx);
17086 cx.notify();
17087 }
17088
17089 pub fn toggle_focus(
17090 workspace: &mut Workspace,
17091 _: &actions::ToggleFocus,
17092 window: &mut Window,
17093 cx: &mut Context<Workspace>,
17094 ) {
17095 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
17096 return;
17097 };
17098 workspace.activate_item(&item, true, true, window, cx);
17099 }
17100
17101 pub fn toggle_fold(
17102 &mut self,
17103 _: &actions::ToggleFold,
17104 window: &mut Window,
17105 cx: &mut Context<Self>,
17106 ) {
17107 if self.is_singleton(cx) {
17108 let selection = self.selections.newest::<Point>(cx);
17109
17110 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17111 let range = if selection.is_empty() {
17112 let point = selection.head().to_display_point(&display_map);
17113 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17114 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17115 .to_point(&display_map);
17116 start..end
17117 } else {
17118 selection.range()
17119 };
17120 if display_map.folds_in_range(range).next().is_some() {
17121 self.unfold_lines(&Default::default(), window, cx)
17122 } else {
17123 self.fold(&Default::default(), window, cx)
17124 }
17125 } else {
17126 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17127 let buffer_ids: HashSet<_> = self
17128 .selections
17129 .disjoint_anchor_ranges()
17130 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17131 .collect();
17132
17133 let should_unfold = buffer_ids
17134 .iter()
17135 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17136
17137 for buffer_id in buffer_ids {
17138 if should_unfold {
17139 self.unfold_buffer(buffer_id, cx);
17140 } else {
17141 self.fold_buffer(buffer_id, cx);
17142 }
17143 }
17144 }
17145 }
17146
17147 pub fn toggle_fold_recursive(
17148 &mut self,
17149 _: &actions::ToggleFoldRecursive,
17150 window: &mut Window,
17151 cx: &mut Context<Self>,
17152 ) {
17153 let selection = self.selections.newest::<Point>(cx);
17154
17155 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17156 let range = if selection.is_empty() {
17157 let point = selection.head().to_display_point(&display_map);
17158 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17159 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17160 .to_point(&display_map);
17161 start..end
17162 } else {
17163 selection.range()
17164 };
17165 if display_map.folds_in_range(range).next().is_some() {
17166 self.unfold_recursive(&Default::default(), window, cx)
17167 } else {
17168 self.fold_recursive(&Default::default(), window, cx)
17169 }
17170 }
17171
17172 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
17173 if self.is_singleton(cx) {
17174 let mut to_fold = Vec::new();
17175 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17176 let selections = self.selections.all_adjusted(cx);
17177
17178 for selection in selections {
17179 let range = selection.range().sorted();
17180 let buffer_start_row = range.start.row;
17181
17182 if range.start.row != range.end.row {
17183 let mut found = false;
17184 let mut row = range.start.row;
17185 while row <= range.end.row {
17186 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
17187 {
17188 found = true;
17189 row = crease.range().end.row + 1;
17190 to_fold.push(crease);
17191 } else {
17192 row += 1
17193 }
17194 }
17195 if found {
17196 continue;
17197 }
17198 }
17199
17200 for row in (0..=range.start.row).rev() {
17201 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17202 if crease.range().end.row >= buffer_start_row {
17203 to_fold.push(crease);
17204 if row <= range.start.row {
17205 break;
17206 }
17207 }
17208 }
17209 }
17210 }
17211
17212 self.fold_creases(to_fold, true, window, cx);
17213 } else {
17214 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17215 let buffer_ids = self
17216 .selections
17217 .disjoint_anchor_ranges()
17218 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17219 .collect::<HashSet<_>>();
17220 for buffer_id in buffer_ids {
17221 self.fold_buffer(buffer_id, cx);
17222 }
17223 }
17224 }
17225
17226 pub fn toggle_fold_all(
17227 &mut self,
17228 _: &actions::ToggleFoldAll,
17229 window: &mut Window,
17230 cx: &mut Context<Self>,
17231 ) {
17232 if self.buffer.read(cx).is_singleton() {
17233 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17234 let has_folds = display_map
17235 .folds_in_range(0..display_map.buffer_snapshot.len())
17236 .next()
17237 .is_some();
17238
17239 if has_folds {
17240 self.unfold_all(&actions::UnfoldAll, window, cx);
17241 } else {
17242 self.fold_all(&actions::FoldAll, window, cx);
17243 }
17244 } else {
17245 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
17246 let should_unfold = buffer_ids
17247 .iter()
17248 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17249
17250 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17251 editor
17252 .update_in(cx, |editor, _, cx| {
17253 for buffer_id in buffer_ids {
17254 if should_unfold {
17255 editor.unfold_buffer(buffer_id, cx);
17256 } else {
17257 editor.fold_buffer(buffer_id, cx);
17258 }
17259 }
17260 })
17261 .ok();
17262 });
17263 }
17264 }
17265
17266 fn fold_at_level(
17267 &mut self,
17268 fold_at: &FoldAtLevel,
17269 window: &mut Window,
17270 cx: &mut Context<Self>,
17271 ) {
17272 if !self.buffer.read(cx).is_singleton() {
17273 return;
17274 }
17275
17276 let fold_at_level = fold_at.0;
17277 let snapshot = self.buffer.read(cx).snapshot(cx);
17278 let mut to_fold = Vec::new();
17279 let mut stack = vec![(0, snapshot.max_row().0, 1)];
17280
17281 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
17282 while start_row < end_row {
17283 match self
17284 .snapshot(window, cx)
17285 .crease_for_buffer_row(MultiBufferRow(start_row))
17286 {
17287 Some(crease) => {
17288 let nested_start_row = crease.range().start.row + 1;
17289 let nested_end_row = crease.range().end.row;
17290
17291 if current_level < fold_at_level {
17292 stack.push((nested_start_row, nested_end_row, current_level + 1));
17293 } else if current_level == fold_at_level {
17294 to_fold.push(crease);
17295 }
17296
17297 start_row = nested_end_row + 1;
17298 }
17299 None => start_row += 1,
17300 }
17301 }
17302 }
17303
17304 self.fold_creases(to_fold, true, window, cx);
17305 }
17306
17307 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
17308 if self.buffer.read(cx).is_singleton() {
17309 let mut fold_ranges = Vec::new();
17310 let snapshot = self.buffer.read(cx).snapshot(cx);
17311
17312 for row in 0..snapshot.max_row().0 {
17313 if let Some(foldable_range) = self
17314 .snapshot(window, cx)
17315 .crease_for_buffer_row(MultiBufferRow(row))
17316 {
17317 fold_ranges.push(foldable_range);
17318 }
17319 }
17320
17321 self.fold_creases(fold_ranges, true, window, cx);
17322 } else {
17323 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17324 editor
17325 .update_in(cx, |editor, _, cx| {
17326 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17327 editor.fold_buffer(buffer_id, cx);
17328 }
17329 })
17330 .ok();
17331 });
17332 }
17333 }
17334
17335 pub fn fold_function_bodies(
17336 &mut self,
17337 _: &actions::FoldFunctionBodies,
17338 window: &mut Window,
17339 cx: &mut Context<Self>,
17340 ) {
17341 let snapshot = self.buffer.read(cx).snapshot(cx);
17342
17343 let ranges = snapshot
17344 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
17345 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
17346 .collect::<Vec<_>>();
17347
17348 let creases = ranges
17349 .into_iter()
17350 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
17351 .collect();
17352
17353 self.fold_creases(creases, true, window, cx);
17354 }
17355
17356 pub fn fold_recursive(
17357 &mut self,
17358 _: &actions::FoldRecursive,
17359 window: &mut Window,
17360 cx: &mut Context<Self>,
17361 ) {
17362 let mut to_fold = Vec::new();
17363 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17364 let selections = self.selections.all_adjusted(cx);
17365
17366 for selection in selections {
17367 let range = selection.range().sorted();
17368 let buffer_start_row = range.start.row;
17369
17370 if range.start.row != range.end.row {
17371 let mut found = false;
17372 for row in range.start.row..=range.end.row {
17373 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17374 found = true;
17375 to_fold.push(crease);
17376 }
17377 }
17378 if found {
17379 continue;
17380 }
17381 }
17382
17383 for row in (0..=range.start.row).rev() {
17384 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17385 if crease.range().end.row >= buffer_start_row {
17386 to_fold.push(crease);
17387 } else {
17388 break;
17389 }
17390 }
17391 }
17392 }
17393
17394 self.fold_creases(to_fold, true, window, cx);
17395 }
17396
17397 pub fn fold_at(
17398 &mut self,
17399 buffer_row: MultiBufferRow,
17400 window: &mut Window,
17401 cx: &mut Context<Self>,
17402 ) {
17403 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17404
17405 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
17406 let autoscroll = self
17407 .selections
17408 .all::<Point>(cx)
17409 .iter()
17410 .any(|selection| crease.range().overlaps(&selection.range()));
17411
17412 self.fold_creases(vec![crease], autoscroll, window, cx);
17413 }
17414 }
17415
17416 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
17417 if self.is_singleton(cx) {
17418 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17419 let buffer = &display_map.buffer_snapshot;
17420 let selections = self.selections.all::<Point>(cx);
17421 let ranges = selections
17422 .iter()
17423 .map(|s| {
17424 let range = s.display_range(&display_map).sorted();
17425 let mut start = range.start.to_point(&display_map);
17426 let mut end = range.end.to_point(&display_map);
17427 start.column = 0;
17428 end.column = buffer.line_len(MultiBufferRow(end.row));
17429 start..end
17430 })
17431 .collect::<Vec<_>>();
17432
17433 self.unfold_ranges(&ranges, true, true, cx);
17434 } else {
17435 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17436 let buffer_ids = self
17437 .selections
17438 .disjoint_anchor_ranges()
17439 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17440 .collect::<HashSet<_>>();
17441 for buffer_id in buffer_ids {
17442 self.unfold_buffer(buffer_id, cx);
17443 }
17444 }
17445 }
17446
17447 pub fn unfold_recursive(
17448 &mut self,
17449 _: &UnfoldRecursive,
17450 _window: &mut Window,
17451 cx: &mut Context<Self>,
17452 ) {
17453 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17454 let selections = self.selections.all::<Point>(cx);
17455 let ranges = selections
17456 .iter()
17457 .map(|s| {
17458 let mut range = s.display_range(&display_map).sorted();
17459 *range.start.column_mut() = 0;
17460 *range.end.column_mut() = display_map.line_len(range.end.row());
17461 let start = range.start.to_point(&display_map);
17462 let end = range.end.to_point(&display_map);
17463 start..end
17464 })
17465 .collect::<Vec<_>>();
17466
17467 self.unfold_ranges(&ranges, true, true, cx);
17468 }
17469
17470 pub fn unfold_at(
17471 &mut self,
17472 buffer_row: MultiBufferRow,
17473 _window: &mut Window,
17474 cx: &mut Context<Self>,
17475 ) {
17476 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17477
17478 let intersection_range = Point::new(buffer_row.0, 0)
17479 ..Point::new(
17480 buffer_row.0,
17481 display_map.buffer_snapshot.line_len(buffer_row),
17482 );
17483
17484 let autoscroll = self
17485 .selections
17486 .all::<Point>(cx)
17487 .iter()
17488 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
17489
17490 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
17491 }
17492
17493 pub fn unfold_all(
17494 &mut self,
17495 _: &actions::UnfoldAll,
17496 _window: &mut Window,
17497 cx: &mut Context<Self>,
17498 ) {
17499 if self.buffer.read(cx).is_singleton() {
17500 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17501 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
17502 } else {
17503 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
17504 editor
17505 .update(cx, |editor, cx| {
17506 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17507 editor.unfold_buffer(buffer_id, cx);
17508 }
17509 })
17510 .ok();
17511 });
17512 }
17513 }
17514
17515 pub fn fold_selected_ranges(
17516 &mut self,
17517 _: &FoldSelectedRanges,
17518 window: &mut Window,
17519 cx: &mut Context<Self>,
17520 ) {
17521 let selections = self.selections.all_adjusted(cx);
17522 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17523 let ranges = selections
17524 .into_iter()
17525 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
17526 .collect::<Vec<_>>();
17527 self.fold_creases(ranges, true, window, cx);
17528 }
17529
17530 pub fn fold_ranges<T: ToOffset + Clone>(
17531 &mut self,
17532 ranges: Vec<Range<T>>,
17533 auto_scroll: bool,
17534 window: &mut Window,
17535 cx: &mut Context<Self>,
17536 ) {
17537 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17538 let ranges = ranges
17539 .into_iter()
17540 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
17541 .collect::<Vec<_>>();
17542 self.fold_creases(ranges, auto_scroll, window, cx);
17543 }
17544
17545 pub fn fold_creases<T: ToOffset + Clone>(
17546 &mut self,
17547 creases: Vec<Crease<T>>,
17548 auto_scroll: bool,
17549 _window: &mut Window,
17550 cx: &mut Context<Self>,
17551 ) {
17552 if creases.is_empty() {
17553 return;
17554 }
17555
17556 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
17557
17558 if auto_scroll {
17559 self.request_autoscroll(Autoscroll::fit(), cx);
17560 }
17561
17562 cx.notify();
17563
17564 self.scrollbar_marker_state.dirty = true;
17565 self.folds_did_change(cx);
17566 }
17567
17568 /// Removes any folds whose ranges intersect any of the given ranges.
17569 pub fn unfold_ranges<T: ToOffset + Clone>(
17570 &mut self,
17571 ranges: &[Range<T>],
17572 inclusive: bool,
17573 auto_scroll: bool,
17574 cx: &mut Context<Self>,
17575 ) {
17576 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17577 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
17578 });
17579 self.folds_did_change(cx);
17580 }
17581
17582 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17583 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
17584 return;
17585 }
17586 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17587 self.display_map.update(cx, |display_map, cx| {
17588 display_map.fold_buffers([buffer_id], cx)
17589 });
17590 cx.emit(EditorEvent::BufferFoldToggled {
17591 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
17592 folded: true,
17593 });
17594 cx.notify();
17595 }
17596
17597 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17598 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
17599 return;
17600 }
17601 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17602 self.display_map.update(cx, |display_map, cx| {
17603 display_map.unfold_buffers([buffer_id], cx);
17604 });
17605 cx.emit(EditorEvent::BufferFoldToggled {
17606 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
17607 folded: false,
17608 });
17609 cx.notify();
17610 }
17611
17612 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
17613 self.display_map.read(cx).is_buffer_folded(buffer)
17614 }
17615
17616 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
17617 self.display_map.read(cx).folded_buffers()
17618 }
17619
17620 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17621 self.display_map.update(cx, |display_map, cx| {
17622 display_map.disable_header_for_buffer(buffer_id, cx);
17623 });
17624 cx.notify();
17625 }
17626
17627 /// Removes any folds with the given ranges.
17628 pub fn remove_folds_with_type<T: ToOffset + Clone>(
17629 &mut self,
17630 ranges: &[Range<T>],
17631 type_id: TypeId,
17632 auto_scroll: bool,
17633 cx: &mut Context<Self>,
17634 ) {
17635 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17636 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
17637 });
17638 self.folds_did_change(cx);
17639 }
17640
17641 fn remove_folds_with<T: ToOffset + Clone>(
17642 &mut self,
17643 ranges: &[Range<T>],
17644 auto_scroll: bool,
17645 cx: &mut Context<Self>,
17646 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
17647 ) {
17648 if ranges.is_empty() {
17649 return;
17650 }
17651
17652 let mut buffers_affected = HashSet::default();
17653 let multi_buffer = self.buffer().read(cx);
17654 for range in ranges {
17655 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
17656 buffers_affected.insert(buffer.read(cx).remote_id());
17657 };
17658 }
17659
17660 self.display_map.update(cx, update);
17661
17662 if auto_scroll {
17663 self.request_autoscroll(Autoscroll::fit(), cx);
17664 }
17665
17666 cx.notify();
17667 self.scrollbar_marker_state.dirty = true;
17668 self.active_indent_guides_state.dirty = true;
17669 }
17670
17671 pub fn update_renderer_widths(
17672 &mut self,
17673 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
17674 cx: &mut Context<Self>,
17675 ) -> bool {
17676 self.display_map
17677 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
17678 }
17679
17680 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
17681 self.display_map.read(cx).fold_placeholder.clone()
17682 }
17683
17684 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
17685 self.buffer.update(cx, |buffer, cx| {
17686 buffer.set_all_diff_hunks_expanded(cx);
17687 });
17688 }
17689
17690 pub fn expand_all_diff_hunks(
17691 &mut self,
17692 _: &ExpandAllDiffHunks,
17693 _window: &mut Window,
17694 cx: &mut Context<Self>,
17695 ) {
17696 self.buffer.update(cx, |buffer, cx| {
17697 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
17698 });
17699 }
17700
17701 pub fn toggle_selected_diff_hunks(
17702 &mut self,
17703 _: &ToggleSelectedDiffHunks,
17704 _window: &mut Window,
17705 cx: &mut Context<Self>,
17706 ) {
17707 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17708 self.toggle_diff_hunks_in_ranges(ranges, cx);
17709 }
17710
17711 pub fn diff_hunks_in_ranges<'a>(
17712 &'a self,
17713 ranges: &'a [Range<Anchor>],
17714 buffer: &'a MultiBufferSnapshot,
17715 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
17716 ranges.iter().flat_map(move |range| {
17717 let end_excerpt_id = range.end.excerpt_id;
17718 let range = range.to_point(buffer);
17719 let mut peek_end = range.end;
17720 if range.end.row < buffer.max_row().0 {
17721 peek_end = Point::new(range.end.row + 1, 0);
17722 }
17723 buffer
17724 .diff_hunks_in_range(range.start..peek_end)
17725 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
17726 })
17727 }
17728
17729 pub fn has_stageable_diff_hunks_in_ranges(
17730 &self,
17731 ranges: &[Range<Anchor>],
17732 snapshot: &MultiBufferSnapshot,
17733 ) -> bool {
17734 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
17735 hunks.any(|hunk| hunk.status().has_secondary_hunk())
17736 }
17737
17738 pub fn toggle_staged_selected_diff_hunks(
17739 &mut self,
17740 _: &::git::ToggleStaged,
17741 _: &mut Window,
17742 cx: &mut Context<Self>,
17743 ) {
17744 let snapshot = self.buffer.read(cx).snapshot(cx);
17745 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17746 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
17747 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17748 }
17749
17750 pub fn set_render_diff_hunk_controls(
17751 &mut self,
17752 render_diff_hunk_controls: RenderDiffHunkControlsFn,
17753 cx: &mut Context<Self>,
17754 ) {
17755 self.render_diff_hunk_controls = render_diff_hunk_controls;
17756 cx.notify();
17757 }
17758
17759 pub fn stage_and_next(
17760 &mut self,
17761 _: &::git::StageAndNext,
17762 window: &mut Window,
17763 cx: &mut Context<Self>,
17764 ) {
17765 self.do_stage_or_unstage_and_next(true, window, cx);
17766 }
17767
17768 pub fn unstage_and_next(
17769 &mut self,
17770 _: &::git::UnstageAndNext,
17771 window: &mut Window,
17772 cx: &mut Context<Self>,
17773 ) {
17774 self.do_stage_or_unstage_and_next(false, window, cx);
17775 }
17776
17777 pub fn stage_or_unstage_diff_hunks(
17778 &mut self,
17779 stage: bool,
17780 ranges: Vec<Range<Anchor>>,
17781 cx: &mut Context<Self>,
17782 ) {
17783 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
17784 cx.spawn(async move |this, cx| {
17785 task.await?;
17786 this.update(cx, |this, cx| {
17787 let snapshot = this.buffer.read(cx).snapshot(cx);
17788 let chunk_by = this
17789 .diff_hunks_in_ranges(&ranges, &snapshot)
17790 .chunk_by(|hunk| hunk.buffer_id);
17791 for (buffer_id, hunks) in &chunk_by {
17792 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
17793 }
17794 })
17795 })
17796 .detach_and_log_err(cx);
17797 }
17798
17799 fn save_buffers_for_ranges_if_needed(
17800 &mut self,
17801 ranges: &[Range<Anchor>],
17802 cx: &mut Context<Editor>,
17803 ) -> Task<Result<()>> {
17804 let multibuffer = self.buffer.read(cx);
17805 let snapshot = multibuffer.read(cx);
17806 let buffer_ids: HashSet<_> = ranges
17807 .iter()
17808 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
17809 .collect();
17810 drop(snapshot);
17811
17812 let mut buffers = HashSet::default();
17813 for buffer_id in buffer_ids {
17814 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
17815 let buffer = buffer_entity.read(cx);
17816 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
17817 {
17818 buffers.insert(buffer_entity);
17819 }
17820 }
17821 }
17822
17823 if let Some(project) = &self.project {
17824 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
17825 } else {
17826 Task::ready(Ok(()))
17827 }
17828 }
17829
17830 fn do_stage_or_unstage_and_next(
17831 &mut self,
17832 stage: bool,
17833 window: &mut Window,
17834 cx: &mut Context<Self>,
17835 ) {
17836 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
17837
17838 if ranges.iter().any(|range| range.start != range.end) {
17839 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17840 return;
17841 }
17842
17843 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17844 let snapshot = self.snapshot(window, cx);
17845 let position = self.selections.newest::<Point>(cx).head();
17846 let mut row = snapshot
17847 .buffer_snapshot
17848 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
17849 .find(|hunk| hunk.row_range.start.0 > position.row)
17850 .map(|hunk| hunk.row_range.start);
17851
17852 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
17853 // Outside of the project diff editor, wrap around to the beginning.
17854 if !all_diff_hunks_expanded {
17855 row = row.or_else(|| {
17856 snapshot
17857 .buffer_snapshot
17858 .diff_hunks_in_range(Point::zero()..position)
17859 .find(|hunk| hunk.row_range.end.0 < position.row)
17860 .map(|hunk| hunk.row_range.start)
17861 });
17862 }
17863
17864 if let Some(row) = row {
17865 let destination = Point::new(row.0, 0);
17866 let autoscroll = Autoscroll::center();
17867
17868 self.unfold_ranges(&[destination..destination], false, false, cx);
17869 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17870 s.select_ranges([destination..destination]);
17871 });
17872 }
17873 }
17874
17875 fn do_stage_or_unstage(
17876 &self,
17877 stage: bool,
17878 buffer_id: BufferId,
17879 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
17880 cx: &mut App,
17881 ) -> Option<()> {
17882 let project = self.project.as_ref()?;
17883 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
17884 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
17885 let buffer_snapshot = buffer.read(cx).snapshot();
17886 let file_exists = buffer_snapshot
17887 .file()
17888 .is_some_and(|file| file.disk_state().exists());
17889 diff.update(cx, |diff, cx| {
17890 diff.stage_or_unstage_hunks(
17891 stage,
17892 &hunks
17893 .map(|hunk| buffer_diff::DiffHunk {
17894 buffer_range: hunk.buffer_range,
17895 diff_base_byte_range: hunk.diff_base_byte_range,
17896 secondary_status: hunk.secondary_status,
17897 range: Point::zero()..Point::zero(), // unused
17898 })
17899 .collect::<Vec<_>>(),
17900 &buffer_snapshot,
17901 file_exists,
17902 cx,
17903 )
17904 });
17905 None
17906 }
17907
17908 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
17909 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17910 self.buffer
17911 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
17912 }
17913
17914 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
17915 self.buffer.update(cx, |buffer, cx| {
17916 let ranges = vec![Anchor::min()..Anchor::max()];
17917 if !buffer.all_diff_hunks_expanded()
17918 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
17919 {
17920 buffer.collapse_diff_hunks(ranges, cx);
17921 true
17922 } else {
17923 false
17924 }
17925 })
17926 }
17927
17928 fn toggle_diff_hunks_in_ranges(
17929 &mut self,
17930 ranges: Vec<Range<Anchor>>,
17931 cx: &mut Context<Editor>,
17932 ) {
17933 self.buffer.update(cx, |buffer, cx| {
17934 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
17935 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
17936 })
17937 }
17938
17939 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
17940 self.buffer.update(cx, |buffer, cx| {
17941 let snapshot = buffer.snapshot(cx);
17942 let excerpt_id = range.end.excerpt_id;
17943 let point_range = range.to_point(&snapshot);
17944 let expand = !buffer.single_hunk_is_expanded(range, cx);
17945 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
17946 })
17947 }
17948
17949 pub(crate) fn apply_all_diff_hunks(
17950 &mut self,
17951 _: &ApplyAllDiffHunks,
17952 window: &mut Window,
17953 cx: &mut Context<Self>,
17954 ) {
17955 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17956
17957 let buffers = self.buffer.read(cx).all_buffers();
17958 for branch_buffer in buffers {
17959 branch_buffer.update(cx, |branch_buffer, cx| {
17960 branch_buffer.merge_into_base(Vec::new(), cx);
17961 });
17962 }
17963
17964 if let Some(project) = self.project.clone() {
17965 self.save(
17966 SaveOptions {
17967 format: true,
17968 autosave: false,
17969 },
17970 project,
17971 window,
17972 cx,
17973 )
17974 .detach_and_log_err(cx);
17975 }
17976 }
17977
17978 pub(crate) fn apply_selected_diff_hunks(
17979 &mut self,
17980 _: &ApplyDiffHunk,
17981 window: &mut Window,
17982 cx: &mut Context<Self>,
17983 ) {
17984 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17985 let snapshot = self.snapshot(window, cx);
17986 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
17987 let mut ranges_by_buffer = HashMap::default();
17988 self.transact(window, cx, |editor, _window, cx| {
17989 for hunk in hunks {
17990 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
17991 ranges_by_buffer
17992 .entry(buffer.clone())
17993 .or_insert_with(Vec::new)
17994 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
17995 }
17996 }
17997
17998 for (buffer, ranges) in ranges_by_buffer {
17999 buffer.update(cx, |buffer, cx| {
18000 buffer.merge_into_base(ranges, cx);
18001 });
18002 }
18003 });
18004
18005 if let Some(project) = self.project.clone() {
18006 self.save(
18007 SaveOptions {
18008 format: true,
18009 autosave: false,
18010 },
18011 project,
18012 window,
18013 cx,
18014 )
18015 .detach_and_log_err(cx);
18016 }
18017 }
18018
18019 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
18020 if hovered != self.gutter_hovered {
18021 self.gutter_hovered = hovered;
18022 cx.notify();
18023 }
18024 }
18025
18026 pub fn insert_blocks(
18027 &mut self,
18028 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
18029 autoscroll: Option<Autoscroll>,
18030 cx: &mut Context<Self>,
18031 ) -> Vec<CustomBlockId> {
18032 let blocks = self
18033 .display_map
18034 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
18035 if let Some(autoscroll) = autoscroll {
18036 self.request_autoscroll(autoscroll, cx);
18037 }
18038 cx.notify();
18039 blocks
18040 }
18041
18042 pub fn resize_blocks(
18043 &mut self,
18044 heights: HashMap<CustomBlockId, u32>,
18045 autoscroll: Option<Autoscroll>,
18046 cx: &mut Context<Self>,
18047 ) {
18048 self.display_map
18049 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
18050 if let Some(autoscroll) = autoscroll {
18051 self.request_autoscroll(autoscroll, cx);
18052 }
18053 cx.notify();
18054 }
18055
18056 pub fn replace_blocks(
18057 &mut self,
18058 renderers: HashMap<CustomBlockId, RenderBlock>,
18059 autoscroll: Option<Autoscroll>,
18060 cx: &mut Context<Self>,
18061 ) {
18062 self.display_map
18063 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
18064 if let Some(autoscroll) = autoscroll {
18065 self.request_autoscroll(autoscroll, cx);
18066 }
18067 cx.notify();
18068 }
18069
18070 pub fn remove_blocks(
18071 &mut self,
18072 block_ids: HashSet<CustomBlockId>,
18073 autoscroll: Option<Autoscroll>,
18074 cx: &mut Context<Self>,
18075 ) {
18076 self.display_map.update(cx, |display_map, cx| {
18077 display_map.remove_blocks(block_ids, cx)
18078 });
18079 if let Some(autoscroll) = autoscroll {
18080 self.request_autoscroll(autoscroll, cx);
18081 }
18082 cx.notify();
18083 }
18084
18085 pub fn row_for_block(
18086 &self,
18087 block_id: CustomBlockId,
18088 cx: &mut Context<Self>,
18089 ) -> Option<DisplayRow> {
18090 self.display_map
18091 .update(cx, |map, cx| map.row_for_block(block_id, cx))
18092 }
18093
18094 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
18095 self.focused_block = Some(focused_block);
18096 }
18097
18098 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
18099 self.focused_block.take()
18100 }
18101
18102 pub fn insert_creases(
18103 &mut self,
18104 creases: impl IntoIterator<Item = Crease<Anchor>>,
18105 cx: &mut Context<Self>,
18106 ) -> Vec<CreaseId> {
18107 self.display_map
18108 .update(cx, |map, cx| map.insert_creases(creases, cx))
18109 }
18110
18111 pub fn remove_creases(
18112 &mut self,
18113 ids: impl IntoIterator<Item = CreaseId>,
18114 cx: &mut Context<Self>,
18115 ) -> Vec<(CreaseId, Range<Anchor>)> {
18116 self.display_map
18117 .update(cx, |map, cx| map.remove_creases(ids, cx))
18118 }
18119
18120 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
18121 self.display_map
18122 .update(cx, |map, cx| map.snapshot(cx))
18123 .longest_row()
18124 }
18125
18126 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
18127 self.display_map
18128 .update(cx, |map, cx| map.snapshot(cx))
18129 .max_point()
18130 }
18131
18132 pub fn text(&self, cx: &App) -> String {
18133 self.buffer.read(cx).read(cx).text()
18134 }
18135
18136 pub fn is_empty(&self, cx: &App) -> bool {
18137 self.buffer.read(cx).read(cx).is_empty()
18138 }
18139
18140 pub fn text_option(&self, cx: &App) -> Option<String> {
18141 let text = self.text(cx);
18142 let text = text.trim();
18143
18144 if text.is_empty() {
18145 return None;
18146 }
18147
18148 Some(text.to_string())
18149 }
18150
18151 pub fn set_text(
18152 &mut self,
18153 text: impl Into<Arc<str>>,
18154 window: &mut Window,
18155 cx: &mut Context<Self>,
18156 ) {
18157 self.transact(window, cx, |this, _, cx| {
18158 this.buffer
18159 .read(cx)
18160 .as_singleton()
18161 .expect("you can only call set_text on editors for singleton buffers")
18162 .update(cx, |buffer, cx| buffer.set_text(text, cx));
18163 });
18164 }
18165
18166 pub fn display_text(&self, cx: &mut App) -> String {
18167 self.display_map
18168 .update(cx, |map, cx| map.snapshot(cx))
18169 .text()
18170 }
18171
18172 fn create_minimap(
18173 &self,
18174 minimap_settings: MinimapSettings,
18175 window: &mut Window,
18176 cx: &mut Context<Self>,
18177 ) -> Option<Entity<Self>> {
18178 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
18179 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
18180 }
18181
18182 fn initialize_new_minimap(
18183 &self,
18184 minimap_settings: MinimapSettings,
18185 window: &mut Window,
18186 cx: &mut Context<Self>,
18187 ) -> Entity<Self> {
18188 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
18189
18190 let mut minimap = Editor::new_internal(
18191 EditorMode::Minimap {
18192 parent: cx.weak_entity(),
18193 },
18194 self.buffer.clone(),
18195 None,
18196 Some(self.display_map.clone()),
18197 window,
18198 cx,
18199 );
18200 minimap.scroll_manager.clone_state(&self.scroll_manager);
18201 minimap.set_text_style_refinement(TextStyleRefinement {
18202 font_size: Some(MINIMAP_FONT_SIZE),
18203 font_weight: Some(MINIMAP_FONT_WEIGHT),
18204 ..Default::default()
18205 });
18206 minimap.update_minimap_configuration(minimap_settings, cx);
18207 cx.new(|_| minimap)
18208 }
18209
18210 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
18211 let current_line_highlight = minimap_settings
18212 .current_line_highlight
18213 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
18214 self.set_current_line_highlight(Some(current_line_highlight));
18215 }
18216
18217 pub fn minimap(&self) -> Option<&Entity<Self>> {
18218 self.minimap
18219 .as_ref()
18220 .filter(|_| self.minimap_visibility.visible())
18221 }
18222
18223 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
18224 let mut wrap_guides = smallvec![];
18225
18226 if self.show_wrap_guides == Some(false) {
18227 return wrap_guides;
18228 }
18229
18230 let settings = self.buffer.read(cx).language_settings(cx);
18231 if settings.show_wrap_guides {
18232 match self.soft_wrap_mode(cx) {
18233 SoftWrap::Column(soft_wrap) => {
18234 wrap_guides.push((soft_wrap as usize, true));
18235 }
18236 SoftWrap::Bounded(soft_wrap) => {
18237 wrap_guides.push((soft_wrap as usize, true));
18238 }
18239 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
18240 }
18241 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
18242 }
18243
18244 wrap_guides
18245 }
18246
18247 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
18248 let settings = self.buffer.read(cx).language_settings(cx);
18249 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
18250 match mode {
18251 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
18252 SoftWrap::None
18253 }
18254 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
18255 language_settings::SoftWrap::PreferredLineLength => {
18256 SoftWrap::Column(settings.preferred_line_length)
18257 }
18258 language_settings::SoftWrap::Bounded => {
18259 SoftWrap::Bounded(settings.preferred_line_length)
18260 }
18261 }
18262 }
18263
18264 pub fn set_soft_wrap_mode(
18265 &mut self,
18266 mode: language_settings::SoftWrap,
18267
18268 cx: &mut Context<Self>,
18269 ) {
18270 self.soft_wrap_mode_override = Some(mode);
18271 cx.notify();
18272 }
18273
18274 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
18275 self.hard_wrap = hard_wrap;
18276 cx.notify();
18277 }
18278
18279 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
18280 self.text_style_refinement = Some(style);
18281 }
18282
18283 /// called by the Element so we know what style we were most recently rendered with.
18284 pub(crate) fn set_style(
18285 &mut self,
18286 style: EditorStyle,
18287 window: &mut Window,
18288 cx: &mut Context<Self>,
18289 ) {
18290 // We intentionally do not inform the display map about the minimap style
18291 // so that wrapping is not recalculated and stays consistent for the editor
18292 // and its linked minimap.
18293 if !self.mode.is_minimap() {
18294 let rem_size = window.rem_size();
18295 self.display_map.update(cx, |map, cx| {
18296 map.set_font(
18297 style.text.font(),
18298 style.text.font_size.to_pixels(rem_size),
18299 cx,
18300 )
18301 });
18302 }
18303 self.style = Some(style);
18304 }
18305
18306 pub fn style(&self) -> Option<&EditorStyle> {
18307 self.style.as_ref()
18308 }
18309
18310 // Called by the element. This method is not designed to be called outside of the editor
18311 // element's layout code because it does not notify when rewrapping is computed synchronously.
18312 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
18313 self.display_map
18314 .update(cx, |map, cx| map.set_wrap_width(width, cx))
18315 }
18316
18317 pub fn set_soft_wrap(&mut self) {
18318 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
18319 }
18320
18321 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
18322 if self.soft_wrap_mode_override.is_some() {
18323 self.soft_wrap_mode_override.take();
18324 } else {
18325 let soft_wrap = match self.soft_wrap_mode(cx) {
18326 SoftWrap::GitDiff => return,
18327 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
18328 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
18329 language_settings::SoftWrap::None
18330 }
18331 };
18332 self.soft_wrap_mode_override = Some(soft_wrap);
18333 }
18334 cx.notify();
18335 }
18336
18337 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
18338 let Some(workspace) = self.workspace() else {
18339 return;
18340 };
18341 let fs = workspace.read(cx).app_state().fs.clone();
18342 let current_show = TabBarSettings::get_global(cx).show;
18343 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
18344 setting.show = Some(!current_show);
18345 });
18346 }
18347
18348 pub fn toggle_indent_guides(
18349 &mut self,
18350 _: &ToggleIndentGuides,
18351 _: &mut Window,
18352 cx: &mut Context<Self>,
18353 ) {
18354 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
18355 self.buffer
18356 .read(cx)
18357 .language_settings(cx)
18358 .indent_guides
18359 .enabled
18360 });
18361 self.show_indent_guides = Some(!currently_enabled);
18362 cx.notify();
18363 }
18364
18365 fn should_show_indent_guides(&self) -> Option<bool> {
18366 self.show_indent_guides
18367 }
18368
18369 pub fn toggle_line_numbers(
18370 &mut self,
18371 _: &ToggleLineNumbers,
18372 _: &mut Window,
18373 cx: &mut Context<Self>,
18374 ) {
18375 let mut editor_settings = EditorSettings::get_global(cx).clone();
18376 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
18377 EditorSettings::override_global(editor_settings, cx);
18378 }
18379
18380 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
18381 if let Some(show_line_numbers) = self.show_line_numbers {
18382 return show_line_numbers;
18383 }
18384 EditorSettings::get_global(cx).gutter.line_numbers
18385 }
18386
18387 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
18388 self.use_relative_line_numbers
18389 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
18390 }
18391
18392 pub fn toggle_relative_line_numbers(
18393 &mut self,
18394 _: &ToggleRelativeLineNumbers,
18395 _: &mut Window,
18396 cx: &mut Context<Self>,
18397 ) {
18398 let is_relative = self.should_use_relative_line_numbers(cx);
18399 self.set_relative_line_number(Some(!is_relative), cx)
18400 }
18401
18402 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
18403 self.use_relative_line_numbers = is_relative;
18404 cx.notify();
18405 }
18406
18407 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
18408 self.show_gutter = show_gutter;
18409 cx.notify();
18410 }
18411
18412 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
18413 self.show_scrollbars = ScrollbarAxes {
18414 horizontal: show,
18415 vertical: show,
18416 };
18417 cx.notify();
18418 }
18419
18420 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18421 self.show_scrollbars.vertical = show;
18422 cx.notify();
18423 }
18424
18425 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18426 self.show_scrollbars.horizontal = show;
18427 cx.notify();
18428 }
18429
18430 pub fn set_minimap_visibility(
18431 &mut self,
18432 minimap_visibility: MinimapVisibility,
18433 window: &mut Window,
18434 cx: &mut Context<Self>,
18435 ) {
18436 if self.minimap_visibility != minimap_visibility {
18437 if minimap_visibility.visible() && self.minimap.is_none() {
18438 let minimap_settings = EditorSettings::get_global(cx).minimap;
18439 self.minimap =
18440 self.create_minimap(minimap_settings.with_show_override(), window, cx);
18441 }
18442 self.minimap_visibility = minimap_visibility;
18443 cx.notify();
18444 }
18445 }
18446
18447 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18448 self.set_show_scrollbars(false, cx);
18449 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
18450 }
18451
18452 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18453 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
18454 }
18455
18456 /// Normally the text in full mode and auto height editors is padded on the
18457 /// left side by roughly half a character width for improved hit testing.
18458 ///
18459 /// Use this method to disable this for cases where this is not wanted (e.g.
18460 /// if you want to align the editor text with some other text above or below)
18461 /// or if you want to add this padding to single-line editors.
18462 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
18463 self.offset_content = offset_content;
18464 cx.notify();
18465 }
18466
18467 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
18468 self.show_line_numbers = Some(show_line_numbers);
18469 cx.notify();
18470 }
18471
18472 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
18473 self.disable_expand_excerpt_buttons = true;
18474 cx.notify();
18475 }
18476
18477 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
18478 self.show_git_diff_gutter = Some(show_git_diff_gutter);
18479 cx.notify();
18480 }
18481
18482 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
18483 self.show_code_actions = Some(show_code_actions);
18484 cx.notify();
18485 }
18486
18487 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
18488 self.show_runnables = Some(show_runnables);
18489 cx.notify();
18490 }
18491
18492 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
18493 self.show_breakpoints = Some(show_breakpoints);
18494 cx.notify();
18495 }
18496
18497 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
18498 if self.display_map.read(cx).masked != masked {
18499 self.display_map.update(cx, |map, _| map.masked = masked);
18500 }
18501 cx.notify()
18502 }
18503
18504 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
18505 self.show_wrap_guides = Some(show_wrap_guides);
18506 cx.notify();
18507 }
18508
18509 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
18510 self.show_indent_guides = Some(show_indent_guides);
18511 cx.notify();
18512 }
18513
18514 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
18515 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
18516 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
18517 if let Some(dir) = file.abs_path(cx).parent() {
18518 return Some(dir.to_owned());
18519 }
18520 }
18521
18522 if let Some(project_path) = buffer.read(cx).project_path(cx) {
18523 return Some(project_path.path.to_path_buf());
18524 }
18525 }
18526
18527 None
18528 }
18529
18530 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
18531 self.active_excerpt(cx)?
18532 .1
18533 .read(cx)
18534 .file()
18535 .and_then(|f| f.as_local())
18536 }
18537
18538 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18539 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18540 let buffer = buffer.read(cx);
18541 if let Some(project_path) = buffer.project_path(cx) {
18542 let project = self.project.as_ref()?.read(cx);
18543 project.absolute_path(&project_path, cx)
18544 } else {
18545 buffer
18546 .file()
18547 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
18548 }
18549 })
18550 }
18551
18552 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18553 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18554 let project_path = buffer.read(cx).project_path(cx)?;
18555 let project = self.project.as_ref()?.read(cx);
18556 let entry = project.entry_for_path(&project_path, cx)?;
18557 let path = entry.path.to_path_buf();
18558 Some(path)
18559 })
18560 }
18561
18562 pub fn reveal_in_finder(
18563 &mut self,
18564 _: &RevealInFileManager,
18565 _window: &mut Window,
18566 cx: &mut Context<Self>,
18567 ) {
18568 if let Some(target) = self.target_file(cx) {
18569 cx.reveal_path(&target.abs_path(cx));
18570 }
18571 }
18572
18573 pub fn copy_path(
18574 &mut self,
18575 _: &zed_actions::workspace::CopyPath,
18576 _window: &mut Window,
18577 cx: &mut Context<Self>,
18578 ) {
18579 if let Some(path) = self.target_file_abs_path(cx) {
18580 if let Some(path) = path.to_str() {
18581 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18582 }
18583 }
18584 }
18585
18586 pub fn copy_relative_path(
18587 &mut self,
18588 _: &zed_actions::workspace::CopyRelativePath,
18589 _window: &mut Window,
18590 cx: &mut Context<Self>,
18591 ) {
18592 if let Some(path) = self.target_file_path(cx) {
18593 if let Some(path) = path.to_str() {
18594 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18595 }
18596 }
18597 }
18598
18599 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
18600 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
18601 buffer.read(cx).project_path(cx)
18602 } else {
18603 None
18604 }
18605 }
18606
18607 // Returns true if the editor handled a go-to-line request
18608 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
18609 maybe!({
18610 let breakpoint_store = self.breakpoint_store.as_ref()?;
18611
18612 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
18613 else {
18614 self.clear_row_highlights::<ActiveDebugLine>();
18615 return None;
18616 };
18617
18618 let position = active_stack_frame.position;
18619 let buffer_id = position.buffer_id?;
18620 let snapshot = self
18621 .project
18622 .as_ref()?
18623 .read(cx)
18624 .buffer_for_id(buffer_id, cx)?
18625 .read(cx)
18626 .snapshot();
18627
18628 let mut handled = false;
18629 for (id, ExcerptRange { context, .. }) in
18630 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
18631 {
18632 if context.start.cmp(&position, &snapshot).is_ge()
18633 || context.end.cmp(&position, &snapshot).is_lt()
18634 {
18635 continue;
18636 }
18637 let snapshot = self.buffer.read(cx).snapshot(cx);
18638 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
18639
18640 handled = true;
18641 self.clear_row_highlights::<ActiveDebugLine>();
18642
18643 self.go_to_line::<ActiveDebugLine>(
18644 multibuffer_anchor,
18645 Some(cx.theme().colors().editor_debugger_active_line_background),
18646 window,
18647 cx,
18648 );
18649
18650 cx.notify();
18651 }
18652
18653 handled.then_some(())
18654 })
18655 .is_some()
18656 }
18657
18658 pub fn copy_file_name_without_extension(
18659 &mut self,
18660 _: &CopyFileNameWithoutExtension,
18661 _: &mut Window,
18662 cx: &mut Context<Self>,
18663 ) {
18664 if let Some(file) = self.target_file(cx) {
18665 if let Some(file_stem) = file.path().file_stem() {
18666 if let Some(name) = file_stem.to_str() {
18667 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18668 }
18669 }
18670 }
18671 }
18672
18673 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
18674 if let Some(file) = self.target_file(cx) {
18675 if let Some(file_name) = file.path().file_name() {
18676 if let Some(name) = file_name.to_str() {
18677 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18678 }
18679 }
18680 }
18681 }
18682
18683 pub fn toggle_git_blame(
18684 &mut self,
18685 _: &::git::Blame,
18686 window: &mut Window,
18687 cx: &mut Context<Self>,
18688 ) {
18689 self.show_git_blame_gutter = !self.show_git_blame_gutter;
18690
18691 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
18692 self.start_git_blame(true, window, cx);
18693 }
18694
18695 cx.notify();
18696 }
18697
18698 pub fn toggle_git_blame_inline(
18699 &mut self,
18700 _: &ToggleGitBlameInline,
18701 window: &mut Window,
18702 cx: &mut Context<Self>,
18703 ) {
18704 self.toggle_git_blame_inline_internal(true, window, cx);
18705 cx.notify();
18706 }
18707
18708 pub fn open_git_blame_commit(
18709 &mut self,
18710 _: &OpenGitBlameCommit,
18711 window: &mut Window,
18712 cx: &mut Context<Self>,
18713 ) {
18714 self.open_git_blame_commit_internal(window, cx);
18715 }
18716
18717 fn open_git_blame_commit_internal(
18718 &mut self,
18719 window: &mut Window,
18720 cx: &mut Context<Self>,
18721 ) -> Option<()> {
18722 let blame = self.blame.as_ref()?;
18723 let snapshot = self.snapshot(window, cx);
18724 let cursor = self.selections.newest::<Point>(cx).head();
18725 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
18726 let blame_entry = blame
18727 .update(cx, |blame, cx| {
18728 blame
18729 .blame_for_rows(
18730 &[RowInfo {
18731 buffer_id: Some(buffer.remote_id()),
18732 buffer_row: Some(point.row),
18733 ..Default::default()
18734 }],
18735 cx,
18736 )
18737 .next()
18738 })
18739 .flatten()?;
18740 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
18741 let repo = blame.read(cx).repository(cx)?;
18742 let workspace = self.workspace()?.downgrade();
18743 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
18744 None
18745 }
18746
18747 pub fn git_blame_inline_enabled(&self) -> bool {
18748 self.git_blame_inline_enabled
18749 }
18750
18751 pub fn toggle_selection_menu(
18752 &mut self,
18753 _: &ToggleSelectionMenu,
18754 _: &mut Window,
18755 cx: &mut Context<Self>,
18756 ) {
18757 self.show_selection_menu = self
18758 .show_selection_menu
18759 .map(|show_selections_menu| !show_selections_menu)
18760 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
18761
18762 cx.notify();
18763 }
18764
18765 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
18766 self.show_selection_menu
18767 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
18768 }
18769
18770 fn start_git_blame(
18771 &mut self,
18772 user_triggered: bool,
18773 window: &mut Window,
18774 cx: &mut Context<Self>,
18775 ) {
18776 if let Some(project) = self.project.as_ref() {
18777 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
18778 return;
18779 };
18780
18781 if buffer.read(cx).file().is_none() {
18782 return;
18783 }
18784
18785 let focused = self.focus_handle(cx).contains_focused(window, cx);
18786
18787 let project = project.clone();
18788 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
18789 self.blame_subscription =
18790 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
18791 self.blame = Some(blame);
18792 }
18793 }
18794
18795 fn toggle_git_blame_inline_internal(
18796 &mut self,
18797 user_triggered: bool,
18798 window: &mut Window,
18799 cx: &mut Context<Self>,
18800 ) {
18801 if self.git_blame_inline_enabled {
18802 self.git_blame_inline_enabled = false;
18803 self.show_git_blame_inline = false;
18804 self.show_git_blame_inline_delay_task.take();
18805 } else {
18806 self.git_blame_inline_enabled = true;
18807 self.start_git_blame_inline(user_triggered, window, cx);
18808 }
18809
18810 cx.notify();
18811 }
18812
18813 fn start_git_blame_inline(
18814 &mut self,
18815 user_triggered: bool,
18816 window: &mut Window,
18817 cx: &mut Context<Self>,
18818 ) {
18819 self.start_git_blame(user_triggered, window, cx);
18820
18821 if ProjectSettings::get_global(cx)
18822 .git
18823 .inline_blame_delay()
18824 .is_some()
18825 {
18826 self.start_inline_blame_timer(window, cx);
18827 } else {
18828 self.show_git_blame_inline = true
18829 }
18830 }
18831
18832 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
18833 self.blame.as_ref()
18834 }
18835
18836 pub fn show_git_blame_gutter(&self) -> bool {
18837 self.show_git_blame_gutter
18838 }
18839
18840 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
18841 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
18842 }
18843
18844 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
18845 self.show_git_blame_inline
18846 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
18847 && !self.newest_selection_head_on_empty_line(cx)
18848 && self.has_blame_entries(cx)
18849 }
18850
18851 fn has_blame_entries(&self, cx: &App) -> bool {
18852 self.blame()
18853 .map_or(false, |blame| blame.read(cx).has_generated_entries())
18854 }
18855
18856 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
18857 let cursor_anchor = self.selections.newest_anchor().head();
18858
18859 let snapshot = self.buffer.read(cx).snapshot(cx);
18860 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
18861
18862 snapshot.line_len(buffer_row) == 0
18863 }
18864
18865 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
18866 let buffer_and_selection = maybe!({
18867 let selection = self.selections.newest::<Point>(cx);
18868 let selection_range = selection.range();
18869
18870 let multi_buffer = self.buffer().read(cx);
18871 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18872 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
18873
18874 let (buffer, range, _) = if selection.reversed {
18875 buffer_ranges.first()
18876 } else {
18877 buffer_ranges.last()
18878 }?;
18879
18880 let selection = text::ToPoint::to_point(&range.start, &buffer).row
18881 ..text::ToPoint::to_point(&range.end, &buffer).row;
18882 Some((
18883 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
18884 selection,
18885 ))
18886 });
18887
18888 let Some((buffer, selection)) = buffer_and_selection else {
18889 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
18890 };
18891
18892 let Some(project) = self.project.as_ref() else {
18893 return Task::ready(Err(anyhow!("editor does not have project")));
18894 };
18895
18896 project.update(cx, |project, cx| {
18897 project.get_permalink_to_line(&buffer, selection, cx)
18898 })
18899 }
18900
18901 pub fn copy_permalink_to_line(
18902 &mut self,
18903 _: &CopyPermalinkToLine,
18904 window: &mut Window,
18905 cx: &mut Context<Self>,
18906 ) {
18907 let permalink_task = self.get_permalink_to_line(cx);
18908 let workspace = self.workspace();
18909
18910 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18911 Ok(permalink) => {
18912 cx.update(|_, cx| {
18913 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
18914 })
18915 .ok();
18916 }
18917 Err(err) => {
18918 let message = format!("Failed to copy permalink: {err}");
18919
18920 anyhow::Result::<()>::Err(err).log_err();
18921
18922 if let Some(workspace) = workspace {
18923 workspace
18924 .update_in(cx, |workspace, _, cx| {
18925 struct CopyPermalinkToLine;
18926
18927 workspace.show_toast(
18928 Toast::new(
18929 NotificationId::unique::<CopyPermalinkToLine>(),
18930 message,
18931 ),
18932 cx,
18933 )
18934 })
18935 .ok();
18936 }
18937 }
18938 })
18939 .detach();
18940 }
18941
18942 pub fn copy_file_location(
18943 &mut self,
18944 _: &CopyFileLocation,
18945 _: &mut Window,
18946 cx: &mut Context<Self>,
18947 ) {
18948 let selection = self.selections.newest::<Point>(cx).start.row + 1;
18949 if let Some(file) = self.target_file(cx) {
18950 if let Some(path) = file.path().to_str() {
18951 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
18952 }
18953 }
18954 }
18955
18956 pub fn open_permalink_to_line(
18957 &mut self,
18958 _: &OpenPermalinkToLine,
18959 window: &mut Window,
18960 cx: &mut Context<Self>,
18961 ) {
18962 let permalink_task = self.get_permalink_to_line(cx);
18963 let workspace = self.workspace();
18964
18965 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18966 Ok(permalink) => {
18967 cx.update(|_, cx| {
18968 cx.open_url(permalink.as_ref());
18969 })
18970 .ok();
18971 }
18972 Err(err) => {
18973 let message = format!("Failed to open permalink: {err}");
18974
18975 anyhow::Result::<()>::Err(err).log_err();
18976
18977 if let Some(workspace) = workspace {
18978 workspace
18979 .update(cx, |workspace, cx| {
18980 struct OpenPermalinkToLine;
18981
18982 workspace.show_toast(
18983 Toast::new(
18984 NotificationId::unique::<OpenPermalinkToLine>(),
18985 message,
18986 ),
18987 cx,
18988 )
18989 })
18990 .ok();
18991 }
18992 }
18993 })
18994 .detach();
18995 }
18996
18997 pub fn insert_uuid_v4(
18998 &mut self,
18999 _: &InsertUuidV4,
19000 window: &mut Window,
19001 cx: &mut Context<Self>,
19002 ) {
19003 self.insert_uuid(UuidVersion::V4, window, cx);
19004 }
19005
19006 pub fn insert_uuid_v7(
19007 &mut self,
19008 _: &InsertUuidV7,
19009 window: &mut Window,
19010 cx: &mut Context<Self>,
19011 ) {
19012 self.insert_uuid(UuidVersion::V7, window, cx);
19013 }
19014
19015 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
19016 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19017 self.transact(window, cx, |this, window, cx| {
19018 let edits = this
19019 .selections
19020 .all::<Point>(cx)
19021 .into_iter()
19022 .map(|selection| {
19023 let uuid = match version {
19024 UuidVersion::V4 => uuid::Uuid::new_v4(),
19025 UuidVersion::V7 => uuid::Uuid::now_v7(),
19026 };
19027
19028 (selection.range(), uuid.to_string())
19029 });
19030 this.edit(edits, cx);
19031 this.refresh_inline_completion(true, false, window, cx);
19032 });
19033 }
19034
19035 pub fn open_selections_in_multibuffer(
19036 &mut self,
19037 _: &OpenSelectionsInMultibuffer,
19038 window: &mut Window,
19039 cx: &mut Context<Self>,
19040 ) {
19041 let multibuffer = self.buffer.read(cx);
19042
19043 let Some(buffer) = multibuffer.as_singleton() else {
19044 return;
19045 };
19046
19047 let Some(workspace) = self.workspace() else {
19048 return;
19049 };
19050
19051 let title = multibuffer.title(cx).to_string();
19052
19053 let locations = self
19054 .selections
19055 .all_anchors(cx)
19056 .into_iter()
19057 .map(|selection| Location {
19058 buffer: buffer.clone(),
19059 range: selection.start.text_anchor..selection.end.text_anchor,
19060 })
19061 .collect::<Vec<_>>();
19062
19063 cx.spawn_in(window, async move |_, cx| {
19064 workspace.update_in(cx, |workspace, window, cx| {
19065 Self::open_locations_in_multibuffer(
19066 workspace,
19067 locations,
19068 format!("Selections for '{title}'"),
19069 false,
19070 MultibufferSelectionMode::All,
19071 window,
19072 cx,
19073 );
19074 })
19075 })
19076 .detach();
19077 }
19078
19079 /// Adds a row highlight for the given range. If a row has multiple highlights, the
19080 /// last highlight added will be used.
19081 ///
19082 /// If the range ends at the beginning of a line, then that line will not be highlighted.
19083 pub fn highlight_rows<T: 'static>(
19084 &mut self,
19085 range: Range<Anchor>,
19086 color: Hsla,
19087 options: RowHighlightOptions,
19088 cx: &mut Context<Self>,
19089 ) {
19090 let snapshot = self.buffer().read(cx).snapshot(cx);
19091 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19092 let ix = row_highlights.binary_search_by(|highlight| {
19093 Ordering::Equal
19094 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
19095 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
19096 });
19097
19098 if let Err(mut ix) = ix {
19099 let index = post_inc(&mut self.highlight_order);
19100
19101 // If this range intersects with the preceding highlight, then merge it with
19102 // the preceding highlight. Otherwise insert a new highlight.
19103 let mut merged = false;
19104 if ix > 0 {
19105 let prev_highlight = &mut row_highlights[ix - 1];
19106 if prev_highlight
19107 .range
19108 .end
19109 .cmp(&range.start, &snapshot)
19110 .is_ge()
19111 {
19112 ix -= 1;
19113 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
19114 prev_highlight.range.end = range.end;
19115 }
19116 merged = true;
19117 prev_highlight.index = index;
19118 prev_highlight.color = color;
19119 prev_highlight.options = options;
19120 }
19121 }
19122
19123 if !merged {
19124 row_highlights.insert(
19125 ix,
19126 RowHighlight {
19127 range: range.clone(),
19128 index,
19129 color,
19130 options,
19131 type_id: TypeId::of::<T>(),
19132 },
19133 );
19134 }
19135
19136 // If any of the following highlights intersect with this one, merge them.
19137 while let Some(next_highlight) = row_highlights.get(ix + 1) {
19138 let highlight = &row_highlights[ix];
19139 if next_highlight
19140 .range
19141 .start
19142 .cmp(&highlight.range.end, &snapshot)
19143 .is_le()
19144 {
19145 if next_highlight
19146 .range
19147 .end
19148 .cmp(&highlight.range.end, &snapshot)
19149 .is_gt()
19150 {
19151 row_highlights[ix].range.end = next_highlight.range.end;
19152 }
19153 row_highlights.remove(ix + 1);
19154 } else {
19155 break;
19156 }
19157 }
19158 }
19159 }
19160
19161 /// Remove any highlighted row ranges of the given type that intersect the
19162 /// given ranges.
19163 pub fn remove_highlighted_rows<T: 'static>(
19164 &mut self,
19165 ranges_to_remove: Vec<Range<Anchor>>,
19166 cx: &mut Context<Self>,
19167 ) {
19168 let snapshot = self.buffer().read(cx).snapshot(cx);
19169 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19170 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19171 row_highlights.retain(|highlight| {
19172 while let Some(range_to_remove) = ranges_to_remove.peek() {
19173 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
19174 Ordering::Less | Ordering::Equal => {
19175 ranges_to_remove.next();
19176 }
19177 Ordering::Greater => {
19178 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
19179 Ordering::Less | Ordering::Equal => {
19180 return false;
19181 }
19182 Ordering::Greater => break,
19183 }
19184 }
19185 }
19186 }
19187
19188 true
19189 })
19190 }
19191
19192 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
19193 pub fn clear_row_highlights<T: 'static>(&mut self) {
19194 self.highlighted_rows.remove(&TypeId::of::<T>());
19195 }
19196
19197 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
19198 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
19199 self.highlighted_rows
19200 .get(&TypeId::of::<T>())
19201 .map_or(&[] as &[_], |vec| vec.as_slice())
19202 .iter()
19203 .map(|highlight| (highlight.range.clone(), highlight.color))
19204 }
19205
19206 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
19207 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
19208 /// Allows to ignore certain kinds of highlights.
19209 pub fn highlighted_display_rows(
19210 &self,
19211 window: &mut Window,
19212 cx: &mut App,
19213 ) -> BTreeMap<DisplayRow, LineHighlight> {
19214 let snapshot = self.snapshot(window, cx);
19215 let mut used_highlight_orders = HashMap::default();
19216 self.highlighted_rows
19217 .iter()
19218 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
19219 .fold(
19220 BTreeMap::<DisplayRow, LineHighlight>::new(),
19221 |mut unique_rows, highlight| {
19222 let start = highlight.range.start.to_display_point(&snapshot);
19223 let end = highlight.range.end.to_display_point(&snapshot);
19224 let start_row = start.row().0;
19225 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
19226 && end.column() == 0
19227 {
19228 end.row().0.saturating_sub(1)
19229 } else {
19230 end.row().0
19231 };
19232 for row in start_row..=end_row {
19233 let used_index =
19234 used_highlight_orders.entry(row).or_insert(highlight.index);
19235 if highlight.index >= *used_index {
19236 *used_index = highlight.index;
19237 unique_rows.insert(
19238 DisplayRow(row),
19239 LineHighlight {
19240 include_gutter: highlight.options.include_gutter,
19241 border: None,
19242 background: highlight.color.into(),
19243 type_id: Some(highlight.type_id),
19244 },
19245 );
19246 }
19247 }
19248 unique_rows
19249 },
19250 )
19251 }
19252
19253 pub fn highlighted_display_row_for_autoscroll(
19254 &self,
19255 snapshot: &DisplaySnapshot,
19256 ) -> Option<DisplayRow> {
19257 self.highlighted_rows
19258 .values()
19259 .flat_map(|highlighted_rows| highlighted_rows.iter())
19260 .filter_map(|highlight| {
19261 if highlight.options.autoscroll {
19262 Some(highlight.range.start.to_display_point(snapshot).row())
19263 } else {
19264 None
19265 }
19266 })
19267 .min()
19268 }
19269
19270 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
19271 self.highlight_background::<SearchWithinRange>(
19272 ranges,
19273 |colors| colors.colors().editor_document_highlight_read_background,
19274 cx,
19275 )
19276 }
19277
19278 pub fn set_breadcrumb_header(&mut self, new_header: String) {
19279 self.breadcrumb_header = Some(new_header);
19280 }
19281
19282 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
19283 self.clear_background_highlights::<SearchWithinRange>(cx);
19284 }
19285
19286 pub fn highlight_background<T: 'static>(
19287 &mut self,
19288 ranges: &[Range<Anchor>],
19289 color_fetcher: fn(&Theme) -> Hsla,
19290 cx: &mut Context<Self>,
19291 ) {
19292 self.background_highlights.insert(
19293 HighlightKey::Type(TypeId::of::<T>()),
19294 (color_fetcher, Arc::from(ranges)),
19295 );
19296 self.scrollbar_marker_state.dirty = true;
19297 cx.notify();
19298 }
19299
19300 pub fn highlight_background_key<T: 'static>(
19301 &mut self,
19302 key: usize,
19303 ranges: &[Range<Anchor>],
19304 color_fetcher: fn(&Theme) -> Hsla,
19305 cx: &mut Context<Self>,
19306 ) {
19307 self.background_highlights.insert(
19308 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19309 (color_fetcher, Arc::from(ranges)),
19310 );
19311 self.scrollbar_marker_state.dirty = true;
19312 cx.notify();
19313 }
19314
19315 pub fn clear_background_highlights<T: 'static>(
19316 &mut self,
19317 cx: &mut Context<Self>,
19318 ) -> Option<BackgroundHighlight> {
19319 let text_highlights = self
19320 .background_highlights
19321 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
19322 if !text_highlights.1.is_empty() {
19323 self.scrollbar_marker_state.dirty = true;
19324 cx.notify();
19325 }
19326 Some(text_highlights)
19327 }
19328
19329 pub fn highlight_gutter<T: 'static>(
19330 &mut self,
19331 ranges: impl Into<Vec<Range<Anchor>>>,
19332 color_fetcher: fn(&App) -> Hsla,
19333 cx: &mut Context<Self>,
19334 ) {
19335 self.gutter_highlights
19336 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
19337 cx.notify();
19338 }
19339
19340 pub fn clear_gutter_highlights<T: 'static>(
19341 &mut self,
19342 cx: &mut Context<Self>,
19343 ) -> Option<GutterHighlight> {
19344 cx.notify();
19345 self.gutter_highlights.remove(&TypeId::of::<T>())
19346 }
19347
19348 pub fn insert_gutter_highlight<T: 'static>(
19349 &mut self,
19350 range: Range<Anchor>,
19351 color_fetcher: fn(&App) -> Hsla,
19352 cx: &mut Context<Self>,
19353 ) {
19354 let snapshot = self.buffer().read(cx).snapshot(cx);
19355 let mut highlights = self
19356 .gutter_highlights
19357 .remove(&TypeId::of::<T>())
19358 .map(|(_, highlights)| highlights)
19359 .unwrap_or_default();
19360 let ix = highlights.binary_search_by(|highlight| {
19361 Ordering::Equal
19362 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
19363 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
19364 });
19365 if let Err(ix) = ix {
19366 highlights.insert(ix, range);
19367 }
19368 self.gutter_highlights
19369 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
19370 }
19371
19372 pub fn remove_gutter_highlights<T: 'static>(
19373 &mut self,
19374 ranges_to_remove: Vec<Range<Anchor>>,
19375 cx: &mut Context<Self>,
19376 ) {
19377 let snapshot = self.buffer().read(cx).snapshot(cx);
19378 let Some((color_fetcher, mut gutter_highlights)) =
19379 self.gutter_highlights.remove(&TypeId::of::<T>())
19380 else {
19381 return;
19382 };
19383 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19384 gutter_highlights.retain(|highlight| {
19385 while let Some(range_to_remove) = ranges_to_remove.peek() {
19386 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
19387 Ordering::Less | Ordering::Equal => {
19388 ranges_to_remove.next();
19389 }
19390 Ordering::Greater => {
19391 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
19392 Ordering::Less | Ordering::Equal => {
19393 return false;
19394 }
19395 Ordering::Greater => break,
19396 }
19397 }
19398 }
19399 }
19400
19401 true
19402 });
19403 self.gutter_highlights
19404 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
19405 }
19406
19407 #[cfg(feature = "test-support")]
19408 pub fn all_text_highlights(
19409 &self,
19410 window: &mut Window,
19411 cx: &mut Context<Self>,
19412 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
19413 let snapshot = self.snapshot(window, cx);
19414 self.display_map.update(cx, |display_map, _| {
19415 display_map
19416 .all_text_highlights()
19417 .map(|highlight| {
19418 let (style, ranges) = highlight.as_ref();
19419 (
19420 *style,
19421 ranges
19422 .iter()
19423 .map(|range| range.clone().to_display_points(&snapshot))
19424 .collect(),
19425 )
19426 })
19427 .collect()
19428 })
19429 }
19430
19431 #[cfg(feature = "test-support")]
19432 pub fn all_text_background_highlights(
19433 &self,
19434 window: &mut Window,
19435 cx: &mut Context<Self>,
19436 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19437 let snapshot = self.snapshot(window, cx);
19438 let buffer = &snapshot.buffer_snapshot;
19439 let start = buffer.anchor_before(0);
19440 let end = buffer.anchor_after(buffer.len());
19441 self.background_highlights_in_range(start..end, &snapshot, cx.theme())
19442 }
19443
19444 #[cfg(feature = "test-support")]
19445 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
19446 let snapshot = self.buffer().read(cx).snapshot(cx);
19447
19448 let highlights = self
19449 .background_highlights
19450 .get(&HighlightKey::Type(TypeId::of::<
19451 items::BufferSearchHighlights,
19452 >()));
19453
19454 if let Some((_color, ranges)) = highlights {
19455 ranges
19456 .iter()
19457 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
19458 .collect_vec()
19459 } else {
19460 vec![]
19461 }
19462 }
19463
19464 fn document_highlights_for_position<'a>(
19465 &'a self,
19466 position: Anchor,
19467 buffer: &'a MultiBufferSnapshot,
19468 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
19469 let read_highlights = self
19470 .background_highlights
19471 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
19472 .map(|h| &h.1);
19473 let write_highlights = self
19474 .background_highlights
19475 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
19476 .map(|h| &h.1);
19477 let left_position = position.bias_left(buffer);
19478 let right_position = position.bias_right(buffer);
19479 read_highlights
19480 .into_iter()
19481 .chain(write_highlights)
19482 .flat_map(move |ranges| {
19483 let start_ix = match ranges.binary_search_by(|probe| {
19484 let cmp = probe.end.cmp(&left_position, buffer);
19485 if cmp.is_ge() {
19486 Ordering::Greater
19487 } else {
19488 Ordering::Less
19489 }
19490 }) {
19491 Ok(i) | Err(i) => i,
19492 };
19493
19494 ranges[start_ix..]
19495 .iter()
19496 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
19497 })
19498 }
19499
19500 pub fn has_background_highlights<T: 'static>(&self) -> bool {
19501 self.background_highlights
19502 .get(&HighlightKey::Type(TypeId::of::<T>()))
19503 .map_or(false, |(_, highlights)| !highlights.is_empty())
19504 }
19505
19506 pub fn background_highlights_in_range(
19507 &self,
19508 search_range: Range<Anchor>,
19509 display_snapshot: &DisplaySnapshot,
19510 theme: &Theme,
19511 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19512 let mut results = Vec::new();
19513 for (color_fetcher, ranges) in self.background_highlights.values() {
19514 let color = color_fetcher(theme);
19515 let start_ix = match ranges.binary_search_by(|probe| {
19516 let cmp = probe
19517 .end
19518 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19519 if cmp.is_gt() {
19520 Ordering::Greater
19521 } else {
19522 Ordering::Less
19523 }
19524 }) {
19525 Ok(i) | Err(i) => i,
19526 };
19527 for range in &ranges[start_ix..] {
19528 if range
19529 .start
19530 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19531 .is_ge()
19532 {
19533 break;
19534 }
19535
19536 let start = range.start.to_display_point(display_snapshot);
19537 let end = range.end.to_display_point(display_snapshot);
19538 results.push((start..end, color))
19539 }
19540 }
19541 results
19542 }
19543
19544 pub fn background_highlight_row_ranges<T: 'static>(
19545 &self,
19546 search_range: Range<Anchor>,
19547 display_snapshot: &DisplaySnapshot,
19548 count: usize,
19549 ) -> Vec<RangeInclusive<DisplayPoint>> {
19550 let mut results = Vec::new();
19551 let Some((_, ranges)) = self
19552 .background_highlights
19553 .get(&HighlightKey::Type(TypeId::of::<T>()))
19554 else {
19555 return vec![];
19556 };
19557
19558 let start_ix = match ranges.binary_search_by(|probe| {
19559 let cmp = probe
19560 .end
19561 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19562 if cmp.is_gt() {
19563 Ordering::Greater
19564 } else {
19565 Ordering::Less
19566 }
19567 }) {
19568 Ok(i) | Err(i) => i,
19569 };
19570 let mut push_region = |start: Option<Point>, end: Option<Point>| {
19571 if let (Some(start_display), Some(end_display)) = (start, end) {
19572 results.push(
19573 start_display.to_display_point(display_snapshot)
19574 ..=end_display.to_display_point(display_snapshot),
19575 );
19576 }
19577 };
19578 let mut start_row: Option<Point> = None;
19579 let mut end_row: Option<Point> = None;
19580 if ranges.len() > count {
19581 return Vec::new();
19582 }
19583 for range in &ranges[start_ix..] {
19584 if range
19585 .start
19586 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19587 .is_ge()
19588 {
19589 break;
19590 }
19591 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
19592 if let Some(current_row) = &end_row {
19593 if end.row == current_row.row {
19594 continue;
19595 }
19596 }
19597 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
19598 if start_row.is_none() {
19599 assert_eq!(end_row, None);
19600 start_row = Some(start);
19601 end_row = Some(end);
19602 continue;
19603 }
19604 if let Some(current_end) = end_row.as_mut() {
19605 if start.row > current_end.row + 1 {
19606 push_region(start_row, end_row);
19607 start_row = Some(start);
19608 end_row = Some(end);
19609 } else {
19610 // Merge two hunks.
19611 *current_end = end;
19612 }
19613 } else {
19614 unreachable!();
19615 }
19616 }
19617 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
19618 push_region(start_row, end_row);
19619 results
19620 }
19621
19622 pub fn gutter_highlights_in_range(
19623 &self,
19624 search_range: Range<Anchor>,
19625 display_snapshot: &DisplaySnapshot,
19626 cx: &App,
19627 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19628 let mut results = Vec::new();
19629 for (color_fetcher, ranges) in self.gutter_highlights.values() {
19630 let color = color_fetcher(cx);
19631 let start_ix = match ranges.binary_search_by(|probe| {
19632 let cmp = probe
19633 .end
19634 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19635 if cmp.is_gt() {
19636 Ordering::Greater
19637 } else {
19638 Ordering::Less
19639 }
19640 }) {
19641 Ok(i) | Err(i) => i,
19642 };
19643 for range in &ranges[start_ix..] {
19644 if range
19645 .start
19646 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19647 .is_ge()
19648 {
19649 break;
19650 }
19651
19652 let start = range.start.to_display_point(display_snapshot);
19653 let end = range.end.to_display_point(display_snapshot);
19654 results.push((start..end, color))
19655 }
19656 }
19657 results
19658 }
19659
19660 /// Get the text ranges corresponding to the redaction query
19661 pub fn redacted_ranges(
19662 &self,
19663 search_range: Range<Anchor>,
19664 display_snapshot: &DisplaySnapshot,
19665 cx: &App,
19666 ) -> Vec<Range<DisplayPoint>> {
19667 display_snapshot
19668 .buffer_snapshot
19669 .redacted_ranges(search_range, |file| {
19670 if let Some(file) = file {
19671 file.is_private()
19672 && EditorSettings::get(
19673 Some(SettingsLocation {
19674 worktree_id: file.worktree_id(cx),
19675 path: file.path().as_ref(),
19676 }),
19677 cx,
19678 )
19679 .redact_private_values
19680 } else {
19681 false
19682 }
19683 })
19684 .map(|range| {
19685 range.start.to_display_point(display_snapshot)
19686 ..range.end.to_display_point(display_snapshot)
19687 })
19688 .collect()
19689 }
19690
19691 pub fn highlight_text_key<T: 'static>(
19692 &mut self,
19693 key: usize,
19694 ranges: Vec<Range<Anchor>>,
19695 style: HighlightStyle,
19696 cx: &mut Context<Self>,
19697 ) {
19698 self.display_map.update(cx, |map, _| {
19699 map.highlight_text(
19700 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19701 ranges,
19702 style,
19703 );
19704 });
19705 cx.notify();
19706 }
19707
19708 pub fn highlight_text<T: 'static>(
19709 &mut self,
19710 ranges: Vec<Range<Anchor>>,
19711 style: HighlightStyle,
19712 cx: &mut Context<Self>,
19713 ) {
19714 self.display_map.update(cx, |map, _| {
19715 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
19716 });
19717 cx.notify();
19718 }
19719
19720 pub(crate) fn highlight_inlays<T: 'static>(
19721 &mut self,
19722 highlights: Vec<InlayHighlight>,
19723 style: HighlightStyle,
19724 cx: &mut Context<Self>,
19725 ) {
19726 self.display_map.update(cx, |map, _| {
19727 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
19728 });
19729 cx.notify();
19730 }
19731
19732 pub fn text_highlights<'a, T: 'static>(
19733 &'a self,
19734 cx: &'a App,
19735 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
19736 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
19737 }
19738
19739 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
19740 let cleared = self
19741 .display_map
19742 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
19743 if cleared {
19744 cx.notify();
19745 }
19746 }
19747
19748 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
19749 (self.read_only(cx) || self.blink_manager.read(cx).visible())
19750 && self.focus_handle.is_focused(window)
19751 }
19752
19753 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
19754 self.show_cursor_when_unfocused = is_enabled;
19755 cx.notify();
19756 }
19757
19758 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
19759 cx.notify();
19760 }
19761
19762 fn on_debug_session_event(
19763 &mut self,
19764 _session: Entity<Session>,
19765 event: &SessionEvent,
19766 cx: &mut Context<Self>,
19767 ) {
19768 match event {
19769 SessionEvent::InvalidateInlineValue => {
19770 self.refresh_inline_values(cx);
19771 }
19772 _ => {}
19773 }
19774 }
19775
19776 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
19777 let Some(project) = self.project.clone() else {
19778 return;
19779 };
19780
19781 if !self.inline_value_cache.enabled {
19782 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
19783 self.splice_inlays(&inlays, Vec::new(), cx);
19784 return;
19785 }
19786
19787 let current_execution_position = self
19788 .highlighted_rows
19789 .get(&TypeId::of::<ActiveDebugLine>())
19790 .and_then(|lines| lines.last().map(|line| line.range.end));
19791
19792 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
19793 let inline_values = editor
19794 .update(cx, |editor, cx| {
19795 let Some(current_execution_position) = current_execution_position else {
19796 return Some(Task::ready(Ok(Vec::new())));
19797 };
19798
19799 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
19800 let snapshot = buffer.snapshot(cx);
19801
19802 let excerpt = snapshot.excerpt_containing(
19803 current_execution_position..current_execution_position,
19804 )?;
19805
19806 editor.buffer.read(cx).buffer(excerpt.buffer_id())
19807 })?;
19808
19809 let range =
19810 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
19811
19812 project.inline_values(buffer, range, cx)
19813 })
19814 .ok()
19815 .flatten()?
19816 .await
19817 .context("refreshing debugger inlays")
19818 .log_err()?;
19819
19820 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
19821
19822 for (buffer_id, inline_value) in inline_values
19823 .into_iter()
19824 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
19825 {
19826 buffer_inline_values
19827 .entry(buffer_id)
19828 .or_default()
19829 .push(inline_value);
19830 }
19831
19832 editor
19833 .update(cx, |editor, cx| {
19834 let snapshot = editor.buffer.read(cx).snapshot(cx);
19835 let mut new_inlays = Vec::default();
19836
19837 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
19838 let buffer_id = buffer_snapshot.remote_id();
19839 buffer_inline_values
19840 .get(&buffer_id)
19841 .into_iter()
19842 .flatten()
19843 .for_each(|hint| {
19844 let inlay = Inlay::debugger(
19845 post_inc(&mut editor.next_inlay_id),
19846 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
19847 hint.text(),
19848 );
19849 if !inlay.text.chars().contains(&'\n') {
19850 new_inlays.push(inlay);
19851 }
19852 });
19853 }
19854
19855 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
19856 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
19857
19858 editor.splice_inlays(&inlay_ids, new_inlays, cx);
19859 })
19860 .ok()?;
19861 Some(())
19862 });
19863 }
19864
19865 fn on_buffer_event(
19866 &mut self,
19867 multibuffer: &Entity<MultiBuffer>,
19868 event: &multi_buffer::Event,
19869 window: &mut Window,
19870 cx: &mut Context<Self>,
19871 ) {
19872 match event {
19873 multi_buffer::Event::Edited {
19874 singleton_buffer_edited,
19875 edited_buffer,
19876 } => {
19877 self.scrollbar_marker_state.dirty = true;
19878 self.active_indent_guides_state.dirty = true;
19879 self.refresh_active_diagnostics(cx);
19880 self.refresh_code_actions(window, cx);
19881 self.refresh_selected_text_highlights(true, window, cx);
19882 self.refresh_single_line_folds(window, cx);
19883 refresh_matching_bracket_highlights(self, window, cx);
19884 if self.has_active_inline_completion() {
19885 self.update_visible_inline_completion(window, cx);
19886 }
19887 if let Some(project) = self.project.as_ref() {
19888 if let Some(edited_buffer) = edited_buffer {
19889 project.update(cx, |project, cx| {
19890 self.registered_buffers
19891 .entry(edited_buffer.read(cx).remote_id())
19892 .or_insert_with(|| {
19893 project
19894 .register_buffer_with_language_servers(&edited_buffer, cx)
19895 });
19896 });
19897 }
19898 }
19899 cx.emit(EditorEvent::BufferEdited);
19900 cx.emit(SearchEvent::MatchesInvalidated);
19901
19902 if let Some(buffer) = edited_buffer {
19903 self.update_lsp_data(false, Some(buffer.read(cx).remote_id()), window, cx);
19904 }
19905
19906 if *singleton_buffer_edited {
19907 if let Some(buffer) = edited_buffer {
19908 if buffer.read(cx).file().is_none() {
19909 cx.emit(EditorEvent::TitleChanged);
19910 }
19911 }
19912 if let Some(project) = &self.project {
19913 #[allow(clippy::mutable_key_type)]
19914 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
19915 multibuffer
19916 .all_buffers()
19917 .into_iter()
19918 .filter_map(|buffer| {
19919 buffer.update(cx, |buffer, cx| {
19920 let language = buffer.language()?;
19921 let should_discard = project.update(cx, |project, cx| {
19922 project.is_local()
19923 && !project.has_language_servers_for(buffer, cx)
19924 });
19925 should_discard.not().then_some(language.clone())
19926 })
19927 })
19928 .collect::<HashSet<_>>()
19929 });
19930 if !languages_affected.is_empty() {
19931 self.refresh_inlay_hints(
19932 InlayHintRefreshReason::BufferEdited(languages_affected),
19933 cx,
19934 );
19935 }
19936 }
19937 }
19938
19939 let Some(project) = &self.project else { return };
19940 let (telemetry, is_via_ssh) = {
19941 let project = project.read(cx);
19942 let telemetry = project.client().telemetry().clone();
19943 let is_via_ssh = project.is_via_ssh();
19944 (telemetry, is_via_ssh)
19945 };
19946 refresh_linked_ranges(self, window, cx);
19947 telemetry.log_edit_event("editor", is_via_ssh);
19948 }
19949 multi_buffer::Event::ExcerptsAdded {
19950 buffer,
19951 predecessor,
19952 excerpts,
19953 } => {
19954 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19955 let buffer_id = buffer.read(cx).remote_id();
19956 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
19957 if let Some(project) = &self.project {
19958 update_uncommitted_diff_for_buffer(
19959 cx.entity(),
19960 project,
19961 [buffer.clone()],
19962 self.buffer.clone(),
19963 cx,
19964 )
19965 .detach();
19966 }
19967 }
19968 self.update_lsp_data(false, Some(buffer_id), window, cx);
19969 cx.emit(EditorEvent::ExcerptsAdded {
19970 buffer: buffer.clone(),
19971 predecessor: *predecessor,
19972 excerpts: excerpts.clone(),
19973 });
19974 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
19975 }
19976 multi_buffer::Event::ExcerptsRemoved {
19977 ids,
19978 removed_buffer_ids,
19979 } => {
19980 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
19981 let buffer = self.buffer.read(cx);
19982 self.registered_buffers
19983 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
19984 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19985 cx.emit(EditorEvent::ExcerptsRemoved {
19986 ids: ids.clone(),
19987 removed_buffer_ids: removed_buffer_ids.clone(),
19988 });
19989 }
19990 multi_buffer::Event::ExcerptsEdited {
19991 excerpt_ids,
19992 buffer_ids,
19993 } => {
19994 self.display_map.update(cx, |map, cx| {
19995 map.unfold_buffers(buffer_ids.iter().copied(), cx)
19996 });
19997 cx.emit(EditorEvent::ExcerptsEdited {
19998 ids: excerpt_ids.clone(),
19999 });
20000 }
20001 multi_buffer::Event::ExcerptsExpanded { ids } => {
20002 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20003 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
20004 }
20005 multi_buffer::Event::Reparsed(buffer_id) => {
20006 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20007 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20008
20009 cx.emit(EditorEvent::Reparsed(*buffer_id));
20010 }
20011 multi_buffer::Event::DiffHunksToggled => {
20012 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20013 }
20014 multi_buffer::Event::LanguageChanged(buffer_id) => {
20015 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
20016 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20017 cx.emit(EditorEvent::Reparsed(*buffer_id));
20018 cx.notify();
20019 }
20020 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
20021 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
20022 multi_buffer::Event::FileHandleChanged
20023 | multi_buffer::Event::Reloaded
20024 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
20025 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
20026 multi_buffer::Event::DiagnosticsUpdated => {
20027 self.update_diagnostics_state(window, cx);
20028 }
20029 _ => {}
20030 };
20031 }
20032
20033 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
20034 if !self.diagnostics_enabled() {
20035 return;
20036 }
20037 self.refresh_active_diagnostics(cx);
20038 self.refresh_inline_diagnostics(true, window, cx);
20039 self.scrollbar_marker_state.dirty = true;
20040 cx.notify();
20041 }
20042
20043 pub fn start_temporary_diff_override(&mut self) {
20044 self.load_diff_task.take();
20045 self.temporary_diff_override = true;
20046 }
20047
20048 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
20049 self.temporary_diff_override = false;
20050 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
20051 self.buffer.update(cx, |buffer, cx| {
20052 buffer.set_all_diff_hunks_collapsed(cx);
20053 });
20054
20055 if let Some(project) = self.project.clone() {
20056 self.load_diff_task = Some(
20057 update_uncommitted_diff_for_buffer(
20058 cx.entity(),
20059 &project,
20060 self.buffer.read(cx).all_buffers(),
20061 self.buffer.clone(),
20062 cx,
20063 )
20064 .shared(),
20065 );
20066 }
20067 }
20068
20069 fn on_display_map_changed(
20070 &mut self,
20071 _: Entity<DisplayMap>,
20072 _: &mut Window,
20073 cx: &mut Context<Self>,
20074 ) {
20075 cx.notify();
20076 }
20077
20078 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20079 if self.diagnostics_enabled() {
20080 let new_severity = EditorSettings::get_global(cx)
20081 .diagnostics_max_severity
20082 .unwrap_or(DiagnosticSeverity::Hint);
20083 self.set_max_diagnostics_severity(new_severity, cx);
20084 }
20085 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20086 self.update_edit_prediction_settings(cx);
20087 self.refresh_inline_completion(true, false, window, cx);
20088 self.refresh_inline_values(cx);
20089 self.refresh_inlay_hints(
20090 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
20091 self.selections.newest_anchor().head(),
20092 &self.buffer.read(cx).snapshot(cx),
20093 cx,
20094 )),
20095 cx,
20096 );
20097
20098 let old_cursor_shape = self.cursor_shape;
20099
20100 {
20101 let editor_settings = EditorSettings::get_global(cx);
20102 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
20103 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
20104 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
20105 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
20106 }
20107
20108 if old_cursor_shape != self.cursor_shape {
20109 cx.emit(EditorEvent::CursorShapeChanged);
20110 }
20111
20112 let project_settings = ProjectSettings::get_global(cx);
20113 self.serialize_dirty_buffers =
20114 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
20115
20116 if self.mode.is_full() {
20117 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
20118 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
20119 if self.show_inline_diagnostics != show_inline_diagnostics {
20120 self.show_inline_diagnostics = show_inline_diagnostics;
20121 self.refresh_inline_diagnostics(false, window, cx);
20122 }
20123
20124 if self.git_blame_inline_enabled != inline_blame_enabled {
20125 self.toggle_git_blame_inline_internal(false, window, cx);
20126 }
20127
20128 let minimap_settings = EditorSettings::get_global(cx).minimap;
20129 if self.minimap_visibility != MinimapVisibility::Disabled {
20130 if self.minimap_visibility.settings_visibility()
20131 != minimap_settings.minimap_enabled()
20132 {
20133 self.set_minimap_visibility(
20134 MinimapVisibility::for_mode(self.mode(), cx),
20135 window,
20136 cx,
20137 );
20138 } else if let Some(minimap_entity) = self.minimap.as_ref() {
20139 minimap_entity.update(cx, |minimap_editor, cx| {
20140 minimap_editor.update_minimap_configuration(minimap_settings, cx)
20141 })
20142 }
20143 }
20144 }
20145
20146 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
20147 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
20148 }) {
20149 if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
20150 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
20151 }
20152 self.refresh_colors(false, None, window, cx);
20153 }
20154
20155 cx.notify();
20156 }
20157
20158 pub fn set_searchable(&mut self, searchable: bool) {
20159 self.searchable = searchable;
20160 }
20161
20162 pub fn searchable(&self) -> bool {
20163 self.searchable
20164 }
20165
20166 fn open_proposed_changes_editor(
20167 &mut self,
20168 _: &OpenProposedChangesEditor,
20169 window: &mut Window,
20170 cx: &mut Context<Self>,
20171 ) {
20172 let Some(workspace) = self.workspace() else {
20173 cx.propagate();
20174 return;
20175 };
20176
20177 let selections = self.selections.all::<usize>(cx);
20178 let multi_buffer = self.buffer.read(cx);
20179 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20180 let mut new_selections_by_buffer = HashMap::default();
20181 for selection in selections {
20182 for (buffer, range, _) in
20183 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
20184 {
20185 let mut range = range.to_point(buffer);
20186 range.start.column = 0;
20187 range.end.column = buffer.line_len(range.end.row);
20188 new_selections_by_buffer
20189 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
20190 .or_insert(Vec::new())
20191 .push(range)
20192 }
20193 }
20194
20195 let proposed_changes_buffers = new_selections_by_buffer
20196 .into_iter()
20197 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
20198 .collect::<Vec<_>>();
20199 let proposed_changes_editor = cx.new(|cx| {
20200 ProposedChangesEditor::new(
20201 "Proposed changes",
20202 proposed_changes_buffers,
20203 self.project.clone(),
20204 window,
20205 cx,
20206 )
20207 });
20208
20209 window.defer(cx, move |window, cx| {
20210 workspace.update(cx, |workspace, cx| {
20211 workspace.active_pane().update(cx, |pane, cx| {
20212 pane.add_item(
20213 Box::new(proposed_changes_editor),
20214 true,
20215 true,
20216 None,
20217 window,
20218 cx,
20219 );
20220 });
20221 });
20222 });
20223 }
20224
20225 pub fn open_excerpts_in_split(
20226 &mut self,
20227 _: &OpenExcerptsSplit,
20228 window: &mut Window,
20229 cx: &mut Context<Self>,
20230 ) {
20231 self.open_excerpts_common(None, true, window, cx)
20232 }
20233
20234 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
20235 self.open_excerpts_common(None, false, window, cx)
20236 }
20237
20238 fn open_excerpts_common(
20239 &mut self,
20240 jump_data: Option<JumpData>,
20241 split: bool,
20242 window: &mut Window,
20243 cx: &mut Context<Self>,
20244 ) {
20245 let Some(workspace) = self.workspace() else {
20246 cx.propagate();
20247 return;
20248 };
20249
20250 if self.buffer.read(cx).is_singleton() {
20251 cx.propagate();
20252 return;
20253 }
20254
20255 let mut new_selections_by_buffer = HashMap::default();
20256 match &jump_data {
20257 Some(JumpData::MultiBufferPoint {
20258 excerpt_id,
20259 position,
20260 anchor,
20261 line_offset_from_top,
20262 }) => {
20263 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20264 if let Some(buffer) = multi_buffer_snapshot
20265 .buffer_id_for_excerpt(*excerpt_id)
20266 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
20267 {
20268 let buffer_snapshot = buffer.read(cx).snapshot();
20269 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
20270 language::ToPoint::to_point(anchor, &buffer_snapshot)
20271 } else {
20272 buffer_snapshot.clip_point(*position, Bias::Left)
20273 };
20274 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
20275 new_selections_by_buffer.insert(
20276 buffer,
20277 (
20278 vec![jump_to_offset..jump_to_offset],
20279 Some(*line_offset_from_top),
20280 ),
20281 );
20282 }
20283 }
20284 Some(JumpData::MultiBufferRow {
20285 row,
20286 line_offset_from_top,
20287 }) => {
20288 let point = MultiBufferPoint::new(row.0, 0);
20289 if let Some((buffer, buffer_point, _)) =
20290 self.buffer.read(cx).point_to_buffer_point(point, cx)
20291 {
20292 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
20293 new_selections_by_buffer
20294 .entry(buffer)
20295 .or_insert((Vec::new(), Some(*line_offset_from_top)))
20296 .0
20297 .push(buffer_offset..buffer_offset)
20298 }
20299 }
20300 None => {
20301 let selections = self.selections.all::<usize>(cx);
20302 let multi_buffer = self.buffer.read(cx);
20303 for selection in selections {
20304 for (snapshot, range, _, anchor) in multi_buffer
20305 .snapshot(cx)
20306 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
20307 {
20308 if let Some(anchor) = anchor {
20309 // selection is in a deleted hunk
20310 let Some(buffer_id) = anchor.buffer_id else {
20311 continue;
20312 };
20313 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
20314 continue;
20315 };
20316 let offset = text::ToOffset::to_offset(
20317 &anchor.text_anchor,
20318 &buffer_handle.read(cx).snapshot(),
20319 );
20320 let range = offset..offset;
20321 new_selections_by_buffer
20322 .entry(buffer_handle)
20323 .or_insert((Vec::new(), None))
20324 .0
20325 .push(range)
20326 } else {
20327 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
20328 else {
20329 continue;
20330 };
20331 new_selections_by_buffer
20332 .entry(buffer_handle)
20333 .or_insert((Vec::new(), None))
20334 .0
20335 .push(range)
20336 }
20337 }
20338 }
20339 }
20340 }
20341
20342 new_selections_by_buffer
20343 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
20344
20345 if new_selections_by_buffer.is_empty() {
20346 return;
20347 }
20348
20349 // We defer the pane interaction because we ourselves are a workspace item
20350 // and activating a new item causes the pane to call a method on us reentrantly,
20351 // which panics if we're on the stack.
20352 window.defer(cx, move |window, cx| {
20353 workspace.update(cx, |workspace, cx| {
20354 let pane = if split {
20355 workspace.adjacent_pane(window, cx)
20356 } else {
20357 workspace.active_pane().clone()
20358 };
20359
20360 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
20361 let editor = buffer
20362 .read(cx)
20363 .file()
20364 .is_none()
20365 .then(|| {
20366 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
20367 // so `workspace.open_project_item` will never find them, always opening a new editor.
20368 // Instead, we try to activate the existing editor in the pane first.
20369 let (editor, pane_item_index) =
20370 pane.read(cx).items().enumerate().find_map(|(i, item)| {
20371 let editor = item.downcast::<Editor>()?;
20372 let singleton_buffer =
20373 editor.read(cx).buffer().read(cx).as_singleton()?;
20374 if singleton_buffer == buffer {
20375 Some((editor, i))
20376 } else {
20377 None
20378 }
20379 })?;
20380 pane.update(cx, |pane, cx| {
20381 pane.activate_item(pane_item_index, true, true, window, cx)
20382 });
20383 Some(editor)
20384 })
20385 .flatten()
20386 .unwrap_or_else(|| {
20387 workspace.open_project_item::<Self>(
20388 pane.clone(),
20389 buffer,
20390 true,
20391 true,
20392 window,
20393 cx,
20394 )
20395 });
20396
20397 editor.update(cx, |editor, cx| {
20398 let autoscroll = match scroll_offset {
20399 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
20400 None => Autoscroll::newest(),
20401 };
20402 let nav_history = editor.nav_history.take();
20403 editor.change_selections(
20404 SelectionEffects::scroll(autoscroll),
20405 window,
20406 cx,
20407 |s| {
20408 s.select_ranges(ranges);
20409 },
20410 );
20411 editor.nav_history = nav_history;
20412 });
20413 }
20414 })
20415 });
20416 }
20417
20418 // For now, don't allow opening excerpts in buffers that aren't backed by
20419 // regular project files.
20420 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
20421 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
20422 }
20423
20424 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
20425 let snapshot = self.buffer.read(cx).read(cx);
20426 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
20427 Some(
20428 ranges
20429 .iter()
20430 .map(move |range| {
20431 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
20432 })
20433 .collect(),
20434 )
20435 }
20436
20437 fn selection_replacement_ranges(
20438 &self,
20439 range: Range<OffsetUtf16>,
20440 cx: &mut App,
20441 ) -> Vec<Range<OffsetUtf16>> {
20442 let selections = self.selections.all::<OffsetUtf16>(cx);
20443 let newest_selection = selections
20444 .iter()
20445 .max_by_key(|selection| selection.id)
20446 .unwrap();
20447 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
20448 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
20449 let snapshot = self.buffer.read(cx).read(cx);
20450 selections
20451 .into_iter()
20452 .map(|mut selection| {
20453 selection.start.0 =
20454 (selection.start.0 as isize).saturating_add(start_delta) as usize;
20455 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
20456 snapshot.clip_offset_utf16(selection.start, Bias::Left)
20457 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
20458 })
20459 .collect()
20460 }
20461
20462 fn report_editor_event(
20463 &self,
20464 event_type: &'static str,
20465 file_extension: Option<String>,
20466 cx: &App,
20467 ) {
20468 if cfg!(any(test, feature = "test-support")) {
20469 return;
20470 }
20471
20472 let Some(project) = &self.project else { return };
20473
20474 // If None, we are in a file without an extension
20475 let file = self
20476 .buffer
20477 .read(cx)
20478 .as_singleton()
20479 .and_then(|b| b.read(cx).file());
20480 let file_extension = file_extension.or(file
20481 .as_ref()
20482 .and_then(|file| Path::new(file.file_name(cx)).extension())
20483 .and_then(|e| e.to_str())
20484 .map(|a| a.to_string()));
20485
20486 let vim_mode = vim_enabled(cx);
20487
20488 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
20489 let copilot_enabled = edit_predictions_provider
20490 == language::language_settings::EditPredictionProvider::Copilot;
20491 let copilot_enabled_for_language = self
20492 .buffer
20493 .read(cx)
20494 .language_settings(cx)
20495 .show_edit_predictions;
20496
20497 let project = project.read(cx);
20498 telemetry::event!(
20499 event_type,
20500 file_extension,
20501 vim_mode,
20502 copilot_enabled,
20503 copilot_enabled_for_language,
20504 edit_predictions_provider,
20505 is_via_ssh = project.is_via_ssh(),
20506 );
20507 }
20508
20509 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
20510 /// with each line being an array of {text, highlight} objects.
20511 fn copy_highlight_json(
20512 &mut self,
20513 _: &CopyHighlightJson,
20514 window: &mut Window,
20515 cx: &mut Context<Self>,
20516 ) {
20517 #[derive(Serialize)]
20518 struct Chunk<'a> {
20519 text: String,
20520 highlight: Option<&'a str>,
20521 }
20522
20523 let snapshot = self.buffer.read(cx).snapshot(cx);
20524 let range = self
20525 .selected_text_range(false, window, cx)
20526 .and_then(|selection| {
20527 if selection.range.is_empty() {
20528 None
20529 } else {
20530 Some(selection.range)
20531 }
20532 })
20533 .unwrap_or_else(|| 0..snapshot.len());
20534
20535 let chunks = snapshot.chunks(range, true);
20536 let mut lines = Vec::new();
20537 let mut line: VecDeque<Chunk> = VecDeque::new();
20538
20539 let Some(style) = self.style.as_ref() else {
20540 return;
20541 };
20542
20543 for chunk in chunks {
20544 let highlight = chunk
20545 .syntax_highlight_id
20546 .and_then(|id| id.name(&style.syntax));
20547 let mut chunk_lines = chunk.text.split('\n').peekable();
20548 while let Some(text) = chunk_lines.next() {
20549 let mut merged_with_last_token = false;
20550 if let Some(last_token) = line.back_mut() {
20551 if last_token.highlight == highlight {
20552 last_token.text.push_str(text);
20553 merged_with_last_token = true;
20554 }
20555 }
20556
20557 if !merged_with_last_token {
20558 line.push_back(Chunk {
20559 text: text.into(),
20560 highlight,
20561 });
20562 }
20563
20564 if chunk_lines.peek().is_some() {
20565 if line.len() > 1 && line.front().unwrap().text.is_empty() {
20566 line.pop_front();
20567 }
20568 if line.len() > 1 && line.back().unwrap().text.is_empty() {
20569 line.pop_back();
20570 }
20571
20572 lines.push(mem::take(&mut line));
20573 }
20574 }
20575 }
20576
20577 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
20578 return;
20579 };
20580 cx.write_to_clipboard(ClipboardItem::new_string(lines));
20581 }
20582
20583 pub fn open_context_menu(
20584 &mut self,
20585 _: &OpenContextMenu,
20586 window: &mut Window,
20587 cx: &mut Context<Self>,
20588 ) {
20589 self.request_autoscroll(Autoscroll::newest(), cx);
20590 let position = self.selections.newest_display(cx).start;
20591 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
20592 }
20593
20594 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
20595 &self.inlay_hint_cache
20596 }
20597
20598 pub fn replay_insert_event(
20599 &mut self,
20600 text: &str,
20601 relative_utf16_range: Option<Range<isize>>,
20602 window: &mut Window,
20603 cx: &mut Context<Self>,
20604 ) {
20605 if !self.input_enabled {
20606 cx.emit(EditorEvent::InputIgnored { text: text.into() });
20607 return;
20608 }
20609 if let Some(relative_utf16_range) = relative_utf16_range {
20610 let selections = self.selections.all::<OffsetUtf16>(cx);
20611 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20612 let new_ranges = selections.into_iter().map(|range| {
20613 let start = OffsetUtf16(
20614 range
20615 .head()
20616 .0
20617 .saturating_add_signed(relative_utf16_range.start),
20618 );
20619 let end = OffsetUtf16(
20620 range
20621 .head()
20622 .0
20623 .saturating_add_signed(relative_utf16_range.end),
20624 );
20625 start..end
20626 });
20627 s.select_ranges(new_ranges);
20628 });
20629 }
20630
20631 self.handle_input(text, window, cx);
20632 }
20633
20634 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
20635 let Some(provider) = self.semantics_provider.as_ref() else {
20636 return false;
20637 };
20638
20639 let mut supports = false;
20640 self.buffer().update(cx, |this, cx| {
20641 this.for_each_buffer(|buffer| {
20642 supports |= provider.supports_inlay_hints(buffer, cx);
20643 });
20644 });
20645
20646 supports
20647 }
20648
20649 pub fn is_focused(&self, window: &Window) -> bool {
20650 self.focus_handle.is_focused(window)
20651 }
20652
20653 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20654 cx.emit(EditorEvent::Focused);
20655
20656 if let Some(descendant) = self
20657 .last_focused_descendant
20658 .take()
20659 .and_then(|descendant| descendant.upgrade())
20660 {
20661 window.focus(&descendant);
20662 } else {
20663 if let Some(blame) = self.blame.as_ref() {
20664 blame.update(cx, GitBlame::focus)
20665 }
20666
20667 self.blink_manager.update(cx, BlinkManager::enable);
20668 self.show_cursor_names(window, cx);
20669 self.buffer.update(cx, |buffer, cx| {
20670 buffer.finalize_last_transaction(cx);
20671 if self.leader_id.is_none() {
20672 buffer.set_active_selections(
20673 &self.selections.disjoint_anchors(),
20674 self.selections.line_mode,
20675 self.cursor_shape,
20676 cx,
20677 );
20678 }
20679 });
20680 }
20681 }
20682
20683 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
20684 cx.emit(EditorEvent::FocusedIn)
20685 }
20686
20687 fn handle_focus_out(
20688 &mut self,
20689 event: FocusOutEvent,
20690 _window: &mut Window,
20691 cx: &mut Context<Self>,
20692 ) {
20693 if event.blurred != self.focus_handle {
20694 self.last_focused_descendant = Some(event.blurred);
20695 }
20696 self.selection_drag_state = SelectionDragState::None;
20697 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
20698 }
20699
20700 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20701 self.blink_manager.update(cx, BlinkManager::disable);
20702 self.buffer
20703 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
20704
20705 if let Some(blame) = self.blame.as_ref() {
20706 blame.update(cx, GitBlame::blur)
20707 }
20708 if !self.hover_state.focused(window, cx) {
20709 hide_hover(self, cx);
20710 }
20711 if !self
20712 .context_menu
20713 .borrow()
20714 .as_ref()
20715 .is_some_and(|context_menu| context_menu.focused(window, cx))
20716 {
20717 self.hide_context_menu(window, cx);
20718 }
20719 self.discard_inline_completion(false, cx);
20720 cx.emit(EditorEvent::Blurred);
20721 cx.notify();
20722 }
20723
20724 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20725 let mut pending: String = window
20726 .pending_input_keystrokes()
20727 .into_iter()
20728 .flatten()
20729 .filter_map(|keystroke| {
20730 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
20731 keystroke.key_char.clone()
20732 } else {
20733 None
20734 }
20735 })
20736 .collect();
20737
20738 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
20739 pending = "".to_string();
20740 }
20741
20742 let existing_pending = self
20743 .text_highlights::<PendingInput>(cx)
20744 .map(|(_, ranges)| ranges.iter().cloned().collect::<Vec<_>>());
20745 if existing_pending.is_none() && pending.is_empty() {
20746 return;
20747 }
20748 let transaction =
20749 self.transact(window, cx, |this, window, cx| {
20750 let selections = this.selections.all::<usize>(cx);
20751 let edits = selections
20752 .iter()
20753 .map(|selection| (selection.end..selection.end, pending.clone()));
20754 this.edit(edits, cx);
20755 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20756 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
20757 sel.start + ix * pending.len()..sel.end + ix * pending.len()
20758 }));
20759 });
20760 if let Some(existing_ranges) = existing_pending {
20761 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
20762 this.edit(edits, cx);
20763 }
20764 });
20765
20766 let snapshot = self.snapshot(window, cx);
20767 let ranges = self
20768 .selections
20769 .all::<usize>(cx)
20770 .into_iter()
20771 .map(|selection| {
20772 snapshot.buffer_snapshot.anchor_after(selection.end)
20773 ..snapshot
20774 .buffer_snapshot
20775 .anchor_before(selection.end + pending.len())
20776 })
20777 .collect();
20778
20779 if pending.is_empty() {
20780 self.clear_highlights::<PendingInput>(cx);
20781 } else {
20782 self.highlight_text::<PendingInput>(
20783 ranges,
20784 HighlightStyle {
20785 underline: Some(UnderlineStyle {
20786 thickness: px(1.),
20787 color: None,
20788 wavy: false,
20789 }),
20790 ..Default::default()
20791 },
20792 cx,
20793 );
20794 }
20795
20796 self.ime_transaction = self.ime_transaction.or(transaction);
20797 if let Some(transaction) = self.ime_transaction {
20798 self.buffer.update(cx, |buffer, cx| {
20799 buffer.group_until_transaction(transaction, cx);
20800 });
20801 }
20802
20803 if self.text_highlights::<PendingInput>(cx).is_none() {
20804 self.ime_transaction.take();
20805 }
20806 }
20807
20808 pub fn register_action_renderer(
20809 &mut self,
20810 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
20811 ) -> Subscription {
20812 let id = self.next_editor_action_id.post_inc();
20813 self.editor_actions
20814 .borrow_mut()
20815 .insert(id, Box::new(listener));
20816
20817 let editor_actions = self.editor_actions.clone();
20818 Subscription::new(move || {
20819 editor_actions.borrow_mut().remove(&id);
20820 })
20821 }
20822
20823 pub fn register_action<A: Action>(
20824 &mut self,
20825 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
20826 ) -> Subscription {
20827 let id = self.next_editor_action_id.post_inc();
20828 let listener = Arc::new(listener);
20829 self.editor_actions.borrow_mut().insert(
20830 id,
20831 Box::new(move |_, window, _| {
20832 let listener = listener.clone();
20833 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
20834 let action = action.downcast_ref().unwrap();
20835 if phase == DispatchPhase::Bubble {
20836 listener(action, window, cx)
20837 }
20838 })
20839 }),
20840 );
20841
20842 let editor_actions = self.editor_actions.clone();
20843 Subscription::new(move || {
20844 editor_actions.borrow_mut().remove(&id);
20845 })
20846 }
20847
20848 pub fn file_header_size(&self) -> u32 {
20849 FILE_HEADER_HEIGHT
20850 }
20851
20852 pub fn restore(
20853 &mut self,
20854 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
20855 window: &mut Window,
20856 cx: &mut Context<Self>,
20857 ) {
20858 let workspace = self.workspace();
20859 let project = self.project.as_ref();
20860 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
20861 let mut tasks = Vec::new();
20862 for (buffer_id, changes) in revert_changes {
20863 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
20864 buffer.update(cx, |buffer, cx| {
20865 buffer.edit(
20866 changes
20867 .into_iter()
20868 .map(|(range, text)| (range, text.to_string())),
20869 None,
20870 cx,
20871 );
20872 });
20873
20874 if let Some(project) =
20875 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
20876 {
20877 project.update(cx, |project, cx| {
20878 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
20879 })
20880 }
20881 }
20882 }
20883 tasks
20884 });
20885 cx.spawn_in(window, async move |_, cx| {
20886 for (buffer, task) in save_tasks {
20887 let result = task.await;
20888 if result.is_err() {
20889 let Some(path) = buffer
20890 .read_with(cx, |buffer, cx| buffer.project_path(cx))
20891 .ok()
20892 else {
20893 continue;
20894 };
20895 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
20896 let Some(task) = cx
20897 .update_window_entity(&workspace, |workspace, window, cx| {
20898 workspace
20899 .open_path_preview(path, None, false, false, false, window, cx)
20900 })
20901 .ok()
20902 else {
20903 continue;
20904 };
20905 task.await.log_err();
20906 }
20907 }
20908 }
20909 })
20910 .detach();
20911 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
20912 selections.refresh()
20913 });
20914 }
20915
20916 pub fn to_pixel_point(
20917 &self,
20918 source: multi_buffer::Anchor,
20919 editor_snapshot: &EditorSnapshot,
20920 window: &mut Window,
20921 ) -> Option<gpui::Point<Pixels>> {
20922 let source_point = source.to_display_point(editor_snapshot);
20923 self.display_to_pixel_point(source_point, editor_snapshot, window)
20924 }
20925
20926 pub fn display_to_pixel_point(
20927 &self,
20928 source: DisplayPoint,
20929 editor_snapshot: &EditorSnapshot,
20930 window: &mut Window,
20931 ) -> Option<gpui::Point<Pixels>> {
20932 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
20933 let text_layout_details = self.text_layout_details(window);
20934 let scroll_top = text_layout_details
20935 .scroll_anchor
20936 .scroll_position(editor_snapshot)
20937 .y;
20938
20939 if source.row().as_f32() < scroll_top.floor() {
20940 return None;
20941 }
20942 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
20943 let source_y = line_height * (source.row().as_f32() - scroll_top);
20944 Some(gpui::Point::new(source_x, source_y))
20945 }
20946
20947 pub fn has_visible_completions_menu(&self) -> bool {
20948 !self.edit_prediction_preview_is_active()
20949 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
20950 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
20951 })
20952 }
20953
20954 pub fn register_addon<T: Addon>(&mut self, instance: T) {
20955 if self.mode.is_minimap() {
20956 return;
20957 }
20958 self.addons
20959 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
20960 }
20961
20962 pub fn unregister_addon<T: Addon>(&mut self) {
20963 self.addons.remove(&std::any::TypeId::of::<T>());
20964 }
20965
20966 pub fn addon<T: Addon>(&self) -> Option<&T> {
20967 let type_id = std::any::TypeId::of::<T>();
20968 self.addons
20969 .get(&type_id)
20970 .and_then(|item| item.to_any().downcast_ref::<T>())
20971 }
20972
20973 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
20974 let type_id = std::any::TypeId::of::<T>();
20975 self.addons
20976 .get_mut(&type_id)
20977 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
20978 }
20979
20980 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
20981 let text_layout_details = self.text_layout_details(window);
20982 let style = &text_layout_details.editor_style;
20983 let font_id = window.text_system().resolve_font(&style.text.font());
20984 let font_size = style.text.font_size.to_pixels(window.rem_size());
20985 let line_height = style.text.line_height_in_pixels(window.rem_size());
20986 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
20987 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
20988
20989 CharacterDimensions {
20990 em_width,
20991 em_advance,
20992 line_height,
20993 }
20994 }
20995
20996 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
20997 self.load_diff_task.clone()
20998 }
20999
21000 fn read_metadata_from_db(
21001 &mut self,
21002 item_id: u64,
21003 workspace_id: WorkspaceId,
21004 window: &mut Window,
21005 cx: &mut Context<Editor>,
21006 ) {
21007 if self.is_singleton(cx)
21008 && !self.mode.is_minimap()
21009 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
21010 {
21011 let buffer_snapshot = OnceCell::new();
21012
21013 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
21014 if !folds.is_empty() {
21015 let snapshot =
21016 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21017 self.fold_ranges(
21018 folds
21019 .into_iter()
21020 .map(|(start, end)| {
21021 snapshot.clip_offset(start, Bias::Left)
21022 ..snapshot.clip_offset(end, Bias::Right)
21023 })
21024 .collect(),
21025 false,
21026 window,
21027 cx,
21028 );
21029 }
21030 }
21031
21032 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
21033 if !selections.is_empty() {
21034 let snapshot =
21035 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21036 // skip adding the initial selection to selection history
21037 self.selection_history.mode = SelectionHistoryMode::Skipping;
21038 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21039 s.select_ranges(selections.into_iter().map(|(start, end)| {
21040 snapshot.clip_offset(start, Bias::Left)
21041 ..snapshot.clip_offset(end, Bias::Right)
21042 }));
21043 });
21044 self.selection_history.mode = SelectionHistoryMode::Normal;
21045 }
21046 };
21047 }
21048
21049 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
21050 }
21051
21052 fn update_lsp_data(
21053 &mut self,
21054 ignore_cache: bool,
21055 for_buffer: Option<BufferId>,
21056 window: &mut Window,
21057 cx: &mut Context<'_, Self>,
21058 ) {
21059 self.pull_diagnostics(for_buffer, window, cx);
21060 self.refresh_colors(ignore_cache, for_buffer, window, cx);
21061 }
21062}
21063
21064fn vim_enabled(cx: &App) -> bool {
21065 cx.global::<SettingsStore>()
21066 .raw_user_settings()
21067 .get("vim_mode")
21068 == Some(&serde_json::Value::Bool(true))
21069}
21070
21071fn process_completion_for_edit(
21072 completion: &Completion,
21073 intent: CompletionIntent,
21074 buffer: &Entity<Buffer>,
21075 cursor_position: &text::Anchor,
21076 cx: &mut Context<Editor>,
21077) -> CompletionEdit {
21078 let buffer = buffer.read(cx);
21079 let buffer_snapshot = buffer.snapshot();
21080 let (snippet, new_text) = if completion.is_snippet() {
21081 // Workaround for typescript language server issues so that methods don't expand within
21082 // strings and functions with type expressions. The previous point is used because the query
21083 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
21084 let mut snippet_source = completion.new_text.clone();
21085 let mut previous_point = text::ToPoint::to_point(cursor_position, buffer);
21086 previous_point.column = previous_point.column.saturating_sub(1);
21087 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point) {
21088 if scope.prefers_label_for_snippet_in_completion() {
21089 if let Some(label) = completion.label() {
21090 if matches!(
21091 completion.kind(),
21092 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
21093 ) {
21094 snippet_source = label;
21095 }
21096 }
21097 }
21098 }
21099 match Snippet::parse(&snippet_source).log_err() {
21100 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
21101 None => (None, completion.new_text.clone()),
21102 }
21103 } else {
21104 (None, completion.new_text.clone())
21105 };
21106
21107 let mut range_to_replace = {
21108 let replace_range = &completion.replace_range;
21109 if let CompletionSource::Lsp {
21110 insert_range: Some(insert_range),
21111 ..
21112 } = &completion.source
21113 {
21114 debug_assert_eq!(
21115 insert_range.start, replace_range.start,
21116 "insert_range and replace_range should start at the same position"
21117 );
21118 debug_assert!(
21119 insert_range
21120 .start
21121 .cmp(&cursor_position, &buffer_snapshot)
21122 .is_le(),
21123 "insert_range should start before or at cursor position"
21124 );
21125 debug_assert!(
21126 replace_range
21127 .start
21128 .cmp(&cursor_position, &buffer_snapshot)
21129 .is_le(),
21130 "replace_range should start before or at cursor position"
21131 );
21132 debug_assert!(
21133 insert_range
21134 .end
21135 .cmp(&cursor_position, &buffer_snapshot)
21136 .is_le(),
21137 "insert_range should end before or at cursor position"
21138 );
21139
21140 let should_replace = match intent {
21141 CompletionIntent::CompleteWithInsert => false,
21142 CompletionIntent::CompleteWithReplace => true,
21143 CompletionIntent::Complete | CompletionIntent::Compose => {
21144 let insert_mode =
21145 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
21146 .completions
21147 .lsp_insert_mode;
21148 match insert_mode {
21149 LspInsertMode::Insert => false,
21150 LspInsertMode::Replace => true,
21151 LspInsertMode::ReplaceSubsequence => {
21152 let mut text_to_replace = buffer.chars_for_range(
21153 buffer.anchor_before(replace_range.start)
21154 ..buffer.anchor_after(replace_range.end),
21155 );
21156 let mut current_needle = text_to_replace.next();
21157 for haystack_ch in completion.label.text.chars() {
21158 if let Some(needle_ch) = current_needle {
21159 if haystack_ch.eq_ignore_ascii_case(&needle_ch) {
21160 current_needle = text_to_replace.next();
21161 }
21162 }
21163 }
21164 current_needle.is_none()
21165 }
21166 LspInsertMode::ReplaceSuffix => {
21167 if replace_range
21168 .end
21169 .cmp(&cursor_position, &buffer_snapshot)
21170 .is_gt()
21171 {
21172 let range_after_cursor = *cursor_position..replace_range.end;
21173 let text_after_cursor = buffer
21174 .text_for_range(
21175 buffer.anchor_before(range_after_cursor.start)
21176 ..buffer.anchor_after(range_after_cursor.end),
21177 )
21178 .collect::<String>()
21179 .to_ascii_lowercase();
21180 completion
21181 .label
21182 .text
21183 .to_ascii_lowercase()
21184 .ends_with(&text_after_cursor)
21185 } else {
21186 true
21187 }
21188 }
21189 }
21190 }
21191 };
21192
21193 if should_replace {
21194 replace_range.clone()
21195 } else {
21196 insert_range.clone()
21197 }
21198 } else {
21199 replace_range.clone()
21200 }
21201 };
21202
21203 if range_to_replace
21204 .end
21205 .cmp(&cursor_position, &buffer_snapshot)
21206 .is_lt()
21207 {
21208 range_to_replace.end = *cursor_position;
21209 }
21210
21211 CompletionEdit {
21212 new_text,
21213 replace_range: range_to_replace.to_offset(&buffer),
21214 snippet,
21215 }
21216}
21217
21218struct CompletionEdit {
21219 new_text: String,
21220 replace_range: Range<usize>,
21221 snippet: Option<Snippet>,
21222}
21223
21224fn insert_extra_newline_brackets(
21225 buffer: &MultiBufferSnapshot,
21226 range: Range<usize>,
21227 language: &language::LanguageScope,
21228) -> bool {
21229 let leading_whitespace_len = buffer
21230 .reversed_chars_at(range.start)
21231 .take_while(|c| c.is_whitespace() && *c != '\n')
21232 .map(|c| c.len_utf8())
21233 .sum::<usize>();
21234 let trailing_whitespace_len = buffer
21235 .chars_at(range.end)
21236 .take_while(|c| c.is_whitespace() && *c != '\n')
21237 .map(|c| c.len_utf8())
21238 .sum::<usize>();
21239 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
21240
21241 language.brackets().any(|(pair, enabled)| {
21242 let pair_start = pair.start.trim_end();
21243 let pair_end = pair.end.trim_start();
21244
21245 enabled
21246 && pair.newline
21247 && buffer.contains_str_at(range.end, pair_end)
21248 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
21249 })
21250}
21251
21252fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
21253 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
21254 [(buffer, range, _)] => (*buffer, range.clone()),
21255 _ => return false,
21256 };
21257 let pair = {
21258 let mut result: Option<BracketMatch> = None;
21259
21260 for pair in buffer
21261 .all_bracket_ranges(range.clone())
21262 .filter(move |pair| {
21263 pair.open_range.start <= range.start && pair.close_range.end >= range.end
21264 })
21265 {
21266 let len = pair.close_range.end - pair.open_range.start;
21267
21268 if let Some(existing) = &result {
21269 let existing_len = existing.close_range.end - existing.open_range.start;
21270 if len > existing_len {
21271 continue;
21272 }
21273 }
21274
21275 result = Some(pair);
21276 }
21277
21278 result
21279 };
21280 let Some(pair) = pair else {
21281 return false;
21282 };
21283 pair.newline_only
21284 && buffer
21285 .chars_for_range(pair.open_range.end..range.start)
21286 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
21287 .all(|c| c.is_whitespace() && c != '\n')
21288}
21289
21290fn update_uncommitted_diff_for_buffer(
21291 editor: Entity<Editor>,
21292 project: &Entity<Project>,
21293 buffers: impl IntoIterator<Item = Entity<Buffer>>,
21294 buffer: Entity<MultiBuffer>,
21295 cx: &mut App,
21296) -> Task<()> {
21297 let mut tasks = Vec::new();
21298 project.update(cx, |project, cx| {
21299 for buffer in buffers {
21300 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
21301 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
21302 }
21303 }
21304 });
21305 cx.spawn(async move |cx| {
21306 let diffs = future::join_all(tasks).await;
21307 if editor
21308 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
21309 .unwrap_or(false)
21310 {
21311 return;
21312 }
21313
21314 buffer
21315 .update(cx, |buffer, cx| {
21316 for diff in diffs.into_iter().flatten() {
21317 buffer.add_diff(diff, cx);
21318 }
21319 })
21320 .ok();
21321 })
21322}
21323
21324fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
21325 let tab_size = tab_size.get() as usize;
21326 let mut width = offset;
21327
21328 for ch in text.chars() {
21329 width += if ch == '\t' {
21330 tab_size - (width % tab_size)
21331 } else {
21332 1
21333 };
21334 }
21335
21336 width - offset
21337}
21338
21339#[cfg(test)]
21340mod tests {
21341 use super::*;
21342
21343 #[test]
21344 fn test_string_size_with_expanded_tabs() {
21345 let nz = |val| NonZeroU32::new(val).unwrap();
21346 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
21347 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
21348 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
21349 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
21350 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
21351 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
21352 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
21353 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
21354 }
21355}
21356
21357/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
21358struct WordBreakingTokenizer<'a> {
21359 input: &'a str,
21360}
21361
21362impl<'a> WordBreakingTokenizer<'a> {
21363 fn new(input: &'a str) -> Self {
21364 Self { input }
21365 }
21366}
21367
21368fn is_char_ideographic(ch: char) -> bool {
21369 use unicode_script::Script::*;
21370 use unicode_script::UnicodeScript;
21371 matches!(ch.script(), Han | Tangut | Yi)
21372}
21373
21374fn is_grapheme_ideographic(text: &str) -> bool {
21375 text.chars().any(is_char_ideographic)
21376}
21377
21378fn is_grapheme_whitespace(text: &str) -> bool {
21379 text.chars().any(|x| x.is_whitespace())
21380}
21381
21382fn should_stay_with_preceding_ideograph(text: &str) -> bool {
21383 text.chars().next().map_or(false, |ch| {
21384 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
21385 })
21386}
21387
21388#[derive(PartialEq, Eq, Debug, Clone, Copy)]
21389enum WordBreakToken<'a> {
21390 Word { token: &'a str, grapheme_len: usize },
21391 InlineWhitespace { token: &'a str, grapheme_len: usize },
21392 Newline,
21393}
21394
21395impl<'a> Iterator for WordBreakingTokenizer<'a> {
21396 /// Yields a span, the count of graphemes in the token, and whether it was
21397 /// whitespace. Note that it also breaks at word boundaries.
21398 type Item = WordBreakToken<'a>;
21399
21400 fn next(&mut self) -> Option<Self::Item> {
21401 use unicode_segmentation::UnicodeSegmentation;
21402 if self.input.is_empty() {
21403 return None;
21404 }
21405
21406 let mut iter = self.input.graphemes(true).peekable();
21407 let mut offset = 0;
21408 let mut grapheme_len = 0;
21409 if let Some(first_grapheme) = iter.next() {
21410 let is_newline = first_grapheme == "\n";
21411 let is_whitespace = is_grapheme_whitespace(first_grapheme);
21412 offset += first_grapheme.len();
21413 grapheme_len += 1;
21414 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
21415 if let Some(grapheme) = iter.peek().copied() {
21416 if should_stay_with_preceding_ideograph(grapheme) {
21417 offset += grapheme.len();
21418 grapheme_len += 1;
21419 }
21420 }
21421 } else {
21422 let mut words = self.input[offset..].split_word_bound_indices().peekable();
21423 let mut next_word_bound = words.peek().copied();
21424 if next_word_bound.map_or(false, |(i, _)| i == 0) {
21425 next_word_bound = words.next();
21426 }
21427 while let Some(grapheme) = iter.peek().copied() {
21428 if next_word_bound.map_or(false, |(i, _)| i == offset) {
21429 break;
21430 };
21431 if is_grapheme_whitespace(grapheme) != is_whitespace
21432 || (grapheme == "\n") != is_newline
21433 {
21434 break;
21435 };
21436 offset += grapheme.len();
21437 grapheme_len += 1;
21438 iter.next();
21439 }
21440 }
21441 let token = &self.input[..offset];
21442 self.input = &self.input[offset..];
21443 if token == "\n" {
21444 Some(WordBreakToken::Newline)
21445 } else if is_whitespace {
21446 Some(WordBreakToken::InlineWhitespace {
21447 token,
21448 grapheme_len,
21449 })
21450 } else {
21451 Some(WordBreakToken::Word {
21452 token,
21453 grapheme_len,
21454 })
21455 }
21456 } else {
21457 None
21458 }
21459 }
21460}
21461
21462#[test]
21463fn test_word_breaking_tokenizer() {
21464 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
21465 ("", &[]),
21466 (" ", &[whitespace(" ", 2)]),
21467 ("Ʒ", &[word("Ʒ", 1)]),
21468 ("Ǽ", &[word("Ǽ", 1)]),
21469 ("⋑", &[word("⋑", 1)]),
21470 ("⋑⋑", &[word("⋑⋑", 2)]),
21471 (
21472 "原理,进而",
21473 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
21474 ),
21475 (
21476 "hello world",
21477 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
21478 ),
21479 (
21480 "hello, world",
21481 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
21482 ),
21483 (
21484 " hello world",
21485 &[
21486 whitespace(" ", 2),
21487 word("hello", 5),
21488 whitespace(" ", 1),
21489 word("world", 5),
21490 ],
21491 ),
21492 (
21493 "这是什么 \n 钢笔",
21494 &[
21495 word("这", 1),
21496 word("是", 1),
21497 word("什", 1),
21498 word("么", 1),
21499 whitespace(" ", 1),
21500 newline(),
21501 whitespace(" ", 1),
21502 word("钢", 1),
21503 word("笔", 1),
21504 ],
21505 ),
21506 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
21507 ];
21508
21509 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21510 WordBreakToken::Word {
21511 token,
21512 grapheme_len,
21513 }
21514 }
21515
21516 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21517 WordBreakToken::InlineWhitespace {
21518 token,
21519 grapheme_len,
21520 }
21521 }
21522
21523 fn newline() -> WordBreakToken<'static> {
21524 WordBreakToken::Newline
21525 }
21526
21527 for (input, result) in tests {
21528 assert_eq!(
21529 WordBreakingTokenizer::new(input)
21530 .collect::<Vec<_>>()
21531 .as_slice(),
21532 *result,
21533 );
21534 }
21535}
21536
21537fn wrap_with_prefix(
21538 first_line_prefix: String,
21539 subsequent_lines_prefix: String,
21540 unwrapped_text: String,
21541 wrap_column: usize,
21542 tab_size: NonZeroU32,
21543 preserve_existing_whitespace: bool,
21544) -> String {
21545 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
21546 let subsequent_lines_prefix_len =
21547 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
21548 let mut wrapped_text = String::new();
21549 let mut current_line = first_line_prefix.clone();
21550 let mut is_first_line = true;
21551
21552 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
21553 let mut current_line_len = first_line_prefix_len;
21554 let mut in_whitespace = false;
21555 for token in tokenizer {
21556 let have_preceding_whitespace = in_whitespace;
21557 match token {
21558 WordBreakToken::Word {
21559 token,
21560 grapheme_len,
21561 } => {
21562 in_whitespace = false;
21563 let current_prefix_len = if is_first_line {
21564 first_line_prefix_len
21565 } else {
21566 subsequent_lines_prefix_len
21567 };
21568 if current_line_len + grapheme_len > wrap_column
21569 && current_line_len != current_prefix_len
21570 {
21571 wrapped_text.push_str(current_line.trim_end());
21572 wrapped_text.push('\n');
21573 is_first_line = false;
21574 current_line = subsequent_lines_prefix.clone();
21575 current_line_len = subsequent_lines_prefix_len;
21576 }
21577 current_line.push_str(token);
21578 current_line_len += grapheme_len;
21579 }
21580 WordBreakToken::InlineWhitespace {
21581 mut token,
21582 mut grapheme_len,
21583 } => {
21584 in_whitespace = true;
21585 if have_preceding_whitespace && !preserve_existing_whitespace {
21586 continue;
21587 }
21588 if !preserve_existing_whitespace {
21589 token = " ";
21590 grapheme_len = 1;
21591 }
21592 let current_prefix_len = if is_first_line {
21593 first_line_prefix_len
21594 } else {
21595 subsequent_lines_prefix_len
21596 };
21597 if current_line_len + grapheme_len > wrap_column {
21598 wrapped_text.push_str(current_line.trim_end());
21599 wrapped_text.push('\n');
21600 is_first_line = false;
21601 current_line = subsequent_lines_prefix.clone();
21602 current_line_len = subsequent_lines_prefix_len;
21603 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
21604 current_line.push_str(token);
21605 current_line_len += grapheme_len;
21606 }
21607 }
21608 WordBreakToken::Newline => {
21609 in_whitespace = true;
21610 let current_prefix_len = if is_first_line {
21611 first_line_prefix_len
21612 } else {
21613 subsequent_lines_prefix_len
21614 };
21615 if preserve_existing_whitespace {
21616 wrapped_text.push_str(current_line.trim_end());
21617 wrapped_text.push('\n');
21618 is_first_line = false;
21619 current_line = subsequent_lines_prefix.clone();
21620 current_line_len = subsequent_lines_prefix_len;
21621 } else if have_preceding_whitespace {
21622 continue;
21623 } else if current_line_len + 1 > wrap_column
21624 && current_line_len != current_prefix_len
21625 {
21626 wrapped_text.push_str(current_line.trim_end());
21627 wrapped_text.push('\n');
21628 is_first_line = false;
21629 current_line = subsequent_lines_prefix.clone();
21630 current_line_len = subsequent_lines_prefix_len;
21631 } else if current_line_len != current_prefix_len {
21632 current_line.push(' ');
21633 current_line_len += 1;
21634 }
21635 }
21636 }
21637 }
21638
21639 if !current_line.is_empty() {
21640 wrapped_text.push_str(¤t_line);
21641 }
21642 wrapped_text
21643}
21644
21645#[test]
21646fn test_wrap_with_prefix() {
21647 assert_eq!(
21648 wrap_with_prefix(
21649 "# ".to_string(),
21650 "# ".to_string(),
21651 "abcdefg".to_string(),
21652 4,
21653 NonZeroU32::new(4).unwrap(),
21654 false,
21655 ),
21656 "# abcdefg"
21657 );
21658 assert_eq!(
21659 wrap_with_prefix(
21660 "".to_string(),
21661 "".to_string(),
21662 "\thello world".to_string(),
21663 8,
21664 NonZeroU32::new(4).unwrap(),
21665 false,
21666 ),
21667 "hello\nworld"
21668 );
21669 assert_eq!(
21670 wrap_with_prefix(
21671 "// ".to_string(),
21672 "// ".to_string(),
21673 "xx \nyy zz aa bb cc".to_string(),
21674 12,
21675 NonZeroU32::new(4).unwrap(),
21676 false,
21677 ),
21678 "// xx yy zz\n// aa bb cc"
21679 );
21680 assert_eq!(
21681 wrap_with_prefix(
21682 String::new(),
21683 String::new(),
21684 "这是什么 \n 钢笔".to_string(),
21685 3,
21686 NonZeroU32::new(4).unwrap(),
21687 false,
21688 ),
21689 "这是什\n么 钢\n笔"
21690 );
21691}
21692
21693pub trait CollaborationHub {
21694 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
21695 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
21696 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
21697}
21698
21699impl CollaborationHub for Entity<Project> {
21700 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
21701 self.read(cx).collaborators()
21702 }
21703
21704 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
21705 self.read(cx).user_store().read(cx).participant_indices()
21706 }
21707
21708 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
21709 let this = self.read(cx);
21710 let user_ids = this.collaborators().values().map(|c| c.user_id);
21711 this.user_store().read(cx).participant_names(user_ids, cx)
21712 }
21713}
21714
21715pub trait SemanticsProvider {
21716 fn hover(
21717 &self,
21718 buffer: &Entity<Buffer>,
21719 position: text::Anchor,
21720 cx: &mut App,
21721 ) -> Option<Task<Vec<project::Hover>>>;
21722
21723 fn inline_values(
21724 &self,
21725 buffer_handle: Entity<Buffer>,
21726 range: Range<text::Anchor>,
21727 cx: &mut App,
21728 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21729
21730 fn inlay_hints(
21731 &self,
21732 buffer_handle: Entity<Buffer>,
21733 range: Range<text::Anchor>,
21734 cx: &mut App,
21735 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21736
21737 fn resolve_inlay_hint(
21738 &self,
21739 hint: InlayHint,
21740 buffer_handle: Entity<Buffer>,
21741 server_id: LanguageServerId,
21742 cx: &mut App,
21743 ) -> Option<Task<anyhow::Result<InlayHint>>>;
21744
21745 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
21746
21747 fn document_highlights(
21748 &self,
21749 buffer: &Entity<Buffer>,
21750 position: text::Anchor,
21751 cx: &mut App,
21752 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
21753
21754 fn definitions(
21755 &self,
21756 buffer: &Entity<Buffer>,
21757 position: text::Anchor,
21758 kind: GotoDefinitionKind,
21759 cx: &mut App,
21760 ) -> Option<Task<Result<Vec<LocationLink>>>>;
21761
21762 fn range_for_rename(
21763 &self,
21764 buffer: &Entity<Buffer>,
21765 position: text::Anchor,
21766 cx: &mut App,
21767 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
21768
21769 fn perform_rename(
21770 &self,
21771 buffer: &Entity<Buffer>,
21772 position: text::Anchor,
21773 new_name: String,
21774 cx: &mut App,
21775 ) -> Option<Task<Result<ProjectTransaction>>>;
21776}
21777
21778pub trait CompletionProvider {
21779 fn completions(
21780 &self,
21781 excerpt_id: ExcerptId,
21782 buffer: &Entity<Buffer>,
21783 buffer_position: text::Anchor,
21784 trigger: CompletionContext,
21785 window: &mut Window,
21786 cx: &mut Context<Editor>,
21787 ) -> Task<Result<Vec<CompletionResponse>>>;
21788
21789 fn resolve_completions(
21790 &self,
21791 _buffer: Entity<Buffer>,
21792 _completion_indices: Vec<usize>,
21793 _completions: Rc<RefCell<Box<[Completion]>>>,
21794 _cx: &mut Context<Editor>,
21795 ) -> Task<Result<bool>> {
21796 Task::ready(Ok(false))
21797 }
21798
21799 fn apply_additional_edits_for_completion(
21800 &self,
21801 _buffer: Entity<Buffer>,
21802 _completions: Rc<RefCell<Box<[Completion]>>>,
21803 _completion_index: usize,
21804 _push_to_history: bool,
21805 _cx: &mut Context<Editor>,
21806 ) -> Task<Result<Option<language::Transaction>>> {
21807 Task::ready(Ok(None))
21808 }
21809
21810 fn is_completion_trigger(
21811 &self,
21812 buffer: &Entity<Buffer>,
21813 position: language::Anchor,
21814 text: &str,
21815 trigger_in_words: bool,
21816 menu_is_open: bool,
21817 cx: &mut Context<Editor>,
21818 ) -> bool;
21819
21820 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
21821
21822 fn sort_completions(&self) -> bool {
21823 true
21824 }
21825
21826 fn filter_completions(&self) -> bool {
21827 true
21828 }
21829}
21830
21831pub trait CodeActionProvider {
21832 fn id(&self) -> Arc<str>;
21833
21834 fn code_actions(
21835 &self,
21836 buffer: &Entity<Buffer>,
21837 range: Range<text::Anchor>,
21838 window: &mut Window,
21839 cx: &mut App,
21840 ) -> Task<Result<Vec<CodeAction>>>;
21841
21842 fn apply_code_action(
21843 &self,
21844 buffer_handle: Entity<Buffer>,
21845 action: CodeAction,
21846 excerpt_id: ExcerptId,
21847 push_to_history: bool,
21848 window: &mut Window,
21849 cx: &mut App,
21850 ) -> Task<Result<ProjectTransaction>>;
21851}
21852
21853impl CodeActionProvider for Entity<Project> {
21854 fn id(&self) -> Arc<str> {
21855 "project".into()
21856 }
21857
21858 fn code_actions(
21859 &self,
21860 buffer: &Entity<Buffer>,
21861 range: Range<text::Anchor>,
21862 _window: &mut Window,
21863 cx: &mut App,
21864 ) -> Task<Result<Vec<CodeAction>>> {
21865 self.update(cx, |project, cx| {
21866 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
21867 let code_actions = project.code_actions(buffer, range, None, cx);
21868 cx.background_spawn(async move {
21869 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
21870 Ok(code_lens_actions
21871 .context("code lens fetch")?
21872 .into_iter()
21873 .chain(code_actions.context("code action fetch")?)
21874 .collect())
21875 })
21876 })
21877 }
21878
21879 fn apply_code_action(
21880 &self,
21881 buffer_handle: Entity<Buffer>,
21882 action: CodeAction,
21883 _excerpt_id: ExcerptId,
21884 push_to_history: bool,
21885 _window: &mut Window,
21886 cx: &mut App,
21887 ) -> Task<Result<ProjectTransaction>> {
21888 self.update(cx, |project, cx| {
21889 project.apply_code_action(buffer_handle, action, push_to_history, cx)
21890 })
21891 }
21892}
21893
21894fn snippet_completions(
21895 project: &Project,
21896 buffer: &Entity<Buffer>,
21897 buffer_position: text::Anchor,
21898 cx: &mut App,
21899) -> Task<Result<CompletionResponse>> {
21900 let languages = buffer.read(cx).languages_at(buffer_position);
21901 let snippet_store = project.snippets().read(cx);
21902
21903 let scopes: Vec<_> = languages
21904 .iter()
21905 .filter_map(|language| {
21906 let language_name = language.lsp_id();
21907 let snippets = snippet_store.snippets_for(Some(language_name), cx);
21908
21909 if snippets.is_empty() {
21910 None
21911 } else {
21912 Some((language.default_scope(), snippets))
21913 }
21914 })
21915 .collect();
21916
21917 if scopes.is_empty() {
21918 return Task::ready(Ok(CompletionResponse {
21919 completions: vec![],
21920 is_incomplete: false,
21921 }));
21922 }
21923
21924 let snapshot = buffer.read(cx).text_snapshot();
21925 let chars: String = snapshot
21926 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
21927 .collect();
21928 let executor = cx.background_executor().clone();
21929
21930 cx.background_spawn(async move {
21931 let mut is_incomplete = false;
21932 let mut completions: Vec<Completion> = Vec::new();
21933 for (scope, snippets) in scopes.into_iter() {
21934 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
21935 let mut last_word = chars
21936 .chars()
21937 .take_while(|c| classifier.is_word(*c))
21938 .collect::<String>();
21939 last_word = last_word.chars().rev().collect();
21940
21941 if last_word.is_empty() {
21942 return Ok(CompletionResponse {
21943 completions: vec![],
21944 is_incomplete: true,
21945 });
21946 }
21947
21948 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
21949 let to_lsp = |point: &text::Anchor| {
21950 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
21951 point_to_lsp(end)
21952 };
21953 let lsp_end = to_lsp(&buffer_position);
21954
21955 let candidates = snippets
21956 .iter()
21957 .enumerate()
21958 .flat_map(|(ix, snippet)| {
21959 snippet
21960 .prefix
21961 .iter()
21962 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
21963 })
21964 .collect::<Vec<StringMatchCandidate>>();
21965
21966 const MAX_RESULTS: usize = 100;
21967 let mut matches = fuzzy::match_strings(
21968 &candidates,
21969 &last_word,
21970 last_word.chars().any(|c| c.is_uppercase()),
21971 true,
21972 MAX_RESULTS,
21973 &Default::default(),
21974 executor.clone(),
21975 )
21976 .await;
21977
21978 if matches.len() >= MAX_RESULTS {
21979 is_incomplete = true;
21980 }
21981
21982 // Remove all candidates where the query's start does not match the start of any word in the candidate
21983 if let Some(query_start) = last_word.chars().next() {
21984 matches.retain(|string_match| {
21985 split_words(&string_match.string).any(|word| {
21986 // Check that the first codepoint of the word as lowercase matches the first
21987 // codepoint of the query as lowercase
21988 word.chars()
21989 .flat_map(|codepoint| codepoint.to_lowercase())
21990 .zip(query_start.to_lowercase())
21991 .all(|(word_cp, query_cp)| word_cp == query_cp)
21992 })
21993 });
21994 }
21995
21996 let matched_strings = matches
21997 .into_iter()
21998 .map(|m| m.string)
21999 .collect::<HashSet<_>>();
22000
22001 completions.extend(snippets.iter().filter_map(|snippet| {
22002 let matching_prefix = snippet
22003 .prefix
22004 .iter()
22005 .find(|prefix| matched_strings.contains(*prefix))?;
22006 let start = as_offset - last_word.len();
22007 let start = snapshot.anchor_before(start);
22008 let range = start..buffer_position;
22009 let lsp_start = to_lsp(&start);
22010 let lsp_range = lsp::Range {
22011 start: lsp_start,
22012 end: lsp_end,
22013 };
22014 Some(Completion {
22015 replace_range: range,
22016 new_text: snippet.body.clone(),
22017 source: CompletionSource::Lsp {
22018 insert_range: None,
22019 server_id: LanguageServerId(usize::MAX),
22020 resolved: true,
22021 lsp_completion: Box::new(lsp::CompletionItem {
22022 label: snippet.prefix.first().unwrap().clone(),
22023 kind: Some(CompletionItemKind::SNIPPET),
22024 label_details: snippet.description.as_ref().map(|description| {
22025 lsp::CompletionItemLabelDetails {
22026 detail: Some(description.clone()),
22027 description: None,
22028 }
22029 }),
22030 insert_text_format: Some(InsertTextFormat::SNIPPET),
22031 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22032 lsp::InsertReplaceEdit {
22033 new_text: snippet.body.clone(),
22034 insert: lsp_range,
22035 replace: lsp_range,
22036 },
22037 )),
22038 filter_text: Some(snippet.body.clone()),
22039 sort_text: Some(char::MAX.to_string()),
22040 ..lsp::CompletionItem::default()
22041 }),
22042 lsp_defaults: None,
22043 },
22044 label: CodeLabel {
22045 text: matching_prefix.clone(),
22046 runs: Vec::new(),
22047 filter_range: 0..matching_prefix.len(),
22048 },
22049 icon_path: None,
22050 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
22051 single_line: snippet.name.clone().into(),
22052 plain_text: snippet
22053 .description
22054 .clone()
22055 .map(|description| description.into()),
22056 }),
22057 insert_text_mode: None,
22058 confirm: None,
22059 })
22060 }))
22061 }
22062
22063 Ok(CompletionResponse {
22064 completions,
22065 is_incomplete,
22066 })
22067 })
22068}
22069
22070impl CompletionProvider for Entity<Project> {
22071 fn completions(
22072 &self,
22073 _excerpt_id: ExcerptId,
22074 buffer: &Entity<Buffer>,
22075 buffer_position: text::Anchor,
22076 options: CompletionContext,
22077 _window: &mut Window,
22078 cx: &mut Context<Editor>,
22079 ) -> Task<Result<Vec<CompletionResponse>>> {
22080 self.update(cx, |project, cx| {
22081 let snippets = snippet_completions(project, buffer, buffer_position, cx);
22082 let project_completions = project.completions(buffer, buffer_position, options, cx);
22083 cx.background_spawn(async move {
22084 let mut responses = project_completions.await?;
22085 let snippets = snippets.await?;
22086 if !snippets.completions.is_empty() {
22087 responses.push(snippets);
22088 }
22089 Ok(responses)
22090 })
22091 })
22092 }
22093
22094 fn resolve_completions(
22095 &self,
22096 buffer: Entity<Buffer>,
22097 completion_indices: Vec<usize>,
22098 completions: Rc<RefCell<Box<[Completion]>>>,
22099 cx: &mut Context<Editor>,
22100 ) -> Task<Result<bool>> {
22101 self.update(cx, |project, cx| {
22102 project.lsp_store().update(cx, |lsp_store, cx| {
22103 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
22104 })
22105 })
22106 }
22107
22108 fn apply_additional_edits_for_completion(
22109 &self,
22110 buffer: Entity<Buffer>,
22111 completions: Rc<RefCell<Box<[Completion]>>>,
22112 completion_index: usize,
22113 push_to_history: bool,
22114 cx: &mut Context<Editor>,
22115 ) -> Task<Result<Option<language::Transaction>>> {
22116 self.update(cx, |project, cx| {
22117 project.lsp_store().update(cx, |lsp_store, cx| {
22118 lsp_store.apply_additional_edits_for_completion(
22119 buffer,
22120 completions,
22121 completion_index,
22122 push_to_history,
22123 cx,
22124 )
22125 })
22126 })
22127 }
22128
22129 fn is_completion_trigger(
22130 &self,
22131 buffer: &Entity<Buffer>,
22132 position: language::Anchor,
22133 text: &str,
22134 trigger_in_words: bool,
22135 menu_is_open: bool,
22136 cx: &mut Context<Editor>,
22137 ) -> bool {
22138 let mut chars = text.chars();
22139 let char = if let Some(char) = chars.next() {
22140 char
22141 } else {
22142 return false;
22143 };
22144 if chars.next().is_some() {
22145 return false;
22146 }
22147
22148 let buffer = buffer.read(cx);
22149 let snapshot = buffer.snapshot();
22150 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
22151 return false;
22152 }
22153 let classifier = snapshot.char_classifier_at(position).for_completion(true);
22154 if trigger_in_words && classifier.is_word(char) {
22155 return true;
22156 }
22157
22158 buffer.completion_triggers().contains(text)
22159 }
22160}
22161
22162impl SemanticsProvider for Entity<Project> {
22163 fn hover(
22164 &self,
22165 buffer: &Entity<Buffer>,
22166 position: text::Anchor,
22167 cx: &mut App,
22168 ) -> Option<Task<Vec<project::Hover>>> {
22169 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
22170 }
22171
22172 fn document_highlights(
22173 &self,
22174 buffer: &Entity<Buffer>,
22175 position: text::Anchor,
22176 cx: &mut App,
22177 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
22178 Some(self.update(cx, |project, cx| {
22179 project.document_highlights(buffer, position, cx)
22180 }))
22181 }
22182
22183 fn definitions(
22184 &self,
22185 buffer: &Entity<Buffer>,
22186 position: text::Anchor,
22187 kind: GotoDefinitionKind,
22188 cx: &mut App,
22189 ) -> Option<Task<Result<Vec<LocationLink>>>> {
22190 Some(self.update(cx, |project, cx| match kind {
22191 GotoDefinitionKind::Symbol => project.definitions(&buffer, position, cx),
22192 GotoDefinitionKind::Declaration => project.declarations(&buffer, position, cx),
22193 GotoDefinitionKind::Type => project.type_definitions(&buffer, position, cx),
22194 GotoDefinitionKind::Implementation => project.implementations(&buffer, position, cx),
22195 }))
22196 }
22197
22198 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
22199 // TODO: make this work for remote projects
22200 self.update(cx, |project, cx| {
22201 if project
22202 .active_debug_session(cx)
22203 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
22204 {
22205 return true;
22206 }
22207
22208 buffer.update(cx, |buffer, cx| {
22209 project.any_language_server_supports_inlay_hints(buffer, cx)
22210 })
22211 })
22212 }
22213
22214 fn inline_values(
22215 &self,
22216 buffer_handle: Entity<Buffer>,
22217 range: Range<text::Anchor>,
22218 cx: &mut App,
22219 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22220 self.update(cx, |project, cx| {
22221 let (session, active_stack_frame) = project.active_debug_session(cx)?;
22222
22223 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
22224 })
22225 }
22226
22227 fn inlay_hints(
22228 &self,
22229 buffer_handle: Entity<Buffer>,
22230 range: Range<text::Anchor>,
22231 cx: &mut App,
22232 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22233 Some(self.update(cx, |project, cx| {
22234 project.inlay_hints(buffer_handle, range, cx)
22235 }))
22236 }
22237
22238 fn resolve_inlay_hint(
22239 &self,
22240 hint: InlayHint,
22241 buffer_handle: Entity<Buffer>,
22242 server_id: LanguageServerId,
22243 cx: &mut App,
22244 ) -> Option<Task<anyhow::Result<InlayHint>>> {
22245 Some(self.update(cx, |project, cx| {
22246 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
22247 }))
22248 }
22249
22250 fn range_for_rename(
22251 &self,
22252 buffer: &Entity<Buffer>,
22253 position: text::Anchor,
22254 cx: &mut App,
22255 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
22256 Some(self.update(cx, |project, cx| {
22257 let buffer = buffer.clone();
22258 let task = project.prepare_rename(buffer.clone(), position, cx);
22259 cx.spawn(async move |_, cx| {
22260 Ok(match task.await? {
22261 PrepareRenameResponse::Success(range) => Some(range),
22262 PrepareRenameResponse::InvalidPosition => None,
22263 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
22264 // Fallback on using TreeSitter info to determine identifier range
22265 buffer.read_with(cx, |buffer, _| {
22266 let snapshot = buffer.snapshot();
22267 let (range, kind) = snapshot.surrounding_word(position, false);
22268 if kind != Some(CharKind::Word) {
22269 return None;
22270 }
22271 Some(
22272 snapshot.anchor_before(range.start)
22273 ..snapshot.anchor_after(range.end),
22274 )
22275 })?
22276 }
22277 })
22278 })
22279 }))
22280 }
22281
22282 fn perform_rename(
22283 &self,
22284 buffer: &Entity<Buffer>,
22285 position: text::Anchor,
22286 new_name: String,
22287 cx: &mut App,
22288 ) -> Option<Task<Result<ProjectTransaction>>> {
22289 Some(self.update(cx, |project, cx| {
22290 project.perform_rename(buffer.clone(), position, new_name, cx)
22291 }))
22292 }
22293}
22294
22295fn inlay_hint_settings(
22296 location: Anchor,
22297 snapshot: &MultiBufferSnapshot,
22298 cx: &mut Context<Editor>,
22299) -> InlayHintSettings {
22300 let file = snapshot.file_at(location);
22301 let language = snapshot.language_at(location).map(|l| l.name());
22302 language_settings(language, file, cx).inlay_hints
22303}
22304
22305fn consume_contiguous_rows(
22306 contiguous_row_selections: &mut Vec<Selection<Point>>,
22307 selection: &Selection<Point>,
22308 display_map: &DisplaySnapshot,
22309 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
22310) -> (MultiBufferRow, MultiBufferRow) {
22311 contiguous_row_selections.push(selection.clone());
22312 let start_row = starting_row(selection, display_map);
22313 let mut end_row = ending_row(selection, display_map);
22314
22315 while let Some(next_selection) = selections.peek() {
22316 if next_selection.start.row <= end_row.0 {
22317 end_row = ending_row(next_selection, display_map);
22318 contiguous_row_selections.push(selections.next().unwrap().clone());
22319 } else {
22320 break;
22321 }
22322 }
22323 (start_row, end_row)
22324}
22325
22326fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22327 if selection.start.column > 0 {
22328 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
22329 } else {
22330 MultiBufferRow(selection.start.row)
22331 }
22332}
22333
22334fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22335 if next_selection.end.column > 0 || next_selection.is_empty() {
22336 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
22337 } else {
22338 MultiBufferRow(next_selection.end.row)
22339 }
22340}
22341
22342impl EditorSnapshot {
22343 pub fn remote_selections_in_range<'a>(
22344 &'a self,
22345 range: &'a Range<Anchor>,
22346 collaboration_hub: &dyn CollaborationHub,
22347 cx: &'a App,
22348 ) -> impl 'a + Iterator<Item = RemoteSelection> {
22349 let participant_names = collaboration_hub.user_names(cx);
22350 let participant_indices = collaboration_hub.user_participant_indices(cx);
22351 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
22352 let collaborators_by_replica_id = collaborators_by_peer_id
22353 .values()
22354 .map(|collaborator| (collaborator.replica_id, collaborator))
22355 .collect::<HashMap<_, _>>();
22356 self.buffer_snapshot
22357 .selections_in_range(range, false)
22358 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
22359 if replica_id == AGENT_REPLICA_ID {
22360 Some(RemoteSelection {
22361 replica_id,
22362 selection,
22363 cursor_shape,
22364 line_mode,
22365 collaborator_id: CollaboratorId::Agent,
22366 user_name: Some("Agent".into()),
22367 color: cx.theme().players().agent(),
22368 })
22369 } else {
22370 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
22371 let participant_index = participant_indices.get(&collaborator.user_id).copied();
22372 let user_name = participant_names.get(&collaborator.user_id).cloned();
22373 Some(RemoteSelection {
22374 replica_id,
22375 selection,
22376 cursor_shape,
22377 line_mode,
22378 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
22379 user_name,
22380 color: if let Some(index) = participant_index {
22381 cx.theme().players().color_for_participant(index.0)
22382 } else {
22383 cx.theme().players().absent()
22384 },
22385 })
22386 }
22387 })
22388 }
22389
22390 pub fn hunks_for_ranges(
22391 &self,
22392 ranges: impl IntoIterator<Item = Range<Point>>,
22393 ) -> Vec<MultiBufferDiffHunk> {
22394 let mut hunks = Vec::new();
22395 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
22396 HashMap::default();
22397 for query_range in ranges {
22398 let query_rows =
22399 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
22400 for hunk in self.buffer_snapshot.diff_hunks_in_range(
22401 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
22402 ) {
22403 // Include deleted hunks that are adjacent to the query range, because
22404 // otherwise they would be missed.
22405 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
22406 if hunk.status().is_deleted() {
22407 intersects_range |= hunk.row_range.start == query_rows.end;
22408 intersects_range |= hunk.row_range.end == query_rows.start;
22409 }
22410 if intersects_range {
22411 if !processed_buffer_rows
22412 .entry(hunk.buffer_id)
22413 .or_default()
22414 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
22415 {
22416 continue;
22417 }
22418 hunks.push(hunk);
22419 }
22420 }
22421 }
22422
22423 hunks
22424 }
22425
22426 fn display_diff_hunks_for_rows<'a>(
22427 &'a self,
22428 display_rows: Range<DisplayRow>,
22429 folded_buffers: &'a HashSet<BufferId>,
22430 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
22431 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
22432 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
22433
22434 self.buffer_snapshot
22435 .diff_hunks_in_range(buffer_start..buffer_end)
22436 .filter_map(|hunk| {
22437 if folded_buffers.contains(&hunk.buffer_id) {
22438 return None;
22439 }
22440
22441 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
22442 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
22443
22444 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
22445 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
22446
22447 let display_hunk = if hunk_display_start.column() != 0 {
22448 DisplayDiffHunk::Folded {
22449 display_row: hunk_display_start.row(),
22450 }
22451 } else {
22452 let mut end_row = hunk_display_end.row();
22453 if hunk_display_end.column() > 0 {
22454 end_row.0 += 1;
22455 }
22456 let is_created_file = hunk.is_created_file();
22457 DisplayDiffHunk::Unfolded {
22458 status: hunk.status(),
22459 diff_base_byte_range: hunk.diff_base_byte_range,
22460 display_row_range: hunk_display_start.row()..end_row,
22461 multi_buffer_range: Anchor::range_in_buffer(
22462 hunk.excerpt_id,
22463 hunk.buffer_id,
22464 hunk.buffer_range,
22465 ),
22466 is_created_file,
22467 }
22468 };
22469
22470 Some(display_hunk)
22471 })
22472 }
22473
22474 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
22475 self.display_snapshot.buffer_snapshot.language_at(position)
22476 }
22477
22478 pub fn is_focused(&self) -> bool {
22479 self.is_focused
22480 }
22481
22482 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
22483 self.placeholder_text.as_ref()
22484 }
22485
22486 pub fn scroll_position(&self) -> gpui::Point<f32> {
22487 self.scroll_anchor.scroll_position(&self.display_snapshot)
22488 }
22489
22490 fn gutter_dimensions(
22491 &self,
22492 font_id: FontId,
22493 font_size: Pixels,
22494 max_line_number_width: Pixels,
22495 cx: &App,
22496 ) -> Option<GutterDimensions> {
22497 if !self.show_gutter {
22498 return None;
22499 }
22500
22501 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
22502 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
22503
22504 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
22505 matches!(
22506 ProjectSettings::get_global(cx).git.git_gutter,
22507 Some(GitGutterSetting::TrackedFiles)
22508 )
22509 });
22510 let gutter_settings = EditorSettings::get_global(cx).gutter;
22511 let show_line_numbers = self
22512 .show_line_numbers
22513 .unwrap_or(gutter_settings.line_numbers);
22514 let line_gutter_width = if show_line_numbers {
22515 // Avoid flicker-like gutter resizes when the line number gains another digit by
22516 // only resizing the gutter on files with > 10**min_line_number_digits lines.
22517 let min_width_for_number_on_gutter =
22518 ch_advance * gutter_settings.min_line_number_digits as f32;
22519 max_line_number_width.max(min_width_for_number_on_gutter)
22520 } else {
22521 0.0.into()
22522 };
22523
22524 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
22525 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
22526
22527 let git_blame_entries_width =
22528 self.git_blame_gutter_max_author_length
22529 .map(|max_author_length| {
22530 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
22531 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
22532
22533 /// The number of characters to dedicate to gaps and margins.
22534 const SPACING_WIDTH: usize = 4;
22535
22536 let max_char_count = max_author_length.min(renderer.max_author_length())
22537 + ::git::SHORT_SHA_LENGTH
22538 + MAX_RELATIVE_TIMESTAMP.len()
22539 + SPACING_WIDTH;
22540
22541 ch_advance * max_char_count
22542 });
22543
22544 let is_singleton = self.buffer_snapshot.is_singleton();
22545
22546 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
22547 left_padding += if !is_singleton {
22548 ch_width * 4.0
22549 } else if show_runnables || show_breakpoints {
22550 ch_width * 3.0
22551 } else if show_git_gutter && show_line_numbers {
22552 ch_width * 2.0
22553 } else if show_git_gutter || show_line_numbers {
22554 ch_width
22555 } else {
22556 px(0.)
22557 };
22558
22559 let shows_folds = is_singleton && gutter_settings.folds;
22560
22561 let right_padding = if shows_folds && show_line_numbers {
22562 ch_width * 4.0
22563 } else if shows_folds || (!is_singleton && show_line_numbers) {
22564 ch_width * 3.0
22565 } else if show_line_numbers {
22566 ch_width
22567 } else {
22568 px(0.)
22569 };
22570
22571 Some(GutterDimensions {
22572 left_padding,
22573 right_padding,
22574 width: line_gutter_width + left_padding + right_padding,
22575 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
22576 git_blame_entries_width,
22577 })
22578 }
22579
22580 pub fn render_crease_toggle(
22581 &self,
22582 buffer_row: MultiBufferRow,
22583 row_contains_cursor: bool,
22584 editor: Entity<Editor>,
22585 window: &mut Window,
22586 cx: &mut App,
22587 ) -> Option<AnyElement> {
22588 let folded = self.is_line_folded(buffer_row);
22589 let mut is_foldable = false;
22590
22591 if let Some(crease) = self
22592 .crease_snapshot
22593 .query_row(buffer_row, &self.buffer_snapshot)
22594 {
22595 is_foldable = true;
22596 match crease {
22597 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
22598 if let Some(render_toggle) = render_toggle {
22599 let toggle_callback =
22600 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
22601 if folded {
22602 editor.update(cx, |editor, cx| {
22603 editor.fold_at(buffer_row, window, cx)
22604 });
22605 } else {
22606 editor.update(cx, |editor, cx| {
22607 editor.unfold_at(buffer_row, window, cx)
22608 });
22609 }
22610 });
22611 return Some((render_toggle)(
22612 buffer_row,
22613 folded,
22614 toggle_callback,
22615 window,
22616 cx,
22617 ));
22618 }
22619 }
22620 }
22621 }
22622
22623 is_foldable |= self.starts_indent(buffer_row);
22624
22625 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
22626 Some(
22627 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
22628 .toggle_state(folded)
22629 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
22630 if folded {
22631 this.unfold_at(buffer_row, window, cx);
22632 } else {
22633 this.fold_at(buffer_row, window, cx);
22634 }
22635 }))
22636 .into_any_element(),
22637 )
22638 } else {
22639 None
22640 }
22641 }
22642
22643 pub fn render_crease_trailer(
22644 &self,
22645 buffer_row: MultiBufferRow,
22646 window: &mut Window,
22647 cx: &mut App,
22648 ) -> Option<AnyElement> {
22649 let folded = self.is_line_folded(buffer_row);
22650 if let Crease::Inline { render_trailer, .. } = self
22651 .crease_snapshot
22652 .query_row(buffer_row, &self.buffer_snapshot)?
22653 {
22654 let render_trailer = render_trailer.as_ref()?;
22655 Some(render_trailer(buffer_row, folded, window, cx))
22656 } else {
22657 None
22658 }
22659 }
22660}
22661
22662impl Deref for EditorSnapshot {
22663 type Target = DisplaySnapshot;
22664
22665 fn deref(&self) -> &Self::Target {
22666 &self.display_snapshot
22667 }
22668}
22669
22670#[derive(Clone, Debug, PartialEq, Eq)]
22671pub enum EditorEvent {
22672 InputIgnored {
22673 text: Arc<str>,
22674 },
22675 InputHandled {
22676 utf16_range_to_replace: Option<Range<isize>>,
22677 text: Arc<str>,
22678 },
22679 ExcerptsAdded {
22680 buffer: Entity<Buffer>,
22681 predecessor: ExcerptId,
22682 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
22683 },
22684 ExcerptsRemoved {
22685 ids: Vec<ExcerptId>,
22686 removed_buffer_ids: Vec<BufferId>,
22687 },
22688 BufferFoldToggled {
22689 ids: Vec<ExcerptId>,
22690 folded: bool,
22691 },
22692 ExcerptsEdited {
22693 ids: Vec<ExcerptId>,
22694 },
22695 ExcerptsExpanded {
22696 ids: Vec<ExcerptId>,
22697 },
22698 BufferEdited,
22699 Edited {
22700 transaction_id: clock::Lamport,
22701 },
22702 Reparsed(BufferId),
22703 Focused,
22704 FocusedIn,
22705 Blurred,
22706 DirtyChanged,
22707 Saved,
22708 TitleChanged,
22709 DiffBaseChanged,
22710 SelectionsChanged {
22711 local: bool,
22712 },
22713 ScrollPositionChanged {
22714 local: bool,
22715 autoscroll: bool,
22716 },
22717 Closed,
22718 TransactionUndone {
22719 transaction_id: clock::Lamport,
22720 },
22721 TransactionBegun {
22722 transaction_id: clock::Lamport,
22723 },
22724 Reloaded,
22725 CursorShapeChanged,
22726 PushedToNavHistory {
22727 anchor: Anchor,
22728 is_deactivate: bool,
22729 },
22730}
22731
22732impl EventEmitter<EditorEvent> for Editor {}
22733
22734impl Focusable for Editor {
22735 fn focus_handle(&self, _cx: &App) -> FocusHandle {
22736 self.focus_handle.clone()
22737 }
22738}
22739
22740impl Render for Editor {
22741 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22742 let settings = ThemeSettings::get_global(cx);
22743
22744 let mut text_style = match self.mode {
22745 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
22746 color: cx.theme().colors().editor_foreground,
22747 font_family: settings.ui_font.family.clone(),
22748 font_features: settings.ui_font.features.clone(),
22749 font_fallbacks: settings.ui_font.fallbacks.clone(),
22750 font_size: rems(0.875).into(),
22751 font_weight: settings.ui_font.weight,
22752 line_height: relative(settings.buffer_line_height.value()),
22753 ..Default::default()
22754 },
22755 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
22756 color: cx.theme().colors().editor_foreground,
22757 font_family: settings.buffer_font.family.clone(),
22758 font_features: settings.buffer_font.features.clone(),
22759 font_fallbacks: settings.buffer_font.fallbacks.clone(),
22760 font_size: settings.buffer_font_size(cx).into(),
22761 font_weight: settings.buffer_font.weight,
22762 line_height: relative(settings.buffer_line_height.value()),
22763 ..Default::default()
22764 },
22765 };
22766 if let Some(text_style_refinement) = &self.text_style_refinement {
22767 text_style.refine(text_style_refinement)
22768 }
22769
22770 let background = match self.mode {
22771 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
22772 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
22773 EditorMode::Full { .. } => cx.theme().colors().editor_background,
22774 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
22775 };
22776
22777 EditorElement::new(
22778 &cx.entity(),
22779 EditorStyle {
22780 background,
22781 border: cx.theme().colors().border,
22782 local_player: cx.theme().players().local(),
22783 text: text_style,
22784 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
22785 syntax: cx.theme().syntax().clone(),
22786 status: cx.theme().status().clone(),
22787 inlay_hints_style: make_inlay_hints_style(cx),
22788 inline_completion_styles: make_suggestion_styles(cx),
22789 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
22790 show_underlines: self.diagnostics_enabled(),
22791 },
22792 )
22793 }
22794}
22795
22796impl EntityInputHandler for Editor {
22797 fn text_for_range(
22798 &mut self,
22799 range_utf16: Range<usize>,
22800 adjusted_range: &mut Option<Range<usize>>,
22801 _: &mut Window,
22802 cx: &mut Context<Self>,
22803 ) -> Option<String> {
22804 let snapshot = self.buffer.read(cx).read(cx);
22805 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
22806 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
22807 if (start.0..end.0) != range_utf16 {
22808 adjusted_range.replace(start.0..end.0);
22809 }
22810 Some(snapshot.text_for_range(start..end).collect())
22811 }
22812
22813 fn selected_text_range(
22814 &mut self,
22815 ignore_disabled_input: bool,
22816 _: &mut Window,
22817 cx: &mut Context<Self>,
22818 ) -> Option<UTF16Selection> {
22819 // Prevent the IME menu from appearing when holding down an alphabetic key
22820 // while input is disabled.
22821 if !ignore_disabled_input && !self.input_enabled {
22822 return None;
22823 }
22824
22825 let selection = self.selections.newest::<OffsetUtf16>(cx);
22826 let range = selection.range();
22827
22828 Some(UTF16Selection {
22829 range: range.start.0..range.end.0,
22830 reversed: selection.reversed,
22831 })
22832 }
22833
22834 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
22835 let snapshot = self.buffer.read(cx).read(cx);
22836 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
22837 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
22838 }
22839
22840 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
22841 self.clear_highlights::<InputComposition>(cx);
22842 self.ime_transaction.take();
22843 }
22844
22845 fn replace_text_in_range(
22846 &mut self,
22847 range_utf16: Option<Range<usize>>,
22848 text: &str,
22849 window: &mut Window,
22850 cx: &mut Context<Self>,
22851 ) {
22852 if !self.input_enabled {
22853 cx.emit(EditorEvent::InputIgnored { text: text.into() });
22854 return;
22855 }
22856
22857 self.transact(window, cx, |this, window, cx| {
22858 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
22859 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
22860 Some(this.selection_replacement_ranges(range_utf16, cx))
22861 } else {
22862 this.marked_text_ranges(cx)
22863 };
22864
22865 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
22866 let newest_selection_id = this.selections.newest_anchor().id;
22867 this.selections
22868 .all::<OffsetUtf16>(cx)
22869 .iter()
22870 .zip(ranges_to_replace.iter())
22871 .find_map(|(selection, range)| {
22872 if selection.id == newest_selection_id {
22873 Some(
22874 (range.start.0 as isize - selection.head().0 as isize)
22875 ..(range.end.0 as isize - selection.head().0 as isize),
22876 )
22877 } else {
22878 None
22879 }
22880 })
22881 });
22882
22883 cx.emit(EditorEvent::InputHandled {
22884 utf16_range_to_replace: range_to_replace,
22885 text: text.into(),
22886 });
22887
22888 if let Some(new_selected_ranges) = new_selected_ranges {
22889 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22890 selections.select_ranges(new_selected_ranges)
22891 });
22892 this.backspace(&Default::default(), window, cx);
22893 }
22894
22895 this.handle_input(text, window, cx);
22896 });
22897
22898 if let Some(transaction) = self.ime_transaction {
22899 self.buffer.update(cx, |buffer, cx| {
22900 buffer.group_until_transaction(transaction, cx);
22901 });
22902 }
22903
22904 self.unmark_text(window, cx);
22905 }
22906
22907 fn replace_and_mark_text_in_range(
22908 &mut self,
22909 range_utf16: Option<Range<usize>>,
22910 text: &str,
22911 new_selected_range_utf16: Option<Range<usize>>,
22912 window: &mut Window,
22913 cx: &mut Context<Self>,
22914 ) {
22915 if !self.input_enabled {
22916 return;
22917 }
22918
22919 let transaction = self.transact(window, cx, |this, window, cx| {
22920 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
22921 let snapshot = this.buffer.read(cx).read(cx);
22922 if let Some(relative_range_utf16) = range_utf16.as_ref() {
22923 for marked_range in &mut marked_ranges {
22924 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
22925 marked_range.start.0 += relative_range_utf16.start;
22926 marked_range.start =
22927 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
22928 marked_range.end =
22929 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
22930 }
22931 }
22932 Some(marked_ranges)
22933 } else if let Some(range_utf16) = range_utf16 {
22934 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
22935 Some(this.selection_replacement_ranges(range_utf16, cx))
22936 } else {
22937 None
22938 };
22939
22940 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
22941 let newest_selection_id = this.selections.newest_anchor().id;
22942 this.selections
22943 .all::<OffsetUtf16>(cx)
22944 .iter()
22945 .zip(ranges_to_replace.iter())
22946 .find_map(|(selection, range)| {
22947 if selection.id == newest_selection_id {
22948 Some(
22949 (range.start.0 as isize - selection.head().0 as isize)
22950 ..(range.end.0 as isize - selection.head().0 as isize),
22951 )
22952 } else {
22953 None
22954 }
22955 })
22956 });
22957
22958 cx.emit(EditorEvent::InputHandled {
22959 utf16_range_to_replace: range_to_replace,
22960 text: text.into(),
22961 });
22962
22963 if let Some(ranges) = ranges_to_replace {
22964 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22965 s.select_ranges(ranges)
22966 });
22967 }
22968
22969 let marked_ranges = {
22970 let snapshot = this.buffer.read(cx).read(cx);
22971 this.selections
22972 .disjoint_anchors()
22973 .iter()
22974 .map(|selection| {
22975 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
22976 })
22977 .collect::<Vec<_>>()
22978 };
22979
22980 if text.is_empty() {
22981 this.unmark_text(window, cx);
22982 } else {
22983 this.highlight_text::<InputComposition>(
22984 marked_ranges.clone(),
22985 HighlightStyle {
22986 underline: Some(UnderlineStyle {
22987 thickness: px(1.),
22988 color: None,
22989 wavy: false,
22990 }),
22991 ..Default::default()
22992 },
22993 cx,
22994 );
22995 }
22996
22997 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
22998 let use_autoclose = this.use_autoclose;
22999 let use_auto_surround = this.use_auto_surround;
23000 this.set_use_autoclose(false);
23001 this.set_use_auto_surround(false);
23002 this.handle_input(text, window, cx);
23003 this.set_use_autoclose(use_autoclose);
23004 this.set_use_auto_surround(use_auto_surround);
23005
23006 if let Some(new_selected_range) = new_selected_range_utf16 {
23007 let snapshot = this.buffer.read(cx).read(cx);
23008 let new_selected_ranges = marked_ranges
23009 .into_iter()
23010 .map(|marked_range| {
23011 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
23012 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
23013 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
23014 snapshot.clip_offset_utf16(new_start, Bias::Left)
23015 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
23016 })
23017 .collect::<Vec<_>>();
23018
23019 drop(snapshot);
23020 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23021 selections.select_ranges(new_selected_ranges)
23022 });
23023 }
23024 });
23025
23026 self.ime_transaction = self.ime_transaction.or(transaction);
23027 if let Some(transaction) = self.ime_transaction {
23028 self.buffer.update(cx, |buffer, cx| {
23029 buffer.group_until_transaction(transaction, cx);
23030 });
23031 }
23032
23033 if self.text_highlights::<InputComposition>(cx).is_none() {
23034 self.ime_transaction.take();
23035 }
23036 }
23037
23038 fn bounds_for_range(
23039 &mut self,
23040 range_utf16: Range<usize>,
23041 element_bounds: gpui::Bounds<Pixels>,
23042 window: &mut Window,
23043 cx: &mut Context<Self>,
23044 ) -> Option<gpui::Bounds<Pixels>> {
23045 let text_layout_details = self.text_layout_details(window);
23046 let CharacterDimensions {
23047 em_width,
23048 em_advance,
23049 line_height,
23050 } = self.character_dimensions(window);
23051
23052 let snapshot = self.snapshot(window, cx);
23053 let scroll_position = snapshot.scroll_position();
23054 let scroll_left = scroll_position.x * em_advance;
23055
23056 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
23057 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
23058 + self.gutter_dimensions.full_width();
23059 let y = line_height * (start.row().as_f32() - scroll_position.y);
23060
23061 Some(Bounds {
23062 origin: element_bounds.origin + point(x, y),
23063 size: size(em_width, line_height),
23064 })
23065 }
23066
23067 fn character_index_for_point(
23068 &mut self,
23069 point: gpui::Point<Pixels>,
23070 _window: &mut Window,
23071 _cx: &mut Context<Self>,
23072 ) -> Option<usize> {
23073 let position_map = self.last_position_map.as_ref()?;
23074 if !position_map.text_hitbox.contains(&point) {
23075 return None;
23076 }
23077 let display_point = position_map.point_for_position(point).previous_valid;
23078 let anchor = position_map
23079 .snapshot
23080 .display_point_to_anchor(display_point, Bias::Left);
23081 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
23082 Some(utf16_offset.0)
23083 }
23084}
23085
23086trait SelectionExt {
23087 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
23088 fn spanned_rows(
23089 &self,
23090 include_end_if_at_line_start: bool,
23091 map: &DisplaySnapshot,
23092 ) -> Range<MultiBufferRow>;
23093}
23094
23095impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
23096 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
23097 let start = self
23098 .start
23099 .to_point(&map.buffer_snapshot)
23100 .to_display_point(map);
23101 let end = self
23102 .end
23103 .to_point(&map.buffer_snapshot)
23104 .to_display_point(map);
23105 if self.reversed {
23106 end..start
23107 } else {
23108 start..end
23109 }
23110 }
23111
23112 fn spanned_rows(
23113 &self,
23114 include_end_if_at_line_start: bool,
23115 map: &DisplaySnapshot,
23116 ) -> Range<MultiBufferRow> {
23117 let start = self.start.to_point(&map.buffer_snapshot);
23118 let mut end = self.end.to_point(&map.buffer_snapshot);
23119 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
23120 end.row -= 1;
23121 }
23122
23123 let buffer_start = map.prev_line_boundary(start).0;
23124 let buffer_end = map.next_line_boundary(end).0;
23125 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
23126 }
23127}
23128
23129impl<T: InvalidationRegion> InvalidationStack<T> {
23130 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
23131 where
23132 S: Clone + ToOffset,
23133 {
23134 while let Some(region) = self.last() {
23135 let all_selections_inside_invalidation_ranges =
23136 if selections.len() == region.ranges().len() {
23137 selections
23138 .iter()
23139 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
23140 .all(|(selection, invalidation_range)| {
23141 let head = selection.head().to_offset(buffer);
23142 invalidation_range.start <= head && invalidation_range.end >= head
23143 })
23144 } else {
23145 false
23146 };
23147
23148 if all_selections_inside_invalidation_ranges {
23149 break;
23150 } else {
23151 self.pop();
23152 }
23153 }
23154 }
23155}
23156
23157impl<T> Default for InvalidationStack<T> {
23158 fn default() -> Self {
23159 Self(Default::default())
23160 }
23161}
23162
23163impl<T> Deref for InvalidationStack<T> {
23164 type Target = Vec<T>;
23165
23166 fn deref(&self) -> &Self::Target {
23167 &self.0
23168 }
23169}
23170
23171impl<T> DerefMut for InvalidationStack<T> {
23172 fn deref_mut(&mut self) -> &mut Self::Target {
23173 &mut self.0
23174 }
23175}
23176
23177impl InvalidationRegion for SnippetState {
23178 fn ranges(&self) -> &[Range<Anchor>] {
23179 &self.ranges[self.active_index]
23180 }
23181}
23182
23183fn inline_completion_edit_text(
23184 current_snapshot: &BufferSnapshot,
23185 edits: &[(Range<Anchor>, String)],
23186 edit_preview: &EditPreview,
23187 include_deletions: bool,
23188 cx: &App,
23189) -> HighlightedText {
23190 let edits = edits
23191 .iter()
23192 .map(|(anchor, text)| {
23193 (
23194 anchor.start.text_anchor..anchor.end.text_anchor,
23195 text.clone(),
23196 )
23197 })
23198 .collect::<Vec<_>>();
23199
23200 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
23201}
23202
23203pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
23204 match severity {
23205 lsp::DiagnosticSeverity::ERROR => colors.error,
23206 lsp::DiagnosticSeverity::WARNING => colors.warning,
23207 lsp::DiagnosticSeverity::INFORMATION => colors.info,
23208 lsp::DiagnosticSeverity::HINT => colors.info,
23209 _ => colors.ignored,
23210 }
23211}
23212
23213pub fn styled_runs_for_code_label<'a>(
23214 label: &'a CodeLabel,
23215 syntax_theme: &'a theme::SyntaxTheme,
23216) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
23217 let fade_out = HighlightStyle {
23218 fade_out: Some(0.35),
23219 ..Default::default()
23220 };
23221
23222 let mut prev_end = label.filter_range.end;
23223 label
23224 .runs
23225 .iter()
23226 .enumerate()
23227 .flat_map(move |(ix, (range, highlight_id))| {
23228 let style = if let Some(style) = highlight_id.style(syntax_theme) {
23229 style
23230 } else {
23231 return Default::default();
23232 };
23233 let mut muted_style = style;
23234 muted_style.highlight(fade_out);
23235
23236 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
23237 if range.start >= label.filter_range.end {
23238 if range.start > prev_end {
23239 runs.push((prev_end..range.start, fade_out));
23240 }
23241 runs.push((range.clone(), muted_style));
23242 } else if range.end <= label.filter_range.end {
23243 runs.push((range.clone(), style));
23244 } else {
23245 runs.push((range.start..label.filter_range.end, style));
23246 runs.push((label.filter_range.end..range.end, muted_style));
23247 }
23248 prev_end = cmp::max(prev_end, range.end);
23249
23250 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
23251 runs.push((prev_end..label.text.len(), fade_out));
23252 }
23253
23254 runs
23255 })
23256}
23257
23258pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
23259 let mut prev_index = 0;
23260 let mut prev_codepoint: Option<char> = None;
23261 text.char_indices()
23262 .chain([(text.len(), '\0')])
23263 .filter_map(move |(index, codepoint)| {
23264 let prev_codepoint = prev_codepoint.replace(codepoint)?;
23265 let is_boundary = index == text.len()
23266 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
23267 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
23268 if is_boundary {
23269 let chunk = &text[prev_index..index];
23270 prev_index = index;
23271 Some(chunk)
23272 } else {
23273 None
23274 }
23275 })
23276}
23277
23278pub trait RangeToAnchorExt: Sized {
23279 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
23280
23281 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
23282 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
23283 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
23284 }
23285}
23286
23287impl<T: ToOffset> RangeToAnchorExt for Range<T> {
23288 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
23289 let start_offset = self.start.to_offset(snapshot);
23290 let end_offset = self.end.to_offset(snapshot);
23291 if start_offset == end_offset {
23292 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
23293 } else {
23294 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
23295 }
23296 }
23297}
23298
23299pub trait RowExt {
23300 fn as_f32(&self) -> f32;
23301
23302 fn next_row(&self) -> Self;
23303
23304 fn previous_row(&self) -> Self;
23305
23306 fn minus(&self, other: Self) -> u32;
23307}
23308
23309impl RowExt for DisplayRow {
23310 fn as_f32(&self) -> f32 {
23311 self.0 as f32
23312 }
23313
23314 fn next_row(&self) -> Self {
23315 Self(self.0 + 1)
23316 }
23317
23318 fn previous_row(&self) -> Self {
23319 Self(self.0.saturating_sub(1))
23320 }
23321
23322 fn minus(&self, other: Self) -> u32 {
23323 self.0 - other.0
23324 }
23325}
23326
23327impl RowExt for MultiBufferRow {
23328 fn as_f32(&self) -> f32 {
23329 self.0 as f32
23330 }
23331
23332 fn next_row(&self) -> Self {
23333 Self(self.0 + 1)
23334 }
23335
23336 fn previous_row(&self) -> Self {
23337 Self(self.0.saturating_sub(1))
23338 }
23339
23340 fn minus(&self, other: Self) -> u32 {
23341 self.0 - other.0
23342 }
23343}
23344
23345trait RowRangeExt {
23346 type Row;
23347
23348 fn len(&self) -> usize;
23349
23350 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
23351}
23352
23353impl RowRangeExt for Range<MultiBufferRow> {
23354 type Row = MultiBufferRow;
23355
23356 fn len(&self) -> usize {
23357 (self.end.0 - self.start.0) as usize
23358 }
23359
23360 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
23361 (self.start.0..self.end.0).map(MultiBufferRow)
23362 }
23363}
23364
23365impl RowRangeExt for Range<DisplayRow> {
23366 type Row = DisplayRow;
23367
23368 fn len(&self) -> usize {
23369 (self.end.0 - self.start.0) as usize
23370 }
23371
23372 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
23373 (self.start.0..self.end.0).map(DisplayRow)
23374 }
23375}
23376
23377/// If select range has more than one line, we
23378/// just point the cursor to range.start.
23379fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
23380 if range.start.row == range.end.row {
23381 range
23382 } else {
23383 range.start..range.start
23384 }
23385}
23386pub struct KillRing(ClipboardItem);
23387impl Global for KillRing {}
23388
23389const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
23390
23391enum BreakpointPromptEditAction {
23392 Log,
23393 Condition,
23394 HitCondition,
23395}
23396
23397struct BreakpointPromptEditor {
23398 pub(crate) prompt: Entity<Editor>,
23399 editor: WeakEntity<Editor>,
23400 breakpoint_anchor: Anchor,
23401 breakpoint: Breakpoint,
23402 edit_action: BreakpointPromptEditAction,
23403 block_ids: HashSet<CustomBlockId>,
23404 editor_margins: Arc<Mutex<EditorMargins>>,
23405 _subscriptions: Vec<Subscription>,
23406}
23407
23408impl BreakpointPromptEditor {
23409 const MAX_LINES: u8 = 4;
23410
23411 fn new(
23412 editor: WeakEntity<Editor>,
23413 breakpoint_anchor: Anchor,
23414 breakpoint: Breakpoint,
23415 edit_action: BreakpointPromptEditAction,
23416 window: &mut Window,
23417 cx: &mut Context<Self>,
23418 ) -> Self {
23419 let base_text = match edit_action {
23420 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
23421 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
23422 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
23423 }
23424 .map(|msg| msg.to_string())
23425 .unwrap_or_default();
23426
23427 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
23428 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
23429
23430 let prompt = cx.new(|cx| {
23431 let mut prompt = Editor::new(
23432 EditorMode::AutoHeight {
23433 min_lines: 1,
23434 max_lines: Some(Self::MAX_LINES as usize),
23435 },
23436 buffer,
23437 None,
23438 window,
23439 cx,
23440 );
23441 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
23442 prompt.set_show_cursor_when_unfocused(false, cx);
23443 prompt.set_placeholder_text(
23444 match edit_action {
23445 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
23446 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
23447 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
23448 },
23449 cx,
23450 );
23451
23452 prompt
23453 });
23454
23455 Self {
23456 prompt,
23457 editor,
23458 breakpoint_anchor,
23459 breakpoint,
23460 edit_action,
23461 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
23462 block_ids: Default::default(),
23463 _subscriptions: vec![],
23464 }
23465 }
23466
23467 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
23468 self.block_ids.extend(block_ids)
23469 }
23470
23471 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
23472 if let Some(editor) = self.editor.upgrade() {
23473 let message = self
23474 .prompt
23475 .read(cx)
23476 .buffer
23477 .read(cx)
23478 .as_singleton()
23479 .expect("A multi buffer in breakpoint prompt isn't possible")
23480 .read(cx)
23481 .as_rope()
23482 .to_string();
23483
23484 editor.update(cx, |editor, cx| {
23485 editor.edit_breakpoint_at_anchor(
23486 self.breakpoint_anchor,
23487 self.breakpoint.clone(),
23488 match self.edit_action {
23489 BreakpointPromptEditAction::Log => {
23490 BreakpointEditAction::EditLogMessage(message.into())
23491 }
23492 BreakpointPromptEditAction::Condition => {
23493 BreakpointEditAction::EditCondition(message.into())
23494 }
23495 BreakpointPromptEditAction::HitCondition => {
23496 BreakpointEditAction::EditHitCondition(message.into())
23497 }
23498 },
23499 cx,
23500 );
23501
23502 editor.remove_blocks(self.block_ids.clone(), None, cx);
23503 cx.focus_self(window);
23504 });
23505 }
23506 }
23507
23508 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
23509 self.editor
23510 .update(cx, |editor, cx| {
23511 editor.remove_blocks(self.block_ids.clone(), None, cx);
23512 window.focus(&editor.focus_handle);
23513 })
23514 .log_err();
23515 }
23516
23517 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
23518 let settings = ThemeSettings::get_global(cx);
23519 let text_style = TextStyle {
23520 color: if self.prompt.read(cx).read_only(cx) {
23521 cx.theme().colors().text_disabled
23522 } else {
23523 cx.theme().colors().text
23524 },
23525 font_family: settings.buffer_font.family.clone(),
23526 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23527 font_size: settings.buffer_font_size(cx).into(),
23528 font_weight: settings.buffer_font.weight,
23529 line_height: relative(settings.buffer_line_height.value()),
23530 ..Default::default()
23531 };
23532 EditorElement::new(
23533 &self.prompt,
23534 EditorStyle {
23535 background: cx.theme().colors().editor_background,
23536 local_player: cx.theme().players().local(),
23537 text: text_style,
23538 ..Default::default()
23539 },
23540 )
23541 }
23542}
23543
23544impl Render for BreakpointPromptEditor {
23545 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23546 let editor_margins = *self.editor_margins.lock();
23547 let gutter_dimensions = editor_margins.gutter;
23548 h_flex()
23549 .key_context("Editor")
23550 .bg(cx.theme().colors().editor_background)
23551 .border_y_1()
23552 .border_color(cx.theme().status().info_border)
23553 .size_full()
23554 .py(window.line_height() / 2.5)
23555 .on_action(cx.listener(Self::confirm))
23556 .on_action(cx.listener(Self::cancel))
23557 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
23558 .child(div().flex_1().child(self.render_prompt_editor(cx)))
23559 }
23560}
23561
23562impl Focusable for BreakpointPromptEditor {
23563 fn focus_handle(&self, cx: &App) -> FocusHandle {
23564 self.prompt.focus_handle(cx)
23565 }
23566}
23567
23568fn all_edits_insertions_or_deletions(
23569 edits: &Vec<(Range<Anchor>, String)>,
23570 snapshot: &MultiBufferSnapshot,
23571) -> bool {
23572 let mut all_insertions = true;
23573 let mut all_deletions = true;
23574
23575 for (range, new_text) in edits.iter() {
23576 let range_is_empty = range.to_offset(&snapshot).is_empty();
23577 let text_is_empty = new_text.is_empty();
23578
23579 if range_is_empty != text_is_empty {
23580 if range_is_empty {
23581 all_deletions = false;
23582 } else {
23583 all_insertions = false;
23584 }
23585 } else {
23586 return false;
23587 }
23588
23589 if !all_insertions && !all_deletions {
23590 return false;
23591 }
23592 }
23593 all_insertions || all_deletions
23594}
23595
23596struct MissingEditPredictionKeybindingTooltip;
23597
23598impl Render for MissingEditPredictionKeybindingTooltip {
23599 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23600 ui::tooltip_container(window, cx, |container, _, cx| {
23601 container
23602 .flex_shrink_0()
23603 .max_w_80()
23604 .min_h(rems_from_px(124.))
23605 .justify_between()
23606 .child(
23607 v_flex()
23608 .flex_1()
23609 .text_ui_sm(cx)
23610 .child(Label::new("Conflict with Accept Keybinding"))
23611 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
23612 )
23613 .child(
23614 h_flex()
23615 .pb_1()
23616 .gap_1()
23617 .items_end()
23618 .w_full()
23619 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
23620 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
23621 }))
23622 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
23623 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
23624 })),
23625 )
23626 })
23627 }
23628}
23629
23630#[derive(Debug, Clone, Copy, PartialEq)]
23631pub struct LineHighlight {
23632 pub background: Background,
23633 pub border: Option<gpui::Hsla>,
23634 pub include_gutter: bool,
23635 pub type_id: Option<TypeId>,
23636}
23637
23638struct LineManipulationResult {
23639 pub new_text: String,
23640 pub line_count_before: usize,
23641 pub line_count_after: usize,
23642}
23643
23644fn render_diff_hunk_controls(
23645 row: u32,
23646 status: &DiffHunkStatus,
23647 hunk_range: Range<Anchor>,
23648 is_created_file: bool,
23649 line_height: Pixels,
23650 editor: &Entity<Editor>,
23651 _window: &mut Window,
23652 cx: &mut App,
23653) -> AnyElement {
23654 h_flex()
23655 .h(line_height)
23656 .mr_1()
23657 .gap_1()
23658 .px_0p5()
23659 .pb_1()
23660 .border_x_1()
23661 .border_b_1()
23662 .border_color(cx.theme().colors().border_variant)
23663 .rounded_b_lg()
23664 .bg(cx.theme().colors().editor_background)
23665 .gap_1()
23666 .block_mouse_except_scroll()
23667 .shadow_md()
23668 .child(if status.has_secondary_hunk() {
23669 Button::new(("stage", row as u64), "Stage")
23670 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
23671 .tooltip({
23672 let focus_handle = editor.focus_handle(cx);
23673 move |window, cx| {
23674 Tooltip::for_action_in(
23675 "Stage Hunk",
23676 &::git::ToggleStaged,
23677 &focus_handle,
23678 window,
23679 cx,
23680 )
23681 }
23682 })
23683 .on_click({
23684 let editor = editor.clone();
23685 move |_event, _window, cx| {
23686 editor.update(cx, |editor, cx| {
23687 editor.stage_or_unstage_diff_hunks(
23688 true,
23689 vec![hunk_range.start..hunk_range.start],
23690 cx,
23691 );
23692 });
23693 }
23694 })
23695 } else {
23696 Button::new(("unstage", row as u64), "Unstage")
23697 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
23698 .tooltip({
23699 let focus_handle = editor.focus_handle(cx);
23700 move |window, cx| {
23701 Tooltip::for_action_in(
23702 "Unstage Hunk",
23703 &::git::ToggleStaged,
23704 &focus_handle,
23705 window,
23706 cx,
23707 )
23708 }
23709 })
23710 .on_click({
23711 let editor = editor.clone();
23712 move |_event, _window, cx| {
23713 editor.update(cx, |editor, cx| {
23714 editor.stage_or_unstage_diff_hunks(
23715 false,
23716 vec![hunk_range.start..hunk_range.start],
23717 cx,
23718 );
23719 });
23720 }
23721 })
23722 })
23723 .child(
23724 Button::new(("restore", row as u64), "Restore")
23725 .tooltip({
23726 let focus_handle = editor.focus_handle(cx);
23727 move |window, cx| {
23728 Tooltip::for_action_in(
23729 "Restore Hunk",
23730 &::git::Restore,
23731 &focus_handle,
23732 window,
23733 cx,
23734 )
23735 }
23736 })
23737 .on_click({
23738 let editor = editor.clone();
23739 move |_event, window, cx| {
23740 editor.update(cx, |editor, cx| {
23741 let snapshot = editor.snapshot(window, cx);
23742 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
23743 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
23744 });
23745 }
23746 })
23747 .disabled(is_created_file),
23748 )
23749 .when(
23750 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
23751 |el| {
23752 el.child(
23753 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
23754 .shape(IconButtonShape::Square)
23755 .icon_size(IconSize::Small)
23756 // .disabled(!has_multiple_hunks)
23757 .tooltip({
23758 let focus_handle = editor.focus_handle(cx);
23759 move |window, cx| {
23760 Tooltip::for_action_in(
23761 "Next Hunk",
23762 &GoToHunk,
23763 &focus_handle,
23764 window,
23765 cx,
23766 )
23767 }
23768 })
23769 .on_click({
23770 let editor = editor.clone();
23771 move |_event, window, cx| {
23772 editor.update(cx, |editor, cx| {
23773 let snapshot = editor.snapshot(window, cx);
23774 let position =
23775 hunk_range.end.to_point(&snapshot.buffer_snapshot);
23776 editor.go_to_hunk_before_or_after_position(
23777 &snapshot,
23778 position,
23779 Direction::Next,
23780 window,
23781 cx,
23782 );
23783 editor.expand_selected_diff_hunks(cx);
23784 });
23785 }
23786 }),
23787 )
23788 .child(
23789 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
23790 .shape(IconButtonShape::Square)
23791 .icon_size(IconSize::Small)
23792 // .disabled(!has_multiple_hunks)
23793 .tooltip({
23794 let focus_handle = editor.focus_handle(cx);
23795 move |window, cx| {
23796 Tooltip::for_action_in(
23797 "Previous Hunk",
23798 &GoToPreviousHunk,
23799 &focus_handle,
23800 window,
23801 cx,
23802 )
23803 }
23804 })
23805 .on_click({
23806 let editor = editor.clone();
23807 move |_event, window, cx| {
23808 editor.update(cx, |editor, cx| {
23809 let snapshot = editor.snapshot(window, cx);
23810 let point =
23811 hunk_range.start.to_point(&snapshot.buffer_snapshot);
23812 editor.go_to_hunk_before_or_after_position(
23813 &snapshot,
23814 point,
23815 Direction::Prev,
23816 window,
23817 cx,
23818 );
23819 editor.expand_selected_diff_hunks(cx);
23820 });
23821 }
23822 }),
23823 )
23824 },
23825 )
23826 .into_any_element()
23827}