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, 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, 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.
1308pub struct SelectionEffects {
1309 nav_history: Option<bool>,
1310 completions: bool,
1311 scroll: Option<Autoscroll>,
1312}
1313
1314impl Default for SelectionEffects {
1315 fn default() -> Self {
1316 Self {
1317 nav_history: None,
1318 completions: true,
1319 scroll: Some(Autoscroll::fit()),
1320 }
1321 }
1322}
1323impl SelectionEffects {
1324 pub fn scroll(scroll: Autoscroll) -> Self {
1325 Self {
1326 scroll: Some(scroll),
1327 ..Default::default()
1328 }
1329 }
1330
1331 pub fn no_scroll() -> Self {
1332 Self {
1333 scroll: None,
1334 ..Default::default()
1335 }
1336 }
1337
1338 pub fn completions(self, completions: bool) -> Self {
1339 Self {
1340 completions,
1341 ..self
1342 }
1343 }
1344
1345 pub fn nav_history(self, nav_history: bool) -> Self {
1346 Self {
1347 nav_history: Some(nav_history),
1348 ..self
1349 }
1350 }
1351}
1352
1353struct DeferredSelectionEffectsState {
1354 changed: bool,
1355 effects: SelectionEffects,
1356 old_cursor_position: Anchor,
1357 history_entry: SelectionHistoryEntry,
1358}
1359
1360#[derive(Default)]
1361struct SelectionHistory {
1362 #[allow(clippy::type_complexity)]
1363 selections_by_transaction:
1364 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1365 mode: SelectionHistoryMode,
1366 undo_stack: VecDeque<SelectionHistoryEntry>,
1367 redo_stack: VecDeque<SelectionHistoryEntry>,
1368}
1369
1370impl SelectionHistory {
1371 #[track_caller]
1372 fn insert_transaction(
1373 &mut self,
1374 transaction_id: TransactionId,
1375 selections: Arc<[Selection<Anchor>]>,
1376 ) {
1377 if selections.is_empty() {
1378 log::error!(
1379 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1380 std::panic::Location::caller()
1381 );
1382 return;
1383 }
1384 self.selections_by_transaction
1385 .insert(transaction_id, (selections, None));
1386 }
1387
1388 #[allow(clippy::type_complexity)]
1389 fn transaction(
1390 &self,
1391 transaction_id: TransactionId,
1392 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1393 self.selections_by_transaction.get(&transaction_id)
1394 }
1395
1396 #[allow(clippy::type_complexity)]
1397 fn transaction_mut(
1398 &mut self,
1399 transaction_id: TransactionId,
1400 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1401 self.selections_by_transaction.get_mut(&transaction_id)
1402 }
1403
1404 fn push(&mut self, entry: SelectionHistoryEntry) {
1405 if !entry.selections.is_empty() {
1406 match self.mode {
1407 SelectionHistoryMode::Normal => {
1408 self.push_undo(entry);
1409 self.redo_stack.clear();
1410 }
1411 SelectionHistoryMode::Undoing => self.push_redo(entry),
1412 SelectionHistoryMode::Redoing => self.push_undo(entry),
1413 SelectionHistoryMode::Skipping => {}
1414 }
1415 }
1416 }
1417
1418 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1419 if self
1420 .undo_stack
1421 .back()
1422 .map_or(true, |e| e.selections != entry.selections)
1423 {
1424 self.undo_stack.push_back(entry);
1425 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1426 self.undo_stack.pop_front();
1427 }
1428 }
1429 }
1430
1431 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1432 if self
1433 .redo_stack
1434 .back()
1435 .map_or(true, |e| e.selections != entry.selections)
1436 {
1437 self.redo_stack.push_back(entry);
1438 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1439 self.redo_stack.pop_front();
1440 }
1441 }
1442 }
1443}
1444
1445#[derive(Clone, Copy)]
1446pub struct RowHighlightOptions {
1447 pub autoscroll: bool,
1448 pub include_gutter: bool,
1449}
1450
1451impl Default for RowHighlightOptions {
1452 fn default() -> Self {
1453 Self {
1454 autoscroll: Default::default(),
1455 include_gutter: true,
1456 }
1457 }
1458}
1459
1460struct RowHighlight {
1461 index: usize,
1462 range: Range<Anchor>,
1463 color: Hsla,
1464 options: RowHighlightOptions,
1465 type_id: TypeId,
1466}
1467
1468#[derive(Clone, Debug)]
1469struct AddSelectionsState {
1470 groups: Vec<AddSelectionsGroup>,
1471}
1472
1473#[derive(Clone, Debug)]
1474struct AddSelectionsGroup {
1475 above: bool,
1476 stack: Vec<usize>,
1477}
1478
1479#[derive(Clone)]
1480struct SelectNextState {
1481 query: AhoCorasick,
1482 wordwise: bool,
1483 done: bool,
1484}
1485
1486impl std::fmt::Debug for SelectNextState {
1487 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1488 f.debug_struct(std::any::type_name::<Self>())
1489 .field("wordwise", &self.wordwise)
1490 .field("done", &self.done)
1491 .finish()
1492 }
1493}
1494
1495#[derive(Debug)]
1496struct AutocloseRegion {
1497 selection_id: usize,
1498 range: Range<Anchor>,
1499 pair: BracketPair,
1500}
1501
1502#[derive(Debug)]
1503struct SnippetState {
1504 ranges: Vec<Vec<Range<Anchor>>>,
1505 active_index: usize,
1506 choices: Vec<Option<Vec<String>>>,
1507}
1508
1509#[doc(hidden)]
1510pub struct RenameState {
1511 pub range: Range<Anchor>,
1512 pub old_name: Arc<str>,
1513 pub editor: Entity<Editor>,
1514 block_id: CustomBlockId,
1515}
1516
1517struct InvalidationStack<T>(Vec<T>);
1518
1519struct RegisteredInlineCompletionProvider {
1520 provider: Arc<dyn InlineCompletionProviderHandle>,
1521 _subscription: Subscription,
1522}
1523
1524#[derive(Debug, PartialEq, Eq)]
1525pub struct ActiveDiagnosticGroup {
1526 pub active_range: Range<Anchor>,
1527 pub active_message: String,
1528 pub group_id: usize,
1529 pub blocks: HashSet<CustomBlockId>,
1530}
1531
1532#[derive(Debug, PartialEq, Eq)]
1533
1534pub(crate) enum ActiveDiagnostic {
1535 None,
1536 All,
1537 Group(ActiveDiagnosticGroup),
1538}
1539
1540#[derive(Serialize, Deserialize, Clone, Debug)]
1541pub struct ClipboardSelection {
1542 /// The number of bytes in this selection.
1543 pub len: usize,
1544 /// Whether this was a full-line selection.
1545 pub is_entire_line: bool,
1546 /// The indentation of the first line when this content was originally copied.
1547 pub first_line_indent: u32,
1548}
1549
1550// selections, scroll behavior, was newest selection reversed
1551type SelectSyntaxNodeHistoryState = (
1552 Box<[Selection<usize>]>,
1553 SelectSyntaxNodeScrollBehavior,
1554 bool,
1555);
1556
1557#[derive(Default)]
1558struct SelectSyntaxNodeHistory {
1559 stack: Vec<SelectSyntaxNodeHistoryState>,
1560 // disable temporarily to allow changing selections without losing the stack
1561 pub disable_clearing: bool,
1562}
1563
1564impl SelectSyntaxNodeHistory {
1565 pub fn try_clear(&mut self) {
1566 if !self.disable_clearing {
1567 self.stack.clear();
1568 }
1569 }
1570
1571 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1572 self.stack.push(selection);
1573 }
1574
1575 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1576 self.stack.pop()
1577 }
1578}
1579
1580enum SelectSyntaxNodeScrollBehavior {
1581 CursorTop,
1582 FitSelection,
1583 CursorBottom,
1584}
1585
1586#[derive(Debug)]
1587pub(crate) struct NavigationData {
1588 cursor_anchor: Anchor,
1589 cursor_position: Point,
1590 scroll_anchor: ScrollAnchor,
1591 scroll_top_row: u32,
1592}
1593
1594#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1595pub enum GotoDefinitionKind {
1596 Symbol,
1597 Declaration,
1598 Type,
1599 Implementation,
1600}
1601
1602#[derive(Debug, Clone)]
1603enum InlayHintRefreshReason {
1604 ModifiersChanged(bool),
1605 Toggle(bool),
1606 SettingsChange(InlayHintSettings),
1607 NewLinesShown,
1608 BufferEdited(HashSet<Arc<Language>>),
1609 RefreshRequested,
1610 ExcerptsRemoved(Vec<ExcerptId>),
1611}
1612
1613impl InlayHintRefreshReason {
1614 fn description(&self) -> &'static str {
1615 match self {
1616 Self::ModifiersChanged(_) => "modifiers changed",
1617 Self::Toggle(_) => "toggle",
1618 Self::SettingsChange(_) => "settings change",
1619 Self::NewLinesShown => "new lines shown",
1620 Self::BufferEdited(_) => "buffer edited",
1621 Self::RefreshRequested => "refresh requested",
1622 Self::ExcerptsRemoved(_) => "excerpts removed",
1623 }
1624 }
1625}
1626
1627pub enum FormatTarget {
1628 Buffers(HashSet<Entity<Buffer>>),
1629 Ranges(Vec<Range<MultiBufferPoint>>),
1630}
1631
1632pub(crate) struct FocusedBlock {
1633 id: BlockId,
1634 focus_handle: WeakFocusHandle,
1635}
1636
1637#[derive(Clone)]
1638enum JumpData {
1639 MultiBufferRow {
1640 row: MultiBufferRow,
1641 line_offset_from_top: u32,
1642 },
1643 MultiBufferPoint {
1644 excerpt_id: ExcerptId,
1645 position: Point,
1646 anchor: text::Anchor,
1647 line_offset_from_top: u32,
1648 },
1649}
1650
1651pub enum MultibufferSelectionMode {
1652 First,
1653 All,
1654}
1655
1656#[derive(Clone, Copy, Debug, Default)]
1657pub struct RewrapOptions {
1658 pub override_language_settings: bool,
1659 pub preserve_existing_whitespace: bool,
1660}
1661
1662impl Editor {
1663 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1664 let buffer = cx.new(|cx| Buffer::local("", cx));
1665 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1666 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1667 }
1668
1669 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1670 let buffer = cx.new(|cx| Buffer::local("", cx));
1671 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1672 Self::new(EditorMode::full(), buffer, None, window, cx)
1673 }
1674
1675 pub fn auto_height(
1676 min_lines: usize,
1677 max_lines: usize,
1678 window: &mut Window,
1679 cx: &mut Context<Self>,
1680 ) -> Self {
1681 let buffer = cx.new(|cx| Buffer::local("", cx));
1682 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1683 Self::new(
1684 EditorMode::AutoHeight {
1685 min_lines,
1686 max_lines: Some(max_lines),
1687 },
1688 buffer,
1689 None,
1690 window,
1691 cx,
1692 )
1693 }
1694
1695 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1696 /// The editor grows as tall as needed to fit its content.
1697 pub fn auto_height_unbounded(
1698 min_lines: usize,
1699 window: &mut Window,
1700 cx: &mut Context<Self>,
1701 ) -> Self {
1702 let buffer = cx.new(|cx| Buffer::local("", cx));
1703 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1704 Self::new(
1705 EditorMode::AutoHeight {
1706 min_lines,
1707 max_lines: None,
1708 },
1709 buffer,
1710 None,
1711 window,
1712 cx,
1713 )
1714 }
1715
1716 pub fn for_buffer(
1717 buffer: Entity<Buffer>,
1718 project: Option<Entity<Project>>,
1719 window: &mut Window,
1720 cx: &mut Context<Self>,
1721 ) -> Self {
1722 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1723 Self::new(EditorMode::full(), buffer, project, window, cx)
1724 }
1725
1726 pub fn for_multibuffer(
1727 buffer: Entity<MultiBuffer>,
1728 project: Option<Entity<Project>>,
1729 window: &mut Window,
1730 cx: &mut Context<Self>,
1731 ) -> Self {
1732 Self::new(EditorMode::full(), buffer, project, window, cx)
1733 }
1734
1735 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1736 let mut clone = Self::new(
1737 self.mode.clone(),
1738 self.buffer.clone(),
1739 self.project.clone(),
1740 window,
1741 cx,
1742 );
1743 self.display_map.update(cx, |display_map, cx| {
1744 let snapshot = display_map.snapshot(cx);
1745 clone.display_map.update(cx, |display_map, cx| {
1746 display_map.set_state(&snapshot, cx);
1747 });
1748 });
1749 clone.folds_did_change(cx);
1750 clone.selections.clone_state(&self.selections);
1751 clone.scroll_manager.clone_state(&self.scroll_manager);
1752 clone.searchable = self.searchable;
1753 clone.read_only = self.read_only;
1754 clone
1755 }
1756
1757 pub fn new(
1758 mode: EditorMode,
1759 buffer: Entity<MultiBuffer>,
1760 project: Option<Entity<Project>>,
1761 window: &mut Window,
1762 cx: &mut Context<Self>,
1763 ) -> Self {
1764 Editor::new_internal(mode, buffer, project, None, window, cx)
1765 }
1766
1767 fn new_internal(
1768 mode: EditorMode,
1769 buffer: Entity<MultiBuffer>,
1770 project: Option<Entity<Project>>,
1771 display_map: Option<Entity<DisplayMap>>,
1772 window: &mut Window,
1773 cx: &mut Context<Self>,
1774 ) -> Self {
1775 debug_assert!(
1776 display_map.is_none() || mode.is_minimap(),
1777 "Providing a display map for a new editor is only intended for the minimap and might have unindended side effects otherwise!"
1778 );
1779
1780 let full_mode = mode.is_full();
1781 let is_minimap = mode.is_minimap();
1782 let diagnostics_max_severity = if full_mode {
1783 EditorSettings::get_global(cx)
1784 .diagnostics_max_severity
1785 .unwrap_or(DiagnosticSeverity::Hint)
1786 } else {
1787 DiagnosticSeverity::Off
1788 };
1789 let style = window.text_style();
1790 let font_size = style.font_size.to_pixels(window.rem_size());
1791 let editor = cx.entity().downgrade();
1792 let fold_placeholder = FoldPlaceholder {
1793 constrain_width: true,
1794 render: Arc::new(move |fold_id, fold_range, cx| {
1795 let editor = editor.clone();
1796 div()
1797 .id(fold_id)
1798 .bg(cx.theme().colors().ghost_element_background)
1799 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1800 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1801 .rounded_xs()
1802 .size_full()
1803 .cursor_pointer()
1804 .child("⋯")
1805 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1806 .on_click(move |_, _window, cx| {
1807 editor
1808 .update(cx, |editor, cx| {
1809 editor.unfold_ranges(
1810 &[fold_range.start..fold_range.end],
1811 true,
1812 false,
1813 cx,
1814 );
1815 cx.stop_propagation();
1816 })
1817 .ok();
1818 })
1819 .into_any()
1820 }),
1821 merge_adjacent: true,
1822 ..FoldPlaceholder::default()
1823 };
1824 let display_map = display_map.unwrap_or_else(|| {
1825 cx.new(|cx| {
1826 DisplayMap::new(
1827 buffer.clone(),
1828 style.font(),
1829 font_size,
1830 None,
1831 FILE_HEADER_HEIGHT,
1832 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1833 fold_placeholder,
1834 diagnostics_max_severity,
1835 cx,
1836 )
1837 })
1838 });
1839
1840 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1841
1842 let blink_manager = cx.new(|cx| {
1843 let mut blink_manager = BlinkManager::new(CURSOR_BLINK_INTERVAL, cx);
1844 if is_minimap {
1845 blink_manager.disable(cx);
1846 }
1847 blink_manager
1848 });
1849
1850 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1851 .then(|| language_settings::SoftWrap::None);
1852
1853 let mut project_subscriptions = Vec::new();
1854 if full_mode {
1855 if let Some(project) = project.as_ref() {
1856 project_subscriptions.push(cx.subscribe_in(
1857 project,
1858 window,
1859 |editor, _, event, window, cx| match event {
1860 project::Event::RefreshCodeLens => {
1861 // we always query lens with actions, without storing them, always refreshing them
1862 }
1863 project::Event::RefreshInlayHints => {
1864 editor
1865 .refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1866 }
1867 project::Event::LanguageServerAdded(..)
1868 | project::Event::LanguageServerRemoved(..) => {
1869 if editor.tasks_update_task.is_none() {
1870 editor.tasks_update_task =
1871 Some(editor.refresh_runnables(window, cx));
1872 }
1873 editor.update_lsp_data(true, None, window, cx);
1874 }
1875 project::Event::SnippetEdit(id, snippet_edits) => {
1876 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1877 let focus_handle = editor.focus_handle(cx);
1878 if focus_handle.is_focused(window) {
1879 let snapshot = buffer.read(cx).snapshot();
1880 for (range, snippet) in snippet_edits {
1881 let editor_range =
1882 language::range_from_lsp(*range).to_offset(&snapshot);
1883 editor
1884 .insert_snippet(
1885 &[editor_range],
1886 snippet.clone(),
1887 window,
1888 cx,
1889 )
1890 .ok();
1891 }
1892 }
1893 }
1894 }
1895 _ => {}
1896 },
1897 ));
1898 if let Some(task_inventory) = project
1899 .read(cx)
1900 .task_store()
1901 .read(cx)
1902 .task_inventory()
1903 .cloned()
1904 {
1905 project_subscriptions.push(cx.observe_in(
1906 &task_inventory,
1907 window,
1908 |editor, _, window, cx| {
1909 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1910 },
1911 ));
1912 };
1913
1914 project_subscriptions.push(cx.subscribe_in(
1915 &project.read(cx).breakpoint_store(),
1916 window,
1917 |editor, _, event, window, cx| match event {
1918 BreakpointStoreEvent::ClearDebugLines => {
1919 editor.clear_row_highlights::<ActiveDebugLine>();
1920 editor.refresh_inline_values(cx);
1921 }
1922 BreakpointStoreEvent::SetDebugLine => {
1923 if editor.go_to_active_debug_line(window, cx) {
1924 cx.stop_propagation();
1925 }
1926
1927 editor.refresh_inline_values(cx);
1928 }
1929 _ => {}
1930 },
1931 ));
1932 let git_store = project.read(cx).git_store().clone();
1933 let project = project.clone();
1934 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
1935 match event {
1936 GitStoreEvent::RepositoryUpdated(
1937 _,
1938 RepositoryEvent::Updated {
1939 new_instance: true, ..
1940 },
1941 _,
1942 ) => {
1943 this.load_diff_task = Some(
1944 update_uncommitted_diff_for_buffer(
1945 cx.entity(),
1946 &project,
1947 this.buffer.read(cx).all_buffers(),
1948 this.buffer.clone(),
1949 cx,
1950 )
1951 .shared(),
1952 );
1953 }
1954 _ => {}
1955 }
1956 }));
1957 }
1958 }
1959
1960 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1961
1962 let inlay_hint_settings =
1963 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1964 let focus_handle = cx.focus_handle();
1965 if !is_minimap {
1966 cx.on_focus(&focus_handle, window, Self::handle_focus)
1967 .detach();
1968 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1969 .detach();
1970 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1971 .detach();
1972 cx.on_blur(&focus_handle, window, Self::handle_blur)
1973 .detach();
1974 cx.observe_pending_input(window, Self::observe_pending_input)
1975 .detach();
1976 }
1977
1978 let show_indent_guides = if matches!(
1979 mode,
1980 EditorMode::SingleLine { .. } | EditorMode::Minimap { .. }
1981 ) {
1982 Some(false)
1983 } else {
1984 None
1985 };
1986
1987 let breakpoint_store = match (&mode, project.as_ref()) {
1988 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
1989 _ => None,
1990 };
1991
1992 let mut code_action_providers = Vec::new();
1993 let mut load_uncommitted_diff = None;
1994 if let Some(project) = project.clone() {
1995 load_uncommitted_diff = Some(
1996 update_uncommitted_diff_for_buffer(
1997 cx.entity(),
1998 &project,
1999 buffer.read(cx).all_buffers(),
2000 buffer.clone(),
2001 cx,
2002 )
2003 .shared(),
2004 );
2005 code_action_providers.push(Rc::new(project) as Rc<_>);
2006 }
2007
2008 let mut editor = Self {
2009 focus_handle,
2010 show_cursor_when_unfocused: false,
2011 last_focused_descendant: None,
2012 buffer: buffer.clone(),
2013 display_map: display_map.clone(),
2014 selections,
2015 scroll_manager: ScrollManager::new(cx),
2016 columnar_selection_state: None,
2017 add_selections_state: None,
2018 select_next_state: None,
2019 select_prev_state: None,
2020 selection_history: SelectionHistory::default(),
2021 defer_selection_effects: false,
2022 deferred_selection_effects_state: None,
2023 autoclose_regions: Vec::new(),
2024 snippet_stack: InvalidationStack::default(),
2025 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2026 ime_transaction: None,
2027 active_diagnostics: ActiveDiagnostic::None,
2028 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2029 inline_diagnostics_update: Task::ready(()),
2030 inline_diagnostics: Vec::new(),
2031 soft_wrap_mode_override,
2032 diagnostics_max_severity,
2033 hard_wrap: None,
2034 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2035 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2036 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2037 project,
2038 blink_manager: blink_manager.clone(),
2039 show_local_selections: true,
2040 show_scrollbars: ScrollbarAxes {
2041 horizontal: full_mode,
2042 vertical: full_mode,
2043 },
2044 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2045 offset_content: !matches!(mode, EditorMode::SingleLine { .. }),
2046 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2047 show_gutter: full_mode,
2048 show_line_numbers: (!full_mode).then_some(false),
2049 use_relative_line_numbers: None,
2050 disable_expand_excerpt_buttons: !full_mode,
2051 show_git_diff_gutter: None,
2052 show_code_actions: None,
2053 show_runnables: None,
2054 show_breakpoints: None,
2055 show_wrap_guides: None,
2056 show_indent_guides,
2057 placeholder_text: None,
2058 highlight_order: 0,
2059 highlighted_rows: HashMap::default(),
2060 background_highlights: TreeMap::default(),
2061 gutter_highlights: TreeMap::default(),
2062 scrollbar_marker_state: ScrollbarMarkerState::default(),
2063 active_indent_guides_state: ActiveIndentGuidesState::default(),
2064 nav_history: None,
2065 context_menu: RefCell::new(None),
2066 context_menu_options: None,
2067 mouse_context_menu: None,
2068 completion_tasks: Vec::new(),
2069 inline_blame_popover: None,
2070 inline_blame_popover_show_task: None,
2071 signature_help_state: SignatureHelpState::default(),
2072 auto_signature_help: None,
2073 find_all_references_task_sources: Vec::new(),
2074 next_completion_id: 0,
2075 next_inlay_id: 0,
2076 code_action_providers,
2077 available_code_actions: None,
2078 code_actions_task: None,
2079 quick_selection_highlight_task: None,
2080 debounced_selection_highlight_task: None,
2081 document_highlights_task: None,
2082 linked_editing_range_task: None,
2083 pending_rename: None,
2084 searchable: !is_minimap,
2085 cursor_shape: EditorSettings::get_global(cx)
2086 .cursor_shape
2087 .unwrap_or_default(),
2088 current_line_highlight: None,
2089 autoindent_mode: Some(AutoindentMode::EachLine),
2090 collapse_matches: false,
2091 workspace: None,
2092 input_enabled: !is_minimap,
2093 use_modal_editing: full_mode,
2094 read_only: is_minimap,
2095 use_autoclose: true,
2096 use_auto_surround: true,
2097 auto_replace_emoji_shortcode: false,
2098 jsx_tag_auto_close_enabled_in_any_buffer: false,
2099 leader_id: None,
2100 remote_id: None,
2101 hover_state: HoverState::default(),
2102 pending_mouse_down: None,
2103 hovered_link_state: None,
2104 edit_prediction_provider: None,
2105 active_inline_completion: None,
2106 stale_inline_completion_in_menu: None,
2107 edit_prediction_preview: EditPredictionPreview::Inactive {
2108 released_too_fast: false,
2109 },
2110 inline_diagnostics_enabled: full_mode,
2111 diagnostics_enabled: full_mode,
2112 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2113 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
2114 gutter_hovered: false,
2115 pixel_position_of_newest_cursor: None,
2116 last_bounds: None,
2117 last_position_map: None,
2118 expect_bounds_change: None,
2119 gutter_dimensions: GutterDimensions::default(),
2120 style: None,
2121 show_cursor_names: false,
2122 hovered_cursors: HashMap::default(),
2123 next_editor_action_id: EditorActionId::default(),
2124 editor_actions: Rc::default(),
2125 inline_completions_hidden_for_vim_mode: false,
2126 show_inline_completions_override: None,
2127 menu_inline_completions_policy: MenuInlineCompletionsPolicy::ByProvider,
2128 edit_prediction_settings: EditPredictionSettings::Disabled,
2129 edit_prediction_indent_conflict: false,
2130 edit_prediction_requires_modifier_in_indent_conflict: true,
2131 custom_context_menu: None,
2132 show_git_blame_gutter: false,
2133 show_git_blame_inline: false,
2134 show_selection_menu: None,
2135 show_git_blame_inline_delay_task: None,
2136 git_blame_inline_enabled: full_mode
2137 && ProjectSettings::get_global(cx).git.inline_blame_enabled(),
2138 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2139 serialize_dirty_buffers: !is_minimap
2140 && ProjectSettings::get_global(cx)
2141 .session
2142 .restore_unsaved_buffers,
2143 blame: None,
2144 blame_subscription: None,
2145 tasks: BTreeMap::default(),
2146
2147 breakpoint_store,
2148 gutter_breakpoint_indicator: (None, None),
2149 hovered_diff_hunk_row: None,
2150 _subscriptions: (!is_minimap)
2151 .then(|| {
2152 vec![
2153 cx.observe(&buffer, Self::on_buffer_changed),
2154 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
2155 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2156 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2157 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2158 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2159 cx.observe_window_activation(window, |editor, window, cx| {
2160 let active = window.is_window_active();
2161 editor.blink_manager.update(cx, |blink_manager, cx| {
2162 if active {
2163 blink_manager.enable(cx);
2164 } else {
2165 blink_manager.disable(cx);
2166 }
2167 });
2168 if active {
2169 editor.show_mouse_cursor(cx);
2170 }
2171 }),
2172 ]
2173 })
2174 .unwrap_or_default(),
2175 tasks_update_task: None,
2176 pull_diagnostics_task: Task::ready(()),
2177 colors: None,
2178 next_color_inlay_id: 0,
2179 linked_edit_ranges: Default::default(),
2180 in_project_search: false,
2181 previous_search_ranges: None,
2182 breadcrumb_header: None,
2183 focused_block: None,
2184 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2185 addons: HashMap::default(),
2186 registered_buffers: HashMap::default(),
2187 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2188 selection_mark_mode: false,
2189 toggle_fold_multiple_buffers: Task::ready(()),
2190 serialize_selections: Task::ready(()),
2191 serialize_folds: Task::ready(()),
2192 text_style_refinement: None,
2193 load_diff_task: load_uncommitted_diff,
2194 temporary_diff_override: false,
2195 mouse_cursor_hidden: false,
2196 minimap: None,
2197 hide_mouse_mode: EditorSettings::get_global(cx)
2198 .hide_mouse
2199 .unwrap_or_default(),
2200 change_list: ChangeList::new(),
2201 mode,
2202 selection_drag_state: SelectionDragState::None,
2203 folding_newlines: Task::ready(()),
2204 };
2205
2206 if is_minimap {
2207 return editor;
2208 }
2209
2210 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2211 editor
2212 ._subscriptions
2213 .push(cx.observe(breakpoints, |_, _, cx| {
2214 cx.notify();
2215 }));
2216 }
2217 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2218 editor._subscriptions.extend(project_subscriptions);
2219
2220 editor._subscriptions.push(cx.subscribe_in(
2221 &cx.entity(),
2222 window,
2223 |editor, _, e: &EditorEvent, window, cx| match e {
2224 EditorEvent::ScrollPositionChanged { local, .. } => {
2225 if *local {
2226 let new_anchor = editor.scroll_manager.anchor();
2227 let snapshot = editor.snapshot(window, cx);
2228 editor.update_restoration_data(cx, move |data| {
2229 data.scroll_position = (
2230 new_anchor.top_row(&snapshot.buffer_snapshot),
2231 new_anchor.offset,
2232 );
2233 });
2234 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2235 editor.inline_blame_popover.take();
2236 }
2237 }
2238 EditorEvent::Edited { .. } => {
2239 if !vim_enabled(cx) {
2240 let (map, selections) = editor.selections.all_adjusted_display(cx);
2241 let pop_state = editor
2242 .change_list
2243 .last()
2244 .map(|previous| {
2245 previous.len() == selections.len()
2246 && previous.iter().enumerate().all(|(ix, p)| {
2247 p.to_display_point(&map).row()
2248 == selections[ix].head().row()
2249 })
2250 })
2251 .unwrap_or(false);
2252 let new_positions = selections
2253 .into_iter()
2254 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
2255 .collect();
2256 editor
2257 .change_list
2258 .push_to_change_list(pop_state, new_positions);
2259 }
2260 }
2261 _ => (),
2262 },
2263 ));
2264
2265 if let Some(dap_store) = editor
2266 .project
2267 .as_ref()
2268 .map(|project| project.read(cx).dap_store())
2269 {
2270 let weak_editor = cx.weak_entity();
2271
2272 editor
2273 ._subscriptions
2274 .push(
2275 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2276 let session_entity = cx.entity();
2277 weak_editor
2278 .update(cx, |editor, cx| {
2279 editor._subscriptions.push(
2280 cx.subscribe(&session_entity, Self::on_debug_session_event),
2281 );
2282 })
2283 .ok();
2284 }),
2285 );
2286
2287 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2288 editor
2289 ._subscriptions
2290 .push(cx.subscribe(&session, Self::on_debug_session_event));
2291 }
2292 }
2293
2294 // skip adding the initial selection to selection history
2295 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2296 editor.end_selection(window, cx);
2297 editor.selection_history.mode = SelectionHistoryMode::Normal;
2298
2299 editor.scroll_manager.show_scrollbars(window, cx);
2300 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &buffer, cx);
2301
2302 if full_mode {
2303 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2304 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2305
2306 if editor.git_blame_inline_enabled {
2307 editor.start_git_blame_inline(false, window, cx);
2308 }
2309
2310 editor.go_to_active_debug_line(window, cx);
2311
2312 if let Some(buffer) = buffer.read(cx).as_singleton() {
2313 if let Some(project) = editor.project.as_ref() {
2314 let handle = project.update(cx, |project, cx| {
2315 project.register_buffer_with_language_servers(&buffer, cx)
2316 });
2317 editor
2318 .registered_buffers
2319 .insert(buffer.read(cx).remote_id(), handle);
2320 }
2321 }
2322
2323 editor.minimap =
2324 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2325 editor.colors = Some(LspColorData::new(cx));
2326 editor.update_lsp_data(false, None, window, cx);
2327 }
2328
2329 if editor.mode.is_full() {
2330 editor.report_editor_event("Editor Opened", None, cx);
2331 }
2332
2333 editor
2334 }
2335
2336 pub fn deploy_mouse_context_menu(
2337 &mut self,
2338 position: gpui::Point<Pixels>,
2339 context_menu: Entity<ContextMenu>,
2340 window: &mut Window,
2341 cx: &mut Context<Self>,
2342 ) {
2343 self.mouse_context_menu = Some(MouseContextMenu::new(
2344 self,
2345 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2346 context_menu,
2347 window,
2348 cx,
2349 ));
2350 }
2351
2352 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2353 self.mouse_context_menu
2354 .as_ref()
2355 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2356 }
2357
2358 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2359 self.key_context_internal(self.has_active_inline_completion(), window, cx)
2360 }
2361
2362 fn key_context_internal(
2363 &self,
2364 has_active_edit_prediction: bool,
2365 window: &Window,
2366 cx: &App,
2367 ) -> KeyContext {
2368 let mut key_context = KeyContext::new_with_defaults();
2369 key_context.add("Editor");
2370 let mode = match self.mode {
2371 EditorMode::SingleLine { .. } => "single_line",
2372 EditorMode::AutoHeight { .. } => "auto_height",
2373 EditorMode::Minimap { .. } => "minimap",
2374 EditorMode::Full { .. } => "full",
2375 };
2376
2377 if EditorSettings::jupyter_enabled(cx) {
2378 key_context.add("jupyter");
2379 }
2380
2381 key_context.set("mode", mode);
2382 if self.pending_rename.is_some() {
2383 key_context.add("renaming");
2384 }
2385
2386 match self.context_menu.borrow().as_ref() {
2387 Some(CodeContextMenu::Completions(menu)) => {
2388 if menu.visible() {
2389 key_context.add("menu");
2390 key_context.add("showing_completions");
2391 }
2392 }
2393 Some(CodeContextMenu::CodeActions(menu)) => {
2394 if menu.visible() {
2395 key_context.add("menu");
2396 key_context.add("showing_code_actions")
2397 }
2398 }
2399 None => {}
2400 }
2401
2402 if self.signature_help_state.has_multiple_signatures() {
2403 key_context.add("showing_signature_help");
2404 }
2405
2406 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2407 if !self.focus_handle(cx).contains_focused(window, cx)
2408 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2409 {
2410 for addon in self.addons.values() {
2411 addon.extend_key_context(&mut key_context, cx)
2412 }
2413 }
2414
2415 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2416 if let Some(extension) = singleton_buffer
2417 .read(cx)
2418 .file()
2419 .and_then(|file| file.path().extension()?.to_str())
2420 {
2421 key_context.set("extension", extension.to_string());
2422 }
2423 } else {
2424 key_context.add("multibuffer");
2425 }
2426
2427 if has_active_edit_prediction {
2428 if self.edit_prediction_in_conflict() {
2429 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2430 } else {
2431 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2432 key_context.add("copilot_suggestion");
2433 }
2434 }
2435
2436 if self.selection_mark_mode {
2437 key_context.add("selection_mode");
2438 }
2439
2440 key_context
2441 }
2442
2443 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2444 if self.mouse_cursor_hidden {
2445 self.mouse_cursor_hidden = false;
2446 cx.notify();
2447 }
2448 }
2449
2450 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2451 let hide_mouse_cursor = match origin {
2452 HideMouseCursorOrigin::TypingAction => {
2453 matches!(
2454 self.hide_mouse_mode,
2455 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2456 )
2457 }
2458 HideMouseCursorOrigin::MovementAction => {
2459 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2460 }
2461 };
2462 if self.mouse_cursor_hidden != hide_mouse_cursor {
2463 self.mouse_cursor_hidden = hide_mouse_cursor;
2464 cx.notify();
2465 }
2466 }
2467
2468 pub fn edit_prediction_in_conflict(&self) -> bool {
2469 if !self.show_edit_predictions_in_menu() {
2470 return false;
2471 }
2472
2473 let showing_completions = self
2474 .context_menu
2475 .borrow()
2476 .as_ref()
2477 .map_or(false, |context| {
2478 matches!(context, CodeContextMenu::Completions(_))
2479 });
2480
2481 showing_completions
2482 || self.edit_prediction_requires_modifier()
2483 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2484 // bindings to insert tab characters.
2485 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2486 }
2487
2488 pub fn accept_edit_prediction_keybind(
2489 &self,
2490 accept_partial: bool,
2491 window: &Window,
2492 cx: &App,
2493 ) -> AcceptEditPredictionBinding {
2494 let key_context = self.key_context_internal(true, window, cx);
2495 let in_conflict = self.edit_prediction_in_conflict();
2496
2497 let bindings = if accept_partial {
2498 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2499 } else {
2500 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2501 };
2502
2503 // TODO: if the binding contains multiple keystrokes, display all of them, not
2504 // just the first one.
2505 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2506 !in_conflict
2507 || binding
2508 .keystrokes()
2509 .first()
2510 .map_or(false, |keystroke| keystroke.modifiers.modified())
2511 }))
2512 }
2513
2514 pub fn new_file(
2515 workspace: &mut Workspace,
2516 _: &workspace::NewFile,
2517 window: &mut Window,
2518 cx: &mut Context<Workspace>,
2519 ) {
2520 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2521 "Failed to create buffer",
2522 window,
2523 cx,
2524 |e, _, _| match e.error_code() {
2525 ErrorCode::RemoteUpgradeRequired => Some(format!(
2526 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2527 e.error_tag("required").unwrap_or("the latest version")
2528 )),
2529 _ => None,
2530 },
2531 );
2532 }
2533
2534 pub fn new_in_workspace(
2535 workspace: &mut Workspace,
2536 window: &mut Window,
2537 cx: &mut Context<Workspace>,
2538 ) -> Task<Result<Entity<Editor>>> {
2539 let project = workspace.project().clone();
2540 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2541
2542 cx.spawn_in(window, async move |workspace, cx| {
2543 let buffer = create.await?;
2544 workspace.update_in(cx, |workspace, window, cx| {
2545 let editor =
2546 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2547 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2548 editor
2549 })
2550 })
2551 }
2552
2553 fn new_file_vertical(
2554 workspace: &mut Workspace,
2555 _: &workspace::NewFileSplitVertical,
2556 window: &mut Window,
2557 cx: &mut Context<Workspace>,
2558 ) {
2559 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2560 }
2561
2562 fn new_file_horizontal(
2563 workspace: &mut Workspace,
2564 _: &workspace::NewFileSplitHorizontal,
2565 window: &mut Window,
2566 cx: &mut Context<Workspace>,
2567 ) {
2568 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2569 }
2570
2571 fn new_file_in_direction(
2572 workspace: &mut Workspace,
2573 direction: SplitDirection,
2574 window: &mut Window,
2575 cx: &mut Context<Workspace>,
2576 ) {
2577 let project = workspace.project().clone();
2578 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2579
2580 cx.spawn_in(window, async move |workspace, cx| {
2581 let buffer = create.await?;
2582 workspace.update_in(cx, move |workspace, window, cx| {
2583 workspace.split_item(
2584 direction,
2585 Box::new(
2586 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2587 ),
2588 window,
2589 cx,
2590 )
2591 })?;
2592 anyhow::Ok(())
2593 })
2594 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2595 match e.error_code() {
2596 ErrorCode::RemoteUpgradeRequired => Some(format!(
2597 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2598 e.error_tag("required").unwrap_or("the latest version")
2599 )),
2600 _ => None,
2601 }
2602 });
2603 }
2604
2605 pub fn leader_id(&self) -> Option<CollaboratorId> {
2606 self.leader_id
2607 }
2608
2609 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2610 &self.buffer
2611 }
2612
2613 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2614 self.workspace.as_ref()?.0.upgrade()
2615 }
2616
2617 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2618 self.buffer().read(cx).title(cx)
2619 }
2620
2621 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2622 let git_blame_gutter_max_author_length = self
2623 .render_git_blame_gutter(cx)
2624 .then(|| {
2625 if let Some(blame) = self.blame.as_ref() {
2626 let max_author_length =
2627 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2628 Some(max_author_length)
2629 } else {
2630 None
2631 }
2632 })
2633 .flatten();
2634
2635 EditorSnapshot {
2636 mode: self.mode.clone(),
2637 show_gutter: self.show_gutter,
2638 show_line_numbers: self.show_line_numbers,
2639 show_git_diff_gutter: self.show_git_diff_gutter,
2640 show_code_actions: self.show_code_actions,
2641 show_runnables: self.show_runnables,
2642 show_breakpoints: self.show_breakpoints,
2643 git_blame_gutter_max_author_length,
2644 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2645 scroll_anchor: self.scroll_manager.anchor(),
2646 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2647 placeholder_text: self.placeholder_text.clone(),
2648 is_focused: self.focus_handle.is_focused(window),
2649 current_line_highlight: self
2650 .current_line_highlight
2651 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2652 gutter_hovered: self.gutter_hovered,
2653 }
2654 }
2655
2656 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2657 self.buffer.read(cx).language_at(point, cx)
2658 }
2659
2660 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2661 self.buffer.read(cx).read(cx).file_at(point).cloned()
2662 }
2663
2664 pub fn active_excerpt(
2665 &self,
2666 cx: &App,
2667 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2668 self.buffer
2669 .read(cx)
2670 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2671 }
2672
2673 pub fn mode(&self) -> &EditorMode {
2674 &self.mode
2675 }
2676
2677 pub fn set_mode(&mut self, mode: EditorMode) {
2678 self.mode = mode;
2679 }
2680
2681 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2682 self.collaboration_hub.as_deref()
2683 }
2684
2685 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2686 self.collaboration_hub = Some(hub);
2687 }
2688
2689 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2690 self.in_project_search = in_project_search;
2691 }
2692
2693 pub fn set_custom_context_menu(
2694 &mut self,
2695 f: impl 'static
2696 + Fn(
2697 &mut Self,
2698 DisplayPoint,
2699 &mut Window,
2700 &mut Context<Self>,
2701 ) -> Option<Entity<ui::ContextMenu>>,
2702 ) {
2703 self.custom_context_menu = Some(Box::new(f))
2704 }
2705
2706 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2707 self.completion_provider = provider;
2708 }
2709
2710 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2711 self.semantics_provider.clone()
2712 }
2713
2714 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2715 self.semantics_provider = provider;
2716 }
2717
2718 pub fn set_edit_prediction_provider<T>(
2719 &mut self,
2720 provider: Option<Entity<T>>,
2721 window: &mut Window,
2722 cx: &mut Context<Self>,
2723 ) where
2724 T: EditPredictionProvider,
2725 {
2726 self.edit_prediction_provider =
2727 provider.map(|provider| RegisteredInlineCompletionProvider {
2728 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2729 if this.focus_handle.is_focused(window) {
2730 this.update_visible_inline_completion(window, cx);
2731 }
2732 }),
2733 provider: Arc::new(provider),
2734 });
2735 self.update_edit_prediction_settings(cx);
2736 self.refresh_inline_completion(false, false, window, cx);
2737 }
2738
2739 pub fn placeholder_text(&self) -> Option<&str> {
2740 self.placeholder_text.as_deref()
2741 }
2742
2743 pub fn set_placeholder_text(
2744 &mut self,
2745 placeholder_text: impl Into<Arc<str>>,
2746 cx: &mut Context<Self>,
2747 ) {
2748 let placeholder_text = Some(placeholder_text.into());
2749 if self.placeholder_text != placeholder_text {
2750 self.placeholder_text = placeholder_text;
2751 cx.notify();
2752 }
2753 }
2754
2755 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2756 self.cursor_shape = cursor_shape;
2757
2758 // Disrupt blink for immediate user feedback that the cursor shape has changed
2759 self.blink_manager.update(cx, BlinkManager::show_cursor);
2760
2761 cx.notify();
2762 }
2763
2764 pub fn set_current_line_highlight(
2765 &mut self,
2766 current_line_highlight: Option<CurrentLineHighlight>,
2767 ) {
2768 self.current_line_highlight = current_line_highlight;
2769 }
2770
2771 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2772 self.collapse_matches = collapse_matches;
2773 }
2774
2775 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2776 let buffers = self.buffer.read(cx).all_buffers();
2777 let Some(project) = self.project.as_ref() else {
2778 return;
2779 };
2780 project.update(cx, |project, cx| {
2781 for buffer in buffers {
2782 self.registered_buffers
2783 .entry(buffer.read(cx).remote_id())
2784 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2785 }
2786 })
2787 }
2788
2789 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2790 if self.collapse_matches {
2791 return range.start..range.start;
2792 }
2793 range.clone()
2794 }
2795
2796 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2797 if self.display_map.read(cx).clip_at_line_ends != clip {
2798 self.display_map
2799 .update(cx, |map, _| map.clip_at_line_ends = clip);
2800 }
2801 }
2802
2803 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2804 self.input_enabled = input_enabled;
2805 }
2806
2807 pub fn set_inline_completions_hidden_for_vim_mode(
2808 &mut self,
2809 hidden: bool,
2810 window: &mut Window,
2811 cx: &mut Context<Self>,
2812 ) {
2813 if hidden != self.inline_completions_hidden_for_vim_mode {
2814 self.inline_completions_hidden_for_vim_mode = hidden;
2815 if hidden {
2816 self.update_visible_inline_completion(window, cx);
2817 } else {
2818 self.refresh_inline_completion(true, false, window, cx);
2819 }
2820 }
2821 }
2822
2823 pub fn set_menu_inline_completions_policy(&mut self, value: MenuInlineCompletionsPolicy) {
2824 self.menu_inline_completions_policy = value;
2825 }
2826
2827 pub fn set_autoindent(&mut self, autoindent: bool) {
2828 if autoindent {
2829 self.autoindent_mode = Some(AutoindentMode::EachLine);
2830 } else {
2831 self.autoindent_mode = None;
2832 }
2833 }
2834
2835 pub fn read_only(&self, cx: &App) -> bool {
2836 self.read_only || self.buffer.read(cx).read_only()
2837 }
2838
2839 pub fn set_read_only(&mut self, read_only: bool) {
2840 self.read_only = read_only;
2841 }
2842
2843 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2844 self.use_autoclose = autoclose;
2845 }
2846
2847 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2848 self.use_auto_surround = auto_surround;
2849 }
2850
2851 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2852 self.auto_replace_emoji_shortcode = auto_replace;
2853 }
2854
2855 pub fn toggle_edit_predictions(
2856 &mut self,
2857 _: &ToggleEditPrediction,
2858 window: &mut Window,
2859 cx: &mut Context<Self>,
2860 ) {
2861 if self.show_inline_completions_override.is_some() {
2862 self.set_show_edit_predictions(None, window, cx);
2863 } else {
2864 let show_edit_predictions = !self.edit_predictions_enabled();
2865 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2866 }
2867 }
2868
2869 pub fn set_show_edit_predictions(
2870 &mut self,
2871 show_edit_predictions: Option<bool>,
2872 window: &mut Window,
2873 cx: &mut Context<Self>,
2874 ) {
2875 self.show_inline_completions_override = show_edit_predictions;
2876 self.update_edit_prediction_settings(cx);
2877
2878 if let Some(false) = show_edit_predictions {
2879 self.discard_inline_completion(false, cx);
2880 } else {
2881 self.refresh_inline_completion(false, true, window, cx);
2882 }
2883 }
2884
2885 fn inline_completions_disabled_in_scope(
2886 &self,
2887 buffer: &Entity<Buffer>,
2888 buffer_position: language::Anchor,
2889 cx: &App,
2890 ) -> bool {
2891 let snapshot = buffer.read(cx).snapshot();
2892 let settings = snapshot.settings_at(buffer_position, cx);
2893
2894 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2895 return false;
2896 };
2897
2898 scope.override_name().map_or(false, |scope_name| {
2899 settings
2900 .edit_predictions_disabled_in
2901 .iter()
2902 .any(|s| s == scope_name)
2903 })
2904 }
2905
2906 pub fn set_use_modal_editing(&mut self, to: bool) {
2907 self.use_modal_editing = to;
2908 }
2909
2910 pub fn use_modal_editing(&self) -> bool {
2911 self.use_modal_editing
2912 }
2913
2914 fn selections_did_change(
2915 &mut self,
2916 local: bool,
2917 old_cursor_position: &Anchor,
2918 effects: SelectionEffects,
2919 window: &mut Window,
2920 cx: &mut Context<Self>,
2921 ) {
2922 window.invalidate_character_coordinates();
2923
2924 // Copy selections to primary selection buffer
2925 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2926 if local {
2927 let selections = self.selections.all::<usize>(cx);
2928 let buffer_handle = self.buffer.read(cx).read(cx);
2929
2930 let mut text = String::new();
2931 for (index, selection) in selections.iter().enumerate() {
2932 let text_for_selection = buffer_handle
2933 .text_for_range(selection.start..selection.end)
2934 .collect::<String>();
2935
2936 text.push_str(&text_for_selection);
2937 if index != selections.len() - 1 {
2938 text.push('\n');
2939 }
2940 }
2941
2942 if !text.is_empty() {
2943 cx.write_to_primary(ClipboardItem::new_string(text));
2944 }
2945 }
2946
2947 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
2948 self.buffer.update(cx, |buffer, cx| {
2949 buffer.set_active_selections(
2950 &self.selections.disjoint_anchors(),
2951 self.selections.line_mode,
2952 self.cursor_shape,
2953 cx,
2954 )
2955 });
2956 }
2957 let display_map = self
2958 .display_map
2959 .update(cx, |display_map, cx| display_map.snapshot(cx));
2960 let buffer = &display_map.buffer_snapshot;
2961 if self.selections.count() == 1 {
2962 self.add_selections_state = None;
2963 }
2964 self.select_next_state = None;
2965 self.select_prev_state = None;
2966 self.select_syntax_node_history.try_clear();
2967 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), buffer);
2968 self.snippet_stack
2969 .invalidate(&self.selections.disjoint_anchors(), buffer);
2970 self.take_rename(false, window, cx);
2971
2972 let newest_selection = self.selections.newest_anchor();
2973 let new_cursor_position = newest_selection.head();
2974 let selection_start = newest_selection.start;
2975
2976 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
2977 self.push_to_nav_history(
2978 *old_cursor_position,
2979 Some(new_cursor_position.to_point(buffer)),
2980 false,
2981 effects.nav_history == Some(true),
2982 cx,
2983 );
2984 }
2985
2986 if local {
2987 if let Some(buffer_id) = new_cursor_position.buffer_id {
2988 if !self.registered_buffers.contains_key(&buffer_id) {
2989 if let Some(project) = self.project.as_ref() {
2990 project.update(cx, |project, cx| {
2991 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
2992 return;
2993 };
2994 self.registered_buffers.insert(
2995 buffer_id,
2996 project.register_buffer_with_language_servers(&buffer, cx),
2997 );
2998 })
2999 }
3000 }
3001 }
3002
3003 let mut context_menu = self.context_menu.borrow_mut();
3004 let completion_menu = match context_menu.as_ref() {
3005 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3006 Some(CodeContextMenu::CodeActions(_)) => {
3007 *context_menu = None;
3008 None
3009 }
3010 None => None,
3011 };
3012 let completion_position = completion_menu.map(|menu| menu.initial_position);
3013 drop(context_menu);
3014
3015 if effects.completions {
3016 if let Some(completion_position) = completion_position {
3017 let start_offset = selection_start.to_offset(buffer);
3018 let position_matches = start_offset == completion_position.to_offset(buffer);
3019 let continue_showing = if position_matches {
3020 if self.snippet_stack.is_empty() {
3021 buffer.char_kind_before(start_offset, true) == Some(CharKind::Word)
3022 } else {
3023 // Snippet choices can be shown even when the cursor is in whitespace.
3024 // Dismissing the menu with actions like backspace is handled by
3025 // invalidation regions.
3026 true
3027 }
3028 } else {
3029 false
3030 };
3031
3032 if continue_showing {
3033 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
3034 } else {
3035 self.hide_context_menu(window, cx);
3036 }
3037 }
3038 }
3039
3040 hide_hover(self, cx);
3041
3042 if old_cursor_position.to_display_point(&display_map).row()
3043 != new_cursor_position.to_display_point(&display_map).row()
3044 {
3045 self.available_code_actions.take();
3046 }
3047 self.refresh_code_actions(window, cx);
3048 self.refresh_document_highlights(cx);
3049 self.refresh_selected_text_highlights(false, window, cx);
3050 refresh_matching_bracket_highlights(self, window, cx);
3051 self.update_visible_inline_completion(window, cx);
3052 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3053 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
3054 self.inline_blame_popover.take();
3055 if self.git_blame_inline_enabled {
3056 self.start_inline_blame_timer(window, cx);
3057 }
3058 }
3059
3060 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3061 cx.emit(EditorEvent::SelectionsChanged { local });
3062
3063 let selections = &self.selections.disjoint;
3064 if selections.len() == 1 {
3065 cx.emit(SearchEvent::ActiveMatchChanged)
3066 }
3067 if local {
3068 if let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3069 let inmemory_selections = selections
3070 .iter()
3071 .map(|s| {
3072 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3073 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3074 })
3075 .collect();
3076 self.update_restoration_data(cx, |data| {
3077 data.selections = inmemory_selections;
3078 });
3079
3080 if WorkspaceSettings::get(None, cx).restore_on_startup
3081 != RestoreOnStartupBehavior::None
3082 {
3083 if let Some(workspace_id) =
3084 self.workspace.as_ref().and_then(|workspace| workspace.1)
3085 {
3086 let snapshot = self.buffer().read(cx).snapshot(cx);
3087 let selections = selections.clone();
3088 let background_executor = cx.background_executor().clone();
3089 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3090 self.serialize_selections = cx.background_spawn(async move {
3091 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3092 let db_selections = selections
3093 .iter()
3094 .map(|selection| {
3095 (
3096 selection.start.to_offset(&snapshot),
3097 selection.end.to_offset(&snapshot),
3098 )
3099 })
3100 .collect();
3101
3102 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3103 .await
3104 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
3105 .log_err();
3106 });
3107 }
3108 }
3109 }
3110 }
3111
3112 cx.notify();
3113 }
3114
3115 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3116 use text::ToOffset as _;
3117 use text::ToPoint as _;
3118
3119 if self.mode.is_minimap()
3120 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3121 {
3122 return;
3123 }
3124
3125 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
3126 return;
3127 };
3128
3129 let snapshot = singleton.read(cx).snapshot();
3130 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
3131 let display_snapshot = display_map.snapshot(cx);
3132
3133 display_snapshot
3134 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
3135 .map(|fold| {
3136 fold.range.start.text_anchor.to_point(&snapshot)
3137 ..fold.range.end.text_anchor.to_point(&snapshot)
3138 })
3139 .collect()
3140 });
3141 self.update_restoration_data(cx, |data| {
3142 data.folds = inmemory_folds;
3143 });
3144
3145 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
3146 return;
3147 };
3148 let background_executor = cx.background_executor().clone();
3149 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3150 let db_folds = self.display_map.update(cx, |display_map, cx| {
3151 display_map
3152 .snapshot(cx)
3153 .folds_in_range(0..snapshot.len())
3154 .map(|fold| {
3155 (
3156 fold.range.start.text_anchor.to_offset(&snapshot),
3157 fold.range.end.text_anchor.to_offset(&snapshot),
3158 )
3159 })
3160 .collect()
3161 });
3162 self.serialize_folds = cx.background_spawn(async move {
3163 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3164 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3165 .await
3166 .with_context(|| {
3167 format!(
3168 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3169 )
3170 })
3171 .log_err();
3172 });
3173 }
3174
3175 pub fn sync_selections(
3176 &mut self,
3177 other: Entity<Editor>,
3178 cx: &mut Context<Self>,
3179 ) -> gpui::Subscription {
3180 let other_selections = other.read(cx).selections.disjoint.to_vec();
3181 self.selections.change_with(cx, |selections| {
3182 selections.select_anchors(other_selections);
3183 });
3184
3185 let other_subscription =
3186 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
3187 EditorEvent::SelectionsChanged { local: true } => {
3188 let other_selections = other.read(cx).selections.disjoint.to_vec();
3189 if other_selections.is_empty() {
3190 return;
3191 }
3192 this.selections.change_with(cx, |selections| {
3193 selections.select_anchors(other_selections);
3194 });
3195 }
3196 _ => {}
3197 });
3198
3199 let this_subscription =
3200 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
3201 EditorEvent::SelectionsChanged { local: true } => {
3202 let these_selections = this.selections.disjoint.to_vec();
3203 if these_selections.is_empty() {
3204 return;
3205 }
3206 other.update(cx, |other_editor, cx| {
3207 other_editor.selections.change_with(cx, |selections| {
3208 selections.select_anchors(these_selections);
3209 })
3210 });
3211 }
3212 _ => {}
3213 });
3214
3215 Subscription::join(other_subscription, this_subscription)
3216 }
3217
3218 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3219 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3220 /// effects of selection change occur at the end of the transaction.
3221 pub fn change_selections<R>(
3222 &mut self,
3223 effects: SelectionEffects,
3224 window: &mut Window,
3225 cx: &mut Context<Self>,
3226 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3227 ) -> R {
3228 if let Some(state) = &mut self.deferred_selection_effects_state {
3229 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3230 state.effects.completions = effects.completions;
3231 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3232 let (changed, result) = self.selections.change_with(cx, change);
3233 state.changed |= changed;
3234 return result;
3235 }
3236 let mut state = DeferredSelectionEffectsState {
3237 changed: false,
3238 effects,
3239 old_cursor_position: self.selections.newest_anchor().head(),
3240 history_entry: SelectionHistoryEntry {
3241 selections: self.selections.disjoint_anchors(),
3242 select_next_state: self.select_next_state.clone(),
3243 select_prev_state: self.select_prev_state.clone(),
3244 add_selections_state: self.add_selections_state.clone(),
3245 },
3246 };
3247 let (changed, result) = self.selections.change_with(cx, change);
3248 state.changed = state.changed || changed;
3249 if self.defer_selection_effects {
3250 self.deferred_selection_effects_state = Some(state);
3251 } else {
3252 self.apply_selection_effects(state, window, cx);
3253 }
3254 result
3255 }
3256
3257 /// Defers the effects of selection change, so that the effects of multiple calls to
3258 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3259 /// to selection history and the state of popovers based on selection position aren't
3260 /// erroneously updated.
3261 pub fn with_selection_effects_deferred<R>(
3262 &mut self,
3263 window: &mut Window,
3264 cx: &mut Context<Self>,
3265 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3266 ) -> R {
3267 let already_deferred = self.defer_selection_effects;
3268 self.defer_selection_effects = true;
3269 let result = update(self, window, cx);
3270 if !already_deferred {
3271 self.defer_selection_effects = false;
3272 if let Some(state) = self.deferred_selection_effects_state.take() {
3273 self.apply_selection_effects(state, window, cx);
3274 }
3275 }
3276 result
3277 }
3278
3279 fn apply_selection_effects(
3280 &mut self,
3281 state: DeferredSelectionEffectsState,
3282 window: &mut Window,
3283 cx: &mut Context<Self>,
3284 ) {
3285 if state.changed {
3286 self.selection_history.push(state.history_entry);
3287
3288 if let Some(autoscroll) = state.effects.scroll {
3289 self.request_autoscroll(autoscroll, cx);
3290 }
3291
3292 let old_cursor_position = &state.old_cursor_position;
3293
3294 self.selections_did_change(true, &old_cursor_position, state.effects, window, cx);
3295
3296 if self.should_open_signature_help_automatically(&old_cursor_position, cx) {
3297 self.show_signature_help(&ShowSignatureHelp, window, cx);
3298 }
3299 }
3300 }
3301
3302 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3303 where
3304 I: IntoIterator<Item = (Range<S>, T)>,
3305 S: ToOffset,
3306 T: Into<Arc<str>>,
3307 {
3308 if self.read_only(cx) {
3309 return;
3310 }
3311
3312 self.buffer
3313 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3314 }
3315
3316 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3317 where
3318 I: IntoIterator<Item = (Range<S>, T)>,
3319 S: ToOffset,
3320 T: Into<Arc<str>>,
3321 {
3322 if self.read_only(cx) {
3323 return;
3324 }
3325
3326 self.buffer.update(cx, |buffer, cx| {
3327 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3328 });
3329 }
3330
3331 pub fn edit_with_block_indent<I, S, T>(
3332 &mut self,
3333 edits: I,
3334 original_indent_columns: Vec<Option<u32>>,
3335 cx: &mut Context<Self>,
3336 ) where
3337 I: IntoIterator<Item = (Range<S>, T)>,
3338 S: ToOffset,
3339 T: Into<Arc<str>>,
3340 {
3341 if self.read_only(cx) {
3342 return;
3343 }
3344
3345 self.buffer.update(cx, |buffer, cx| {
3346 buffer.edit(
3347 edits,
3348 Some(AutoindentMode::Block {
3349 original_indent_columns,
3350 }),
3351 cx,
3352 )
3353 });
3354 }
3355
3356 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3357 self.hide_context_menu(window, cx);
3358
3359 match phase {
3360 SelectPhase::Begin {
3361 position,
3362 add,
3363 click_count,
3364 } => self.begin_selection(position, add, click_count, window, cx),
3365 SelectPhase::BeginColumnar {
3366 position,
3367 goal_column,
3368 reset,
3369 mode,
3370 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3371 SelectPhase::Extend {
3372 position,
3373 click_count,
3374 } => self.extend_selection(position, click_count, window, cx),
3375 SelectPhase::Update {
3376 position,
3377 goal_column,
3378 scroll_delta,
3379 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3380 SelectPhase::End => self.end_selection(window, cx),
3381 }
3382 }
3383
3384 fn extend_selection(
3385 &mut self,
3386 position: DisplayPoint,
3387 click_count: usize,
3388 window: &mut Window,
3389 cx: &mut Context<Self>,
3390 ) {
3391 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3392 let tail = self.selections.newest::<usize>(cx).tail();
3393 self.begin_selection(position, false, click_count, window, cx);
3394
3395 let position = position.to_offset(&display_map, Bias::Left);
3396 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3397
3398 let mut pending_selection = self
3399 .selections
3400 .pending_anchor()
3401 .expect("extend_selection not called with pending selection");
3402 if position >= tail {
3403 pending_selection.start = tail_anchor;
3404 } else {
3405 pending_selection.end = tail_anchor;
3406 pending_selection.reversed = true;
3407 }
3408
3409 let mut pending_mode = self.selections.pending_mode().unwrap();
3410 match &mut pending_mode {
3411 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3412 _ => {}
3413 }
3414
3415 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3416 SelectionEffects::scroll(Autoscroll::fit())
3417 } else {
3418 SelectionEffects::no_scroll()
3419 };
3420
3421 self.change_selections(effects, window, cx, |s| {
3422 s.set_pending(pending_selection, pending_mode)
3423 });
3424 }
3425
3426 fn begin_selection(
3427 &mut self,
3428 position: DisplayPoint,
3429 add: bool,
3430 click_count: usize,
3431 window: &mut Window,
3432 cx: &mut Context<Self>,
3433 ) {
3434 if !self.focus_handle.is_focused(window) {
3435 self.last_focused_descendant = None;
3436 window.focus(&self.focus_handle);
3437 }
3438
3439 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3440 let buffer = &display_map.buffer_snapshot;
3441 let position = display_map.clip_point(position, Bias::Left);
3442
3443 let start;
3444 let end;
3445 let mode;
3446 let mut auto_scroll;
3447 match click_count {
3448 1 => {
3449 start = buffer.anchor_before(position.to_point(&display_map));
3450 end = start;
3451 mode = SelectMode::Character;
3452 auto_scroll = true;
3453 }
3454 2 => {
3455 let position = display_map
3456 .clip_point(position, Bias::Left)
3457 .to_offset(&display_map, Bias::Left);
3458 let (range, _) = buffer.surrounding_word(position, false);
3459 start = buffer.anchor_before(range.start);
3460 end = buffer.anchor_before(range.end);
3461 mode = SelectMode::Word(start..end);
3462 auto_scroll = true;
3463 }
3464 3 => {
3465 let position = display_map
3466 .clip_point(position, Bias::Left)
3467 .to_point(&display_map);
3468 let line_start = display_map.prev_line_boundary(position).0;
3469 let next_line_start = buffer.clip_point(
3470 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3471 Bias::Left,
3472 );
3473 start = buffer.anchor_before(line_start);
3474 end = buffer.anchor_before(next_line_start);
3475 mode = SelectMode::Line(start..end);
3476 auto_scroll = true;
3477 }
3478 _ => {
3479 start = buffer.anchor_before(0);
3480 end = buffer.anchor_before(buffer.len());
3481 mode = SelectMode::All;
3482 auto_scroll = false;
3483 }
3484 }
3485 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3486
3487 let point_to_delete: Option<usize> = {
3488 let selected_points: Vec<Selection<Point>> =
3489 self.selections.disjoint_in_range(start..end, cx);
3490
3491 if !add || click_count > 1 {
3492 None
3493 } else if !selected_points.is_empty() {
3494 Some(selected_points[0].id)
3495 } else {
3496 let clicked_point_already_selected =
3497 self.selections.disjoint.iter().find(|selection| {
3498 selection.start.to_point(buffer) == start.to_point(buffer)
3499 || selection.end.to_point(buffer) == end.to_point(buffer)
3500 });
3501
3502 clicked_point_already_selected.map(|selection| selection.id)
3503 }
3504 };
3505
3506 let selections_count = self.selections.count();
3507 let effects = if auto_scroll {
3508 SelectionEffects::default()
3509 } else {
3510 SelectionEffects::no_scroll()
3511 };
3512
3513 self.change_selections(effects, window, cx, |s| {
3514 if let Some(point_to_delete) = point_to_delete {
3515 s.delete(point_to_delete);
3516
3517 if selections_count == 1 {
3518 s.set_pending_anchor_range(start..end, mode);
3519 }
3520 } else {
3521 if !add {
3522 s.clear_disjoint();
3523 }
3524
3525 s.set_pending_anchor_range(start..end, mode);
3526 }
3527 });
3528 }
3529
3530 fn begin_columnar_selection(
3531 &mut self,
3532 position: DisplayPoint,
3533 goal_column: u32,
3534 reset: bool,
3535 mode: ColumnarMode,
3536 window: &mut Window,
3537 cx: &mut Context<Self>,
3538 ) {
3539 if !self.focus_handle.is_focused(window) {
3540 self.last_focused_descendant = None;
3541 window.focus(&self.focus_handle);
3542 }
3543
3544 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3545
3546 if reset {
3547 let pointer_position = display_map
3548 .buffer_snapshot
3549 .anchor_before(position.to_point(&display_map));
3550
3551 self.change_selections(
3552 SelectionEffects::scroll(Autoscroll::newest()),
3553 window,
3554 cx,
3555 |s| {
3556 s.clear_disjoint();
3557 s.set_pending_anchor_range(
3558 pointer_position..pointer_position,
3559 SelectMode::Character,
3560 );
3561 },
3562 );
3563 };
3564
3565 let tail = self.selections.newest::<Point>(cx).tail();
3566 let selection_anchor = display_map.buffer_snapshot.anchor_before(tail);
3567 self.columnar_selection_state = match mode {
3568 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3569 selection_tail: selection_anchor,
3570 display_point: if reset {
3571 if position.column() != goal_column {
3572 Some(DisplayPoint::new(position.row(), goal_column))
3573 } else {
3574 None
3575 }
3576 } else {
3577 None
3578 },
3579 }),
3580 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3581 selection_tail: selection_anchor,
3582 }),
3583 };
3584
3585 if !reset {
3586 self.select_columns(position, goal_column, &display_map, window, cx);
3587 }
3588 }
3589
3590 fn update_selection(
3591 &mut self,
3592 position: DisplayPoint,
3593 goal_column: u32,
3594 scroll_delta: gpui::Point<f32>,
3595 window: &mut Window,
3596 cx: &mut Context<Self>,
3597 ) {
3598 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3599
3600 if self.columnar_selection_state.is_some() {
3601 self.select_columns(position, goal_column, &display_map, window, cx);
3602 } else if let Some(mut pending) = self.selections.pending_anchor() {
3603 let buffer = &display_map.buffer_snapshot;
3604 let head;
3605 let tail;
3606 let mode = self.selections.pending_mode().unwrap();
3607 match &mode {
3608 SelectMode::Character => {
3609 head = position.to_point(&display_map);
3610 tail = pending.tail().to_point(buffer);
3611 }
3612 SelectMode::Word(original_range) => {
3613 let offset = display_map
3614 .clip_point(position, Bias::Left)
3615 .to_offset(&display_map, Bias::Left);
3616 let original_range = original_range.to_offset(buffer);
3617
3618 let head_offset = if buffer.is_inside_word(offset, false)
3619 || original_range.contains(&offset)
3620 {
3621 let (word_range, _) = buffer.surrounding_word(offset, false);
3622 if word_range.start < original_range.start {
3623 word_range.start
3624 } else {
3625 word_range.end
3626 }
3627 } else {
3628 offset
3629 };
3630
3631 head = head_offset.to_point(buffer);
3632 if head_offset <= original_range.start {
3633 tail = original_range.end.to_point(buffer);
3634 } else {
3635 tail = original_range.start.to_point(buffer);
3636 }
3637 }
3638 SelectMode::Line(original_range) => {
3639 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3640
3641 let position = display_map
3642 .clip_point(position, Bias::Left)
3643 .to_point(&display_map);
3644 let line_start = display_map.prev_line_boundary(position).0;
3645 let next_line_start = buffer.clip_point(
3646 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3647 Bias::Left,
3648 );
3649
3650 if line_start < original_range.start {
3651 head = line_start
3652 } else {
3653 head = next_line_start
3654 }
3655
3656 if head <= original_range.start {
3657 tail = original_range.end;
3658 } else {
3659 tail = original_range.start;
3660 }
3661 }
3662 SelectMode::All => {
3663 return;
3664 }
3665 };
3666
3667 if head < tail {
3668 pending.start = buffer.anchor_before(head);
3669 pending.end = buffer.anchor_before(tail);
3670 pending.reversed = true;
3671 } else {
3672 pending.start = buffer.anchor_before(tail);
3673 pending.end = buffer.anchor_before(head);
3674 pending.reversed = false;
3675 }
3676
3677 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3678 s.set_pending(pending, mode);
3679 });
3680 } else {
3681 log::error!("update_selection dispatched with no pending selection");
3682 return;
3683 }
3684
3685 self.apply_scroll_delta(scroll_delta, window, cx);
3686 cx.notify();
3687 }
3688
3689 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3690 self.columnar_selection_state.take();
3691 if self.selections.pending_anchor().is_some() {
3692 let selections = self.selections.all::<usize>(cx);
3693 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3694 s.select(selections);
3695 s.clear_pending();
3696 });
3697 }
3698 }
3699
3700 fn select_columns(
3701 &mut self,
3702 head: DisplayPoint,
3703 goal_column: u32,
3704 display_map: &DisplaySnapshot,
3705 window: &mut Window,
3706 cx: &mut Context<Self>,
3707 ) {
3708 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3709 return;
3710 };
3711
3712 let tail = match columnar_state {
3713 ColumnarSelectionState::FromMouse {
3714 selection_tail,
3715 display_point,
3716 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(&display_map)),
3717 ColumnarSelectionState::FromSelection { selection_tail } => {
3718 selection_tail.to_display_point(&display_map)
3719 }
3720 };
3721
3722 let start_row = cmp::min(tail.row(), head.row());
3723 let end_row = cmp::max(tail.row(), head.row());
3724 let start_column = cmp::min(tail.column(), goal_column);
3725 let end_column = cmp::max(tail.column(), goal_column);
3726 let reversed = start_column < tail.column();
3727
3728 let selection_ranges = (start_row.0..=end_row.0)
3729 .map(DisplayRow)
3730 .filter_map(|row| {
3731 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3732 || start_column <= display_map.line_len(row))
3733 && !display_map.is_block_line(row)
3734 {
3735 let start = display_map
3736 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3737 .to_point(display_map);
3738 let end = display_map
3739 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3740 .to_point(display_map);
3741 if reversed {
3742 Some(end..start)
3743 } else {
3744 Some(start..end)
3745 }
3746 } else {
3747 None
3748 }
3749 })
3750 .collect::<Vec<_>>();
3751
3752 let ranges = match columnar_state {
3753 ColumnarSelectionState::FromMouse { .. } => {
3754 let mut non_empty_ranges = selection_ranges
3755 .iter()
3756 .filter(|selection_range| selection_range.start != selection_range.end)
3757 .peekable();
3758 if non_empty_ranges.peek().is_some() {
3759 non_empty_ranges.cloned().collect()
3760 } else {
3761 selection_ranges
3762 }
3763 }
3764 _ => selection_ranges,
3765 };
3766
3767 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3768 s.select_ranges(ranges);
3769 });
3770 cx.notify();
3771 }
3772
3773 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3774 self.selections
3775 .all_adjusted(cx)
3776 .iter()
3777 .any(|selection| !selection.is_empty())
3778 }
3779
3780 pub fn has_pending_nonempty_selection(&self) -> bool {
3781 let pending_nonempty_selection = match self.selections.pending_anchor() {
3782 Some(Selection { start, end, .. }) => start != end,
3783 None => false,
3784 };
3785
3786 pending_nonempty_selection
3787 || (self.columnar_selection_state.is_some() && self.selections.disjoint.len() > 1)
3788 }
3789
3790 pub fn has_pending_selection(&self) -> bool {
3791 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
3792 }
3793
3794 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3795 self.selection_mark_mode = false;
3796 self.selection_drag_state = SelectionDragState::None;
3797
3798 if self.clear_expanded_diff_hunks(cx) {
3799 cx.notify();
3800 return;
3801 }
3802 if self.dismiss_menus_and_popups(true, window, cx) {
3803 return;
3804 }
3805
3806 if self.mode.is_full()
3807 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
3808 {
3809 return;
3810 }
3811
3812 cx.propagate();
3813 }
3814
3815 pub fn dismiss_menus_and_popups(
3816 &mut self,
3817 is_user_requested: bool,
3818 window: &mut Window,
3819 cx: &mut Context<Self>,
3820 ) -> bool {
3821 if self.take_rename(false, window, cx).is_some() {
3822 return true;
3823 }
3824
3825 if hide_hover(self, cx) {
3826 return true;
3827 }
3828
3829 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3830 return true;
3831 }
3832
3833 if self.hide_context_menu(window, cx).is_some() {
3834 return true;
3835 }
3836
3837 if self.mouse_context_menu.take().is_some() {
3838 return true;
3839 }
3840
3841 if is_user_requested && self.discard_inline_completion(true, cx) {
3842 return true;
3843 }
3844
3845 if self.snippet_stack.pop().is_some() {
3846 return true;
3847 }
3848
3849 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3850 self.dismiss_diagnostics(cx);
3851 return true;
3852 }
3853
3854 false
3855 }
3856
3857 fn linked_editing_ranges_for(
3858 &self,
3859 selection: Range<text::Anchor>,
3860 cx: &App,
3861 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3862 if self.linked_edit_ranges.is_empty() {
3863 return None;
3864 }
3865 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3866 selection.end.buffer_id.and_then(|end_buffer_id| {
3867 if selection.start.buffer_id != Some(end_buffer_id) {
3868 return None;
3869 }
3870 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3871 let snapshot = buffer.read(cx).snapshot();
3872 self.linked_edit_ranges
3873 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3874 .map(|ranges| (ranges, snapshot, buffer))
3875 })?;
3876 use text::ToOffset as TO;
3877 // find offset from the start of current range to current cursor position
3878 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3879
3880 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3881 let start_difference = start_offset - start_byte_offset;
3882 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3883 let end_difference = end_offset - start_byte_offset;
3884 // Current range has associated linked ranges.
3885 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3886 for range in linked_ranges.iter() {
3887 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3888 let end_offset = start_offset + end_difference;
3889 let start_offset = start_offset + start_difference;
3890 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3891 continue;
3892 }
3893 if self.selections.disjoint_anchor_ranges().any(|s| {
3894 if s.start.buffer_id != selection.start.buffer_id
3895 || s.end.buffer_id != selection.end.buffer_id
3896 {
3897 return false;
3898 }
3899 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3900 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3901 }) {
3902 continue;
3903 }
3904 let start = buffer_snapshot.anchor_after(start_offset);
3905 let end = buffer_snapshot.anchor_after(end_offset);
3906 linked_edits
3907 .entry(buffer.clone())
3908 .or_default()
3909 .push(start..end);
3910 }
3911 Some(linked_edits)
3912 }
3913
3914 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3915 let text: Arc<str> = text.into();
3916
3917 if self.read_only(cx) {
3918 return;
3919 }
3920
3921 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
3922
3923 let selections = self.selections.all_adjusted(cx);
3924 let mut bracket_inserted = false;
3925 let mut edits = Vec::new();
3926 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3927 let mut new_selections = Vec::with_capacity(selections.len());
3928 let mut new_autoclose_regions = Vec::new();
3929 let snapshot = self.buffer.read(cx).read(cx);
3930 let mut clear_linked_edit_ranges = false;
3931
3932 for (selection, autoclose_region) in
3933 self.selections_with_autoclose_regions(selections, &snapshot)
3934 {
3935 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3936 // Determine if the inserted text matches the opening or closing
3937 // bracket of any of this language's bracket pairs.
3938 let mut bracket_pair = None;
3939 let mut is_bracket_pair_start = false;
3940 let mut is_bracket_pair_end = false;
3941 if !text.is_empty() {
3942 let mut bracket_pair_matching_end = None;
3943 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3944 // and they are removing the character that triggered IME popup.
3945 for (pair, enabled) in scope.brackets() {
3946 if !pair.close && !pair.surround {
3947 continue;
3948 }
3949
3950 if enabled && pair.start.ends_with(text.as_ref()) {
3951 let prefix_len = pair.start.len() - text.len();
3952 let preceding_text_matches_prefix = prefix_len == 0
3953 || (selection.start.column >= (prefix_len as u32)
3954 && snapshot.contains_str_at(
3955 Point::new(
3956 selection.start.row,
3957 selection.start.column - (prefix_len as u32),
3958 ),
3959 &pair.start[..prefix_len],
3960 ));
3961 if preceding_text_matches_prefix {
3962 bracket_pair = Some(pair.clone());
3963 is_bracket_pair_start = true;
3964 break;
3965 }
3966 }
3967 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
3968 {
3969 // take first bracket pair matching end, but don't break in case a later bracket
3970 // pair matches start
3971 bracket_pair_matching_end = Some(pair.clone());
3972 }
3973 }
3974 if let Some(end) = bracket_pair_matching_end
3975 && bracket_pair.is_none()
3976 {
3977 bracket_pair = Some(end);
3978 is_bracket_pair_end = true;
3979 }
3980 }
3981
3982 if let Some(bracket_pair) = bracket_pair {
3983 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
3984 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
3985 let auto_surround =
3986 self.use_auto_surround && snapshot_settings.use_auto_surround;
3987 if selection.is_empty() {
3988 if is_bracket_pair_start {
3989 // If the inserted text is a suffix of an opening bracket and the
3990 // selection is preceded by the rest of the opening bracket, then
3991 // insert the closing bracket.
3992 let following_text_allows_autoclose = snapshot
3993 .chars_at(selection.start)
3994 .next()
3995 .map_or(true, |c| scope.should_autoclose_before(c));
3996
3997 let preceding_text_allows_autoclose = selection.start.column == 0
3998 || snapshot.reversed_chars_at(selection.start).next().map_or(
3999 true,
4000 |c| {
4001 bracket_pair.start != bracket_pair.end
4002 || !snapshot
4003 .char_classifier_at(selection.start)
4004 .is_word(c)
4005 },
4006 );
4007
4008 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4009 && bracket_pair.start.len() == 1
4010 {
4011 let target = bracket_pair.start.chars().next().unwrap();
4012 let current_line_count = snapshot
4013 .reversed_chars_at(selection.start)
4014 .take_while(|&c| c != '\n')
4015 .filter(|&c| c == target)
4016 .count();
4017 current_line_count % 2 == 1
4018 } else {
4019 false
4020 };
4021
4022 if autoclose
4023 && bracket_pair.close
4024 && following_text_allows_autoclose
4025 && preceding_text_allows_autoclose
4026 && !is_closing_quote
4027 {
4028 let anchor = snapshot.anchor_before(selection.end);
4029 new_selections.push((selection.map(|_| anchor), text.len()));
4030 new_autoclose_regions.push((
4031 anchor,
4032 text.len(),
4033 selection.id,
4034 bracket_pair.clone(),
4035 ));
4036 edits.push((
4037 selection.range(),
4038 format!("{}{}", text, bracket_pair.end).into(),
4039 ));
4040 bracket_inserted = true;
4041 continue;
4042 }
4043 }
4044
4045 if let Some(region) = autoclose_region {
4046 // If the selection is followed by an auto-inserted closing bracket,
4047 // then don't insert that closing bracket again; just move the selection
4048 // past the closing bracket.
4049 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4050 && text.as_ref() == region.pair.end.as_str();
4051 if should_skip {
4052 let anchor = snapshot.anchor_after(selection.end);
4053 new_selections
4054 .push((selection.map(|_| anchor), region.pair.end.len()));
4055 continue;
4056 }
4057 }
4058
4059 let always_treat_brackets_as_autoclosed = snapshot
4060 .language_settings_at(selection.start, cx)
4061 .always_treat_brackets_as_autoclosed;
4062 if always_treat_brackets_as_autoclosed
4063 && is_bracket_pair_end
4064 && snapshot.contains_str_at(selection.end, text.as_ref())
4065 {
4066 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4067 // and the inserted text is a closing bracket and the selection is followed
4068 // by the closing bracket then move the selection past the closing bracket.
4069 let anchor = snapshot.anchor_after(selection.end);
4070 new_selections.push((selection.map(|_| anchor), text.len()));
4071 continue;
4072 }
4073 }
4074 // If an opening bracket is 1 character long and is typed while
4075 // text is selected, then surround that text with the bracket pair.
4076 else if auto_surround
4077 && bracket_pair.surround
4078 && is_bracket_pair_start
4079 && bracket_pair.start.chars().count() == 1
4080 {
4081 edits.push((selection.start..selection.start, text.clone()));
4082 edits.push((
4083 selection.end..selection.end,
4084 bracket_pair.end.as_str().into(),
4085 ));
4086 bracket_inserted = true;
4087 new_selections.push((
4088 Selection {
4089 id: selection.id,
4090 start: snapshot.anchor_after(selection.start),
4091 end: snapshot.anchor_before(selection.end),
4092 reversed: selection.reversed,
4093 goal: selection.goal,
4094 },
4095 0,
4096 ));
4097 continue;
4098 }
4099 }
4100 }
4101
4102 if self.auto_replace_emoji_shortcode
4103 && selection.is_empty()
4104 && text.as_ref().ends_with(':')
4105 {
4106 if let Some(possible_emoji_short_code) =
4107 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4108 {
4109 if !possible_emoji_short_code.is_empty() {
4110 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
4111 let emoji_shortcode_start = Point::new(
4112 selection.start.row,
4113 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4114 );
4115
4116 // Remove shortcode from buffer
4117 edits.push((
4118 emoji_shortcode_start..selection.start,
4119 "".to_string().into(),
4120 ));
4121 new_selections.push((
4122 Selection {
4123 id: selection.id,
4124 start: snapshot.anchor_after(emoji_shortcode_start),
4125 end: snapshot.anchor_before(selection.start),
4126 reversed: selection.reversed,
4127 goal: selection.goal,
4128 },
4129 0,
4130 ));
4131
4132 // Insert emoji
4133 let selection_start_anchor = snapshot.anchor_after(selection.start);
4134 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4135 edits.push((selection.start..selection.end, emoji.to_string().into()));
4136
4137 continue;
4138 }
4139 }
4140 }
4141 }
4142
4143 // If not handling any auto-close operation, then just replace the selected
4144 // text with the given input and move the selection to the end of the
4145 // newly inserted text.
4146 let anchor = snapshot.anchor_after(selection.end);
4147 if !self.linked_edit_ranges.is_empty() {
4148 let start_anchor = snapshot.anchor_before(selection.start);
4149
4150 let is_word_char = text.chars().next().map_or(true, |char| {
4151 let classifier = snapshot
4152 .char_classifier_at(start_anchor.to_offset(&snapshot))
4153 .ignore_punctuation(true);
4154 classifier.is_word(char)
4155 });
4156
4157 if is_word_char {
4158 if let Some(ranges) = self
4159 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4160 {
4161 for (buffer, edits) in ranges {
4162 linked_edits
4163 .entry(buffer.clone())
4164 .or_default()
4165 .extend(edits.into_iter().map(|range| (range, text.clone())));
4166 }
4167 }
4168 } else {
4169 clear_linked_edit_ranges = true;
4170 }
4171 }
4172
4173 new_selections.push((selection.map(|_| anchor), 0));
4174 edits.push((selection.start..selection.end, text.clone()));
4175 }
4176
4177 drop(snapshot);
4178
4179 self.transact(window, cx, |this, window, cx| {
4180 if clear_linked_edit_ranges {
4181 this.linked_edit_ranges.clear();
4182 }
4183 let initial_buffer_versions =
4184 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4185
4186 this.buffer.update(cx, |buffer, cx| {
4187 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4188 });
4189 for (buffer, edits) in linked_edits {
4190 buffer.update(cx, |buffer, cx| {
4191 let snapshot = buffer.snapshot();
4192 let edits = edits
4193 .into_iter()
4194 .map(|(range, text)| {
4195 use text::ToPoint as TP;
4196 let end_point = TP::to_point(&range.end, &snapshot);
4197 let start_point = TP::to_point(&range.start, &snapshot);
4198 (start_point..end_point, text)
4199 })
4200 .sorted_by_key(|(range, _)| range.start);
4201 buffer.edit(edits, None, cx);
4202 })
4203 }
4204 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4205 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4206 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4207 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
4208 .zip(new_selection_deltas)
4209 .map(|(selection, delta)| Selection {
4210 id: selection.id,
4211 start: selection.start + delta,
4212 end: selection.end + delta,
4213 reversed: selection.reversed,
4214 goal: SelectionGoal::None,
4215 })
4216 .collect::<Vec<_>>();
4217
4218 let mut i = 0;
4219 for (position, delta, selection_id, pair) in new_autoclose_regions {
4220 let position = position.to_offset(&map.buffer_snapshot) + delta;
4221 let start = map.buffer_snapshot.anchor_before(position);
4222 let end = map.buffer_snapshot.anchor_after(position);
4223 while let Some(existing_state) = this.autoclose_regions.get(i) {
4224 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
4225 Ordering::Less => i += 1,
4226 Ordering::Greater => break,
4227 Ordering::Equal => {
4228 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
4229 Ordering::Less => i += 1,
4230 Ordering::Equal => break,
4231 Ordering::Greater => break,
4232 }
4233 }
4234 }
4235 }
4236 this.autoclose_regions.insert(
4237 i,
4238 AutocloseRegion {
4239 selection_id,
4240 range: start..end,
4241 pair,
4242 },
4243 );
4244 }
4245
4246 let had_active_inline_completion = this.has_active_inline_completion();
4247 this.change_selections(
4248 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4249 window,
4250 cx,
4251 |s| s.select(new_selections),
4252 );
4253
4254 if !bracket_inserted {
4255 if let Some(on_type_format_task) =
4256 this.trigger_on_type_formatting(text.to_string(), window, cx)
4257 {
4258 on_type_format_task.detach_and_log_err(cx);
4259 }
4260 }
4261
4262 let editor_settings = EditorSettings::get_global(cx);
4263 if bracket_inserted
4264 && (editor_settings.auto_signature_help
4265 || editor_settings.show_signature_help_after_edits)
4266 {
4267 this.show_signature_help(&ShowSignatureHelp, window, cx);
4268 }
4269
4270 let trigger_in_words =
4271 this.show_edit_predictions_in_menu() || !had_active_inline_completion;
4272 if this.hard_wrap.is_some() {
4273 let latest: Range<Point> = this.selections.newest(cx).range();
4274 if latest.is_empty()
4275 && this
4276 .buffer()
4277 .read(cx)
4278 .snapshot(cx)
4279 .line_len(MultiBufferRow(latest.start.row))
4280 == latest.start.column
4281 {
4282 this.rewrap_impl(
4283 RewrapOptions {
4284 override_language_settings: true,
4285 preserve_existing_whitespace: true,
4286 },
4287 cx,
4288 )
4289 }
4290 }
4291 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4292 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
4293 this.refresh_inline_completion(true, false, window, cx);
4294 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4295 });
4296 }
4297
4298 fn find_possible_emoji_shortcode_at_position(
4299 snapshot: &MultiBufferSnapshot,
4300 position: Point,
4301 ) -> Option<String> {
4302 let mut chars = Vec::new();
4303 let mut found_colon = false;
4304 for char in snapshot.reversed_chars_at(position).take(100) {
4305 // Found a possible emoji shortcode in the middle of the buffer
4306 if found_colon {
4307 if char.is_whitespace() {
4308 chars.reverse();
4309 return Some(chars.iter().collect());
4310 }
4311 // If the previous character is not a whitespace, we are in the middle of a word
4312 // and we only want to complete the shortcode if the word is made up of other emojis
4313 let mut containing_word = String::new();
4314 for ch in snapshot
4315 .reversed_chars_at(position)
4316 .skip(chars.len() + 1)
4317 .take(100)
4318 {
4319 if ch.is_whitespace() {
4320 break;
4321 }
4322 containing_word.push(ch);
4323 }
4324 let containing_word = containing_word.chars().rev().collect::<String>();
4325 if util::word_consists_of_emojis(containing_word.as_str()) {
4326 chars.reverse();
4327 return Some(chars.iter().collect());
4328 }
4329 }
4330
4331 if char.is_whitespace() || !char.is_ascii() {
4332 return None;
4333 }
4334 if char == ':' {
4335 found_colon = true;
4336 } else {
4337 chars.push(char);
4338 }
4339 }
4340 // Found a possible emoji shortcode at the beginning of the buffer
4341 chars.reverse();
4342 Some(chars.iter().collect())
4343 }
4344
4345 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4346 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4347 self.transact(window, cx, |this, window, cx| {
4348 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4349 let selections = this.selections.all::<usize>(cx);
4350 let multi_buffer = this.buffer.read(cx);
4351 let buffer = multi_buffer.snapshot(cx);
4352 selections
4353 .iter()
4354 .map(|selection| {
4355 let start_point = selection.start.to_point(&buffer);
4356 let mut existing_indent =
4357 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4358 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4359 let start = selection.start;
4360 let end = selection.end;
4361 let selection_is_empty = start == end;
4362 let language_scope = buffer.language_scope_at(start);
4363 let (
4364 comment_delimiter,
4365 doc_delimiter,
4366 insert_extra_newline,
4367 indent_on_newline,
4368 indent_on_extra_newline,
4369 ) = if let Some(language) = &language_scope {
4370 let mut insert_extra_newline =
4371 insert_extra_newline_brackets(&buffer, start..end, language)
4372 || insert_extra_newline_tree_sitter(&buffer, start..end);
4373
4374 // Comment extension on newline is allowed only for cursor selections
4375 let comment_delimiter = maybe!({
4376 if !selection_is_empty {
4377 return None;
4378 }
4379
4380 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4381 return None;
4382 }
4383
4384 let delimiters = language.line_comment_prefixes();
4385 let max_len_of_delimiter =
4386 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4387 let (snapshot, range) =
4388 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4389
4390 let num_of_whitespaces = snapshot
4391 .chars_for_range(range.clone())
4392 .take_while(|c| c.is_whitespace())
4393 .count();
4394 let comment_candidate = snapshot
4395 .chars_for_range(range.clone())
4396 .skip(num_of_whitespaces)
4397 .take(max_len_of_delimiter)
4398 .collect::<String>();
4399 let (delimiter, trimmed_len) = delimiters
4400 .iter()
4401 .filter_map(|delimiter| {
4402 let prefix = delimiter.trim_end();
4403 if comment_candidate.starts_with(prefix) {
4404 Some((delimiter, prefix.len()))
4405 } else {
4406 None
4407 }
4408 })
4409 .max_by_key(|(_, len)| *len)?;
4410
4411 if let Some(BlockCommentConfig {
4412 start: block_start, ..
4413 }) = language.block_comment()
4414 {
4415 let block_start_trimmed = block_start.trim_end();
4416 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4417 let line_content = snapshot
4418 .chars_for_range(range)
4419 .skip(num_of_whitespaces)
4420 .take(block_start_trimmed.len())
4421 .collect::<String>();
4422
4423 if line_content.starts_with(block_start_trimmed) {
4424 return None;
4425 }
4426 }
4427 }
4428
4429 let cursor_is_placed_after_comment_marker =
4430 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4431 if cursor_is_placed_after_comment_marker {
4432 Some(delimiter.clone())
4433 } else {
4434 None
4435 }
4436 });
4437
4438 let mut indent_on_newline = IndentSize::spaces(0);
4439 let mut indent_on_extra_newline = IndentSize::spaces(0);
4440
4441 let doc_delimiter = maybe!({
4442 if !selection_is_empty {
4443 return None;
4444 }
4445
4446 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4447 return None;
4448 }
4449
4450 let BlockCommentConfig {
4451 start: start_tag,
4452 end: end_tag,
4453 prefix: delimiter,
4454 tab_size: len,
4455 } = language.documentation_comment()?;
4456 let is_within_block_comment = buffer
4457 .language_scope_at(start_point)
4458 .is_some_and(|scope| scope.override_name() == Some("comment"));
4459 if !is_within_block_comment {
4460 return None;
4461 }
4462
4463 let (snapshot, range) =
4464 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4465
4466 let num_of_whitespaces = snapshot
4467 .chars_for_range(range.clone())
4468 .take_while(|c| c.is_whitespace())
4469 .count();
4470
4471 // 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.
4472 let column = start_point.column;
4473 let cursor_is_after_start_tag = {
4474 let start_tag_len = start_tag.len();
4475 let start_tag_line = snapshot
4476 .chars_for_range(range.clone())
4477 .skip(num_of_whitespaces)
4478 .take(start_tag_len)
4479 .collect::<String>();
4480 if start_tag_line.starts_with(start_tag.as_ref()) {
4481 num_of_whitespaces + start_tag_len <= column as usize
4482 } else {
4483 false
4484 }
4485 };
4486
4487 let cursor_is_after_delimiter = {
4488 let delimiter_trim = delimiter.trim_end();
4489 let delimiter_line = snapshot
4490 .chars_for_range(range.clone())
4491 .skip(num_of_whitespaces)
4492 .take(delimiter_trim.len())
4493 .collect::<String>();
4494 if delimiter_line.starts_with(delimiter_trim) {
4495 num_of_whitespaces + delimiter_trim.len() <= column as usize
4496 } else {
4497 false
4498 }
4499 };
4500
4501 let cursor_is_before_end_tag_if_exists = {
4502 let mut char_position = 0u32;
4503 let mut end_tag_offset = None;
4504
4505 'outer: for chunk in snapshot.text_for_range(range.clone()) {
4506 if let Some(byte_pos) = chunk.find(&**end_tag) {
4507 let chars_before_match =
4508 chunk[..byte_pos].chars().count() as u32;
4509 end_tag_offset =
4510 Some(char_position + chars_before_match);
4511 break 'outer;
4512 }
4513 char_position += chunk.chars().count() as u32;
4514 }
4515
4516 if let Some(end_tag_offset) = end_tag_offset {
4517 let cursor_is_before_end_tag = column <= end_tag_offset;
4518 if cursor_is_after_start_tag {
4519 if cursor_is_before_end_tag {
4520 insert_extra_newline = true;
4521 }
4522 let cursor_is_at_start_of_end_tag =
4523 column == end_tag_offset;
4524 if cursor_is_at_start_of_end_tag {
4525 indent_on_extra_newline.len = *len;
4526 }
4527 }
4528 cursor_is_before_end_tag
4529 } else {
4530 true
4531 }
4532 };
4533
4534 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4535 && cursor_is_before_end_tag_if_exists
4536 {
4537 if cursor_is_after_start_tag {
4538 indent_on_newline.len = *len;
4539 }
4540 Some(delimiter.clone())
4541 } else {
4542 None
4543 }
4544 });
4545
4546 (
4547 comment_delimiter,
4548 doc_delimiter,
4549 insert_extra_newline,
4550 indent_on_newline,
4551 indent_on_extra_newline,
4552 )
4553 } else {
4554 (
4555 None,
4556 None,
4557 false,
4558 IndentSize::default(),
4559 IndentSize::default(),
4560 )
4561 };
4562
4563 let prevent_auto_indent = doc_delimiter.is_some();
4564 let delimiter = comment_delimiter.or(doc_delimiter);
4565
4566 let capacity_for_delimiter =
4567 delimiter.as_deref().map(str::len).unwrap_or_default();
4568 let mut new_text = String::with_capacity(
4569 1 + capacity_for_delimiter
4570 + existing_indent.len as usize
4571 + indent_on_newline.len as usize
4572 + indent_on_extra_newline.len as usize,
4573 );
4574 new_text.push('\n');
4575 new_text.extend(existing_indent.chars());
4576 new_text.extend(indent_on_newline.chars());
4577
4578 if let Some(delimiter) = &delimiter {
4579 new_text.push_str(delimiter);
4580 }
4581
4582 if insert_extra_newline {
4583 new_text.push('\n');
4584 new_text.extend(existing_indent.chars());
4585 new_text.extend(indent_on_extra_newline.chars());
4586 }
4587
4588 let anchor = buffer.anchor_after(end);
4589 let new_selection = selection.map(|_| anchor);
4590 (
4591 ((start..end, new_text), prevent_auto_indent),
4592 (insert_extra_newline, new_selection),
4593 )
4594 })
4595 .unzip()
4596 };
4597
4598 let mut auto_indent_edits = Vec::new();
4599 let mut edits = Vec::new();
4600 for (edit, prevent_auto_indent) in edits_with_flags {
4601 if prevent_auto_indent {
4602 edits.push(edit);
4603 } else {
4604 auto_indent_edits.push(edit);
4605 }
4606 }
4607 if !edits.is_empty() {
4608 this.edit(edits, cx);
4609 }
4610 if !auto_indent_edits.is_empty() {
4611 this.edit_with_autoindent(auto_indent_edits, cx);
4612 }
4613
4614 let buffer = this.buffer.read(cx).snapshot(cx);
4615 let new_selections = selection_info
4616 .into_iter()
4617 .map(|(extra_newline_inserted, new_selection)| {
4618 let mut cursor = new_selection.end.to_point(&buffer);
4619 if extra_newline_inserted {
4620 cursor.row -= 1;
4621 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4622 }
4623 new_selection.map(|_| cursor)
4624 })
4625 .collect();
4626
4627 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4628 this.refresh_inline_completion(true, false, window, cx);
4629 });
4630 }
4631
4632 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4633 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4634
4635 let buffer = self.buffer.read(cx);
4636 let snapshot = buffer.snapshot(cx);
4637
4638 let mut edits = Vec::new();
4639 let mut rows = Vec::new();
4640
4641 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4642 let cursor = selection.head();
4643 let row = cursor.row;
4644
4645 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4646
4647 let newline = "\n".to_string();
4648 edits.push((start_of_line..start_of_line, newline));
4649
4650 rows.push(row + rows_inserted as u32);
4651 }
4652
4653 self.transact(window, cx, |editor, window, cx| {
4654 editor.edit(edits, cx);
4655
4656 editor.change_selections(Default::default(), window, cx, |s| {
4657 let mut index = 0;
4658 s.move_cursors_with(|map, _, _| {
4659 let row = rows[index];
4660 index += 1;
4661
4662 let point = Point::new(row, 0);
4663 let boundary = map.next_line_boundary(point).1;
4664 let clipped = map.clip_point(boundary, Bias::Left);
4665
4666 (clipped, SelectionGoal::None)
4667 });
4668 });
4669
4670 let mut indent_edits = Vec::new();
4671 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4672 for row in rows {
4673 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4674 for (row, indent) in indents {
4675 if indent.len == 0 {
4676 continue;
4677 }
4678
4679 let text = match indent.kind {
4680 IndentKind::Space => " ".repeat(indent.len as usize),
4681 IndentKind::Tab => "\t".repeat(indent.len as usize),
4682 };
4683 let point = Point::new(row.0, 0);
4684 indent_edits.push((point..point, text));
4685 }
4686 }
4687 editor.edit(indent_edits, cx);
4688 });
4689 }
4690
4691 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4692 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4693
4694 let buffer = self.buffer.read(cx);
4695 let snapshot = buffer.snapshot(cx);
4696
4697 let mut edits = Vec::new();
4698 let mut rows = Vec::new();
4699 let mut rows_inserted = 0;
4700
4701 for selection in self.selections.all_adjusted(cx) {
4702 let cursor = selection.head();
4703 let row = cursor.row;
4704
4705 let point = Point::new(row + 1, 0);
4706 let start_of_line = snapshot.clip_point(point, Bias::Left);
4707
4708 let newline = "\n".to_string();
4709 edits.push((start_of_line..start_of_line, newline));
4710
4711 rows_inserted += 1;
4712 rows.push(row + rows_inserted);
4713 }
4714
4715 self.transact(window, cx, |editor, window, cx| {
4716 editor.edit(edits, cx);
4717
4718 editor.change_selections(Default::default(), window, cx, |s| {
4719 let mut index = 0;
4720 s.move_cursors_with(|map, _, _| {
4721 let row = rows[index];
4722 index += 1;
4723
4724 let point = Point::new(row, 0);
4725 let boundary = map.next_line_boundary(point).1;
4726 let clipped = map.clip_point(boundary, Bias::Left);
4727
4728 (clipped, SelectionGoal::None)
4729 });
4730 });
4731
4732 let mut indent_edits = Vec::new();
4733 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4734 for row in rows {
4735 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4736 for (row, indent) in indents {
4737 if indent.len == 0 {
4738 continue;
4739 }
4740
4741 let text = match indent.kind {
4742 IndentKind::Space => " ".repeat(indent.len as usize),
4743 IndentKind::Tab => "\t".repeat(indent.len as usize),
4744 };
4745 let point = Point::new(row.0, 0);
4746 indent_edits.push((point..point, text));
4747 }
4748 }
4749 editor.edit(indent_edits, cx);
4750 });
4751 }
4752
4753 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4754 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4755 original_indent_columns: Vec::new(),
4756 });
4757 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4758 }
4759
4760 fn insert_with_autoindent_mode(
4761 &mut self,
4762 text: &str,
4763 autoindent_mode: Option<AutoindentMode>,
4764 window: &mut Window,
4765 cx: &mut Context<Self>,
4766 ) {
4767 if self.read_only(cx) {
4768 return;
4769 }
4770
4771 let text: Arc<str> = text.into();
4772 self.transact(window, cx, |this, window, cx| {
4773 let old_selections = this.selections.all_adjusted(cx);
4774 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4775 let anchors = {
4776 let snapshot = buffer.read(cx);
4777 old_selections
4778 .iter()
4779 .map(|s| {
4780 let anchor = snapshot.anchor_after(s.head());
4781 s.map(|_| anchor)
4782 })
4783 .collect::<Vec<_>>()
4784 };
4785 buffer.edit(
4786 old_selections
4787 .iter()
4788 .map(|s| (s.start..s.end, text.clone())),
4789 autoindent_mode,
4790 cx,
4791 );
4792 anchors
4793 });
4794
4795 this.change_selections(Default::default(), window, cx, |s| {
4796 s.select_anchors(selection_anchors);
4797 });
4798
4799 cx.notify();
4800 });
4801 }
4802
4803 fn trigger_completion_on_input(
4804 &mut self,
4805 text: &str,
4806 trigger_in_words: bool,
4807 window: &mut Window,
4808 cx: &mut Context<Self>,
4809 ) {
4810 let completions_source = self
4811 .context_menu
4812 .borrow()
4813 .as_ref()
4814 .and_then(|menu| match menu {
4815 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4816 CodeContextMenu::CodeActions(_) => None,
4817 });
4818
4819 match completions_source {
4820 Some(CompletionsMenuSource::Words) => {
4821 self.show_word_completions(&ShowWordCompletions, window, cx)
4822 }
4823 Some(CompletionsMenuSource::Normal)
4824 | Some(CompletionsMenuSource::SnippetChoices)
4825 | None
4826 if self.is_completion_trigger(
4827 text,
4828 trigger_in_words,
4829 completions_source.is_some(),
4830 cx,
4831 ) =>
4832 {
4833 self.show_completions(
4834 &ShowCompletions {
4835 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4836 },
4837 window,
4838 cx,
4839 )
4840 }
4841 _ => {
4842 self.hide_context_menu(window, cx);
4843 }
4844 }
4845 }
4846
4847 fn is_completion_trigger(
4848 &self,
4849 text: &str,
4850 trigger_in_words: bool,
4851 menu_is_open: bool,
4852 cx: &mut Context<Self>,
4853 ) -> bool {
4854 let position = self.selections.newest_anchor().head();
4855 let multibuffer = self.buffer.read(cx);
4856 let Some(buffer) = position
4857 .buffer_id
4858 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
4859 else {
4860 return false;
4861 };
4862
4863 if let Some(completion_provider) = &self.completion_provider {
4864 completion_provider.is_completion_trigger(
4865 &buffer,
4866 position.text_anchor,
4867 text,
4868 trigger_in_words,
4869 menu_is_open,
4870 cx,
4871 )
4872 } else {
4873 false
4874 }
4875 }
4876
4877 /// If any empty selections is touching the start of its innermost containing autoclose
4878 /// region, expand it to select the brackets.
4879 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4880 let selections = self.selections.all::<usize>(cx);
4881 let buffer = self.buffer.read(cx).read(cx);
4882 let new_selections = self
4883 .selections_with_autoclose_regions(selections, &buffer)
4884 .map(|(mut selection, region)| {
4885 if !selection.is_empty() {
4886 return selection;
4887 }
4888
4889 if let Some(region) = region {
4890 let mut range = region.range.to_offset(&buffer);
4891 if selection.start == range.start && range.start >= region.pair.start.len() {
4892 range.start -= region.pair.start.len();
4893 if buffer.contains_str_at(range.start, ®ion.pair.start)
4894 && buffer.contains_str_at(range.end, ®ion.pair.end)
4895 {
4896 range.end += region.pair.end.len();
4897 selection.start = range.start;
4898 selection.end = range.end;
4899
4900 return selection;
4901 }
4902 }
4903 }
4904
4905 let always_treat_brackets_as_autoclosed = buffer
4906 .language_settings_at(selection.start, cx)
4907 .always_treat_brackets_as_autoclosed;
4908
4909 if !always_treat_brackets_as_autoclosed {
4910 return selection;
4911 }
4912
4913 if let Some(scope) = buffer.language_scope_at(selection.start) {
4914 for (pair, enabled) in scope.brackets() {
4915 if !enabled || !pair.close {
4916 continue;
4917 }
4918
4919 if buffer.contains_str_at(selection.start, &pair.end) {
4920 let pair_start_len = pair.start.len();
4921 if buffer.contains_str_at(
4922 selection.start.saturating_sub(pair_start_len),
4923 &pair.start,
4924 ) {
4925 selection.start -= pair_start_len;
4926 selection.end += pair.end.len();
4927
4928 return selection;
4929 }
4930 }
4931 }
4932 }
4933
4934 selection
4935 })
4936 .collect();
4937
4938 drop(buffer);
4939 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
4940 selections.select(new_selections)
4941 });
4942 }
4943
4944 /// Iterate the given selections, and for each one, find the smallest surrounding
4945 /// autoclose region. This uses the ordering of the selections and the autoclose
4946 /// regions to avoid repeated comparisons.
4947 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
4948 &'a self,
4949 selections: impl IntoIterator<Item = Selection<D>>,
4950 buffer: &'a MultiBufferSnapshot,
4951 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
4952 let mut i = 0;
4953 let mut regions = self.autoclose_regions.as_slice();
4954 selections.into_iter().map(move |selection| {
4955 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
4956
4957 let mut enclosing = None;
4958 while let Some(pair_state) = regions.get(i) {
4959 if pair_state.range.end.to_offset(buffer) < range.start {
4960 regions = ®ions[i + 1..];
4961 i = 0;
4962 } else if pair_state.range.start.to_offset(buffer) > range.end {
4963 break;
4964 } else {
4965 if pair_state.selection_id == selection.id {
4966 enclosing = Some(pair_state);
4967 }
4968 i += 1;
4969 }
4970 }
4971
4972 (selection, enclosing)
4973 })
4974 }
4975
4976 /// Remove any autoclose regions that no longer contain their selection.
4977 fn invalidate_autoclose_regions(
4978 &mut self,
4979 mut selections: &[Selection<Anchor>],
4980 buffer: &MultiBufferSnapshot,
4981 ) {
4982 self.autoclose_regions.retain(|state| {
4983 let mut i = 0;
4984 while let Some(selection) = selections.get(i) {
4985 if selection.end.cmp(&state.range.start, buffer).is_lt() {
4986 selections = &selections[1..];
4987 continue;
4988 }
4989 if selection.start.cmp(&state.range.end, buffer).is_gt() {
4990 break;
4991 }
4992 if selection.id == state.selection_id {
4993 return true;
4994 } else {
4995 i += 1;
4996 }
4997 }
4998 false
4999 });
5000 }
5001
5002 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5003 let offset = position.to_offset(buffer);
5004 let (word_range, kind) = buffer.surrounding_word(offset, true);
5005 if offset > word_range.start && kind == Some(CharKind::Word) {
5006 Some(
5007 buffer
5008 .text_for_range(word_range.start..offset)
5009 .collect::<String>(),
5010 )
5011 } else {
5012 None
5013 }
5014 }
5015
5016 pub fn toggle_inline_values(
5017 &mut self,
5018 _: &ToggleInlineValues,
5019 _: &mut Window,
5020 cx: &mut Context<Self>,
5021 ) {
5022 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
5023
5024 self.refresh_inline_values(cx);
5025 }
5026
5027 pub fn toggle_inlay_hints(
5028 &mut self,
5029 _: &ToggleInlayHints,
5030 _: &mut Window,
5031 cx: &mut Context<Self>,
5032 ) {
5033 self.refresh_inlay_hints(
5034 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
5035 cx,
5036 );
5037 }
5038
5039 pub fn inlay_hints_enabled(&self) -> bool {
5040 self.inlay_hint_cache.enabled
5041 }
5042
5043 pub fn inline_values_enabled(&self) -> bool {
5044 self.inline_value_cache.enabled
5045 }
5046
5047 #[cfg(any(test, feature = "test-support"))]
5048 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
5049 self.display_map
5050 .read(cx)
5051 .current_inlays()
5052 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
5053 .cloned()
5054 .collect()
5055 }
5056
5057 #[cfg(any(test, feature = "test-support"))]
5058 pub fn all_inlays(&self, cx: &App) -> Vec<Inlay> {
5059 self.display_map
5060 .read(cx)
5061 .current_inlays()
5062 .cloned()
5063 .collect()
5064 }
5065
5066 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
5067 if self.semantics_provider.is_none() || !self.mode.is_full() {
5068 return;
5069 }
5070
5071 let reason_description = reason.description();
5072 let ignore_debounce = matches!(
5073 reason,
5074 InlayHintRefreshReason::SettingsChange(_)
5075 | InlayHintRefreshReason::Toggle(_)
5076 | InlayHintRefreshReason::ExcerptsRemoved(_)
5077 | InlayHintRefreshReason::ModifiersChanged(_)
5078 );
5079 let (invalidate_cache, required_languages) = match reason {
5080 InlayHintRefreshReason::ModifiersChanged(enabled) => {
5081 match self.inlay_hint_cache.modifiers_override(enabled) {
5082 Some(enabled) => {
5083 if enabled {
5084 (InvalidationStrategy::RefreshRequested, None)
5085 } else {
5086 self.splice_inlays(
5087 &self
5088 .visible_inlay_hints(cx)
5089 .iter()
5090 .map(|inlay| inlay.id)
5091 .collect::<Vec<InlayId>>(),
5092 Vec::new(),
5093 cx,
5094 );
5095 return;
5096 }
5097 }
5098 None => return,
5099 }
5100 }
5101 InlayHintRefreshReason::Toggle(enabled) => {
5102 if self.inlay_hint_cache.toggle(enabled) {
5103 if enabled {
5104 (InvalidationStrategy::RefreshRequested, None)
5105 } else {
5106 self.splice_inlays(
5107 &self
5108 .visible_inlay_hints(cx)
5109 .iter()
5110 .map(|inlay| inlay.id)
5111 .collect::<Vec<InlayId>>(),
5112 Vec::new(),
5113 cx,
5114 );
5115 return;
5116 }
5117 } else {
5118 return;
5119 }
5120 }
5121 InlayHintRefreshReason::SettingsChange(new_settings) => {
5122 match self.inlay_hint_cache.update_settings(
5123 &self.buffer,
5124 new_settings,
5125 self.visible_inlay_hints(cx),
5126 cx,
5127 ) {
5128 ControlFlow::Break(Some(InlaySplice {
5129 to_remove,
5130 to_insert,
5131 })) => {
5132 self.splice_inlays(&to_remove, to_insert, cx);
5133 return;
5134 }
5135 ControlFlow::Break(None) => return,
5136 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
5137 }
5138 }
5139 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
5140 if let Some(InlaySplice {
5141 to_remove,
5142 to_insert,
5143 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
5144 {
5145 self.splice_inlays(&to_remove, to_insert, cx);
5146 }
5147 self.display_map.update(cx, |display_map, _| {
5148 display_map.remove_inlays_for_excerpts(&excerpts_removed)
5149 });
5150 return;
5151 }
5152 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
5153 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
5154 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
5155 }
5156 InlayHintRefreshReason::RefreshRequested => {
5157 (InvalidationStrategy::RefreshRequested, None)
5158 }
5159 };
5160
5161 if let Some(InlaySplice {
5162 to_remove,
5163 to_insert,
5164 }) = self.inlay_hint_cache.spawn_hint_refresh(
5165 reason_description,
5166 self.visible_excerpts(required_languages.as_ref(), cx),
5167 invalidate_cache,
5168 ignore_debounce,
5169 cx,
5170 ) {
5171 self.splice_inlays(&to_remove, to_insert, cx);
5172 }
5173 }
5174
5175 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
5176 self.display_map
5177 .read(cx)
5178 .current_inlays()
5179 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
5180 .cloned()
5181 .collect()
5182 }
5183
5184 pub fn visible_excerpts(
5185 &self,
5186 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
5187 cx: &mut Context<Editor>,
5188 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5189 let Some(project) = self.project.as_ref() else {
5190 return HashMap::default();
5191 };
5192 let project = project.read(cx);
5193 let multi_buffer = self.buffer().read(cx);
5194 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5195 let multi_buffer_visible_start = self
5196 .scroll_manager
5197 .anchor()
5198 .anchor
5199 .to_point(&multi_buffer_snapshot);
5200 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5201 multi_buffer_visible_start
5202 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5203 Bias::Left,
5204 );
5205 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
5206 multi_buffer_snapshot
5207 .range_to_buffer_ranges(multi_buffer_visible_range)
5208 .into_iter()
5209 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5210 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5211 let buffer_file = project::File::from_dyn(buffer.file())?;
5212 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5213 let worktree_entry = buffer_worktree
5214 .read(cx)
5215 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
5216 if worktree_entry.is_ignored {
5217 return None;
5218 }
5219
5220 let language = buffer.language()?;
5221 if let Some(restrict_to_languages) = restrict_to_languages {
5222 if !restrict_to_languages.contains(language) {
5223 return None;
5224 }
5225 }
5226 Some((
5227 excerpt_id,
5228 (
5229 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5230 buffer.version().clone(),
5231 excerpt_visible_range,
5232 ),
5233 ))
5234 })
5235 .collect()
5236 }
5237
5238 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5239 TextLayoutDetails {
5240 text_system: window.text_system().clone(),
5241 editor_style: self.style.clone().unwrap(),
5242 rem_size: window.rem_size(),
5243 scroll_anchor: self.scroll_manager.anchor(),
5244 visible_rows: self.visible_line_count(),
5245 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5246 }
5247 }
5248
5249 pub fn splice_inlays(
5250 &self,
5251 to_remove: &[InlayId],
5252 to_insert: Vec<Inlay>,
5253 cx: &mut Context<Self>,
5254 ) {
5255 self.display_map.update(cx, |display_map, cx| {
5256 display_map.splice_inlays(to_remove, to_insert, cx)
5257 });
5258 cx.notify();
5259 }
5260
5261 fn trigger_on_type_formatting(
5262 &self,
5263 input: String,
5264 window: &mut Window,
5265 cx: &mut Context<Self>,
5266 ) -> Option<Task<Result<()>>> {
5267 if input.len() != 1 {
5268 return None;
5269 }
5270
5271 let project = self.project.as_ref()?;
5272 let position = self.selections.newest_anchor().head();
5273 let (buffer, buffer_position) = self
5274 .buffer
5275 .read(cx)
5276 .text_anchor_for_position(position, cx)?;
5277
5278 let settings = language_settings::language_settings(
5279 buffer
5280 .read(cx)
5281 .language_at(buffer_position)
5282 .map(|l| l.name()),
5283 buffer.read(cx).file(),
5284 cx,
5285 );
5286 if !settings.use_on_type_format {
5287 return None;
5288 }
5289
5290 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5291 // hence we do LSP request & edit on host side only — add formats to host's history.
5292 let push_to_lsp_host_history = true;
5293 // If this is not the host, append its history with new edits.
5294 let push_to_client_history = project.read(cx).is_via_collab();
5295
5296 let on_type_formatting = project.update(cx, |project, cx| {
5297 project.on_type_format(
5298 buffer.clone(),
5299 buffer_position,
5300 input,
5301 push_to_lsp_host_history,
5302 cx,
5303 )
5304 });
5305 Some(cx.spawn_in(window, async move |editor, cx| {
5306 if let Some(transaction) = on_type_formatting.await? {
5307 if push_to_client_history {
5308 buffer
5309 .update(cx, |buffer, _| {
5310 buffer.push_transaction(transaction, Instant::now());
5311 buffer.finalize_last_transaction();
5312 })
5313 .ok();
5314 }
5315 editor.update(cx, |editor, cx| {
5316 editor.refresh_document_highlights(cx);
5317 })?;
5318 }
5319 Ok(())
5320 }))
5321 }
5322
5323 pub fn show_word_completions(
5324 &mut self,
5325 _: &ShowWordCompletions,
5326 window: &mut Window,
5327 cx: &mut Context<Self>,
5328 ) {
5329 self.open_or_update_completions_menu(Some(CompletionsMenuSource::Words), None, window, cx);
5330 }
5331
5332 pub fn show_completions(
5333 &mut self,
5334 options: &ShowCompletions,
5335 window: &mut Window,
5336 cx: &mut Context<Self>,
5337 ) {
5338 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5339 }
5340
5341 fn open_or_update_completions_menu(
5342 &mut self,
5343 requested_source: Option<CompletionsMenuSource>,
5344 trigger: Option<&str>,
5345 window: &mut Window,
5346 cx: &mut Context<Self>,
5347 ) {
5348 if self.pending_rename.is_some() {
5349 return;
5350 }
5351
5352 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5353
5354 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5355 // inserted and selected. To handle that case, the start of the selection is used so that
5356 // the menu starts with all choices.
5357 let position = self
5358 .selections
5359 .newest_anchor()
5360 .start
5361 .bias_right(&multibuffer_snapshot);
5362 if position.diff_base_anchor.is_some() {
5363 return;
5364 }
5365 let (buffer, buffer_position) =
5366 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
5367 output
5368 } else {
5369 return;
5370 };
5371 let buffer_snapshot = buffer.read(cx).snapshot();
5372
5373 let query: Option<Arc<String>> =
5374 Self::completion_query(&multibuffer_snapshot, position).map(|query| query.into());
5375
5376 drop(multibuffer_snapshot);
5377
5378 let provider = match requested_source {
5379 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5380 Some(CompletionsMenuSource::Words) => None,
5381 Some(CompletionsMenuSource::SnippetChoices) => {
5382 log::error!("bug: SnippetChoices requested_source is not handled");
5383 None
5384 }
5385 };
5386
5387 let sort_completions = provider
5388 .as_ref()
5389 .map_or(false, |provider| provider.sort_completions());
5390
5391 let filter_completions = provider
5392 .as_ref()
5393 .map_or(true, |provider| provider.filter_completions());
5394
5395 let trigger_kind = match trigger {
5396 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5397 CompletionTriggerKind::TRIGGER_CHARACTER
5398 }
5399 _ => CompletionTriggerKind::INVOKED,
5400 };
5401 let completion_context = CompletionContext {
5402 trigger_character: trigger.and_then(|trigger| {
5403 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5404 Some(String::from(trigger))
5405 } else {
5406 None
5407 }
5408 }),
5409 trigger_kind,
5410 };
5411
5412 // Hide the current completions menu when a trigger char is typed. Without this, cached
5413 // completions from before the trigger char may be reused (#32774). Snippet choices could
5414 // involve trigger chars, so this is skipped in that case.
5415 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER && self.snippet_stack.is_empty()
5416 {
5417 let menu_is_open = matches!(
5418 self.context_menu.borrow().as_ref(),
5419 Some(CodeContextMenu::Completions(_))
5420 );
5421 if menu_is_open {
5422 self.hide_context_menu(window, cx);
5423 }
5424 }
5425
5426 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5427 if filter_completions {
5428 menu.filter(query.clone(), provider.clone(), window, cx);
5429 }
5430 // When `is_incomplete` is false, no need to re-query completions when the current query
5431 // is a suffix of the initial query.
5432 if !menu.is_incomplete {
5433 // If the new query is a suffix of the old query (typing more characters) and
5434 // the previous result was complete, the existing completions can be filtered.
5435 //
5436 // Note that this is always true for snippet completions.
5437 let query_matches = match (&menu.initial_query, &query) {
5438 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5439 (None, _) => true,
5440 _ => false,
5441 };
5442 if query_matches {
5443 let position_matches = if menu.initial_position == position {
5444 true
5445 } else {
5446 let snapshot = self.buffer.read(cx).read(cx);
5447 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5448 };
5449 if position_matches {
5450 return;
5451 }
5452 }
5453 }
5454 };
5455
5456 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5457 buffer_snapshot.surrounding_word(buffer_position, false)
5458 {
5459 let word_to_exclude = buffer_snapshot
5460 .text_for_range(word_range.clone())
5461 .collect::<String>();
5462 (
5463 buffer_snapshot.anchor_before(word_range.start)
5464 ..buffer_snapshot.anchor_after(buffer_position),
5465 Some(word_to_exclude),
5466 )
5467 } else {
5468 (buffer_position..buffer_position, None)
5469 };
5470
5471 let language = buffer_snapshot
5472 .language_at(buffer_position)
5473 .map(|language| language.name());
5474
5475 let completion_settings =
5476 language_settings(language.clone(), buffer_snapshot.file(), cx).completions;
5477
5478 let show_completion_documentation = buffer_snapshot
5479 .settings_at(buffer_position, cx)
5480 .show_completion_documentation;
5481
5482 // The document can be large, so stay in reasonable bounds when searching for words,
5483 // otherwise completion pop-up might be slow to appear.
5484 const WORD_LOOKUP_ROWS: u32 = 5_000;
5485 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5486 let min_word_search = buffer_snapshot.clip_point(
5487 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5488 Bias::Left,
5489 );
5490 let max_word_search = buffer_snapshot.clip_point(
5491 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5492 Bias::Right,
5493 );
5494 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5495 ..buffer_snapshot.point_to_offset(max_word_search);
5496
5497 let skip_digits = query
5498 .as_ref()
5499 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
5500
5501 let (mut words, provider_responses) = match &provider {
5502 Some(provider) => {
5503 let provider_responses = provider.completions(
5504 position.excerpt_id,
5505 &buffer,
5506 buffer_position,
5507 completion_context,
5508 window,
5509 cx,
5510 );
5511
5512 let words = match completion_settings.words {
5513 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
5514 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
5515 .background_spawn(async move {
5516 buffer_snapshot.words_in_range(WordsQuery {
5517 fuzzy_contents: None,
5518 range: word_search_range,
5519 skip_digits,
5520 })
5521 }),
5522 };
5523
5524 (words, provider_responses)
5525 }
5526 None => (
5527 cx.background_spawn(async move {
5528 buffer_snapshot.words_in_range(WordsQuery {
5529 fuzzy_contents: None,
5530 range: word_search_range,
5531 skip_digits,
5532 })
5533 }),
5534 Task::ready(Ok(Vec::new())),
5535 ),
5536 };
5537
5538 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5539
5540 let id = post_inc(&mut self.next_completion_id);
5541 let task = cx.spawn_in(window, async move |editor, cx| {
5542 let Ok(()) = editor.update(cx, |this, _| {
5543 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5544 }) else {
5545 return;
5546 };
5547
5548 // TODO: Ideally completions from different sources would be selectively re-queried, so
5549 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5550 let mut completions = Vec::new();
5551 let mut is_incomplete = false;
5552 if let Some(provider_responses) = provider_responses.await.log_err() {
5553 if !provider_responses.is_empty() {
5554 for response in provider_responses {
5555 completions.extend(response.completions);
5556 is_incomplete = is_incomplete || response.is_incomplete;
5557 }
5558 if completion_settings.words == WordsCompletionMode::Fallback {
5559 words = Task::ready(BTreeMap::default());
5560 }
5561 }
5562 }
5563
5564 let mut words = words.await;
5565 if let Some(word_to_exclude) = &word_to_exclude {
5566 words.remove(word_to_exclude);
5567 }
5568 for lsp_completion in &completions {
5569 words.remove(&lsp_completion.new_text);
5570 }
5571 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5572 replace_range: word_replace_range.clone(),
5573 new_text: word.clone(),
5574 label: CodeLabel::plain(word, None),
5575 icon_path: None,
5576 documentation: None,
5577 source: CompletionSource::BufferWord {
5578 word_range,
5579 resolved: false,
5580 },
5581 insert_text_mode: Some(InsertTextMode::AS_IS),
5582 confirm: None,
5583 }));
5584
5585 let menu = if completions.is_empty() {
5586 None
5587 } else {
5588 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5589 let languages = editor
5590 .workspace
5591 .as_ref()
5592 .and_then(|(workspace, _)| workspace.upgrade())
5593 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5594 let menu = CompletionsMenu::new(
5595 id,
5596 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5597 sort_completions,
5598 show_completion_documentation,
5599 position,
5600 query.clone(),
5601 is_incomplete,
5602 buffer.clone(),
5603 completions.into(),
5604 snippet_sort_order,
5605 languages,
5606 language,
5607 cx,
5608 );
5609
5610 let query = if filter_completions { query } else { None };
5611 let matches_task = if let Some(query) = query {
5612 menu.do_async_filtering(query, cx)
5613 } else {
5614 Task::ready(menu.unfiltered_matches())
5615 };
5616 (menu, matches_task)
5617 }) else {
5618 return;
5619 };
5620
5621 let matches = matches_task.await;
5622
5623 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5624 // Newer menu already set, so exit.
5625 match editor.context_menu.borrow().as_ref() {
5626 Some(CodeContextMenu::Completions(prev_menu)) => {
5627 if prev_menu.id > id {
5628 return;
5629 }
5630 }
5631 _ => {}
5632 };
5633
5634 // Only valid to take prev_menu because it the new menu is immediately set
5635 // below, or the menu is hidden.
5636 match editor.context_menu.borrow_mut().take() {
5637 Some(CodeContextMenu::Completions(prev_menu)) => {
5638 let position_matches =
5639 if prev_menu.initial_position == menu.initial_position {
5640 true
5641 } else {
5642 let snapshot = editor.buffer.read(cx).read(cx);
5643 prev_menu.initial_position.to_offset(&snapshot)
5644 == menu.initial_position.to_offset(&snapshot)
5645 };
5646 if position_matches {
5647 // Preserve markdown cache before `set_filter_results` because it will
5648 // try to populate the documentation cache.
5649 menu.preserve_markdown_cache(prev_menu);
5650 }
5651 }
5652 _ => {}
5653 };
5654
5655 menu.set_filter_results(matches, provider, window, cx);
5656 }) else {
5657 return;
5658 };
5659
5660 menu.visible().then_some(menu)
5661 };
5662
5663 editor
5664 .update_in(cx, |editor, window, cx| {
5665 if editor.focus_handle.is_focused(window) {
5666 if let Some(menu) = menu {
5667 *editor.context_menu.borrow_mut() =
5668 Some(CodeContextMenu::Completions(menu));
5669
5670 crate::hover_popover::hide_hover(editor, cx);
5671 if editor.show_edit_predictions_in_menu() {
5672 editor.update_visible_inline_completion(window, cx);
5673 } else {
5674 editor.discard_inline_completion(false, cx);
5675 }
5676
5677 cx.notify();
5678 return;
5679 }
5680 }
5681
5682 if editor.completion_tasks.len() <= 1 {
5683 // If there are no more completion tasks and the last menu was empty, we should hide it.
5684 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5685 // If it was already hidden and we don't show inline completions in the menu, we should
5686 // also show the inline-completion when available.
5687 if was_hidden && editor.show_edit_predictions_in_menu() {
5688 editor.update_visible_inline_completion(window, cx);
5689 }
5690 }
5691 })
5692 .ok();
5693 });
5694
5695 self.completion_tasks.push((id, task));
5696 }
5697
5698 #[cfg(feature = "test-support")]
5699 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5700 let menu = self.context_menu.borrow();
5701 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5702 let completions = menu.completions.borrow();
5703 Some(completions.to_vec())
5704 } else {
5705 None
5706 }
5707 }
5708
5709 pub fn with_completions_menu_matching_id<R>(
5710 &self,
5711 id: CompletionId,
5712 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5713 ) -> R {
5714 let mut context_menu = self.context_menu.borrow_mut();
5715 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5716 return f(None);
5717 };
5718 if completions_menu.id != id {
5719 return f(None);
5720 }
5721 f(Some(completions_menu))
5722 }
5723
5724 pub fn confirm_completion(
5725 &mut self,
5726 action: &ConfirmCompletion,
5727 window: &mut Window,
5728 cx: &mut Context<Self>,
5729 ) -> Option<Task<Result<()>>> {
5730 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5731 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5732 }
5733
5734 pub fn confirm_completion_insert(
5735 &mut self,
5736 _: &ConfirmCompletionInsert,
5737 window: &mut Window,
5738 cx: &mut Context<Self>,
5739 ) -> Option<Task<Result<()>>> {
5740 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5741 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5742 }
5743
5744 pub fn confirm_completion_replace(
5745 &mut self,
5746 _: &ConfirmCompletionReplace,
5747 window: &mut Window,
5748 cx: &mut Context<Self>,
5749 ) -> Option<Task<Result<()>>> {
5750 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5751 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5752 }
5753
5754 pub fn compose_completion(
5755 &mut self,
5756 action: &ComposeCompletion,
5757 window: &mut Window,
5758 cx: &mut Context<Self>,
5759 ) -> Option<Task<Result<()>>> {
5760 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5761 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5762 }
5763
5764 fn do_completion(
5765 &mut self,
5766 item_ix: Option<usize>,
5767 intent: CompletionIntent,
5768 window: &mut Window,
5769 cx: &mut Context<Editor>,
5770 ) -> Option<Task<Result<()>>> {
5771 use language::ToOffset as _;
5772
5773 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5774 else {
5775 return None;
5776 };
5777
5778 let candidate_id = {
5779 let entries = completions_menu.entries.borrow();
5780 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5781 if self.show_edit_predictions_in_menu() {
5782 self.discard_inline_completion(true, cx);
5783 }
5784 mat.candidate_id
5785 };
5786
5787 let completion = completions_menu
5788 .completions
5789 .borrow()
5790 .get(candidate_id)?
5791 .clone();
5792 cx.stop_propagation();
5793
5794 let buffer_handle = completions_menu.buffer.clone();
5795
5796 let CompletionEdit {
5797 new_text,
5798 snippet,
5799 replace_range,
5800 } = process_completion_for_edit(
5801 &completion,
5802 intent,
5803 &buffer_handle,
5804 &completions_menu.initial_position.text_anchor,
5805 cx,
5806 );
5807
5808 let buffer = buffer_handle.read(cx);
5809 let snapshot = self.buffer.read(cx).snapshot(cx);
5810 let newest_anchor = self.selections.newest_anchor();
5811 let replace_range_multibuffer = {
5812 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5813 let multibuffer_anchor = snapshot
5814 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5815 .unwrap()
5816 ..snapshot
5817 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5818 .unwrap();
5819 multibuffer_anchor.start.to_offset(&snapshot)
5820 ..multibuffer_anchor.end.to_offset(&snapshot)
5821 };
5822 if newest_anchor.head().buffer_id != Some(buffer.remote_id()) {
5823 return None;
5824 }
5825
5826 let old_text = buffer
5827 .text_for_range(replace_range.clone())
5828 .collect::<String>();
5829 let lookbehind = newest_anchor
5830 .start
5831 .text_anchor
5832 .to_offset(buffer)
5833 .saturating_sub(replace_range.start);
5834 let lookahead = replace_range
5835 .end
5836 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5837 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5838 let suffix = &old_text[lookbehind.min(old_text.len())..];
5839
5840 let selections = self.selections.all::<usize>(cx);
5841 let mut ranges = Vec::new();
5842 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5843
5844 for selection in &selections {
5845 let range = if selection.id == newest_anchor.id {
5846 replace_range_multibuffer.clone()
5847 } else {
5848 let mut range = selection.range();
5849
5850 // if prefix is present, don't duplicate it
5851 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5852 range.start = range.start.saturating_sub(lookbehind);
5853
5854 // if suffix is also present, mimic the newest cursor and replace it
5855 if selection.id != newest_anchor.id
5856 && snapshot.contains_str_at(range.end, suffix)
5857 {
5858 range.end += lookahead;
5859 }
5860 }
5861 range
5862 };
5863
5864 ranges.push(range.clone());
5865
5866 if !self.linked_edit_ranges.is_empty() {
5867 let start_anchor = snapshot.anchor_before(range.start);
5868 let end_anchor = snapshot.anchor_after(range.end);
5869 if let Some(ranges) = self
5870 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5871 {
5872 for (buffer, edits) in ranges {
5873 linked_edits
5874 .entry(buffer.clone())
5875 .or_default()
5876 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5877 }
5878 }
5879 }
5880 }
5881
5882 let common_prefix_len = old_text
5883 .chars()
5884 .zip(new_text.chars())
5885 .take_while(|(a, b)| a == b)
5886 .map(|(a, _)| a.len_utf8())
5887 .sum::<usize>();
5888
5889 cx.emit(EditorEvent::InputHandled {
5890 utf16_range_to_replace: None,
5891 text: new_text[common_prefix_len..].into(),
5892 });
5893
5894 self.transact(window, cx, |this, window, cx| {
5895 if let Some(mut snippet) = snippet {
5896 snippet.text = new_text.to_string();
5897 this.insert_snippet(&ranges, snippet, window, cx).log_err();
5898 } else {
5899 this.buffer.update(cx, |buffer, cx| {
5900 let auto_indent = match completion.insert_text_mode {
5901 Some(InsertTextMode::AS_IS) => None,
5902 _ => this.autoindent_mode.clone(),
5903 };
5904 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5905 buffer.edit(edits, auto_indent, cx);
5906 });
5907 }
5908 for (buffer, edits) in linked_edits {
5909 buffer.update(cx, |buffer, cx| {
5910 let snapshot = buffer.snapshot();
5911 let edits = edits
5912 .into_iter()
5913 .map(|(range, text)| {
5914 use text::ToPoint as TP;
5915 let end_point = TP::to_point(&range.end, &snapshot);
5916 let start_point = TP::to_point(&range.start, &snapshot);
5917 (start_point..end_point, text)
5918 })
5919 .sorted_by_key(|(range, _)| range.start);
5920 buffer.edit(edits, None, cx);
5921 })
5922 }
5923
5924 this.refresh_inline_completion(true, false, window, cx);
5925 });
5926
5927 let show_new_completions_on_confirm = completion
5928 .confirm
5929 .as_ref()
5930 .map_or(false, |confirm| confirm(intent, window, cx));
5931 if show_new_completions_on_confirm {
5932 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5933 }
5934
5935 let provider = self.completion_provider.as_ref()?;
5936 drop(completion);
5937 let apply_edits = provider.apply_additional_edits_for_completion(
5938 buffer_handle,
5939 completions_menu.completions.clone(),
5940 candidate_id,
5941 true,
5942 cx,
5943 );
5944
5945 let editor_settings = EditorSettings::get_global(cx);
5946 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5947 // After the code completion is finished, users often want to know what signatures are needed.
5948 // so we should automatically call signature_help
5949 self.show_signature_help(&ShowSignatureHelp, window, cx);
5950 }
5951
5952 Some(cx.foreground_executor().spawn(async move {
5953 apply_edits.await?;
5954 Ok(())
5955 }))
5956 }
5957
5958 pub fn toggle_code_actions(
5959 &mut self,
5960 action: &ToggleCodeActions,
5961 window: &mut Window,
5962 cx: &mut Context<Self>,
5963 ) {
5964 let quick_launch = action.quick_launch;
5965 let mut context_menu = self.context_menu.borrow_mut();
5966 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5967 if code_actions.deployed_from == action.deployed_from {
5968 // Toggle if we're selecting the same one
5969 *context_menu = None;
5970 cx.notify();
5971 return;
5972 } else {
5973 // Otherwise, clear it and start a new one
5974 *context_menu = None;
5975 cx.notify();
5976 }
5977 }
5978 drop(context_menu);
5979 let snapshot = self.snapshot(window, cx);
5980 let deployed_from = action.deployed_from.clone();
5981 let action = action.clone();
5982 self.completion_tasks.clear();
5983 self.discard_inline_completion(false, cx);
5984
5985 let multibuffer_point = match &action.deployed_from {
5986 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
5987 DisplayPoint::new(*row, 0).to_point(&snapshot)
5988 }
5989 _ => self.selections.newest::<Point>(cx).head(),
5990 };
5991 let Some((buffer, buffer_row)) = snapshot
5992 .buffer_snapshot
5993 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
5994 .and_then(|(buffer_snapshot, range)| {
5995 self.buffer()
5996 .read(cx)
5997 .buffer(buffer_snapshot.remote_id())
5998 .map(|buffer| (buffer, range.start.row))
5999 })
6000 else {
6001 return;
6002 };
6003 let buffer_id = buffer.read(cx).remote_id();
6004 let tasks = self
6005 .tasks
6006 .get(&(buffer_id, buffer_row))
6007 .map(|t| Arc::new(t.to_owned()));
6008
6009 if !self.focus_handle.is_focused(window) {
6010 return;
6011 }
6012 let project = self.project.clone();
6013
6014 let code_actions_task = match deployed_from {
6015 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6016 _ => self.code_actions(buffer_row, window, cx),
6017 };
6018
6019 let runnable_task = match deployed_from {
6020 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6021 _ => {
6022 let mut task_context_task = Task::ready(None);
6023 if let Some(tasks) = &tasks {
6024 if let Some(project) = project {
6025 task_context_task =
6026 Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
6027 }
6028 }
6029
6030 cx.spawn_in(window, {
6031 let buffer = buffer.clone();
6032 async move |editor, cx| {
6033 let task_context = task_context_task.await;
6034
6035 let resolved_tasks =
6036 tasks
6037 .zip(task_context.clone())
6038 .map(|(tasks, task_context)| ResolvedTasks {
6039 templates: tasks.resolve(&task_context).collect(),
6040 position: snapshot.buffer_snapshot.anchor_before(Point::new(
6041 multibuffer_point.row,
6042 tasks.column,
6043 )),
6044 });
6045 let debug_scenarios = editor
6046 .update(cx, |editor, cx| {
6047 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6048 })?
6049 .await;
6050 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6051 }
6052 })
6053 }
6054 };
6055
6056 cx.spawn_in(window, async move |editor, cx| {
6057 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6058 let code_actions = code_actions_task.await;
6059 let spawn_straight_away = quick_launch
6060 && resolved_tasks
6061 .as_ref()
6062 .map_or(false, |tasks| tasks.templates.len() == 1)
6063 && code_actions
6064 .as_ref()
6065 .map_or(true, |actions| actions.is_empty())
6066 && debug_scenarios.is_empty();
6067
6068 editor.update_in(cx, |editor, window, cx| {
6069 crate::hover_popover::hide_hover(editor, cx);
6070 let actions = CodeActionContents::new(
6071 resolved_tasks,
6072 code_actions,
6073 debug_scenarios,
6074 task_context.unwrap_or_default(),
6075 );
6076
6077 // Don't show the menu if there are no actions available
6078 if actions.is_empty() {
6079 cx.notify();
6080 return Task::ready(Ok(()));
6081 }
6082
6083 *editor.context_menu.borrow_mut() =
6084 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6085 buffer,
6086 actions,
6087 selected_item: Default::default(),
6088 scroll_handle: UniformListScrollHandle::default(),
6089 deployed_from,
6090 }));
6091 cx.notify();
6092 if spawn_straight_away {
6093 if let Some(task) = editor.confirm_code_action(
6094 &ConfirmCodeAction { item_ix: Some(0) },
6095 window,
6096 cx,
6097 ) {
6098 return task;
6099 }
6100 }
6101
6102 Task::ready(Ok(()))
6103 })
6104 })
6105 .detach_and_log_err(cx);
6106 }
6107
6108 fn debug_scenarios(
6109 &mut self,
6110 resolved_tasks: &Option<ResolvedTasks>,
6111 buffer: &Entity<Buffer>,
6112 cx: &mut App,
6113 ) -> Task<Vec<task::DebugScenario>> {
6114 maybe!({
6115 let project = self.project.as_ref()?;
6116 let dap_store = project.read(cx).dap_store();
6117 let mut scenarios = vec![];
6118 let resolved_tasks = resolved_tasks.as_ref()?;
6119 let buffer = buffer.read(cx);
6120 let language = buffer.language()?;
6121 let file = buffer.file();
6122 let debug_adapter = language_settings(language.name().into(), file, cx)
6123 .debuggers
6124 .first()
6125 .map(SharedString::from)
6126 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6127
6128 dap_store.update(cx, |dap_store, cx| {
6129 for (_, task) in &resolved_tasks.templates {
6130 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6131 task.original_task().clone(),
6132 debug_adapter.clone().into(),
6133 task.display_label().to_owned().into(),
6134 cx,
6135 );
6136 scenarios.push(maybe_scenario);
6137 }
6138 });
6139 Some(cx.background_spawn(async move {
6140 let scenarios = futures::future::join_all(scenarios)
6141 .await
6142 .into_iter()
6143 .flatten()
6144 .collect::<Vec<_>>();
6145 scenarios
6146 }))
6147 })
6148 .unwrap_or_else(|| Task::ready(vec![]))
6149 }
6150
6151 fn code_actions(
6152 &mut self,
6153 buffer_row: u32,
6154 window: &mut Window,
6155 cx: &mut Context<Self>,
6156 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6157 let mut task = self.code_actions_task.take();
6158 cx.spawn_in(window, async move |editor, cx| {
6159 while let Some(prev_task) = task {
6160 prev_task.await.log_err();
6161 task = editor
6162 .update(cx, |this, _| this.code_actions_task.take())
6163 .ok()?;
6164 }
6165
6166 editor
6167 .update(cx, |editor, cx| {
6168 editor
6169 .available_code_actions
6170 .clone()
6171 .and_then(|(location, code_actions)| {
6172 let snapshot = location.buffer.read(cx).snapshot();
6173 let point_range = location.range.to_point(&snapshot);
6174 let point_range = point_range.start.row..=point_range.end.row;
6175 if point_range.contains(&buffer_row) {
6176 Some(code_actions)
6177 } else {
6178 None
6179 }
6180 })
6181 })
6182 .ok()
6183 .flatten()
6184 })
6185 }
6186
6187 pub fn confirm_code_action(
6188 &mut self,
6189 action: &ConfirmCodeAction,
6190 window: &mut Window,
6191 cx: &mut Context<Self>,
6192 ) -> Option<Task<Result<()>>> {
6193 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6194
6195 let actions_menu =
6196 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6197 menu
6198 } else {
6199 return None;
6200 };
6201
6202 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6203 let action = actions_menu.actions.get(action_ix)?;
6204 let title = action.label();
6205 let buffer = actions_menu.buffer;
6206 let workspace = self.workspace()?;
6207
6208 match action {
6209 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6210 workspace.update(cx, |workspace, cx| {
6211 workspace.schedule_resolved_task(
6212 task_source_kind,
6213 resolved_task,
6214 false,
6215 window,
6216 cx,
6217 );
6218
6219 Some(Task::ready(Ok(())))
6220 })
6221 }
6222 CodeActionsItem::CodeAction {
6223 excerpt_id,
6224 action,
6225 provider,
6226 } => {
6227 let apply_code_action =
6228 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6229 let workspace = workspace.downgrade();
6230 Some(cx.spawn_in(window, async move |editor, cx| {
6231 let project_transaction = apply_code_action.await?;
6232 Self::open_project_transaction(
6233 &editor,
6234 workspace,
6235 project_transaction,
6236 title,
6237 cx,
6238 )
6239 .await
6240 }))
6241 }
6242 CodeActionsItem::DebugScenario(scenario) => {
6243 let context = actions_menu.actions.context.clone();
6244
6245 workspace.update(cx, |workspace, cx| {
6246 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6247 workspace.start_debug_session(
6248 scenario,
6249 context,
6250 Some(buffer),
6251 None,
6252 window,
6253 cx,
6254 );
6255 });
6256 Some(Task::ready(Ok(())))
6257 }
6258 }
6259 }
6260
6261 pub async fn open_project_transaction(
6262 this: &WeakEntity<Editor>,
6263 workspace: WeakEntity<Workspace>,
6264 transaction: ProjectTransaction,
6265 title: String,
6266 cx: &mut AsyncWindowContext,
6267 ) -> Result<()> {
6268 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6269 cx.update(|_, cx| {
6270 entries.sort_unstable_by_key(|(buffer, _)| {
6271 buffer.read(cx).file().map(|f| f.path().clone())
6272 });
6273 })?;
6274
6275 // If the project transaction's edits are all contained within this editor, then
6276 // avoid opening a new editor to display them.
6277
6278 if let Some((buffer, transaction)) = entries.first() {
6279 if entries.len() == 1 {
6280 let excerpt = this.update(cx, |editor, cx| {
6281 editor
6282 .buffer()
6283 .read(cx)
6284 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6285 })?;
6286 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
6287 if excerpted_buffer == *buffer {
6288 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6289 let excerpt_range = excerpt_range.to_offset(buffer);
6290 buffer
6291 .edited_ranges_for_transaction::<usize>(transaction)
6292 .all(|range| {
6293 excerpt_range.start <= range.start
6294 && excerpt_range.end >= range.end
6295 })
6296 })?;
6297
6298 if all_edits_within_excerpt {
6299 return Ok(());
6300 }
6301 }
6302 }
6303 }
6304 } else {
6305 return Ok(());
6306 }
6307
6308 let mut ranges_to_highlight = Vec::new();
6309 let excerpt_buffer = cx.new(|cx| {
6310 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6311 for (buffer_handle, transaction) in &entries {
6312 let edited_ranges = buffer_handle
6313 .read(cx)
6314 .edited_ranges_for_transaction::<Point>(transaction)
6315 .collect::<Vec<_>>();
6316 let (ranges, _) = multibuffer.set_excerpts_for_path(
6317 PathKey::for_buffer(buffer_handle, cx),
6318 buffer_handle.clone(),
6319 edited_ranges,
6320 DEFAULT_MULTIBUFFER_CONTEXT,
6321 cx,
6322 );
6323
6324 ranges_to_highlight.extend(ranges);
6325 }
6326 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6327 multibuffer
6328 })?;
6329
6330 workspace.update_in(cx, |workspace, window, cx| {
6331 let project = workspace.project().clone();
6332 let editor =
6333 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6334 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6335 editor.update(cx, |editor, cx| {
6336 editor.highlight_background::<Self>(
6337 &ranges_to_highlight,
6338 |theme| theme.colors().editor_highlighted_line_background,
6339 cx,
6340 );
6341 });
6342 })?;
6343
6344 Ok(())
6345 }
6346
6347 pub fn clear_code_action_providers(&mut self) {
6348 self.code_action_providers.clear();
6349 self.available_code_actions.take();
6350 }
6351
6352 pub fn add_code_action_provider(
6353 &mut self,
6354 provider: Rc<dyn CodeActionProvider>,
6355 window: &mut Window,
6356 cx: &mut Context<Self>,
6357 ) {
6358 if self
6359 .code_action_providers
6360 .iter()
6361 .any(|existing_provider| existing_provider.id() == provider.id())
6362 {
6363 return;
6364 }
6365
6366 self.code_action_providers.push(provider);
6367 self.refresh_code_actions(window, cx);
6368 }
6369
6370 pub fn remove_code_action_provider(
6371 &mut self,
6372 id: Arc<str>,
6373 window: &mut Window,
6374 cx: &mut Context<Self>,
6375 ) {
6376 self.code_action_providers
6377 .retain(|provider| provider.id() != id);
6378 self.refresh_code_actions(window, cx);
6379 }
6380
6381 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6382 !self.code_action_providers.is_empty()
6383 && EditorSettings::get_global(cx).toolbar.code_actions
6384 }
6385
6386 pub fn has_available_code_actions(&self) -> bool {
6387 self.available_code_actions
6388 .as_ref()
6389 .is_some_and(|(_, actions)| !actions.is_empty())
6390 }
6391
6392 fn render_inline_code_actions(
6393 &self,
6394 icon_size: ui::IconSize,
6395 display_row: DisplayRow,
6396 is_active: bool,
6397 cx: &mut Context<Self>,
6398 ) -> AnyElement {
6399 let show_tooltip = !self.context_menu_visible();
6400 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6401 .icon_size(icon_size)
6402 .shape(ui::IconButtonShape::Square)
6403 .style(ButtonStyle::Transparent)
6404 .icon_color(ui::Color::Hidden)
6405 .toggle_state(is_active)
6406 .when(show_tooltip, |this| {
6407 this.tooltip({
6408 let focus_handle = self.focus_handle.clone();
6409 move |window, cx| {
6410 Tooltip::for_action_in(
6411 "Toggle Code Actions",
6412 &ToggleCodeActions {
6413 deployed_from: None,
6414 quick_launch: false,
6415 },
6416 &focus_handle,
6417 window,
6418 cx,
6419 )
6420 }
6421 })
6422 })
6423 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6424 window.focus(&editor.focus_handle(cx));
6425 editor.toggle_code_actions(
6426 &crate::actions::ToggleCodeActions {
6427 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6428 display_row,
6429 )),
6430 quick_launch: false,
6431 },
6432 window,
6433 cx,
6434 );
6435 }))
6436 .into_any_element()
6437 }
6438
6439 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6440 &self.context_menu
6441 }
6442
6443 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
6444 let newest_selection = self.selections.newest_anchor().clone();
6445 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
6446 let buffer = self.buffer.read(cx);
6447 if newest_selection.head().diff_base_anchor.is_some() {
6448 return None;
6449 }
6450 let (start_buffer, start) =
6451 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6452 let (end_buffer, end) =
6453 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6454 if start_buffer != end_buffer {
6455 return None;
6456 }
6457
6458 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6459 cx.background_executor()
6460 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6461 .await;
6462
6463 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6464 let providers = this.code_action_providers.clone();
6465 let tasks = this
6466 .code_action_providers
6467 .iter()
6468 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6469 .collect::<Vec<_>>();
6470 (providers, tasks)
6471 })?;
6472
6473 let mut actions = Vec::new();
6474 for (provider, provider_actions) in
6475 providers.into_iter().zip(future::join_all(tasks).await)
6476 {
6477 if let Some(provider_actions) = provider_actions.log_err() {
6478 actions.extend(provider_actions.into_iter().map(|action| {
6479 AvailableCodeAction {
6480 excerpt_id: newest_selection.start.excerpt_id,
6481 action,
6482 provider: provider.clone(),
6483 }
6484 }));
6485 }
6486 }
6487
6488 this.update(cx, |this, cx| {
6489 this.available_code_actions = if actions.is_empty() {
6490 None
6491 } else {
6492 Some((
6493 Location {
6494 buffer: start_buffer,
6495 range: start..end,
6496 },
6497 actions.into(),
6498 ))
6499 };
6500 cx.notify();
6501 })
6502 }));
6503 None
6504 }
6505
6506 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6507 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6508 self.show_git_blame_inline = false;
6509
6510 self.show_git_blame_inline_delay_task =
6511 Some(cx.spawn_in(window, async move |this, cx| {
6512 cx.background_executor().timer(delay).await;
6513
6514 this.update(cx, |this, cx| {
6515 this.show_git_blame_inline = true;
6516 cx.notify();
6517 })
6518 .log_err();
6519 }));
6520 }
6521 }
6522
6523 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6524 let snapshot = self.snapshot(window, cx);
6525 let cursor = self.selections.newest::<Point>(cx).head();
6526 let Some((buffer, point, _)) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)
6527 else {
6528 return;
6529 };
6530
6531 let Some(blame) = self.blame.as_ref() else {
6532 return;
6533 };
6534
6535 let row_info = RowInfo {
6536 buffer_id: Some(buffer.remote_id()),
6537 buffer_row: Some(point.row),
6538 ..Default::default()
6539 };
6540 let Some(blame_entry) = blame
6541 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6542 .flatten()
6543 else {
6544 return;
6545 };
6546
6547 let anchor = self.selections.newest_anchor().head();
6548 let position = self.to_pixel_point(anchor, &snapshot, window);
6549 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6550 self.show_blame_popover(&blame_entry, position + last_bounds.origin, true, cx);
6551 };
6552 }
6553
6554 fn show_blame_popover(
6555 &mut self,
6556 blame_entry: &BlameEntry,
6557 position: gpui::Point<Pixels>,
6558 ignore_timeout: bool,
6559 cx: &mut Context<Self>,
6560 ) {
6561 if let Some(state) = &mut self.inline_blame_popover {
6562 state.hide_task.take();
6563 } else {
6564 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay;
6565 let blame_entry = blame_entry.clone();
6566 let show_task = cx.spawn(async move |editor, cx| {
6567 if !ignore_timeout {
6568 cx.background_executor()
6569 .timer(std::time::Duration::from_millis(blame_popover_delay))
6570 .await;
6571 }
6572 editor
6573 .update(cx, |editor, cx| {
6574 editor.inline_blame_popover_show_task.take();
6575 let Some(blame) = editor.blame.as_ref() else {
6576 return;
6577 };
6578 let blame = blame.read(cx);
6579 let details = blame.details_for_entry(&blame_entry);
6580 let markdown = cx.new(|cx| {
6581 Markdown::new(
6582 details
6583 .as_ref()
6584 .map(|message| message.message.clone())
6585 .unwrap_or_default(),
6586 None,
6587 None,
6588 cx,
6589 )
6590 });
6591 editor.inline_blame_popover = Some(InlineBlamePopover {
6592 position,
6593 hide_task: None,
6594 popover_bounds: None,
6595 popover_state: InlineBlamePopoverState {
6596 scroll_handle: ScrollHandle::new(),
6597 commit_message: details,
6598 markdown,
6599 },
6600 keyboard_grace: ignore_timeout,
6601 });
6602 cx.notify();
6603 })
6604 .ok();
6605 });
6606 self.inline_blame_popover_show_task = Some(show_task);
6607 }
6608 }
6609
6610 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6611 self.inline_blame_popover_show_task.take();
6612 if let Some(state) = &mut self.inline_blame_popover {
6613 let hide_task = cx.spawn(async move |editor, cx| {
6614 cx.background_executor()
6615 .timer(std::time::Duration::from_millis(100))
6616 .await;
6617 editor
6618 .update(cx, |editor, cx| {
6619 editor.inline_blame_popover.take();
6620 cx.notify();
6621 })
6622 .ok();
6623 });
6624 state.hide_task = Some(hide_task);
6625 }
6626 }
6627
6628 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6629 if self.pending_rename.is_some() {
6630 return None;
6631 }
6632
6633 let provider = self.semantics_provider.clone()?;
6634 let buffer = self.buffer.read(cx);
6635 let newest_selection = self.selections.newest_anchor().clone();
6636 let cursor_position = newest_selection.head();
6637 let (cursor_buffer, cursor_buffer_position) =
6638 buffer.text_anchor_for_position(cursor_position, cx)?;
6639 let (tail_buffer, tail_buffer_position) =
6640 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6641 if cursor_buffer != tail_buffer {
6642 return None;
6643 }
6644
6645 let snapshot = cursor_buffer.read(cx).snapshot();
6646 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, false);
6647 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, false);
6648 if start_word_range != end_word_range {
6649 self.document_highlights_task.take();
6650 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6651 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6652 return None;
6653 }
6654
6655 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6656 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6657 cx.background_executor()
6658 .timer(Duration::from_millis(debounce))
6659 .await;
6660
6661 let highlights = if let Some(highlights) = cx
6662 .update(|cx| {
6663 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6664 })
6665 .ok()
6666 .flatten()
6667 {
6668 highlights.await.log_err()
6669 } else {
6670 None
6671 };
6672
6673 if let Some(highlights) = highlights {
6674 this.update(cx, |this, cx| {
6675 if this.pending_rename.is_some() {
6676 return;
6677 }
6678
6679 let buffer_id = cursor_position.buffer_id;
6680 let buffer = this.buffer.read(cx);
6681 if !buffer
6682 .text_anchor_for_position(cursor_position, cx)
6683 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
6684 {
6685 return;
6686 }
6687
6688 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6689 let mut write_ranges = Vec::new();
6690 let mut read_ranges = Vec::new();
6691 for highlight in highlights {
6692 for (excerpt_id, excerpt_range) in
6693 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
6694 {
6695 let start = highlight
6696 .range
6697 .start
6698 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6699 let end = highlight
6700 .range
6701 .end
6702 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6703 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6704 continue;
6705 }
6706
6707 let range = Anchor {
6708 buffer_id,
6709 excerpt_id,
6710 text_anchor: start,
6711 diff_base_anchor: None,
6712 }..Anchor {
6713 buffer_id,
6714 excerpt_id,
6715 text_anchor: end,
6716 diff_base_anchor: None,
6717 };
6718 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6719 write_ranges.push(range);
6720 } else {
6721 read_ranges.push(range);
6722 }
6723 }
6724 }
6725
6726 this.highlight_background::<DocumentHighlightRead>(
6727 &read_ranges,
6728 |theme| theme.colors().editor_document_highlight_read_background,
6729 cx,
6730 );
6731 this.highlight_background::<DocumentHighlightWrite>(
6732 &write_ranges,
6733 |theme| theme.colors().editor_document_highlight_write_background,
6734 cx,
6735 );
6736 cx.notify();
6737 })
6738 .log_err();
6739 }
6740 }));
6741 None
6742 }
6743
6744 fn prepare_highlight_query_from_selection(
6745 &mut self,
6746 cx: &mut Context<Editor>,
6747 ) -> Option<(String, Range<Anchor>)> {
6748 if matches!(self.mode, EditorMode::SingleLine { .. }) {
6749 return None;
6750 }
6751 if !EditorSettings::get_global(cx).selection_highlight {
6752 return None;
6753 }
6754 if self.selections.count() != 1 || self.selections.line_mode {
6755 return None;
6756 }
6757 let selection = self.selections.newest::<Point>(cx);
6758 if selection.is_empty() || selection.start.row != selection.end.row {
6759 return None;
6760 }
6761 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6762 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6763 let query = multi_buffer_snapshot
6764 .text_for_range(selection_anchor_range.clone())
6765 .collect::<String>();
6766 if query.trim().is_empty() {
6767 return None;
6768 }
6769 Some((query, selection_anchor_range))
6770 }
6771
6772 fn update_selection_occurrence_highlights(
6773 &mut self,
6774 query_text: String,
6775 query_range: Range<Anchor>,
6776 multi_buffer_range_to_query: Range<Point>,
6777 use_debounce: bool,
6778 window: &mut Window,
6779 cx: &mut Context<Editor>,
6780 ) -> Task<()> {
6781 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6782 cx.spawn_in(window, async move |editor, cx| {
6783 if use_debounce {
6784 cx.background_executor()
6785 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6786 .await;
6787 }
6788 let match_task = cx.background_spawn(async move {
6789 let buffer_ranges = multi_buffer_snapshot
6790 .range_to_buffer_ranges(multi_buffer_range_to_query)
6791 .into_iter()
6792 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6793 let mut match_ranges = Vec::new();
6794 let Ok(regex) = project::search::SearchQuery::text(
6795 query_text.clone(),
6796 false,
6797 false,
6798 false,
6799 Default::default(),
6800 Default::default(),
6801 false,
6802 None,
6803 ) else {
6804 return Vec::default();
6805 };
6806 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6807 match_ranges.extend(
6808 regex
6809 .search(&buffer_snapshot, Some(search_range.clone()))
6810 .await
6811 .into_iter()
6812 .filter_map(|match_range| {
6813 let match_start = buffer_snapshot
6814 .anchor_after(search_range.start + match_range.start);
6815 let match_end = buffer_snapshot
6816 .anchor_before(search_range.start + match_range.end);
6817 let match_anchor_range = Anchor::range_in_buffer(
6818 excerpt_id,
6819 buffer_snapshot.remote_id(),
6820 match_start..match_end,
6821 );
6822 (match_anchor_range != query_range).then_some(match_anchor_range)
6823 }),
6824 );
6825 }
6826 match_ranges
6827 });
6828 let match_ranges = match_task.await;
6829 editor
6830 .update_in(cx, |editor, _, cx| {
6831 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6832 if !match_ranges.is_empty() {
6833 editor.highlight_background::<SelectedTextHighlight>(
6834 &match_ranges,
6835 |theme| theme.colors().editor_document_highlight_bracket_background,
6836 cx,
6837 )
6838 }
6839 })
6840 .log_err();
6841 })
6842 }
6843
6844 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
6845 struct NewlineFold;
6846 let type_id = std::any::TypeId::of::<NewlineFold>();
6847 if !self.mode.is_single_line() {
6848 return;
6849 }
6850 let snapshot = self.snapshot(window, cx);
6851 if snapshot.buffer_snapshot.max_point().row == 0 {
6852 return;
6853 }
6854 let task = cx.background_spawn(async move {
6855 let new_newlines = snapshot
6856 .buffer_chars_at(0)
6857 .filter_map(|(c, i)| {
6858 if c == '\n' {
6859 Some(
6860 snapshot.buffer_snapshot.anchor_after(i)
6861 ..snapshot.buffer_snapshot.anchor_before(i + 1),
6862 )
6863 } else {
6864 None
6865 }
6866 })
6867 .collect::<Vec<_>>();
6868 let existing_newlines = snapshot
6869 .folds_in_range(0..snapshot.buffer_snapshot.len())
6870 .filter_map(|fold| {
6871 if fold.placeholder.type_tag == Some(type_id) {
6872 Some(fold.range.start..fold.range.end)
6873 } else {
6874 None
6875 }
6876 })
6877 .collect::<Vec<_>>();
6878
6879 (new_newlines, existing_newlines)
6880 });
6881 self.folding_newlines = cx.spawn(async move |this, cx| {
6882 let (new_newlines, existing_newlines) = task.await;
6883 if new_newlines == existing_newlines {
6884 return;
6885 }
6886 let placeholder = FoldPlaceholder {
6887 render: Arc::new(move |_, _, cx| {
6888 div()
6889 .bg(cx.theme().status().hint_background)
6890 .border_b_1()
6891 .size_full()
6892 .font(ThemeSettings::get_global(cx).buffer_font.clone())
6893 .border_color(cx.theme().status().hint)
6894 .child("\\n")
6895 .into_any()
6896 }),
6897 constrain_width: false,
6898 merge_adjacent: false,
6899 type_tag: Some(type_id),
6900 };
6901 let creases = new_newlines
6902 .into_iter()
6903 .map(|range| Crease::simple(range, placeholder.clone()))
6904 .collect();
6905 this.update(cx, |this, cx| {
6906 this.display_map.update(cx, |display_map, cx| {
6907 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
6908 display_map.fold(creases, cx);
6909 });
6910 })
6911 .ok();
6912 });
6913 }
6914
6915 fn refresh_selected_text_highlights(
6916 &mut self,
6917 on_buffer_edit: bool,
6918 window: &mut Window,
6919 cx: &mut Context<Editor>,
6920 ) {
6921 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
6922 else {
6923 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6924 self.quick_selection_highlight_task.take();
6925 self.debounced_selection_highlight_task.take();
6926 return;
6927 };
6928 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6929 if on_buffer_edit
6930 || self
6931 .quick_selection_highlight_task
6932 .as_ref()
6933 .map_or(true, |(prev_anchor_range, _)| {
6934 prev_anchor_range != &query_range
6935 })
6936 {
6937 let multi_buffer_visible_start = self
6938 .scroll_manager
6939 .anchor()
6940 .anchor
6941 .to_point(&multi_buffer_snapshot);
6942 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
6943 multi_buffer_visible_start
6944 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
6945 Bias::Left,
6946 );
6947 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
6948 self.quick_selection_highlight_task = Some((
6949 query_range.clone(),
6950 self.update_selection_occurrence_highlights(
6951 query_text.clone(),
6952 query_range.clone(),
6953 multi_buffer_visible_range,
6954 false,
6955 window,
6956 cx,
6957 ),
6958 ));
6959 }
6960 if on_buffer_edit
6961 || self
6962 .debounced_selection_highlight_task
6963 .as_ref()
6964 .map_or(true, |(prev_anchor_range, _)| {
6965 prev_anchor_range != &query_range
6966 })
6967 {
6968 let multi_buffer_start = multi_buffer_snapshot
6969 .anchor_before(0)
6970 .to_point(&multi_buffer_snapshot);
6971 let multi_buffer_end = multi_buffer_snapshot
6972 .anchor_after(multi_buffer_snapshot.len())
6973 .to_point(&multi_buffer_snapshot);
6974 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
6975 self.debounced_selection_highlight_task = Some((
6976 query_range.clone(),
6977 self.update_selection_occurrence_highlights(
6978 query_text,
6979 query_range,
6980 multi_buffer_full_range,
6981 true,
6982 window,
6983 cx,
6984 ),
6985 ));
6986 }
6987 }
6988
6989 pub fn refresh_inline_completion(
6990 &mut self,
6991 debounce: bool,
6992 user_requested: bool,
6993 window: &mut Window,
6994 cx: &mut Context<Self>,
6995 ) -> Option<()> {
6996 let provider = self.edit_prediction_provider()?;
6997 let cursor = self.selections.newest_anchor().head();
6998 let (buffer, cursor_buffer_position) =
6999 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7000
7001 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7002 self.discard_inline_completion(false, cx);
7003 return None;
7004 }
7005
7006 if !user_requested
7007 && (!self.should_show_edit_predictions()
7008 || !self.is_focused(window)
7009 || buffer.read(cx).is_empty())
7010 {
7011 self.discard_inline_completion(false, cx);
7012 return None;
7013 }
7014
7015 self.update_visible_inline_completion(window, cx);
7016 provider.refresh(
7017 self.project.clone(),
7018 buffer,
7019 cursor_buffer_position,
7020 debounce,
7021 cx,
7022 );
7023 Some(())
7024 }
7025
7026 fn show_edit_predictions_in_menu(&self) -> bool {
7027 match self.edit_prediction_settings {
7028 EditPredictionSettings::Disabled => false,
7029 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7030 }
7031 }
7032
7033 pub fn edit_predictions_enabled(&self) -> bool {
7034 match self.edit_prediction_settings {
7035 EditPredictionSettings::Disabled => false,
7036 EditPredictionSettings::Enabled { .. } => true,
7037 }
7038 }
7039
7040 fn edit_prediction_requires_modifier(&self) -> bool {
7041 match self.edit_prediction_settings {
7042 EditPredictionSettings::Disabled => false,
7043 EditPredictionSettings::Enabled {
7044 preview_requires_modifier,
7045 ..
7046 } => preview_requires_modifier,
7047 }
7048 }
7049
7050 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7051 if self.edit_prediction_provider.is_none() {
7052 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7053 } else {
7054 let selection = self.selections.newest_anchor();
7055 let cursor = selection.head();
7056
7057 if let Some((buffer, cursor_buffer_position)) =
7058 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7059 {
7060 self.edit_prediction_settings =
7061 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7062 }
7063 }
7064 }
7065
7066 fn edit_prediction_settings_at_position(
7067 &self,
7068 buffer: &Entity<Buffer>,
7069 buffer_position: language::Anchor,
7070 cx: &App,
7071 ) -> EditPredictionSettings {
7072 if !self.mode.is_full()
7073 || !self.show_inline_completions_override.unwrap_or(true)
7074 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
7075 {
7076 return EditPredictionSettings::Disabled;
7077 }
7078
7079 let buffer = buffer.read(cx);
7080
7081 let file = buffer.file();
7082
7083 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7084 return EditPredictionSettings::Disabled;
7085 };
7086
7087 let by_provider = matches!(
7088 self.menu_inline_completions_policy,
7089 MenuInlineCompletionsPolicy::ByProvider
7090 );
7091
7092 let show_in_menu = by_provider
7093 && self
7094 .edit_prediction_provider
7095 .as_ref()
7096 .map_or(false, |provider| {
7097 provider.provider.show_completions_in_menu()
7098 });
7099
7100 let preview_requires_modifier =
7101 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7102
7103 EditPredictionSettings::Enabled {
7104 show_in_menu,
7105 preview_requires_modifier,
7106 }
7107 }
7108
7109 fn should_show_edit_predictions(&self) -> bool {
7110 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7111 }
7112
7113 pub fn edit_prediction_preview_is_active(&self) -> bool {
7114 matches!(
7115 self.edit_prediction_preview,
7116 EditPredictionPreview::Active { .. }
7117 )
7118 }
7119
7120 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7121 let cursor = self.selections.newest_anchor().head();
7122 if let Some((buffer, cursor_position)) =
7123 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7124 {
7125 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7126 } else {
7127 false
7128 }
7129 }
7130
7131 pub fn supports_minimap(&self, cx: &App) -> bool {
7132 !self.minimap_visibility.disabled() && self.is_singleton(cx)
7133 }
7134
7135 fn edit_predictions_enabled_in_buffer(
7136 &self,
7137 buffer: &Entity<Buffer>,
7138 buffer_position: language::Anchor,
7139 cx: &App,
7140 ) -> bool {
7141 maybe!({
7142 if self.read_only(cx) {
7143 return Some(false);
7144 }
7145 let provider = self.edit_prediction_provider()?;
7146 if !provider.is_enabled(&buffer, buffer_position, cx) {
7147 return Some(false);
7148 }
7149 let buffer = buffer.read(cx);
7150 let Some(file) = buffer.file() else {
7151 return Some(true);
7152 };
7153 let settings = all_language_settings(Some(file), cx);
7154 Some(settings.edit_predictions_enabled_for_file(file, cx))
7155 })
7156 .unwrap_or(false)
7157 }
7158
7159 fn cycle_inline_completion(
7160 &mut self,
7161 direction: Direction,
7162 window: &mut Window,
7163 cx: &mut Context<Self>,
7164 ) -> Option<()> {
7165 let provider = self.edit_prediction_provider()?;
7166 let cursor = self.selections.newest_anchor().head();
7167 let (buffer, cursor_buffer_position) =
7168 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7169 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7170 return None;
7171 }
7172
7173 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7174 self.update_visible_inline_completion(window, cx);
7175
7176 Some(())
7177 }
7178
7179 pub fn show_inline_completion(
7180 &mut self,
7181 _: &ShowEditPrediction,
7182 window: &mut Window,
7183 cx: &mut Context<Self>,
7184 ) {
7185 if !self.has_active_inline_completion() {
7186 self.refresh_inline_completion(false, true, window, cx);
7187 return;
7188 }
7189
7190 self.update_visible_inline_completion(window, cx);
7191 }
7192
7193 pub fn display_cursor_names(
7194 &mut self,
7195 _: &DisplayCursorNames,
7196 window: &mut Window,
7197 cx: &mut Context<Self>,
7198 ) {
7199 self.show_cursor_names(window, cx);
7200 }
7201
7202 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7203 self.show_cursor_names = true;
7204 cx.notify();
7205 cx.spawn_in(window, async move |this, cx| {
7206 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7207 this.update(cx, |this, cx| {
7208 this.show_cursor_names = false;
7209 cx.notify()
7210 })
7211 .ok()
7212 })
7213 .detach();
7214 }
7215
7216 pub fn next_edit_prediction(
7217 &mut self,
7218 _: &NextEditPrediction,
7219 window: &mut Window,
7220 cx: &mut Context<Self>,
7221 ) {
7222 if self.has_active_inline_completion() {
7223 self.cycle_inline_completion(Direction::Next, window, cx);
7224 } else {
7225 let is_copilot_disabled = self
7226 .refresh_inline_completion(false, true, window, cx)
7227 .is_none();
7228 if is_copilot_disabled {
7229 cx.propagate();
7230 }
7231 }
7232 }
7233
7234 pub fn previous_edit_prediction(
7235 &mut self,
7236 _: &PreviousEditPrediction,
7237 window: &mut Window,
7238 cx: &mut Context<Self>,
7239 ) {
7240 if self.has_active_inline_completion() {
7241 self.cycle_inline_completion(Direction::Prev, window, cx);
7242 } else {
7243 let is_copilot_disabled = self
7244 .refresh_inline_completion(false, true, window, cx)
7245 .is_none();
7246 if is_copilot_disabled {
7247 cx.propagate();
7248 }
7249 }
7250 }
7251
7252 pub fn accept_edit_prediction(
7253 &mut self,
7254 _: &AcceptEditPrediction,
7255 window: &mut Window,
7256 cx: &mut Context<Self>,
7257 ) {
7258 if self.show_edit_predictions_in_menu() {
7259 self.hide_context_menu(window, cx);
7260 }
7261
7262 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
7263 return;
7264 };
7265
7266 self.report_inline_completion_event(
7267 active_inline_completion.completion_id.clone(),
7268 true,
7269 cx,
7270 );
7271
7272 match &active_inline_completion.completion {
7273 InlineCompletion::Move { target, .. } => {
7274 let target = *target;
7275
7276 if let Some(position_map) = &self.last_position_map {
7277 if position_map
7278 .visible_row_range
7279 .contains(&target.to_display_point(&position_map.snapshot).row())
7280 || !self.edit_prediction_requires_modifier()
7281 {
7282 self.unfold_ranges(&[target..target], true, false, cx);
7283 // Note that this is also done in vim's handler of the Tab action.
7284 self.change_selections(
7285 SelectionEffects::scroll(Autoscroll::newest()),
7286 window,
7287 cx,
7288 |selections| {
7289 selections.select_anchor_ranges([target..target]);
7290 },
7291 );
7292 self.clear_row_highlights::<EditPredictionPreview>();
7293
7294 self.edit_prediction_preview
7295 .set_previous_scroll_position(None);
7296 } else {
7297 self.edit_prediction_preview
7298 .set_previous_scroll_position(Some(
7299 position_map.snapshot.scroll_anchor,
7300 ));
7301
7302 self.highlight_rows::<EditPredictionPreview>(
7303 target..target,
7304 cx.theme().colors().editor_highlighted_line_background,
7305 RowHighlightOptions {
7306 autoscroll: true,
7307 ..Default::default()
7308 },
7309 cx,
7310 );
7311 self.request_autoscroll(Autoscroll::fit(), cx);
7312 }
7313 }
7314 }
7315 InlineCompletion::Edit { edits, .. } => {
7316 if let Some(provider) = self.edit_prediction_provider() {
7317 provider.accept(cx);
7318 }
7319
7320 // Store the transaction ID and selections before applying the edit
7321 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7322
7323 let snapshot = self.buffer.read(cx).snapshot(cx);
7324 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7325
7326 self.buffer.update(cx, |buffer, cx| {
7327 buffer.edit(edits.iter().cloned(), None, cx)
7328 });
7329
7330 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7331 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7332 });
7333
7334 let selections = self.selections.disjoint_anchors();
7335 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7336 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7337 if has_new_transaction {
7338 self.selection_history
7339 .insert_transaction(transaction_id_now, selections);
7340 }
7341 }
7342
7343 self.update_visible_inline_completion(window, cx);
7344 if self.active_inline_completion.is_none() {
7345 self.refresh_inline_completion(true, true, window, cx);
7346 }
7347
7348 cx.notify();
7349 }
7350 }
7351
7352 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7353 }
7354
7355 pub fn accept_partial_inline_completion(
7356 &mut self,
7357 _: &AcceptPartialEditPrediction,
7358 window: &mut Window,
7359 cx: &mut Context<Self>,
7360 ) {
7361 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
7362 return;
7363 };
7364 if self.selections.count() != 1 {
7365 return;
7366 }
7367
7368 self.report_inline_completion_event(
7369 active_inline_completion.completion_id.clone(),
7370 true,
7371 cx,
7372 );
7373
7374 match &active_inline_completion.completion {
7375 InlineCompletion::Move { target, .. } => {
7376 let target = *target;
7377 self.change_selections(
7378 SelectionEffects::scroll(Autoscroll::newest()),
7379 window,
7380 cx,
7381 |selections| {
7382 selections.select_anchor_ranges([target..target]);
7383 },
7384 );
7385 }
7386 InlineCompletion::Edit { edits, .. } => {
7387 // Find an insertion that starts at the cursor position.
7388 let snapshot = self.buffer.read(cx).snapshot(cx);
7389 let cursor_offset = self.selections.newest::<usize>(cx).head();
7390 let insertion = edits.iter().find_map(|(range, text)| {
7391 let range = range.to_offset(&snapshot);
7392 if range.is_empty() && range.start == cursor_offset {
7393 Some(text)
7394 } else {
7395 None
7396 }
7397 });
7398
7399 if let Some(text) = insertion {
7400 let mut partial_completion = text
7401 .chars()
7402 .by_ref()
7403 .take_while(|c| c.is_alphabetic())
7404 .collect::<String>();
7405 if partial_completion.is_empty() {
7406 partial_completion = text
7407 .chars()
7408 .by_ref()
7409 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7410 .collect::<String>();
7411 }
7412
7413 cx.emit(EditorEvent::InputHandled {
7414 utf16_range_to_replace: None,
7415 text: partial_completion.clone().into(),
7416 });
7417
7418 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7419
7420 self.refresh_inline_completion(true, true, window, cx);
7421 cx.notify();
7422 } else {
7423 self.accept_edit_prediction(&Default::default(), window, cx);
7424 }
7425 }
7426 }
7427 }
7428
7429 fn discard_inline_completion(
7430 &mut self,
7431 should_report_inline_completion_event: bool,
7432 cx: &mut Context<Self>,
7433 ) -> bool {
7434 if should_report_inline_completion_event {
7435 let completion_id = self
7436 .active_inline_completion
7437 .as_ref()
7438 .and_then(|active_completion| active_completion.completion_id.clone());
7439
7440 self.report_inline_completion_event(completion_id, false, cx);
7441 }
7442
7443 if let Some(provider) = self.edit_prediction_provider() {
7444 provider.discard(cx);
7445 }
7446
7447 self.take_active_inline_completion(cx)
7448 }
7449
7450 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7451 let Some(provider) = self.edit_prediction_provider() else {
7452 return;
7453 };
7454
7455 let Some((_, buffer, _)) = self
7456 .buffer
7457 .read(cx)
7458 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7459 else {
7460 return;
7461 };
7462
7463 let extension = buffer
7464 .read(cx)
7465 .file()
7466 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
7467
7468 let event_type = match accepted {
7469 true => "Edit Prediction Accepted",
7470 false => "Edit Prediction Discarded",
7471 };
7472 telemetry::event!(
7473 event_type,
7474 provider = provider.name(),
7475 prediction_id = id,
7476 suggestion_accepted = accepted,
7477 file_extension = extension,
7478 );
7479 }
7480
7481 pub fn has_active_inline_completion(&self) -> bool {
7482 self.active_inline_completion.is_some()
7483 }
7484
7485 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
7486 let Some(active_inline_completion) = self.active_inline_completion.take() else {
7487 return false;
7488 };
7489
7490 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
7491 self.clear_highlights::<InlineCompletionHighlight>(cx);
7492 self.stale_inline_completion_in_menu = Some(active_inline_completion);
7493 true
7494 }
7495
7496 /// Returns true when we're displaying the edit prediction popover below the cursor
7497 /// like we are not previewing and the LSP autocomplete menu is visible
7498 /// or we are in `when_holding_modifier` mode.
7499 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7500 if self.edit_prediction_preview_is_active()
7501 || !self.show_edit_predictions_in_menu()
7502 || !self.edit_predictions_enabled()
7503 {
7504 return false;
7505 }
7506
7507 if self.has_visible_completions_menu() {
7508 return true;
7509 }
7510
7511 has_completion && self.edit_prediction_requires_modifier()
7512 }
7513
7514 fn handle_modifiers_changed(
7515 &mut self,
7516 modifiers: Modifiers,
7517 position_map: &PositionMap,
7518 window: &mut Window,
7519 cx: &mut Context<Self>,
7520 ) {
7521 if self.show_edit_predictions_in_menu() {
7522 self.update_edit_prediction_preview(&modifiers, window, cx);
7523 }
7524
7525 self.update_selection_mode(&modifiers, position_map, window, cx);
7526
7527 let mouse_position = window.mouse_position();
7528 if !position_map.text_hitbox.is_hovered(window) {
7529 return;
7530 }
7531
7532 self.update_hovered_link(
7533 position_map.point_for_position(mouse_position),
7534 &position_map.snapshot,
7535 modifiers,
7536 window,
7537 cx,
7538 )
7539 }
7540
7541 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7542 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7543 if invert {
7544 match multi_cursor_setting {
7545 MultiCursorModifier::Alt => modifiers.alt,
7546 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7547 }
7548 } else {
7549 match multi_cursor_setting {
7550 MultiCursorModifier::Alt => modifiers.secondary(),
7551 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7552 }
7553 }
7554 }
7555
7556 fn columnar_selection_mode(
7557 modifiers: &Modifiers,
7558 cx: &mut Context<Self>,
7559 ) -> Option<ColumnarMode> {
7560 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7561 if Self::multi_cursor_modifier(false, modifiers, cx) {
7562 Some(ColumnarMode::FromMouse)
7563 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7564 Some(ColumnarMode::FromSelection)
7565 } else {
7566 None
7567 }
7568 } else {
7569 None
7570 }
7571 }
7572
7573 fn update_selection_mode(
7574 &mut self,
7575 modifiers: &Modifiers,
7576 position_map: &PositionMap,
7577 window: &mut Window,
7578 cx: &mut Context<Self>,
7579 ) {
7580 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7581 return;
7582 };
7583 if self.selections.pending.is_none() {
7584 return;
7585 }
7586
7587 let mouse_position = window.mouse_position();
7588 let point_for_position = position_map.point_for_position(mouse_position);
7589 let position = point_for_position.previous_valid;
7590
7591 self.select(
7592 SelectPhase::BeginColumnar {
7593 position,
7594 reset: false,
7595 mode,
7596 goal_column: point_for_position.exact_unclipped.column(),
7597 },
7598 window,
7599 cx,
7600 );
7601 }
7602
7603 fn update_edit_prediction_preview(
7604 &mut self,
7605 modifiers: &Modifiers,
7606 window: &mut Window,
7607 cx: &mut Context<Self>,
7608 ) {
7609 let mut modifiers_held = false;
7610 if let Some(accept_keystroke) = self
7611 .accept_edit_prediction_keybind(false, window, cx)
7612 .keystroke()
7613 {
7614 modifiers_held = modifiers_held
7615 || (&accept_keystroke.modifiers == modifiers
7616 && accept_keystroke.modifiers.modified());
7617 };
7618 if let Some(accept_partial_keystroke) = self
7619 .accept_edit_prediction_keybind(true, window, cx)
7620 .keystroke()
7621 {
7622 modifiers_held = modifiers_held
7623 || (&accept_partial_keystroke.modifiers == modifiers
7624 && accept_partial_keystroke.modifiers.modified());
7625 }
7626
7627 if modifiers_held {
7628 if matches!(
7629 self.edit_prediction_preview,
7630 EditPredictionPreview::Inactive { .. }
7631 ) {
7632 self.edit_prediction_preview = EditPredictionPreview::Active {
7633 previous_scroll_position: None,
7634 since: Instant::now(),
7635 };
7636
7637 self.update_visible_inline_completion(window, cx);
7638 cx.notify();
7639 }
7640 } else if let EditPredictionPreview::Active {
7641 previous_scroll_position,
7642 since,
7643 } = self.edit_prediction_preview
7644 {
7645 if let (Some(previous_scroll_position), Some(position_map)) =
7646 (previous_scroll_position, self.last_position_map.as_ref())
7647 {
7648 self.set_scroll_position(
7649 previous_scroll_position
7650 .scroll_position(&position_map.snapshot.display_snapshot),
7651 window,
7652 cx,
7653 );
7654 }
7655
7656 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7657 released_too_fast: since.elapsed() < Duration::from_millis(200),
7658 };
7659 self.clear_row_highlights::<EditPredictionPreview>();
7660 self.update_visible_inline_completion(window, cx);
7661 cx.notify();
7662 }
7663 }
7664
7665 fn update_visible_inline_completion(
7666 &mut self,
7667 _window: &mut Window,
7668 cx: &mut Context<Self>,
7669 ) -> Option<()> {
7670 let selection = self.selections.newest_anchor();
7671 let cursor = selection.head();
7672 let multibuffer = self.buffer.read(cx).snapshot(cx);
7673 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7674 let excerpt_id = cursor.excerpt_id;
7675
7676 let show_in_menu = self.show_edit_predictions_in_menu();
7677 let completions_menu_has_precedence = !show_in_menu
7678 && (self.context_menu.borrow().is_some()
7679 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
7680
7681 if completions_menu_has_precedence
7682 || !offset_selection.is_empty()
7683 || self
7684 .active_inline_completion
7685 .as_ref()
7686 .map_or(false, |completion| {
7687 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
7688 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7689 !invalidation_range.contains(&offset_selection.head())
7690 })
7691 {
7692 self.discard_inline_completion(false, cx);
7693 return None;
7694 }
7695
7696 self.take_active_inline_completion(cx);
7697 let Some(provider) = self.edit_prediction_provider() else {
7698 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7699 return None;
7700 };
7701
7702 let (buffer, cursor_buffer_position) =
7703 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7704
7705 self.edit_prediction_settings =
7706 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7707
7708 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7709
7710 if self.edit_prediction_indent_conflict {
7711 let cursor_point = cursor.to_point(&multibuffer);
7712
7713 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7714
7715 if let Some((_, indent)) = indents.iter().next() {
7716 if indent.len == cursor_point.column {
7717 self.edit_prediction_indent_conflict = false;
7718 }
7719 }
7720 }
7721
7722 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7723 let edits = inline_completion
7724 .edits
7725 .into_iter()
7726 .flat_map(|(range, new_text)| {
7727 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7728 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7729 Some((start..end, new_text))
7730 })
7731 .collect::<Vec<_>>();
7732 if edits.is_empty() {
7733 return None;
7734 }
7735
7736 let first_edit_start = edits.first().unwrap().0.start;
7737 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7738 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7739
7740 let last_edit_end = edits.last().unwrap().0.end;
7741 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7742 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7743
7744 let cursor_row = cursor.to_point(&multibuffer).row;
7745
7746 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7747
7748 let mut inlay_ids = Vec::new();
7749 let invalidation_row_range;
7750 let move_invalidation_row_range = if cursor_row < edit_start_row {
7751 Some(cursor_row..edit_end_row)
7752 } else if cursor_row > edit_end_row {
7753 Some(edit_start_row..cursor_row)
7754 } else {
7755 None
7756 };
7757 let is_move =
7758 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
7759 let completion = if is_move {
7760 invalidation_row_range =
7761 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7762 let target = first_edit_start;
7763 InlineCompletion::Move { target, snapshot }
7764 } else {
7765 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7766 && !self.inline_completions_hidden_for_vim_mode;
7767
7768 if show_completions_in_buffer {
7769 if edits
7770 .iter()
7771 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7772 {
7773 let mut inlays = Vec::new();
7774 for (range, new_text) in &edits {
7775 let inlay = Inlay::inline_completion(
7776 post_inc(&mut self.next_inlay_id),
7777 range.start,
7778 new_text.as_str(),
7779 );
7780 inlay_ids.push(inlay.id);
7781 inlays.push(inlay);
7782 }
7783
7784 self.splice_inlays(&[], inlays, cx);
7785 } else {
7786 let background_color = cx.theme().status().deleted_background;
7787 self.highlight_text::<InlineCompletionHighlight>(
7788 edits.iter().map(|(range, _)| range.clone()).collect(),
7789 HighlightStyle {
7790 background_color: Some(background_color),
7791 ..Default::default()
7792 },
7793 cx,
7794 );
7795 }
7796 }
7797
7798 invalidation_row_range = edit_start_row..edit_end_row;
7799
7800 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7801 if provider.show_tab_accept_marker() {
7802 EditDisplayMode::TabAccept
7803 } else {
7804 EditDisplayMode::Inline
7805 }
7806 } else {
7807 EditDisplayMode::DiffPopover
7808 };
7809
7810 InlineCompletion::Edit {
7811 edits,
7812 edit_preview: inline_completion.edit_preview,
7813 display_mode,
7814 snapshot,
7815 }
7816 };
7817
7818 let invalidation_range = multibuffer
7819 .anchor_before(Point::new(invalidation_row_range.start, 0))
7820 ..multibuffer.anchor_after(Point::new(
7821 invalidation_row_range.end,
7822 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7823 ));
7824
7825 self.stale_inline_completion_in_menu = None;
7826 self.active_inline_completion = Some(InlineCompletionState {
7827 inlay_ids,
7828 completion,
7829 completion_id: inline_completion.id,
7830 invalidation_range,
7831 });
7832
7833 cx.notify();
7834
7835 Some(())
7836 }
7837
7838 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
7839 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7840 }
7841
7842 fn clear_tasks(&mut self) {
7843 self.tasks.clear()
7844 }
7845
7846 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7847 if self.tasks.insert(key, value).is_some() {
7848 // This case should hopefully be rare, but just in case...
7849 log::error!(
7850 "multiple different run targets found on a single line, only the last target will be rendered"
7851 )
7852 }
7853 }
7854
7855 /// Get all display points of breakpoints that will be rendered within editor
7856 ///
7857 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7858 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7859 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7860 fn active_breakpoints(
7861 &self,
7862 range: Range<DisplayRow>,
7863 window: &mut Window,
7864 cx: &mut Context<Self>,
7865 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7866 let mut breakpoint_display_points = HashMap::default();
7867
7868 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7869 return breakpoint_display_points;
7870 };
7871
7872 let snapshot = self.snapshot(window, cx);
7873
7874 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
7875 let Some(project) = self.project.as_ref() else {
7876 return breakpoint_display_points;
7877 };
7878
7879 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7880 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7881
7882 for (buffer_snapshot, range, excerpt_id) in
7883 multi_buffer_snapshot.range_to_buffer_ranges(range)
7884 {
7885 let Some(buffer) = project
7886 .read(cx)
7887 .buffer_for_id(buffer_snapshot.remote_id(), cx)
7888 else {
7889 continue;
7890 };
7891 let breakpoints = breakpoint_store.read(cx).breakpoints(
7892 &buffer,
7893 Some(
7894 buffer_snapshot.anchor_before(range.start)
7895 ..buffer_snapshot.anchor_after(range.end),
7896 ),
7897 buffer_snapshot,
7898 cx,
7899 );
7900 for (breakpoint, state) in breakpoints {
7901 let multi_buffer_anchor =
7902 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
7903 let position = multi_buffer_anchor
7904 .to_point(&multi_buffer_snapshot)
7905 .to_display_point(&snapshot);
7906
7907 breakpoint_display_points.insert(
7908 position.row(),
7909 (multi_buffer_anchor, breakpoint.bp.clone(), state),
7910 );
7911 }
7912 }
7913
7914 breakpoint_display_points
7915 }
7916
7917 fn breakpoint_context_menu(
7918 &self,
7919 anchor: Anchor,
7920 window: &mut Window,
7921 cx: &mut Context<Self>,
7922 ) -> Entity<ui::ContextMenu> {
7923 let weak_editor = cx.weak_entity();
7924 let focus_handle = self.focus_handle(cx);
7925
7926 let row = self
7927 .buffer
7928 .read(cx)
7929 .snapshot(cx)
7930 .summary_for_anchor::<Point>(&anchor)
7931 .row;
7932
7933 let breakpoint = self
7934 .breakpoint_at_row(row, window, cx)
7935 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
7936
7937 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
7938 "Edit Log Breakpoint"
7939 } else {
7940 "Set Log Breakpoint"
7941 };
7942
7943 let condition_breakpoint_msg = if breakpoint
7944 .as_ref()
7945 .is_some_and(|bp| bp.1.condition.is_some())
7946 {
7947 "Edit Condition Breakpoint"
7948 } else {
7949 "Set Condition Breakpoint"
7950 };
7951
7952 let hit_condition_breakpoint_msg = if breakpoint
7953 .as_ref()
7954 .is_some_and(|bp| bp.1.hit_condition.is_some())
7955 {
7956 "Edit Hit Condition Breakpoint"
7957 } else {
7958 "Set Hit Condition Breakpoint"
7959 };
7960
7961 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
7962 "Unset Breakpoint"
7963 } else {
7964 "Set Breakpoint"
7965 };
7966
7967 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
7968
7969 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
7970 BreakpointState::Enabled => Some("Disable"),
7971 BreakpointState::Disabled => Some("Enable"),
7972 });
7973
7974 let (anchor, breakpoint) =
7975 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
7976
7977 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
7978 menu.on_blur_subscription(Subscription::new(|| {}))
7979 .context(focus_handle)
7980 .when(run_to_cursor, |this| {
7981 let weak_editor = weak_editor.clone();
7982 this.entry("Run to cursor", None, move |window, cx| {
7983 weak_editor
7984 .update(cx, |editor, cx| {
7985 editor.change_selections(
7986 SelectionEffects::no_scroll(),
7987 window,
7988 cx,
7989 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
7990 );
7991 })
7992 .ok();
7993
7994 window.dispatch_action(Box::new(RunToCursor), cx);
7995 })
7996 .separator()
7997 })
7998 .when_some(toggle_state_msg, |this, msg| {
7999 this.entry(msg, None, {
8000 let weak_editor = weak_editor.clone();
8001 let breakpoint = breakpoint.clone();
8002 move |_window, cx| {
8003 weak_editor
8004 .update(cx, |this, cx| {
8005 this.edit_breakpoint_at_anchor(
8006 anchor,
8007 breakpoint.as_ref().clone(),
8008 BreakpointEditAction::InvertState,
8009 cx,
8010 );
8011 })
8012 .log_err();
8013 }
8014 })
8015 })
8016 .entry(set_breakpoint_msg, None, {
8017 let weak_editor = weak_editor.clone();
8018 let breakpoint = breakpoint.clone();
8019 move |_window, cx| {
8020 weak_editor
8021 .update(cx, |this, cx| {
8022 this.edit_breakpoint_at_anchor(
8023 anchor,
8024 breakpoint.as_ref().clone(),
8025 BreakpointEditAction::Toggle,
8026 cx,
8027 );
8028 })
8029 .log_err();
8030 }
8031 })
8032 .entry(log_breakpoint_msg, None, {
8033 let breakpoint = breakpoint.clone();
8034 let weak_editor = weak_editor.clone();
8035 move |window, cx| {
8036 weak_editor
8037 .update(cx, |this, cx| {
8038 this.add_edit_breakpoint_block(
8039 anchor,
8040 breakpoint.as_ref(),
8041 BreakpointPromptEditAction::Log,
8042 window,
8043 cx,
8044 );
8045 })
8046 .log_err();
8047 }
8048 })
8049 .entry(condition_breakpoint_msg, None, {
8050 let breakpoint = breakpoint.clone();
8051 let weak_editor = weak_editor.clone();
8052 move |window, cx| {
8053 weak_editor
8054 .update(cx, |this, cx| {
8055 this.add_edit_breakpoint_block(
8056 anchor,
8057 breakpoint.as_ref(),
8058 BreakpointPromptEditAction::Condition,
8059 window,
8060 cx,
8061 );
8062 })
8063 .log_err();
8064 }
8065 })
8066 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8067 weak_editor
8068 .update(cx, |this, cx| {
8069 this.add_edit_breakpoint_block(
8070 anchor,
8071 breakpoint.as_ref(),
8072 BreakpointPromptEditAction::HitCondition,
8073 window,
8074 cx,
8075 );
8076 })
8077 .log_err();
8078 })
8079 })
8080 }
8081
8082 fn render_breakpoint(
8083 &self,
8084 position: Anchor,
8085 row: DisplayRow,
8086 breakpoint: &Breakpoint,
8087 state: Option<BreakpointSessionState>,
8088 cx: &mut Context<Self>,
8089 ) -> IconButton {
8090 let is_rejected = state.is_some_and(|s| !s.verified);
8091 // Is it a breakpoint that shows up when hovering over gutter?
8092 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8093 (false, false),
8094 |PhantomBreakpointIndicator {
8095 is_active,
8096 display_row,
8097 collides_with_existing_breakpoint,
8098 }| {
8099 (
8100 is_active && display_row == row,
8101 collides_with_existing_breakpoint,
8102 )
8103 },
8104 );
8105
8106 let (color, icon) = {
8107 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8108 (false, false) => ui::IconName::DebugBreakpoint,
8109 (true, false) => ui::IconName::DebugLogBreakpoint,
8110 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8111 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8112 };
8113
8114 let color = if is_phantom {
8115 Color::Hint
8116 } else if is_rejected {
8117 Color::Disabled
8118 } else {
8119 Color::Debugger
8120 };
8121
8122 (color, icon)
8123 };
8124
8125 let breakpoint = Arc::from(breakpoint.clone());
8126
8127 let alt_as_text = gpui::Keystroke {
8128 modifiers: Modifiers::secondary_key(),
8129 ..Default::default()
8130 };
8131 let primary_action_text = if breakpoint.is_disabled() {
8132 "Enable breakpoint"
8133 } else if is_phantom && !collides_with_existing {
8134 "Set breakpoint"
8135 } else {
8136 "Unset breakpoint"
8137 };
8138 let focus_handle = self.focus_handle.clone();
8139
8140 let meta = if is_rejected {
8141 SharedString::from("No executable code is associated with this line.")
8142 } else if collides_with_existing && !breakpoint.is_disabled() {
8143 SharedString::from(format!(
8144 "{alt_as_text}-click to disable,\nright-click for more options."
8145 ))
8146 } else {
8147 SharedString::from("Right-click for more options.")
8148 };
8149 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8150 .icon_size(IconSize::XSmall)
8151 .size(ui::ButtonSize::None)
8152 .when(is_rejected, |this| {
8153 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8154 })
8155 .icon_color(color)
8156 .style(ButtonStyle::Transparent)
8157 .on_click(cx.listener({
8158 let breakpoint = breakpoint.clone();
8159
8160 move |editor, event: &ClickEvent, window, cx| {
8161 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8162 BreakpointEditAction::InvertState
8163 } else {
8164 BreakpointEditAction::Toggle
8165 };
8166
8167 window.focus(&editor.focus_handle(cx));
8168 editor.edit_breakpoint_at_anchor(
8169 position,
8170 breakpoint.as_ref().clone(),
8171 edit_action,
8172 cx,
8173 );
8174 }
8175 }))
8176 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8177 editor.set_breakpoint_context_menu(
8178 row,
8179 Some(position),
8180 event.down.position,
8181 window,
8182 cx,
8183 );
8184 }))
8185 .tooltip(move |window, cx| {
8186 Tooltip::with_meta_in(
8187 primary_action_text,
8188 Some(&ToggleBreakpoint),
8189 meta.clone(),
8190 &focus_handle,
8191 window,
8192 cx,
8193 )
8194 })
8195 }
8196
8197 fn build_tasks_context(
8198 project: &Entity<Project>,
8199 buffer: &Entity<Buffer>,
8200 buffer_row: u32,
8201 tasks: &Arc<RunnableTasks>,
8202 cx: &mut Context<Self>,
8203 ) -> Task<Option<task::TaskContext>> {
8204 let position = Point::new(buffer_row, tasks.column);
8205 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8206 let location = Location {
8207 buffer: buffer.clone(),
8208 range: range_start..range_start,
8209 };
8210 // Fill in the environmental variables from the tree-sitter captures
8211 let mut captured_task_variables = TaskVariables::default();
8212 for (capture_name, value) in tasks.extra_variables.clone() {
8213 captured_task_variables.insert(
8214 task::VariableName::Custom(capture_name.into()),
8215 value.clone(),
8216 );
8217 }
8218 project.update(cx, |project, cx| {
8219 project.task_store().update(cx, |task_store, cx| {
8220 task_store.task_context_for_location(captured_task_variables, location, cx)
8221 })
8222 })
8223 }
8224
8225 pub fn spawn_nearest_task(
8226 &mut self,
8227 action: &SpawnNearestTask,
8228 window: &mut Window,
8229 cx: &mut Context<Self>,
8230 ) {
8231 let Some((workspace, _)) = self.workspace.clone() else {
8232 return;
8233 };
8234 let Some(project) = self.project.clone() else {
8235 return;
8236 };
8237
8238 // Try to find a closest, enclosing node using tree-sitter that has a
8239 // task
8240 let Some((buffer, buffer_row, tasks)) = self
8241 .find_enclosing_node_task(cx)
8242 // Or find the task that's closest in row-distance.
8243 .or_else(|| self.find_closest_task(cx))
8244 else {
8245 return;
8246 };
8247
8248 let reveal_strategy = action.reveal;
8249 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8250 cx.spawn_in(window, async move |_, cx| {
8251 let context = task_context.await?;
8252 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8253
8254 let resolved = &mut resolved_task.resolved;
8255 resolved.reveal = reveal_strategy;
8256
8257 workspace
8258 .update_in(cx, |workspace, window, cx| {
8259 workspace.schedule_resolved_task(
8260 task_source_kind,
8261 resolved_task,
8262 false,
8263 window,
8264 cx,
8265 );
8266 })
8267 .ok()
8268 })
8269 .detach();
8270 }
8271
8272 fn find_closest_task(
8273 &mut self,
8274 cx: &mut Context<Self>,
8275 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8276 let cursor_row = self.selections.newest_adjusted(cx).head().row;
8277
8278 let ((buffer_id, row), tasks) = self
8279 .tasks
8280 .iter()
8281 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8282
8283 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8284 let tasks = Arc::new(tasks.to_owned());
8285 Some((buffer, *row, tasks))
8286 }
8287
8288 fn find_enclosing_node_task(
8289 &mut self,
8290 cx: &mut Context<Self>,
8291 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8292 let snapshot = self.buffer.read(cx).snapshot(cx);
8293 let offset = self.selections.newest::<usize>(cx).head();
8294 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8295 let buffer_id = excerpt.buffer().remote_id();
8296
8297 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8298 let mut cursor = layer.node().walk();
8299
8300 while cursor.goto_first_child_for_byte(offset).is_some() {
8301 if cursor.node().end_byte() == offset {
8302 cursor.goto_next_sibling();
8303 }
8304 }
8305
8306 // Ascend to the smallest ancestor that contains the range and has a task.
8307 loop {
8308 let node = cursor.node();
8309 let node_range = node.byte_range();
8310 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8311
8312 // Check if this node contains our offset
8313 if node_range.start <= offset && node_range.end >= offset {
8314 // If it contains offset, check for task
8315 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8316 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8317 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8318 }
8319 }
8320
8321 if !cursor.goto_parent() {
8322 break;
8323 }
8324 }
8325 None
8326 }
8327
8328 fn render_run_indicator(
8329 &self,
8330 _style: &EditorStyle,
8331 is_active: bool,
8332 row: DisplayRow,
8333 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8334 cx: &mut Context<Self>,
8335 ) -> IconButton {
8336 let color = Color::Muted;
8337 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8338
8339 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
8340 .shape(ui::IconButtonShape::Square)
8341 .icon_size(IconSize::XSmall)
8342 .icon_color(color)
8343 .toggle_state(is_active)
8344 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8345 let quick_launch = e.down.button == MouseButton::Left;
8346 window.focus(&editor.focus_handle(cx));
8347 editor.toggle_code_actions(
8348 &ToggleCodeActions {
8349 deployed_from: Some(CodeActionSource::RunMenu(row)),
8350 quick_launch,
8351 },
8352 window,
8353 cx,
8354 );
8355 }))
8356 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8357 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
8358 }))
8359 }
8360
8361 pub fn context_menu_visible(&self) -> bool {
8362 !self.edit_prediction_preview_is_active()
8363 && self
8364 .context_menu
8365 .borrow()
8366 .as_ref()
8367 .map_or(false, |menu| menu.visible())
8368 }
8369
8370 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8371 self.context_menu
8372 .borrow()
8373 .as_ref()
8374 .map(|menu| menu.origin())
8375 }
8376
8377 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8378 self.context_menu_options = Some(options);
8379 }
8380
8381 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
8382 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
8383
8384 fn render_edit_prediction_popover(
8385 &mut self,
8386 text_bounds: &Bounds<Pixels>,
8387 content_origin: gpui::Point<Pixels>,
8388 right_margin: Pixels,
8389 editor_snapshot: &EditorSnapshot,
8390 visible_row_range: Range<DisplayRow>,
8391 scroll_top: f32,
8392 scroll_bottom: f32,
8393 line_layouts: &[LineWithInvisibles],
8394 line_height: Pixels,
8395 scroll_pixel_position: gpui::Point<Pixels>,
8396 newest_selection_head: Option<DisplayPoint>,
8397 editor_width: Pixels,
8398 style: &EditorStyle,
8399 window: &mut Window,
8400 cx: &mut App,
8401 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8402 if self.mode().is_minimap() {
8403 return None;
8404 }
8405 let active_inline_completion = self.active_inline_completion.as_ref()?;
8406
8407 if self.edit_prediction_visible_in_cursor_popover(true) {
8408 return None;
8409 }
8410
8411 match &active_inline_completion.completion {
8412 InlineCompletion::Move { target, .. } => {
8413 let target_display_point = target.to_display_point(editor_snapshot);
8414
8415 if self.edit_prediction_requires_modifier() {
8416 if !self.edit_prediction_preview_is_active() {
8417 return None;
8418 }
8419
8420 self.render_edit_prediction_modifier_jump_popover(
8421 text_bounds,
8422 content_origin,
8423 visible_row_range,
8424 line_layouts,
8425 line_height,
8426 scroll_pixel_position,
8427 newest_selection_head,
8428 target_display_point,
8429 window,
8430 cx,
8431 )
8432 } else {
8433 self.render_edit_prediction_eager_jump_popover(
8434 text_bounds,
8435 content_origin,
8436 editor_snapshot,
8437 visible_row_range,
8438 scroll_top,
8439 scroll_bottom,
8440 line_height,
8441 scroll_pixel_position,
8442 target_display_point,
8443 editor_width,
8444 window,
8445 cx,
8446 )
8447 }
8448 }
8449 InlineCompletion::Edit {
8450 display_mode: EditDisplayMode::Inline,
8451 ..
8452 } => None,
8453 InlineCompletion::Edit {
8454 display_mode: EditDisplayMode::TabAccept,
8455 edits,
8456 ..
8457 } => {
8458 let range = &edits.first()?.0;
8459 let target_display_point = range.end.to_display_point(editor_snapshot);
8460
8461 self.render_edit_prediction_end_of_line_popover(
8462 "Accept",
8463 editor_snapshot,
8464 visible_row_range,
8465 target_display_point,
8466 line_height,
8467 scroll_pixel_position,
8468 content_origin,
8469 editor_width,
8470 window,
8471 cx,
8472 )
8473 }
8474 InlineCompletion::Edit {
8475 edits,
8476 edit_preview,
8477 display_mode: EditDisplayMode::DiffPopover,
8478 snapshot,
8479 } => self.render_edit_prediction_diff_popover(
8480 text_bounds,
8481 content_origin,
8482 right_margin,
8483 editor_snapshot,
8484 visible_row_range,
8485 line_layouts,
8486 line_height,
8487 scroll_pixel_position,
8488 newest_selection_head,
8489 editor_width,
8490 style,
8491 edits,
8492 edit_preview,
8493 snapshot,
8494 window,
8495 cx,
8496 ),
8497 }
8498 }
8499
8500 fn render_edit_prediction_modifier_jump_popover(
8501 &mut self,
8502 text_bounds: &Bounds<Pixels>,
8503 content_origin: gpui::Point<Pixels>,
8504 visible_row_range: Range<DisplayRow>,
8505 line_layouts: &[LineWithInvisibles],
8506 line_height: Pixels,
8507 scroll_pixel_position: gpui::Point<Pixels>,
8508 newest_selection_head: Option<DisplayPoint>,
8509 target_display_point: DisplayPoint,
8510 window: &mut Window,
8511 cx: &mut App,
8512 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8513 let scrolled_content_origin =
8514 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
8515
8516 const SCROLL_PADDING_Y: Pixels = px(12.);
8517
8518 if target_display_point.row() < visible_row_range.start {
8519 return self.render_edit_prediction_scroll_popover(
8520 |_| SCROLL_PADDING_Y,
8521 IconName::ArrowUp,
8522 visible_row_range,
8523 line_layouts,
8524 newest_selection_head,
8525 scrolled_content_origin,
8526 window,
8527 cx,
8528 );
8529 } else if target_display_point.row() >= visible_row_range.end {
8530 return self.render_edit_prediction_scroll_popover(
8531 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8532 IconName::ArrowDown,
8533 visible_row_range,
8534 line_layouts,
8535 newest_selection_head,
8536 scrolled_content_origin,
8537 window,
8538 cx,
8539 );
8540 }
8541
8542 const POLE_WIDTH: Pixels = px(2.);
8543
8544 let line_layout =
8545 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8546 let target_column = target_display_point.column() as usize;
8547
8548 let target_x = line_layout.x_for_index(target_column);
8549 let target_y =
8550 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
8551
8552 let flag_on_right = target_x < text_bounds.size.width / 2.;
8553
8554 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8555 border_color.l += 0.001;
8556
8557 let mut element = v_flex()
8558 .items_end()
8559 .when(flag_on_right, |el| el.items_start())
8560 .child(if flag_on_right {
8561 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8562 .rounded_bl(px(0.))
8563 .rounded_tl(px(0.))
8564 .border_l_2()
8565 .border_color(border_color)
8566 } else {
8567 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8568 .rounded_br(px(0.))
8569 .rounded_tr(px(0.))
8570 .border_r_2()
8571 .border_color(border_color)
8572 })
8573 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8574 .into_any();
8575
8576 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8577
8578 let mut origin = scrolled_content_origin + point(target_x, target_y)
8579 - point(
8580 if flag_on_right {
8581 POLE_WIDTH
8582 } else {
8583 size.width - POLE_WIDTH
8584 },
8585 size.height - line_height,
8586 );
8587
8588 origin.x = origin.x.max(content_origin.x);
8589
8590 element.prepaint_at(origin, window, cx);
8591
8592 Some((element, origin))
8593 }
8594
8595 fn render_edit_prediction_scroll_popover(
8596 &mut self,
8597 to_y: impl Fn(Size<Pixels>) -> Pixels,
8598 scroll_icon: IconName,
8599 visible_row_range: Range<DisplayRow>,
8600 line_layouts: &[LineWithInvisibles],
8601 newest_selection_head: Option<DisplayPoint>,
8602 scrolled_content_origin: gpui::Point<Pixels>,
8603 window: &mut Window,
8604 cx: &mut App,
8605 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8606 let mut element = self
8607 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
8608 .into_any();
8609
8610 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8611
8612 let cursor = newest_selection_head?;
8613 let cursor_row_layout =
8614 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8615 let cursor_column = cursor.column() as usize;
8616
8617 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8618
8619 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8620
8621 element.prepaint_at(origin, window, cx);
8622 Some((element, origin))
8623 }
8624
8625 fn render_edit_prediction_eager_jump_popover(
8626 &mut self,
8627 text_bounds: &Bounds<Pixels>,
8628 content_origin: gpui::Point<Pixels>,
8629 editor_snapshot: &EditorSnapshot,
8630 visible_row_range: Range<DisplayRow>,
8631 scroll_top: f32,
8632 scroll_bottom: f32,
8633 line_height: Pixels,
8634 scroll_pixel_position: gpui::Point<Pixels>,
8635 target_display_point: DisplayPoint,
8636 editor_width: Pixels,
8637 window: &mut Window,
8638 cx: &mut App,
8639 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8640 if target_display_point.row().as_f32() < scroll_top {
8641 let mut element = self
8642 .render_edit_prediction_line_popover(
8643 "Jump to Edit",
8644 Some(IconName::ArrowUp),
8645 window,
8646 cx,
8647 )?
8648 .into_any();
8649
8650 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8651 let offset = point(
8652 (text_bounds.size.width - size.width) / 2.,
8653 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8654 );
8655
8656 let origin = text_bounds.origin + offset;
8657 element.prepaint_at(origin, window, cx);
8658 Some((element, origin))
8659 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
8660 let mut element = self
8661 .render_edit_prediction_line_popover(
8662 "Jump to Edit",
8663 Some(IconName::ArrowDown),
8664 window,
8665 cx,
8666 )?
8667 .into_any();
8668
8669 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8670 let offset = point(
8671 (text_bounds.size.width - size.width) / 2.,
8672 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8673 );
8674
8675 let origin = text_bounds.origin + offset;
8676 element.prepaint_at(origin, window, cx);
8677 Some((element, origin))
8678 } else {
8679 self.render_edit_prediction_end_of_line_popover(
8680 "Jump to Edit",
8681 editor_snapshot,
8682 visible_row_range,
8683 target_display_point,
8684 line_height,
8685 scroll_pixel_position,
8686 content_origin,
8687 editor_width,
8688 window,
8689 cx,
8690 )
8691 }
8692 }
8693
8694 fn render_edit_prediction_end_of_line_popover(
8695 self: &mut Editor,
8696 label: &'static str,
8697 editor_snapshot: &EditorSnapshot,
8698 visible_row_range: Range<DisplayRow>,
8699 target_display_point: DisplayPoint,
8700 line_height: Pixels,
8701 scroll_pixel_position: gpui::Point<Pixels>,
8702 content_origin: gpui::Point<Pixels>,
8703 editor_width: Pixels,
8704 window: &mut Window,
8705 cx: &mut App,
8706 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8707 let target_line_end = DisplayPoint::new(
8708 target_display_point.row(),
8709 editor_snapshot.line_len(target_display_point.row()),
8710 );
8711
8712 let mut element = self
8713 .render_edit_prediction_line_popover(label, None, window, cx)?
8714 .into_any();
8715
8716 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8717
8718 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8719
8720 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
8721 let mut origin = start_point
8722 + line_origin
8723 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8724 origin.x = origin.x.max(content_origin.x);
8725
8726 let max_x = content_origin.x + editor_width - size.width;
8727
8728 if origin.x > max_x {
8729 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8730
8731 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8732 origin.y += offset;
8733 IconName::ArrowUp
8734 } else {
8735 origin.y -= offset;
8736 IconName::ArrowDown
8737 };
8738
8739 element = self
8740 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
8741 .into_any();
8742
8743 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8744
8745 origin.x = content_origin.x + editor_width - size.width - px(2.);
8746 }
8747
8748 element.prepaint_at(origin, window, cx);
8749 Some((element, origin))
8750 }
8751
8752 fn render_edit_prediction_diff_popover(
8753 self: &Editor,
8754 text_bounds: &Bounds<Pixels>,
8755 content_origin: gpui::Point<Pixels>,
8756 right_margin: Pixels,
8757 editor_snapshot: &EditorSnapshot,
8758 visible_row_range: Range<DisplayRow>,
8759 line_layouts: &[LineWithInvisibles],
8760 line_height: Pixels,
8761 scroll_pixel_position: gpui::Point<Pixels>,
8762 newest_selection_head: Option<DisplayPoint>,
8763 editor_width: Pixels,
8764 style: &EditorStyle,
8765 edits: &Vec<(Range<Anchor>, String)>,
8766 edit_preview: &Option<language::EditPreview>,
8767 snapshot: &language::BufferSnapshot,
8768 window: &mut Window,
8769 cx: &mut App,
8770 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8771 let edit_start = edits
8772 .first()
8773 .unwrap()
8774 .0
8775 .start
8776 .to_display_point(editor_snapshot);
8777 let edit_end = edits
8778 .last()
8779 .unwrap()
8780 .0
8781 .end
8782 .to_display_point(editor_snapshot);
8783
8784 let is_visible = visible_row_range.contains(&edit_start.row())
8785 || visible_row_range.contains(&edit_end.row());
8786 if !is_visible {
8787 return None;
8788 }
8789
8790 let highlighted_edits =
8791 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
8792
8793 let styled_text = highlighted_edits.to_styled_text(&style.text);
8794 let line_count = highlighted_edits.text.lines().count();
8795
8796 const BORDER_WIDTH: Pixels = px(1.);
8797
8798 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8799 let has_keybind = keybind.is_some();
8800
8801 let mut element = h_flex()
8802 .items_start()
8803 .child(
8804 h_flex()
8805 .bg(cx.theme().colors().editor_background)
8806 .border(BORDER_WIDTH)
8807 .shadow_xs()
8808 .border_color(cx.theme().colors().border)
8809 .rounded_l_lg()
8810 .when(line_count > 1, |el| el.rounded_br_lg())
8811 .pr_1()
8812 .child(styled_text),
8813 )
8814 .child(
8815 h_flex()
8816 .h(line_height + BORDER_WIDTH * 2.)
8817 .px_1p5()
8818 .gap_1()
8819 // Workaround: For some reason, there's a gap if we don't do this
8820 .ml(-BORDER_WIDTH)
8821 .shadow(vec![gpui::BoxShadow {
8822 color: gpui::black().opacity(0.05),
8823 offset: point(px(1.), px(1.)),
8824 blur_radius: px(2.),
8825 spread_radius: px(0.),
8826 }])
8827 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8828 .border(BORDER_WIDTH)
8829 .border_color(cx.theme().colors().border)
8830 .rounded_r_lg()
8831 .id("edit_prediction_diff_popover_keybind")
8832 .when(!has_keybind, |el| {
8833 let status_colors = cx.theme().status();
8834
8835 el.bg(status_colors.error_background)
8836 .border_color(status_colors.error.opacity(0.6))
8837 .child(Icon::new(IconName::Info).color(Color::Error))
8838 .cursor_default()
8839 .hoverable_tooltip(move |_window, cx| {
8840 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8841 })
8842 })
8843 .children(keybind),
8844 )
8845 .into_any();
8846
8847 let longest_row =
8848 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8849 let longest_line_width = if visible_row_range.contains(&longest_row) {
8850 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8851 } else {
8852 layout_line(
8853 longest_row,
8854 editor_snapshot,
8855 style,
8856 editor_width,
8857 |_| false,
8858 window,
8859 cx,
8860 )
8861 .width
8862 };
8863
8864 let viewport_bounds =
8865 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8866 right: -right_margin,
8867 ..Default::default()
8868 });
8869
8870 let x_after_longest =
8871 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
8872 - scroll_pixel_position.x;
8873
8874 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8875
8876 // Fully visible if it can be displayed within the window (allow overlapping other
8877 // panes). However, this is only allowed if the popover starts within text_bounds.
8878 let can_position_to_the_right = x_after_longest < text_bounds.right()
8879 && x_after_longest + element_bounds.width < viewport_bounds.right();
8880
8881 let mut origin = if can_position_to_the_right {
8882 point(
8883 x_after_longest,
8884 text_bounds.origin.y + edit_start.row().as_f32() * line_height
8885 - scroll_pixel_position.y,
8886 )
8887 } else {
8888 let cursor_row = newest_selection_head.map(|head| head.row());
8889 let above_edit = edit_start
8890 .row()
8891 .0
8892 .checked_sub(line_count as u32)
8893 .map(DisplayRow);
8894 let below_edit = Some(edit_end.row() + 1);
8895 let above_cursor =
8896 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
8897 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
8898
8899 // Place the edit popover adjacent to the edit if there is a location
8900 // available that is onscreen and does not obscure the cursor. Otherwise,
8901 // place it adjacent to the cursor.
8902 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
8903 .into_iter()
8904 .flatten()
8905 .find(|&start_row| {
8906 let end_row = start_row + line_count as u32;
8907 visible_row_range.contains(&start_row)
8908 && visible_row_range.contains(&end_row)
8909 && cursor_row.map_or(true, |cursor_row| {
8910 !((start_row..end_row).contains(&cursor_row))
8911 })
8912 })?;
8913
8914 content_origin
8915 + point(
8916 -scroll_pixel_position.x,
8917 row_target.as_f32() * line_height - scroll_pixel_position.y,
8918 )
8919 };
8920
8921 origin.x -= BORDER_WIDTH;
8922
8923 window.defer_draw(element, origin, 1);
8924
8925 // Do not return an element, since it will already be drawn due to defer_draw.
8926 None
8927 }
8928
8929 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
8930 px(30.)
8931 }
8932
8933 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
8934 if self.read_only(cx) {
8935 cx.theme().players().read_only()
8936 } else {
8937 self.style.as_ref().unwrap().local_player
8938 }
8939 }
8940
8941 fn render_edit_prediction_accept_keybind(
8942 &self,
8943 window: &mut Window,
8944 cx: &App,
8945 ) -> Option<AnyElement> {
8946 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
8947 let accept_keystroke = accept_binding.keystroke()?;
8948
8949 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8950
8951 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
8952 Color::Accent
8953 } else {
8954 Color::Muted
8955 };
8956
8957 h_flex()
8958 .px_0p5()
8959 .when(is_platform_style_mac, |parent| parent.gap_0p5())
8960 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8961 .text_size(TextSize::XSmall.rems(cx))
8962 .child(h_flex().children(ui::render_modifiers(
8963 &accept_keystroke.modifiers,
8964 PlatformStyle::platform(),
8965 Some(modifiers_color),
8966 Some(IconSize::XSmall.rems().into()),
8967 true,
8968 )))
8969 .when(is_platform_style_mac, |parent| {
8970 parent.child(accept_keystroke.key.clone())
8971 })
8972 .when(!is_platform_style_mac, |parent| {
8973 parent.child(
8974 Key::new(
8975 util::capitalize(&accept_keystroke.key),
8976 Some(Color::Default),
8977 )
8978 .size(Some(IconSize::XSmall.rems().into())),
8979 )
8980 })
8981 .into_any()
8982 .into()
8983 }
8984
8985 fn render_edit_prediction_line_popover(
8986 &self,
8987 label: impl Into<SharedString>,
8988 icon: Option<IconName>,
8989 window: &mut Window,
8990 cx: &App,
8991 ) -> Option<Stateful<Div>> {
8992 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
8993
8994 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8995 let has_keybind = keybind.is_some();
8996
8997 let result = h_flex()
8998 .id("ep-line-popover")
8999 .py_0p5()
9000 .pl_1()
9001 .pr(padding_right)
9002 .gap_1()
9003 .rounded_md()
9004 .border_1()
9005 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9006 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9007 .shadow_xs()
9008 .when(!has_keybind, |el| {
9009 let status_colors = cx.theme().status();
9010
9011 el.bg(status_colors.error_background)
9012 .border_color(status_colors.error.opacity(0.6))
9013 .pl_2()
9014 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9015 .cursor_default()
9016 .hoverable_tooltip(move |_window, cx| {
9017 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9018 })
9019 })
9020 .children(keybind)
9021 .child(
9022 Label::new(label)
9023 .size(LabelSize::Small)
9024 .when(!has_keybind, |el| {
9025 el.color(cx.theme().status().error.into()).strikethrough()
9026 }),
9027 )
9028 .when(!has_keybind, |el| {
9029 el.child(
9030 h_flex().ml_1().child(
9031 Icon::new(IconName::Info)
9032 .size(IconSize::Small)
9033 .color(cx.theme().status().error.into()),
9034 ),
9035 )
9036 })
9037 .when_some(icon, |element, icon| {
9038 element.child(
9039 div()
9040 .mt(px(1.5))
9041 .child(Icon::new(icon).size(IconSize::Small)),
9042 )
9043 });
9044
9045 Some(result)
9046 }
9047
9048 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9049 let accent_color = cx.theme().colors().text_accent;
9050 let editor_bg_color = cx.theme().colors().editor_background;
9051 editor_bg_color.blend(accent_color.opacity(0.1))
9052 }
9053
9054 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9055 let accent_color = cx.theme().colors().text_accent;
9056 let editor_bg_color = cx.theme().colors().editor_background;
9057 editor_bg_color.blend(accent_color.opacity(0.6))
9058 }
9059
9060 fn render_edit_prediction_cursor_popover(
9061 &self,
9062 min_width: Pixels,
9063 max_width: Pixels,
9064 cursor_point: Point,
9065 style: &EditorStyle,
9066 accept_keystroke: Option<&gpui::Keystroke>,
9067 _window: &Window,
9068 cx: &mut Context<Editor>,
9069 ) -> Option<AnyElement> {
9070 let provider = self.edit_prediction_provider.as_ref()?;
9071
9072 if provider.provider.needs_terms_acceptance(cx) {
9073 return Some(
9074 h_flex()
9075 .min_w(min_width)
9076 .flex_1()
9077 .px_2()
9078 .py_1()
9079 .gap_3()
9080 .elevation_2(cx)
9081 .hover(|style| style.bg(cx.theme().colors().element_hover))
9082 .id("accept-terms")
9083 .cursor_pointer()
9084 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
9085 .on_click(cx.listener(|this, _event, window, cx| {
9086 cx.stop_propagation();
9087 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
9088 window.dispatch_action(
9089 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
9090 cx,
9091 );
9092 }))
9093 .child(
9094 h_flex()
9095 .flex_1()
9096 .gap_2()
9097 .child(Icon::new(IconName::ZedPredict))
9098 .child(Label::new("Accept Terms of Service"))
9099 .child(div().w_full())
9100 .child(
9101 Icon::new(IconName::ArrowUpRight)
9102 .color(Color::Muted)
9103 .size(IconSize::Small),
9104 )
9105 .into_any_element(),
9106 )
9107 .into_any(),
9108 );
9109 }
9110
9111 let is_refreshing = provider.provider.is_refreshing(cx);
9112
9113 fn pending_completion_container() -> Div {
9114 h_flex()
9115 .h_full()
9116 .flex_1()
9117 .gap_2()
9118 .child(Icon::new(IconName::ZedPredict))
9119 }
9120
9121 let completion = match &self.active_inline_completion {
9122 Some(prediction) => {
9123 if !self.has_visible_completions_menu() {
9124 const RADIUS: Pixels = px(6.);
9125 const BORDER_WIDTH: Pixels = px(1.);
9126
9127 return Some(
9128 h_flex()
9129 .elevation_2(cx)
9130 .border(BORDER_WIDTH)
9131 .border_color(cx.theme().colors().border)
9132 .when(accept_keystroke.is_none(), |el| {
9133 el.border_color(cx.theme().status().error)
9134 })
9135 .rounded(RADIUS)
9136 .rounded_tl(px(0.))
9137 .overflow_hidden()
9138 .child(div().px_1p5().child(match &prediction.completion {
9139 InlineCompletion::Move { target, snapshot } => {
9140 use text::ToPoint as _;
9141 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
9142 {
9143 Icon::new(IconName::ZedPredictDown)
9144 } else {
9145 Icon::new(IconName::ZedPredictUp)
9146 }
9147 }
9148 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
9149 }))
9150 .child(
9151 h_flex()
9152 .gap_1()
9153 .py_1()
9154 .px_2()
9155 .rounded_r(RADIUS - BORDER_WIDTH)
9156 .border_l_1()
9157 .border_color(cx.theme().colors().border)
9158 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9159 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9160 el.child(
9161 Label::new("Hold")
9162 .size(LabelSize::Small)
9163 .when(accept_keystroke.is_none(), |el| {
9164 el.strikethrough()
9165 })
9166 .line_height_style(LineHeightStyle::UiLabel),
9167 )
9168 })
9169 .id("edit_prediction_cursor_popover_keybind")
9170 .when(accept_keystroke.is_none(), |el| {
9171 let status_colors = cx.theme().status();
9172
9173 el.bg(status_colors.error_background)
9174 .border_color(status_colors.error.opacity(0.6))
9175 .child(Icon::new(IconName::Info).color(Color::Error))
9176 .cursor_default()
9177 .hoverable_tooltip(move |_window, cx| {
9178 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9179 .into()
9180 })
9181 })
9182 .when_some(
9183 accept_keystroke.as_ref(),
9184 |el, accept_keystroke| {
9185 el.child(h_flex().children(ui::render_modifiers(
9186 &accept_keystroke.modifiers,
9187 PlatformStyle::platform(),
9188 Some(Color::Default),
9189 Some(IconSize::XSmall.rems().into()),
9190 false,
9191 )))
9192 },
9193 ),
9194 )
9195 .into_any(),
9196 );
9197 }
9198
9199 self.render_edit_prediction_cursor_popover_preview(
9200 prediction,
9201 cursor_point,
9202 style,
9203 cx,
9204 )?
9205 }
9206
9207 None if is_refreshing => match &self.stale_inline_completion_in_menu {
9208 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9209 stale_completion,
9210 cursor_point,
9211 style,
9212 cx,
9213 )?,
9214
9215 None => {
9216 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
9217 }
9218 },
9219
9220 None => pending_completion_container().child(Label::new("No Prediction")),
9221 };
9222
9223 let completion = if is_refreshing {
9224 completion
9225 .with_animation(
9226 "loading-completion",
9227 Animation::new(Duration::from_secs(2))
9228 .repeat()
9229 .with_easing(pulsating_between(0.4, 0.8)),
9230 |label, delta| label.opacity(delta),
9231 )
9232 .into_any_element()
9233 } else {
9234 completion.into_any_element()
9235 };
9236
9237 let has_completion = self.active_inline_completion.is_some();
9238
9239 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9240 Some(
9241 h_flex()
9242 .min_w(min_width)
9243 .max_w(max_width)
9244 .flex_1()
9245 .elevation_2(cx)
9246 .border_color(cx.theme().colors().border)
9247 .child(
9248 div()
9249 .flex_1()
9250 .py_1()
9251 .px_2()
9252 .overflow_hidden()
9253 .child(completion),
9254 )
9255 .when_some(accept_keystroke, |el, accept_keystroke| {
9256 if !accept_keystroke.modifiers.modified() {
9257 return el;
9258 }
9259
9260 el.child(
9261 h_flex()
9262 .h_full()
9263 .border_l_1()
9264 .rounded_r_lg()
9265 .border_color(cx.theme().colors().border)
9266 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9267 .gap_1()
9268 .py_1()
9269 .px_2()
9270 .child(
9271 h_flex()
9272 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9273 .when(is_platform_style_mac, |parent| parent.gap_1())
9274 .child(h_flex().children(ui::render_modifiers(
9275 &accept_keystroke.modifiers,
9276 PlatformStyle::platform(),
9277 Some(if !has_completion {
9278 Color::Muted
9279 } else {
9280 Color::Default
9281 }),
9282 None,
9283 false,
9284 ))),
9285 )
9286 .child(Label::new("Preview").into_any_element())
9287 .opacity(if has_completion { 1.0 } else { 0.4 }),
9288 )
9289 })
9290 .into_any(),
9291 )
9292 }
9293
9294 fn render_edit_prediction_cursor_popover_preview(
9295 &self,
9296 completion: &InlineCompletionState,
9297 cursor_point: Point,
9298 style: &EditorStyle,
9299 cx: &mut Context<Editor>,
9300 ) -> Option<Div> {
9301 use text::ToPoint as _;
9302
9303 fn render_relative_row_jump(
9304 prefix: impl Into<String>,
9305 current_row: u32,
9306 target_row: u32,
9307 ) -> Div {
9308 let (row_diff, arrow) = if target_row < current_row {
9309 (current_row - target_row, IconName::ArrowUp)
9310 } else {
9311 (target_row - current_row, IconName::ArrowDown)
9312 };
9313
9314 h_flex()
9315 .child(
9316 Label::new(format!("{}{}", prefix.into(), row_diff))
9317 .color(Color::Muted)
9318 .size(LabelSize::Small),
9319 )
9320 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9321 }
9322
9323 match &completion.completion {
9324 InlineCompletion::Move {
9325 target, snapshot, ..
9326 } => Some(
9327 h_flex()
9328 .px_2()
9329 .gap_2()
9330 .flex_1()
9331 .child(
9332 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
9333 Icon::new(IconName::ZedPredictDown)
9334 } else {
9335 Icon::new(IconName::ZedPredictUp)
9336 },
9337 )
9338 .child(Label::new("Jump to Edit")),
9339 ),
9340
9341 InlineCompletion::Edit {
9342 edits,
9343 edit_preview,
9344 snapshot,
9345 display_mode: _,
9346 } => {
9347 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
9348
9349 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
9350 &snapshot,
9351 &edits,
9352 edit_preview.as_ref()?,
9353 true,
9354 cx,
9355 )
9356 .first_line_preview();
9357
9358 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9359 .with_default_highlights(&style.text, highlighted_edits.highlights);
9360
9361 let preview = h_flex()
9362 .gap_1()
9363 .min_w_16()
9364 .child(styled_text)
9365 .when(has_more_lines, |parent| parent.child("…"));
9366
9367 let left = if first_edit_row != cursor_point.row {
9368 render_relative_row_jump("", cursor_point.row, first_edit_row)
9369 .into_any_element()
9370 } else {
9371 Icon::new(IconName::ZedPredict).into_any_element()
9372 };
9373
9374 Some(
9375 h_flex()
9376 .h_full()
9377 .flex_1()
9378 .gap_2()
9379 .pr_1()
9380 .overflow_x_hidden()
9381 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9382 .child(left)
9383 .child(preview),
9384 )
9385 }
9386 }
9387 }
9388
9389 pub fn render_context_menu(
9390 &self,
9391 style: &EditorStyle,
9392 max_height_in_lines: u32,
9393 window: &mut Window,
9394 cx: &mut Context<Editor>,
9395 ) -> Option<AnyElement> {
9396 let menu = self.context_menu.borrow();
9397 let menu = menu.as_ref()?;
9398 if !menu.visible() {
9399 return None;
9400 };
9401 Some(menu.render(style, max_height_in_lines, window, cx))
9402 }
9403
9404 fn render_context_menu_aside(
9405 &mut self,
9406 max_size: Size<Pixels>,
9407 window: &mut Window,
9408 cx: &mut Context<Editor>,
9409 ) -> Option<AnyElement> {
9410 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9411 if menu.visible() {
9412 menu.render_aside(max_size, window, cx)
9413 } else {
9414 None
9415 }
9416 })
9417 }
9418
9419 fn hide_context_menu(
9420 &mut self,
9421 window: &mut Window,
9422 cx: &mut Context<Self>,
9423 ) -> Option<CodeContextMenu> {
9424 cx.notify();
9425 self.completion_tasks.clear();
9426 let context_menu = self.context_menu.borrow_mut().take();
9427 self.stale_inline_completion_in_menu.take();
9428 self.update_visible_inline_completion(window, cx);
9429 if let Some(CodeContextMenu::Completions(_)) = &context_menu {
9430 if let Some(completion_provider) = &self.completion_provider {
9431 completion_provider.selection_changed(None, window, cx);
9432 }
9433 }
9434 context_menu
9435 }
9436
9437 fn show_snippet_choices(
9438 &mut self,
9439 choices: &Vec<String>,
9440 selection: Range<Anchor>,
9441 cx: &mut Context<Self>,
9442 ) {
9443 let buffer_id = match (&selection.start.buffer_id, &selection.end.buffer_id) {
9444 (Some(a), Some(b)) if a == b => a,
9445 _ => {
9446 log::error!("expected anchor range to have matching buffer IDs");
9447 return;
9448 }
9449 };
9450 let multi_buffer = self.buffer().read(cx);
9451 let Some(buffer) = multi_buffer.buffer(*buffer_id) else {
9452 return;
9453 };
9454
9455 let id = post_inc(&mut self.next_completion_id);
9456 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9457 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9458 CompletionsMenu::new_snippet_choices(
9459 id,
9460 true,
9461 choices,
9462 selection,
9463 buffer,
9464 snippet_sort_order,
9465 ),
9466 ));
9467 }
9468
9469 pub fn insert_snippet(
9470 &mut self,
9471 insertion_ranges: &[Range<usize>],
9472 snippet: Snippet,
9473 window: &mut Window,
9474 cx: &mut Context<Self>,
9475 ) -> Result<()> {
9476 struct Tabstop<T> {
9477 is_end_tabstop: bool,
9478 ranges: Vec<Range<T>>,
9479 choices: Option<Vec<String>>,
9480 }
9481
9482 let tabstops = self.buffer.update(cx, |buffer, cx| {
9483 let snippet_text: Arc<str> = snippet.text.clone().into();
9484 let edits = insertion_ranges
9485 .iter()
9486 .cloned()
9487 .map(|range| (range, snippet_text.clone()));
9488 let autoindent_mode = AutoindentMode::Block {
9489 original_indent_columns: Vec::new(),
9490 };
9491 buffer.edit(edits, Some(autoindent_mode), cx);
9492
9493 let snapshot = &*buffer.read(cx);
9494 let snippet = &snippet;
9495 snippet
9496 .tabstops
9497 .iter()
9498 .map(|tabstop| {
9499 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
9500 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9501 });
9502 let mut tabstop_ranges = tabstop
9503 .ranges
9504 .iter()
9505 .flat_map(|tabstop_range| {
9506 let mut delta = 0_isize;
9507 insertion_ranges.iter().map(move |insertion_range| {
9508 let insertion_start = insertion_range.start as isize + delta;
9509 delta +=
9510 snippet.text.len() as isize - insertion_range.len() as isize;
9511
9512 let start = ((insertion_start + tabstop_range.start) as usize)
9513 .min(snapshot.len());
9514 let end = ((insertion_start + tabstop_range.end) as usize)
9515 .min(snapshot.len());
9516 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9517 })
9518 })
9519 .collect::<Vec<_>>();
9520 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9521
9522 Tabstop {
9523 is_end_tabstop,
9524 ranges: tabstop_ranges,
9525 choices: tabstop.choices.clone(),
9526 }
9527 })
9528 .collect::<Vec<_>>()
9529 });
9530 if let Some(tabstop) = tabstops.first() {
9531 self.change_selections(Default::default(), window, cx, |s| {
9532 // Reverse order so that the first range is the newest created selection.
9533 // Completions will use it and autoscroll will prioritize it.
9534 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9535 });
9536
9537 if let Some(choices) = &tabstop.choices {
9538 if let Some(selection) = tabstop.ranges.first() {
9539 self.show_snippet_choices(choices, selection.clone(), cx)
9540 }
9541 }
9542
9543 // If we're already at the last tabstop and it's at the end of the snippet,
9544 // we're done, we don't need to keep the state around.
9545 if !tabstop.is_end_tabstop {
9546 let choices = tabstops
9547 .iter()
9548 .map(|tabstop| tabstop.choices.clone())
9549 .collect();
9550
9551 let ranges = tabstops
9552 .into_iter()
9553 .map(|tabstop| tabstop.ranges)
9554 .collect::<Vec<_>>();
9555
9556 self.snippet_stack.push(SnippetState {
9557 active_index: 0,
9558 ranges,
9559 choices,
9560 });
9561 }
9562
9563 // Check whether the just-entered snippet ends with an auto-closable bracket.
9564 if self.autoclose_regions.is_empty() {
9565 let snapshot = self.buffer.read(cx).snapshot(cx);
9566 for selection in &mut self.selections.all::<Point>(cx) {
9567 let selection_head = selection.head();
9568 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9569 continue;
9570 };
9571
9572 let mut bracket_pair = None;
9573 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
9574 let prev_chars = snapshot
9575 .reversed_chars_at(selection_head)
9576 .collect::<String>();
9577 for (pair, enabled) in scope.brackets() {
9578 if enabled
9579 && pair.close
9580 && prev_chars.starts_with(pair.start.as_str())
9581 && next_chars.starts_with(pair.end.as_str())
9582 {
9583 bracket_pair = Some(pair.clone());
9584 break;
9585 }
9586 }
9587 if let Some(pair) = bracket_pair {
9588 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9589 let autoclose_enabled =
9590 self.use_autoclose && snapshot_settings.use_autoclose;
9591 if autoclose_enabled {
9592 let start = snapshot.anchor_after(selection_head);
9593 let end = snapshot.anchor_after(selection_head);
9594 self.autoclose_regions.push(AutocloseRegion {
9595 selection_id: selection.id,
9596 range: start..end,
9597 pair,
9598 });
9599 }
9600 }
9601 }
9602 }
9603 }
9604 Ok(())
9605 }
9606
9607 pub fn move_to_next_snippet_tabstop(
9608 &mut self,
9609 window: &mut Window,
9610 cx: &mut Context<Self>,
9611 ) -> bool {
9612 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9613 }
9614
9615 pub fn move_to_prev_snippet_tabstop(
9616 &mut self,
9617 window: &mut Window,
9618 cx: &mut Context<Self>,
9619 ) -> bool {
9620 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9621 }
9622
9623 pub fn move_to_snippet_tabstop(
9624 &mut self,
9625 bias: Bias,
9626 window: &mut Window,
9627 cx: &mut Context<Self>,
9628 ) -> bool {
9629 if let Some(mut snippet) = self.snippet_stack.pop() {
9630 match bias {
9631 Bias::Left => {
9632 if snippet.active_index > 0 {
9633 snippet.active_index -= 1;
9634 } else {
9635 self.snippet_stack.push(snippet);
9636 return false;
9637 }
9638 }
9639 Bias::Right => {
9640 if snippet.active_index + 1 < snippet.ranges.len() {
9641 snippet.active_index += 1;
9642 } else {
9643 self.snippet_stack.push(snippet);
9644 return false;
9645 }
9646 }
9647 }
9648 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9649 self.change_selections(Default::default(), window, cx, |s| {
9650 // Reverse order so that the first range is the newest created selection.
9651 // Completions will use it and autoscroll will prioritize it.
9652 s.select_ranges(current_ranges.iter().rev().cloned())
9653 });
9654
9655 if let Some(choices) = &snippet.choices[snippet.active_index] {
9656 if let Some(selection) = current_ranges.first() {
9657 self.show_snippet_choices(&choices, selection.clone(), cx);
9658 }
9659 }
9660
9661 // If snippet state is not at the last tabstop, push it back on the stack
9662 if snippet.active_index + 1 < snippet.ranges.len() {
9663 self.snippet_stack.push(snippet);
9664 }
9665 return true;
9666 }
9667 }
9668
9669 false
9670 }
9671
9672 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9673 self.transact(window, cx, |this, window, cx| {
9674 this.select_all(&SelectAll, window, cx);
9675 this.insert("", window, cx);
9676 });
9677 }
9678
9679 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9680 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9681 self.transact(window, cx, |this, window, cx| {
9682 this.select_autoclose_pair(window, cx);
9683 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9684 if !this.linked_edit_ranges.is_empty() {
9685 let selections = this.selections.all::<MultiBufferPoint>(cx);
9686 let snapshot = this.buffer.read(cx).snapshot(cx);
9687
9688 for selection in selections.iter() {
9689 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9690 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9691 if selection_start.buffer_id != selection_end.buffer_id {
9692 continue;
9693 }
9694 if let Some(ranges) =
9695 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9696 {
9697 for (buffer, entries) in ranges {
9698 linked_ranges.entry(buffer).or_default().extend(entries);
9699 }
9700 }
9701 }
9702 }
9703
9704 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
9705 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9706 for selection in &mut selections {
9707 if selection.is_empty() {
9708 let old_head = selection.head();
9709 let mut new_head =
9710 movement::left(&display_map, old_head.to_display_point(&display_map))
9711 .to_point(&display_map);
9712 if let Some((buffer, line_buffer_range)) = display_map
9713 .buffer_snapshot
9714 .buffer_line_for_row(MultiBufferRow(old_head.row))
9715 {
9716 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9717 let indent_len = match indent_size.kind {
9718 IndentKind::Space => {
9719 buffer.settings_at(line_buffer_range.start, cx).tab_size
9720 }
9721 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9722 };
9723 if old_head.column <= indent_size.len && old_head.column > 0 {
9724 let indent_len = indent_len.get();
9725 new_head = cmp::min(
9726 new_head,
9727 MultiBufferPoint::new(
9728 old_head.row,
9729 ((old_head.column - 1) / indent_len) * indent_len,
9730 ),
9731 );
9732 }
9733 }
9734
9735 selection.set_head(new_head, SelectionGoal::None);
9736 }
9737 }
9738
9739 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9740 this.insert("", window, cx);
9741 let empty_str: Arc<str> = Arc::from("");
9742 for (buffer, edits) in linked_ranges {
9743 let snapshot = buffer.read(cx).snapshot();
9744 use text::ToPoint as TP;
9745
9746 let edits = edits
9747 .into_iter()
9748 .map(|range| {
9749 let end_point = TP::to_point(&range.end, &snapshot);
9750 let mut start_point = TP::to_point(&range.start, &snapshot);
9751
9752 if end_point == start_point {
9753 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9754 .saturating_sub(1);
9755 start_point =
9756 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9757 };
9758
9759 (start_point..end_point, empty_str.clone())
9760 })
9761 .sorted_by_key(|(range, _)| range.start)
9762 .collect::<Vec<_>>();
9763 buffer.update(cx, |this, cx| {
9764 this.edit(edits, None, cx);
9765 })
9766 }
9767 this.refresh_inline_completion(true, false, window, cx);
9768 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
9769 });
9770 }
9771
9772 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9773 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9774 self.transact(window, cx, |this, window, cx| {
9775 this.change_selections(Default::default(), window, cx, |s| {
9776 s.move_with(|map, selection| {
9777 if selection.is_empty() {
9778 let cursor = movement::right(map, selection.head());
9779 selection.end = cursor;
9780 selection.reversed = true;
9781 selection.goal = SelectionGoal::None;
9782 }
9783 })
9784 });
9785 this.insert("", window, cx);
9786 this.refresh_inline_completion(true, false, window, cx);
9787 });
9788 }
9789
9790 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9791 if self.mode.is_single_line() {
9792 cx.propagate();
9793 return;
9794 }
9795
9796 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9797 if self.move_to_prev_snippet_tabstop(window, cx) {
9798 return;
9799 }
9800 self.outdent(&Outdent, window, cx);
9801 }
9802
9803 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9804 if self.mode.is_single_line() {
9805 cx.propagate();
9806 return;
9807 }
9808
9809 if self.move_to_next_snippet_tabstop(window, cx) {
9810 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9811 return;
9812 }
9813 if self.read_only(cx) {
9814 return;
9815 }
9816 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9817 let mut selections = self.selections.all_adjusted(cx);
9818 let buffer = self.buffer.read(cx);
9819 let snapshot = buffer.snapshot(cx);
9820 let rows_iter = selections.iter().map(|s| s.head().row);
9821 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9822
9823 let has_some_cursor_in_whitespace = selections
9824 .iter()
9825 .filter(|selection| selection.is_empty())
9826 .any(|selection| {
9827 let cursor = selection.head();
9828 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9829 cursor.column < current_indent.len
9830 });
9831
9832 let mut edits = Vec::new();
9833 let mut prev_edited_row = 0;
9834 let mut row_delta = 0;
9835 for selection in &mut selections {
9836 if selection.start.row != prev_edited_row {
9837 row_delta = 0;
9838 }
9839 prev_edited_row = selection.end.row;
9840
9841 // If the selection is non-empty, then increase the indentation of the selected lines.
9842 if !selection.is_empty() {
9843 row_delta =
9844 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9845 continue;
9846 }
9847
9848 let cursor = selection.head();
9849 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9850 if let Some(suggested_indent) =
9851 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
9852 {
9853 // Don't do anything if already at suggested indent
9854 // and there is any other cursor which is not
9855 if has_some_cursor_in_whitespace
9856 && cursor.column == current_indent.len
9857 && current_indent.len == suggested_indent.len
9858 {
9859 continue;
9860 }
9861
9862 // Adjust line and move cursor to suggested indent
9863 // if cursor is not at suggested indent
9864 if cursor.column < suggested_indent.len
9865 && cursor.column <= current_indent.len
9866 && current_indent.len <= suggested_indent.len
9867 {
9868 selection.start = Point::new(cursor.row, suggested_indent.len);
9869 selection.end = selection.start;
9870 if row_delta == 0 {
9871 edits.extend(Buffer::edit_for_indent_size_adjustment(
9872 cursor.row,
9873 current_indent,
9874 suggested_indent,
9875 ));
9876 row_delta = suggested_indent.len - current_indent.len;
9877 }
9878 continue;
9879 }
9880
9881 // If current indent is more than suggested indent
9882 // only move cursor to current indent and skip indent
9883 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
9884 selection.start = Point::new(cursor.row, current_indent.len);
9885 selection.end = selection.start;
9886 continue;
9887 }
9888 }
9889
9890 // Otherwise, insert a hard or soft tab.
9891 let settings = buffer.language_settings_at(cursor, cx);
9892 let tab_size = if settings.hard_tabs {
9893 IndentSize::tab()
9894 } else {
9895 let tab_size = settings.tab_size.get();
9896 let indent_remainder = snapshot
9897 .text_for_range(Point::new(cursor.row, 0)..cursor)
9898 .flat_map(str::chars)
9899 .fold(row_delta % tab_size, |counter: u32, c| {
9900 if c == '\t' {
9901 0
9902 } else {
9903 (counter + 1) % tab_size
9904 }
9905 });
9906
9907 let chars_to_next_tab_stop = tab_size - indent_remainder;
9908 IndentSize::spaces(chars_to_next_tab_stop)
9909 };
9910 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
9911 selection.end = selection.start;
9912 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
9913 row_delta += tab_size.len;
9914 }
9915
9916 self.transact(window, cx, |this, window, cx| {
9917 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9918 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9919 this.refresh_inline_completion(true, false, window, cx);
9920 });
9921 }
9922
9923 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
9924 if self.read_only(cx) {
9925 return;
9926 }
9927 if self.mode.is_single_line() {
9928 cx.propagate();
9929 return;
9930 }
9931
9932 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9933 let mut selections = self.selections.all::<Point>(cx);
9934 let mut prev_edited_row = 0;
9935 let mut row_delta = 0;
9936 let mut edits = Vec::new();
9937 let buffer = self.buffer.read(cx);
9938 let snapshot = buffer.snapshot(cx);
9939 for selection in &mut selections {
9940 if selection.start.row != prev_edited_row {
9941 row_delta = 0;
9942 }
9943 prev_edited_row = selection.end.row;
9944
9945 row_delta =
9946 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9947 }
9948
9949 self.transact(window, cx, |this, window, cx| {
9950 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9951 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9952 });
9953 }
9954
9955 fn indent_selection(
9956 buffer: &MultiBuffer,
9957 snapshot: &MultiBufferSnapshot,
9958 selection: &mut Selection<Point>,
9959 edits: &mut Vec<(Range<Point>, String)>,
9960 delta_for_start_row: u32,
9961 cx: &App,
9962 ) -> u32 {
9963 let settings = buffer.language_settings_at(selection.start, cx);
9964 let tab_size = settings.tab_size.get();
9965 let indent_kind = if settings.hard_tabs {
9966 IndentKind::Tab
9967 } else {
9968 IndentKind::Space
9969 };
9970 let mut start_row = selection.start.row;
9971 let mut end_row = selection.end.row + 1;
9972
9973 // If a selection ends at the beginning of a line, don't indent
9974 // that last line.
9975 if selection.end.column == 0 && selection.end.row > selection.start.row {
9976 end_row -= 1;
9977 }
9978
9979 // Avoid re-indenting a row that has already been indented by a
9980 // previous selection, but still update this selection's column
9981 // to reflect that indentation.
9982 if delta_for_start_row > 0 {
9983 start_row += 1;
9984 selection.start.column += delta_for_start_row;
9985 if selection.end.row == selection.start.row {
9986 selection.end.column += delta_for_start_row;
9987 }
9988 }
9989
9990 let mut delta_for_end_row = 0;
9991 let has_multiple_rows = start_row + 1 != end_row;
9992 for row in start_row..end_row {
9993 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
9994 let indent_delta = match (current_indent.kind, indent_kind) {
9995 (IndentKind::Space, IndentKind::Space) => {
9996 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
9997 IndentSize::spaces(columns_to_next_tab_stop)
9998 }
9999 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10000 (_, IndentKind::Tab) => IndentSize::tab(),
10001 };
10002
10003 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10004 0
10005 } else {
10006 selection.start.column
10007 };
10008 let row_start = Point::new(row, start);
10009 edits.push((
10010 row_start..row_start,
10011 indent_delta.chars().collect::<String>(),
10012 ));
10013
10014 // Update this selection's endpoints to reflect the indentation.
10015 if row == selection.start.row {
10016 selection.start.column += indent_delta.len;
10017 }
10018 if row == selection.end.row {
10019 selection.end.column += indent_delta.len;
10020 delta_for_end_row = indent_delta.len;
10021 }
10022 }
10023
10024 if selection.start.row == selection.end.row {
10025 delta_for_start_row + delta_for_end_row
10026 } else {
10027 delta_for_end_row
10028 }
10029 }
10030
10031 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10032 if self.read_only(cx) {
10033 return;
10034 }
10035 if self.mode.is_single_line() {
10036 cx.propagate();
10037 return;
10038 }
10039
10040 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10041 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10042 let selections = self.selections.all::<Point>(cx);
10043 let mut deletion_ranges = Vec::new();
10044 let mut last_outdent = None;
10045 {
10046 let buffer = self.buffer.read(cx);
10047 let snapshot = buffer.snapshot(cx);
10048 for selection in &selections {
10049 let settings = buffer.language_settings_at(selection.start, cx);
10050 let tab_size = settings.tab_size.get();
10051 let mut rows = selection.spanned_rows(false, &display_map);
10052
10053 // Avoid re-outdenting a row that has already been outdented by a
10054 // previous selection.
10055 if let Some(last_row) = last_outdent {
10056 if last_row == rows.start {
10057 rows.start = rows.start.next_row();
10058 }
10059 }
10060 let has_multiple_rows = rows.len() > 1;
10061 for row in rows.iter_rows() {
10062 let indent_size = snapshot.indent_size_for_line(row);
10063 if indent_size.len > 0 {
10064 let deletion_len = match indent_size.kind {
10065 IndentKind::Space => {
10066 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10067 if columns_to_prev_tab_stop == 0 {
10068 tab_size
10069 } else {
10070 columns_to_prev_tab_stop
10071 }
10072 }
10073 IndentKind::Tab => 1,
10074 };
10075 let start = if has_multiple_rows
10076 || deletion_len > selection.start.column
10077 || indent_size.len < selection.start.column
10078 {
10079 0
10080 } else {
10081 selection.start.column - deletion_len
10082 };
10083 deletion_ranges.push(
10084 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10085 );
10086 last_outdent = Some(row);
10087 }
10088 }
10089 }
10090 }
10091
10092 self.transact(window, cx, |this, window, cx| {
10093 this.buffer.update(cx, |buffer, cx| {
10094 let empty_str: Arc<str> = Arc::default();
10095 buffer.edit(
10096 deletion_ranges
10097 .into_iter()
10098 .map(|range| (range, empty_str.clone())),
10099 None,
10100 cx,
10101 );
10102 });
10103 let selections = this.selections.all::<usize>(cx);
10104 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10105 });
10106 }
10107
10108 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10109 if self.read_only(cx) {
10110 return;
10111 }
10112 if self.mode.is_single_line() {
10113 cx.propagate();
10114 return;
10115 }
10116
10117 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10118 let selections = self
10119 .selections
10120 .all::<usize>(cx)
10121 .into_iter()
10122 .map(|s| s.range());
10123
10124 self.transact(window, cx, |this, window, cx| {
10125 this.buffer.update(cx, |buffer, cx| {
10126 buffer.autoindent_ranges(selections, cx);
10127 });
10128 let selections = this.selections.all::<usize>(cx);
10129 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10130 });
10131 }
10132
10133 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10134 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10135 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10136 let selections = self.selections.all::<Point>(cx);
10137
10138 let mut new_cursors = Vec::new();
10139 let mut edit_ranges = Vec::new();
10140 let mut selections = selections.iter().peekable();
10141 while let Some(selection) = selections.next() {
10142 let mut rows = selection.spanned_rows(false, &display_map);
10143 let goal_display_column = selection.head().to_display_point(&display_map).column();
10144
10145 // Accumulate contiguous regions of rows that we want to delete.
10146 while let Some(next_selection) = selections.peek() {
10147 let next_rows = next_selection.spanned_rows(false, &display_map);
10148 if next_rows.start <= rows.end {
10149 rows.end = next_rows.end;
10150 selections.next().unwrap();
10151 } else {
10152 break;
10153 }
10154 }
10155
10156 let buffer = &display_map.buffer_snapshot;
10157 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
10158 let edit_end;
10159 let cursor_buffer_row;
10160 if buffer.max_point().row >= rows.end.0 {
10161 // If there's a line after the range, delete the \n from the end of the row range
10162 // and position the cursor on the next line.
10163 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
10164 cursor_buffer_row = rows.end;
10165 } else {
10166 // If there isn't a line after the range, delete the \n from the line before the
10167 // start of the row range and position the cursor there.
10168 edit_start = edit_start.saturating_sub(1);
10169 edit_end = buffer.len();
10170 cursor_buffer_row = rows.start.previous_row();
10171 }
10172
10173 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
10174 *cursor.column_mut() =
10175 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
10176
10177 new_cursors.push((
10178 selection.id,
10179 buffer.anchor_after(cursor.to_point(&display_map)),
10180 ));
10181 edit_ranges.push(edit_start..edit_end);
10182 }
10183
10184 self.transact(window, cx, |this, window, cx| {
10185 let buffer = this.buffer.update(cx, |buffer, cx| {
10186 let empty_str: Arc<str> = Arc::default();
10187 buffer.edit(
10188 edit_ranges
10189 .into_iter()
10190 .map(|range| (range, empty_str.clone())),
10191 None,
10192 cx,
10193 );
10194 buffer.snapshot(cx)
10195 });
10196 let new_selections = new_cursors
10197 .into_iter()
10198 .map(|(id, cursor)| {
10199 let cursor = cursor.to_point(&buffer);
10200 Selection {
10201 id,
10202 start: cursor,
10203 end: cursor,
10204 reversed: false,
10205 goal: SelectionGoal::None,
10206 }
10207 })
10208 .collect();
10209
10210 this.change_selections(Default::default(), window, cx, |s| {
10211 s.select(new_selections);
10212 });
10213 });
10214 }
10215
10216 pub fn join_lines_impl(
10217 &mut self,
10218 insert_whitespace: bool,
10219 window: &mut Window,
10220 cx: &mut Context<Self>,
10221 ) {
10222 if self.read_only(cx) {
10223 return;
10224 }
10225 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10226 for selection in self.selections.all::<Point>(cx) {
10227 let start = MultiBufferRow(selection.start.row);
10228 // Treat single line selections as if they include the next line. Otherwise this action
10229 // would do nothing for single line selections individual cursors.
10230 let end = if selection.start.row == selection.end.row {
10231 MultiBufferRow(selection.start.row + 1)
10232 } else {
10233 MultiBufferRow(selection.end.row)
10234 };
10235
10236 if let Some(last_row_range) = row_ranges.last_mut() {
10237 if start <= last_row_range.end {
10238 last_row_range.end = end;
10239 continue;
10240 }
10241 }
10242 row_ranges.push(start..end);
10243 }
10244
10245 let snapshot = self.buffer.read(cx).snapshot(cx);
10246 let mut cursor_positions = Vec::new();
10247 for row_range in &row_ranges {
10248 let anchor = snapshot.anchor_before(Point::new(
10249 row_range.end.previous_row().0,
10250 snapshot.line_len(row_range.end.previous_row()),
10251 ));
10252 cursor_positions.push(anchor..anchor);
10253 }
10254
10255 self.transact(window, cx, |this, window, cx| {
10256 for row_range in row_ranges.into_iter().rev() {
10257 for row in row_range.iter_rows().rev() {
10258 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10259 let next_line_row = row.next_row();
10260 let indent = snapshot.indent_size_for_line(next_line_row);
10261 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10262
10263 let replace =
10264 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10265 " "
10266 } else {
10267 ""
10268 };
10269
10270 this.buffer.update(cx, |buffer, cx| {
10271 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10272 });
10273 }
10274 }
10275
10276 this.change_selections(Default::default(), window, cx, |s| {
10277 s.select_anchor_ranges(cursor_positions)
10278 });
10279 });
10280 }
10281
10282 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10283 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10284 self.join_lines_impl(true, window, cx);
10285 }
10286
10287 pub fn sort_lines_case_sensitive(
10288 &mut self,
10289 _: &SortLinesCaseSensitive,
10290 window: &mut Window,
10291 cx: &mut Context<Self>,
10292 ) {
10293 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10294 }
10295
10296 pub fn sort_lines_by_length(
10297 &mut self,
10298 _: &SortLinesByLength,
10299 window: &mut Window,
10300 cx: &mut Context<Self>,
10301 ) {
10302 self.manipulate_immutable_lines(window, cx, |lines| {
10303 lines.sort_by_key(|&line| line.chars().count())
10304 })
10305 }
10306
10307 pub fn sort_lines_case_insensitive(
10308 &mut self,
10309 _: &SortLinesCaseInsensitive,
10310 window: &mut Window,
10311 cx: &mut Context<Self>,
10312 ) {
10313 self.manipulate_immutable_lines(window, cx, |lines| {
10314 lines.sort_by_key(|line| line.to_lowercase())
10315 })
10316 }
10317
10318 pub fn unique_lines_case_insensitive(
10319 &mut self,
10320 _: &UniqueLinesCaseInsensitive,
10321 window: &mut Window,
10322 cx: &mut Context<Self>,
10323 ) {
10324 self.manipulate_immutable_lines(window, cx, |lines| {
10325 let mut seen = HashSet::default();
10326 lines.retain(|line| seen.insert(line.to_lowercase()));
10327 })
10328 }
10329
10330 pub fn unique_lines_case_sensitive(
10331 &mut self,
10332 _: &UniqueLinesCaseSensitive,
10333 window: &mut Window,
10334 cx: &mut Context<Self>,
10335 ) {
10336 self.manipulate_immutable_lines(window, cx, |lines| {
10337 let mut seen = HashSet::default();
10338 lines.retain(|line| seen.insert(*line));
10339 })
10340 }
10341
10342 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10343 let Some(project) = self.project.clone() else {
10344 return;
10345 };
10346 self.reload(project, window, cx)
10347 .detach_and_notify_err(window, cx);
10348 }
10349
10350 pub fn restore_file(
10351 &mut self,
10352 _: &::git::RestoreFile,
10353 window: &mut Window,
10354 cx: &mut Context<Self>,
10355 ) {
10356 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10357 let mut buffer_ids = HashSet::default();
10358 let snapshot = self.buffer().read(cx).snapshot(cx);
10359 for selection in self.selections.all::<usize>(cx) {
10360 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10361 }
10362
10363 let buffer = self.buffer().read(cx);
10364 let ranges = buffer_ids
10365 .into_iter()
10366 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10367 .collect::<Vec<_>>();
10368
10369 self.restore_hunks_in_ranges(ranges, window, cx);
10370 }
10371
10372 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10373 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10374 let selections = self
10375 .selections
10376 .all(cx)
10377 .into_iter()
10378 .map(|s| s.range())
10379 .collect();
10380 self.restore_hunks_in_ranges(selections, window, cx);
10381 }
10382
10383 pub fn restore_hunks_in_ranges(
10384 &mut self,
10385 ranges: Vec<Range<Point>>,
10386 window: &mut Window,
10387 cx: &mut Context<Editor>,
10388 ) {
10389 let mut revert_changes = HashMap::default();
10390 let chunk_by = self
10391 .snapshot(window, cx)
10392 .hunks_for_ranges(ranges)
10393 .into_iter()
10394 .chunk_by(|hunk| hunk.buffer_id);
10395 for (buffer_id, hunks) in &chunk_by {
10396 let hunks = hunks.collect::<Vec<_>>();
10397 for hunk in &hunks {
10398 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10399 }
10400 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10401 }
10402 drop(chunk_by);
10403 if !revert_changes.is_empty() {
10404 self.transact(window, cx, |editor, window, cx| {
10405 editor.restore(revert_changes, window, cx);
10406 });
10407 }
10408 }
10409
10410 pub fn open_active_item_in_terminal(
10411 &mut self,
10412 _: &OpenInTerminal,
10413 window: &mut Window,
10414 cx: &mut Context<Self>,
10415 ) {
10416 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10417 let project_path = buffer.read(cx).project_path(cx)?;
10418 let project = self.project.as_ref()?.read(cx);
10419 let entry = project.entry_for_path(&project_path, cx)?;
10420 let parent = match &entry.canonical_path {
10421 Some(canonical_path) => canonical_path.to_path_buf(),
10422 None => project.absolute_path(&project_path, cx)?,
10423 }
10424 .parent()?
10425 .to_path_buf();
10426 Some(parent)
10427 }) {
10428 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10429 }
10430 }
10431
10432 fn set_breakpoint_context_menu(
10433 &mut self,
10434 display_row: DisplayRow,
10435 position: Option<Anchor>,
10436 clicked_point: gpui::Point<Pixels>,
10437 window: &mut Window,
10438 cx: &mut Context<Self>,
10439 ) {
10440 let source = self
10441 .buffer
10442 .read(cx)
10443 .snapshot(cx)
10444 .anchor_before(Point::new(display_row.0, 0u32));
10445
10446 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10447
10448 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10449 self,
10450 source,
10451 clicked_point,
10452 context_menu,
10453 window,
10454 cx,
10455 );
10456 }
10457
10458 fn add_edit_breakpoint_block(
10459 &mut self,
10460 anchor: Anchor,
10461 breakpoint: &Breakpoint,
10462 edit_action: BreakpointPromptEditAction,
10463 window: &mut Window,
10464 cx: &mut Context<Self>,
10465 ) {
10466 let weak_editor = cx.weak_entity();
10467 let bp_prompt = cx.new(|cx| {
10468 BreakpointPromptEditor::new(
10469 weak_editor,
10470 anchor,
10471 breakpoint.clone(),
10472 edit_action,
10473 window,
10474 cx,
10475 )
10476 });
10477
10478 let height = bp_prompt.update(cx, |this, cx| {
10479 this.prompt
10480 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10481 });
10482 let cloned_prompt = bp_prompt.clone();
10483 let blocks = vec![BlockProperties {
10484 style: BlockStyle::Sticky,
10485 placement: BlockPlacement::Above(anchor),
10486 height: Some(height),
10487 render: Arc::new(move |cx| {
10488 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10489 cloned_prompt.clone().into_any_element()
10490 }),
10491 priority: 0,
10492 }];
10493
10494 let focus_handle = bp_prompt.focus_handle(cx);
10495 window.focus(&focus_handle);
10496
10497 let block_ids = self.insert_blocks(blocks, None, cx);
10498 bp_prompt.update(cx, |prompt, _| {
10499 prompt.add_block_ids(block_ids);
10500 });
10501 }
10502
10503 pub(crate) fn breakpoint_at_row(
10504 &self,
10505 row: u32,
10506 window: &mut Window,
10507 cx: &mut Context<Self>,
10508 ) -> Option<(Anchor, Breakpoint)> {
10509 let snapshot = self.snapshot(window, cx);
10510 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
10511
10512 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10513 }
10514
10515 pub(crate) fn breakpoint_at_anchor(
10516 &self,
10517 breakpoint_position: Anchor,
10518 snapshot: &EditorSnapshot,
10519 cx: &mut Context<Self>,
10520 ) -> Option<(Anchor, Breakpoint)> {
10521 let project = self.project.clone()?;
10522
10523 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
10524 snapshot
10525 .buffer_snapshot
10526 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
10527 })?;
10528
10529 let enclosing_excerpt = breakpoint_position.excerpt_id;
10530 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
10531 let buffer_snapshot = buffer.read(cx).snapshot();
10532
10533 let row = buffer_snapshot
10534 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10535 .row;
10536
10537 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
10538 let anchor_end = snapshot
10539 .buffer_snapshot
10540 .anchor_after(Point::new(row, line_len));
10541
10542 let bp = self
10543 .breakpoint_store
10544 .as_ref()?
10545 .read_with(cx, |breakpoint_store, cx| {
10546 breakpoint_store
10547 .breakpoints(
10548 &buffer,
10549 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10550 &buffer_snapshot,
10551 cx,
10552 )
10553 .next()
10554 .and_then(|(bp, _)| {
10555 let breakpoint_row = buffer_snapshot
10556 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10557 .row;
10558
10559 if breakpoint_row == row {
10560 snapshot
10561 .buffer_snapshot
10562 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10563 .map(|position| (position, bp.bp.clone()))
10564 } else {
10565 None
10566 }
10567 })
10568 });
10569 bp
10570 }
10571
10572 pub fn edit_log_breakpoint(
10573 &mut self,
10574 _: &EditLogBreakpoint,
10575 window: &mut Window,
10576 cx: &mut Context<Self>,
10577 ) {
10578 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10579 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10580 message: None,
10581 state: BreakpointState::Enabled,
10582 condition: None,
10583 hit_condition: None,
10584 });
10585
10586 self.add_edit_breakpoint_block(
10587 anchor,
10588 &breakpoint,
10589 BreakpointPromptEditAction::Log,
10590 window,
10591 cx,
10592 );
10593 }
10594 }
10595
10596 fn breakpoints_at_cursors(
10597 &self,
10598 window: &mut Window,
10599 cx: &mut Context<Self>,
10600 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10601 let snapshot = self.snapshot(window, cx);
10602 let cursors = self
10603 .selections
10604 .disjoint_anchors()
10605 .into_iter()
10606 .map(|selection| {
10607 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
10608
10609 let breakpoint_position = self
10610 .breakpoint_at_row(cursor_position.row, window, cx)
10611 .map(|bp| bp.0)
10612 .unwrap_or_else(|| {
10613 snapshot
10614 .display_snapshot
10615 .buffer_snapshot
10616 .anchor_after(Point::new(cursor_position.row, 0))
10617 });
10618
10619 let breakpoint = self
10620 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10621 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10622
10623 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10624 })
10625 // 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.
10626 .collect::<HashMap<Anchor, _>>();
10627
10628 cursors.into_iter().collect()
10629 }
10630
10631 pub fn enable_breakpoint(
10632 &mut self,
10633 _: &crate::actions::EnableBreakpoint,
10634 window: &mut Window,
10635 cx: &mut Context<Self>,
10636 ) {
10637 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10638 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10639 continue;
10640 };
10641 self.edit_breakpoint_at_anchor(
10642 anchor,
10643 breakpoint,
10644 BreakpointEditAction::InvertState,
10645 cx,
10646 );
10647 }
10648 }
10649
10650 pub fn disable_breakpoint(
10651 &mut self,
10652 _: &crate::actions::DisableBreakpoint,
10653 window: &mut Window,
10654 cx: &mut Context<Self>,
10655 ) {
10656 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10657 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10658 continue;
10659 };
10660 self.edit_breakpoint_at_anchor(
10661 anchor,
10662 breakpoint,
10663 BreakpointEditAction::InvertState,
10664 cx,
10665 );
10666 }
10667 }
10668
10669 pub fn toggle_breakpoint(
10670 &mut self,
10671 _: &crate::actions::ToggleBreakpoint,
10672 window: &mut Window,
10673 cx: &mut Context<Self>,
10674 ) {
10675 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10676 if let Some(breakpoint) = breakpoint {
10677 self.edit_breakpoint_at_anchor(
10678 anchor,
10679 breakpoint,
10680 BreakpointEditAction::Toggle,
10681 cx,
10682 );
10683 } else {
10684 self.edit_breakpoint_at_anchor(
10685 anchor,
10686 Breakpoint::new_standard(),
10687 BreakpointEditAction::Toggle,
10688 cx,
10689 );
10690 }
10691 }
10692 }
10693
10694 pub fn edit_breakpoint_at_anchor(
10695 &mut self,
10696 breakpoint_position: Anchor,
10697 breakpoint: Breakpoint,
10698 edit_action: BreakpointEditAction,
10699 cx: &mut Context<Self>,
10700 ) {
10701 let Some(breakpoint_store) = &self.breakpoint_store else {
10702 return;
10703 };
10704
10705 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
10706 if breakpoint_position == Anchor::min() {
10707 self.buffer()
10708 .read(cx)
10709 .excerpt_buffer_ids()
10710 .into_iter()
10711 .next()
10712 } else {
10713 None
10714 }
10715 }) else {
10716 return;
10717 };
10718
10719 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
10720 return;
10721 };
10722
10723 breakpoint_store.update(cx, |breakpoint_store, cx| {
10724 breakpoint_store.toggle_breakpoint(
10725 buffer,
10726 BreakpointWithPosition {
10727 position: breakpoint_position.text_anchor,
10728 bp: breakpoint,
10729 },
10730 edit_action,
10731 cx,
10732 );
10733 });
10734
10735 cx.notify();
10736 }
10737
10738 #[cfg(any(test, feature = "test-support"))]
10739 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10740 self.breakpoint_store.clone()
10741 }
10742
10743 pub fn prepare_restore_change(
10744 &self,
10745 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10746 hunk: &MultiBufferDiffHunk,
10747 cx: &mut App,
10748 ) -> Option<()> {
10749 if hunk.is_created_file() {
10750 return None;
10751 }
10752 let buffer = self.buffer.read(cx);
10753 let diff = buffer.diff_for(hunk.buffer_id)?;
10754 let buffer = buffer.buffer(hunk.buffer_id)?;
10755 let buffer = buffer.read(cx);
10756 let original_text = diff
10757 .read(cx)
10758 .base_text()
10759 .as_rope()
10760 .slice(hunk.diff_base_byte_range.clone());
10761 let buffer_snapshot = buffer.snapshot();
10762 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
10763 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
10764 probe
10765 .0
10766 .start
10767 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
10768 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
10769 }) {
10770 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
10771 Some(())
10772 } else {
10773 None
10774 }
10775 }
10776
10777 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
10778 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
10779 }
10780
10781 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
10782 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
10783 }
10784
10785 fn manipulate_lines<M>(
10786 &mut self,
10787 window: &mut Window,
10788 cx: &mut Context<Self>,
10789 mut manipulate: M,
10790 ) where
10791 M: FnMut(&str) -> LineManipulationResult,
10792 {
10793 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10794
10795 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10796 let buffer = self.buffer.read(cx).snapshot(cx);
10797
10798 let mut edits = Vec::new();
10799
10800 let selections = self.selections.all::<Point>(cx);
10801 let mut selections = selections.iter().peekable();
10802 let mut contiguous_row_selections = Vec::new();
10803 let mut new_selections = Vec::new();
10804 let mut added_lines = 0;
10805 let mut removed_lines = 0;
10806
10807 while let Some(selection) = selections.next() {
10808 let (start_row, end_row) = consume_contiguous_rows(
10809 &mut contiguous_row_selections,
10810 selection,
10811 &display_map,
10812 &mut selections,
10813 );
10814
10815 let start_point = Point::new(start_row.0, 0);
10816 let end_point = Point::new(
10817 end_row.previous_row().0,
10818 buffer.line_len(end_row.previous_row()),
10819 );
10820 let text = buffer
10821 .text_for_range(start_point..end_point)
10822 .collect::<String>();
10823
10824 let LineManipulationResult {
10825 new_text,
10826 line_count_before,
10827 line_count_after,
10828 } = manipulate(&text);
10829
10830 edits.push((start_point..end_point, new_text));
10831
10832 // Selections must change based on added and removed line count
10833 let start_row =
10834 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
10835 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
10836 new_selections.push(Selection {
10837 id: selection.id,
10838 start: start_row,
10839 end: end_row,
10840 goal: SelectionGoal::None,
10841 reversed: selection.reversed,
10842 });
10843
10844 if line_count_after > line_count_before {
10845 added_lines += line_count_after - line_count_before;
10846 } else if line_count_before > line_count_after {
10847 removed_lines += line_count_before - line_count_after;
10848 }
10849 }
10850
10851 self.transact(window, cx, |this, window, cx| {
10852 let buffer = this.buffer.update(cx, |buffer, cx| {
10853 buffer.edit(edits, None, cx);
10854 buffer.snapshot(cx)
10855 });
10856
10857 // Recalculate offsets on newly edited buffer
10858 let new_selections = new_selections
10859 .iter()
10860 .map(|s| {
10861 let start_point = Point::new(s.start.0, 0);
10862 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
10863 Selection {
10864 id: s.id,
10865 start: buffer.point_to_offset(start_point),
10866 end: buffer.point_to_offset(end_point),
10867 goal: s.goal,
10868 reversed: s.reversed,
10869 }
10870 })
10871 .collect();
10872
10873 this.change_selections(Default::default(), window, cx, |s| {
10874 s.select(new_selections);
10875 });
10876
10877 this.request_autoscroll(Autoscroll::fit(), cx);
10878 });
10879 }
10880
10881 fn manipulate_immutable_lines<Fn>(
10882 &mut self,
10883 window: &mut Window,
10884 cx: &mut Context<Self>,
10885 mut callback: Fn,
10886 ) where
10887 Fn: FnMut(&mut Vec<&str>),
10888 {
10889 self.manipulate_lines(window, cx, |text| {
10890 let mut lines: Vec<&str> = text.split('\n').collect();
10891 let line_count_before = lines.len();
10892
10893 callback(&mut lines);
10894
10895 LineManipulationResult {
10896 new_text: lines.join("\n"),
10897 line_count_before,
10898 line_count_after: lines.len(),
10899 }
10900 });
10901 }
10902
10903 fn manipulate_mutable_lines<Fn>(
10904 &mut self,
10905 window: &mut Window,
10906 cx: &mut Context<Self>,
10907 mut callback: Fn,
10908 ) where
10909 Fn: FnMut(&mut Vec<Cow<'_, str>>),
10910 {
10911 self.manipulate_lines(window, cx, |text| {
10912 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
10913 let line_count_before = lines.len();
10914
10915 callback(&mut lines);
10916
10917 LineManipulationResult {
10918 new_text: lines.join("\n"),
10919 line_count_before,
10920 line_count_after: lines.len(),
10921 }
10922 });
10923 }
10924
10925 pub fn convert_indentation_to_spaces(
10926 &mut self,
10927 _: &ConvertIndentationToSpaces,
10928 window: &mut Window,
10929 cx: &mut Context<Self>,
10930 ) {
10931 let settings = self.buffer.read(cx).language_settings(cx);
10932 let tab_size = settings.tab_size.get() as usize;
10933
10934 self.manipulate_mutable_lines(window, cx, |lines| {
10935 // Allocates a reasonably sized scratch buffer once for the whole loop
10936 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
10937 // Avoids recomputing spaces that could be inserted many times
10938 let space_cache: Vec<Vec<char>> = (1..=tab_size)
10939 .map(|n| IndentSize::spaces(n as u32).chars().collect())
10940 .collect();
10941
10942 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
10943 let mut chars = line.as_ref().chars();
10944 let mut col = 0;
10945 let mut changed = false;
10946
10947 while let Some(ch) = chars.next() {
10948 match ch {
10949 ' ' => {
10950 reindented_line.push(' ');
10951 col += 1;
10952 }
10953 '\t' => {
10954 // \t are converted to spaces depending on the current column
10955 let spaces_len = tab_size - (col % tab_size);
10956 reindented_line.extend(&space_cache[spaces_len - 1]);
10957 col += spaces_len;
10958 changed = true;
10959 }
10960 _ => {
10961 // If we dont append before break, the character is consumed
10962 reindented_line.push(ch);
10963 break;
10964 }
10965 }
10966 }
10967
10968 if !changed {
10969 reindented_line.clear();
10970 continue;
10971 }
10972 // Append the rest of the line and replace old reference with new one
10973 reindented_line.extend(chars);
10974 *line = Cow::Owned(reindented_line.clone());
10975 reindented_line.clear();
10976 }
10977 });
10978 }
10979
10980 pub fn convert_indentation_to_tabs(
10981 &mut self,
10982 _: &ConvertIndentationToTabs,
10983 window: &mut Window,
10984 cx: &mut Context<Self>,
10985 ) {
10986 let settings = self.buffer.read(cx).language_settings(cx);
10987 let tab_size = settings.tab_size.get() as usize;
10988
10989 self.manipulate_mutable_lines(window, cx, |lines| {
10990 // Allocates a reasonably sized buffer once for the whole loop
10991 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
10992 // Avoids recomputing spaces that could be inserted many times
10993 let space_cache: Vec<Vec<char>> = (1..=tab_size)
10994 .map(|n| IndentSize::spaces(n as u32).chars().collect())
10995 .collect();
10996
10997 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
10998 let mut chars = line.chars();
10999 let mut spaces_count = 0;
11000 let mut first_non_indent_char = None;
11001 let mut changed = false;
11002
11003 while let Some(ch) = chars.next() {
11004 match ch {
11005 ' ' => {
11006 // Keep track of spaces. Append \t when we reach tab_size
11007 spaces_count += 1;
11008 changed = true;
11009 if spaces_count == tab_size {
11010 reindented_line.push('\t');
11011 spaces_count = 0;
11012 }
11013 }
11014 '\t' => {
11015 reindented_line.push('\t');
11016 spaces_count = 0;
11017 }
11018 _ => {
11019 // Dont append it yet, we might have remaining spaces
11020 first_non_indent_char = Some(ch);
11021 break;
11022 }
11023 }
11024 }
11025
11026 if !changed {
11027 reindented_line.clear();
11028 continue;
11029 }
11030 // Remaining spaces that didn't make a full tab stop
11031 if spaces_count > 0 {
11032 reindented_line.extend(&space_cache[spaces_count - 1]);
11033 }
11034 // If we consume an extra character that was not indentation, add it back
11035 if let Some(extra_char) = first_non_indent_char {
11036 reindented_line.push(extra_char);
11037 }
11038 // Append the rest of the line and replace old reference with new one
11039 reindented_line.extend(chars);
11040 *line = Cow::Owned(reindented_line.clone());
11041 reindented_line.clear();
11042 }
11043 });
11044 }
11045
11046 pub fn convert_to_upper_case(
11047 &mut self,
11048 _: &ConvertToUpperCase,
11049 window: &mut Window,
11050 cx: &mut Context<Self>,
11051 ) {
11052 self.manipulate_text(window, cx, |text| text.to_uppercase())
11053 }
11054
11055 pub fn convert_to_lower_case(
11056 &mut self,
11057 _: &ConvertToLowerCase,
11058 window: &mut Window,
11059 cx: &mut Context<Self>,
11060 ) {
11061 self.manipulate_text(window, cx, |text| text.to_lowercase())
11062 }
11063
11064 pub fn convert_to_title_case(
11065 &mut self,
11066 _: &ConvertToTitleCase,
11067 window: &mut Window,
11068 cx: &mut Context<Self>,
11069 ) {
11070 self.manipulate_text(window, cx, |text| {
11071 text.split('\n')
11072 .map(|line| line.to_case(Case::Title))
11073 .join("\n")
11074 })
11075 }
11076
11077 pub fn convert_to_snake_case(
11078 &mut self,
11079 _: &ConvertToSnakeCase,
11080 window: &mut Window,
11081 cx: &mut Context<Self>,
11082 ) {
11083 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11084 }
11085
11086 pub fn convert_to_kebab_case(
11087 &mut self,
11088 _: &ConvertToKebabCase,
11089 window: &mut Window,
11090 cx: &mut Context<Self>,
11091 ) {
11092 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11093 }
11094
11095 pub fn convert_to_upper_camel_case(
11096 &mut self,
11097 _: &ConvertToUpperCamelCase,
11098 window: &mut Window,
11099 cx: &mut Context<Self>,
11100 ) {
11101 self.manipulate_text(window, cx, |text| {
11102 text.split('\n')
11103 .map(|line| line.to_case(Case::UpperCamel))
11104 .join("\n")
11105 })
11106 }
11107
11108 pub fn convert_to_lower_camel_case(
11109 &mut self,
11110 _: &ConvertToLowerCamelCase,
11111 window: &mut Window,
11112 cx: &mut Context<Self>,
11113 ) {
11114 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11115 }
11116
11117 pub fn convert_to_opposite_case(
11118 &mut self,
11119 _: &ConvertToOppositeCase,
11120 window: &mut Window,
11121 cx: &mut Context<Self>,
11122 ) {
11123 self.manipulate_text(window, cx, |text| {
11124 text.chars()
11125 .fold(String::with_capacity(text.len()), |mut t, c| {
11126 if c.is_uppercase() {
11127 t.extend(c.to_lowercase());
11128 } else {
11129 t.extend(c.to_uppercase());
11130 }
11131 t
11132 })
11133 })
11134 }
11135
11136 pub fn convert_to_sentence_case(
11137 &mut self,
11138 _: &ConvertToSentenceCase,
11139 window: &mut Window,
11140 cx: &mut Context<Self>,
11141 ) {
11142 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11143 }
11144
11145 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11146 self.manipulate_text(window, cx, |text| {
11147 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11148 if has_upper_case_characters {
11149 text.to_lowercase()
11150 } else {
11151 text.to_uppercase()
11152 }
11153 })
11154 }
11155
11156 pub fn convert_to_rot13(
11157 &mut self,
11158 _: &ConvertToRot13,
11159 window: &mut Window,
11160 cx: &mut Context<Self>,
11161 ) {
11162 self.manipulate_text(window, cx, |text| {
11163 text.chars()
11164 .map(|c| match c {
11165 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11166 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11167 _ => c,
11168 })
11169 .collect()
11170 })
11171 }
11172
11173 pub fn convert_to_rot47(
11174 &mut self,
11175 _: &ConvertToRot47,
11176 window: &mut Window,
11177 cx: &mut Context<Self>,
11178 ) {
11179 self.manipulate_text(window, cx, |text| {
11180 text.chars()
11181 .map(|c| {
11182 let code_point = c as u32;
11183 if code_point >= 33 && code_point <= 126 {
11184 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11185 }
11186 c
11187 })
11188 .collect()
11189 })
11190 }
11191
11192 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11193 where
11194 Fn: FnMut(&str) -> String,
11195 {
11196 let buffer = self.buffer.read(cx).snapshot(cx);
11197
11198 let mut new_selections = Vec::new();
11199 let mut edits = Vec::new();
11200 let mut selection_adjustment = 0i32;
11201
11202 for selection in self.selections.all::<usize>(cx) {
11203 let selection_is_empty = selection.is_empty();
11204
11205 let (start, end) = if selection_is_empty {
11206 let (word_range, _) = buffer.surrounding_word(selection.start, false);
11207 (word_range.start, word_range.end)
11208 } else {
11209 (selection.start, selection.end)
11210 };
11211
11212 let text = buffer.text_for_range(start..end).collect::<String>();
11213 let old_length = text.len() as i32;
11214 let text = callback(&text);
11215
11216 new_selections.push(Selection {
11217 start: (start as i32 - selection_adjustment) as usize,
11218 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11219 goal: SelectionGoal::None,
11220 ..selection
11221 });
11222
11223 selection_adjustment += old_length - text.len() as i32;
11224
11225 edits.push((start..end, text));
11226 }
11227
11228 self.transact(window, cx, |this, window, cx| {
11229 this.buffer.update(cx, |buffer, cx| {
11230 buffer.edit(edits, None, cx);
11231 });
11232
11233 this.change_selections(Default::default(), window, cx, |s| {
11234 s.select(new_selections);
11235 });
11236
11237 this.request_autoscroll(Autoscroll::fit(), cx);
11238 });
11239 }
11240
11241 pub fn move_selection_on_drop(
11242 &mut self,
11243 selection: &Selection<Anchor>,
11244 target: DisplayPoint,
11245 is_cut: bool,
11246 window: &mut Window,
11247 cx: &mut Context<Self>,
11248 ) {
11249 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11250 let buffer = &display_map.buffer_snapshot;
11251 let mut edits = Vec::new();
11252 let insert_point = display_map
11253 .clip_point(target, Bias::Left)
11254 .to_point(&display_map);
11255 let text = buffer
11256 .text_for_range(selection.start..selection.end)
11257 .collect::<String>();
11258 if is_cut {
11259 edits.push(((selection.start..selection.end), String::new()));
11260 }
11261 let insert_anchor = buffer.anchor_before(insert_point);
11262 edits.push(((insert_anchor..insert_anchor), text));
11263 let last_edit_start = insert_anchor.bias_left(buffer);
11264 let last_edit_end = insert_anchor.bias_right(buffer);
11265 self.transact(window, cx, |this, window, cx| {
11266 this.buffer.update(cx, |buffer, cx| {
11267 buffer.edit(edits, None, cx);
11268 });
11269 this.change_selections(Default::default(), window, cx, |s| {
11270 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11271 });
11272 });
11273 }
11274
11275 pub fn clear_selection_drag_state(&mut self) {
11276 self.selection_drag_state = SelectionDragState::None;
11277 }
11278
11279 pub fn duplicate(
11280 &mut self,
11281 upwards: bool,
11282 whole_lines: bool,
11283 window: &mut Window,
11284 cx: &mut Context<Self>,
11285 ) {
11286 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11287
11288 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11289 let buffer = &display_map.buffer_snapshot;
11290 let selections = self.selections.all::<Point>(cx);
11291
11292 let mut edits = Vec::new();
11293 let mut selections_iter = selections.iter().peekable();
11294 while let Some(selection) = selections_iter.next() {
11295 let mut rows = selection.spanned_rows(false, &display_map);
11296 // duplicate line-wise
11297 if whole_lines || selection.start == selection.end {
11298 // Avoid duplicating the same lines twice.
11299 while let Some(next_selection) = selections_iter.peek() {
11300 let next_rows = next_selection.spanned_rows(false, &display_map);
11301 if next_rows.start < rows.end {
11302 rows.end = next_rows.end;
11303 selections_iter.next().unwrap();
11304 } else {
11305 break;
11306 }
11307 }
11308
11309 // Copy the text from the selected row region and splice it either at the start
11310 // or end of the region.
11311 let start = Point::new(rows.start.0, 0);
11312 let end = Point::new(
11313 rows.end.previous_row().0,
11314 buffer.line_len(rows.end.previous_row()),
11315 );
11316 let text = buffer
11317 .text_for_range(start..end)
11318 .chain(Some("\n"))
11319 .collect::<String>();
11320 let insert_location = if upwards {
11321 Point::new(rows.end.0, 0)
11322 } else {
11323 start
11324 };
11325 edits.push((insert_location..insert_location, text));
11326 } else {
11327 // duplicate character-wise
11328 let start = selection.start;
11329 let end = selection.end;
11330 let text = buffer.text_for_range(start..end).collect::<String>();
11331 edits.push((selection.end..selection.end, text));
11332 }
11333 }
11334
11335 self.transact(window, cx, |this, _, cx| {
11336 this.buffer.update(cx, |buffer, cx| {
11337 buffer.edit(edits, None, cx);
11338 });
11339
11340 this.request_autoscroll(Autoscroll::fit(), cx);
11341 });
11342 }
11343
11344 pub fn duplicate_line_up(
11345 &mut self,
11346 _: &DuplicateLineUp,
11347 window: &mut Window,
11348 cx: &mut Context<Self>,
11349 ) {
11350 self.duplicate(true, true, window, cx);
11351 }
11352
11353 pub fn duplicate_line_down(
11354 &mut self,
11355 _: &DuplicateLineDown,
11356 window: &mut Window,
11357 cx: &mut Context<Self>,
11358 ) {
11359 self.duplicate(false, true, window, cx);
11360 }
11361
11362 pub fn duplicate_selection(
11363 &mut self,
11364 _: &DuplicateSelection,
11365 window: &mut Window,
11366 cx: &mut Context<Self>,
11367 ) {
11368 self.duplicate(false, false, window, cx);
11369 }
11370
11371 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11372 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11373 if self.mode.is_single_line() {
11374 cx.propagate();
11375 return;
11376 }
11377
11378 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11379 let buffer = self.buffer.read(cx).snapshot(cx);
11380
11381 let mut edits = Vec::new();
11382 let mut unfold_ranges = Vec::new();
11383 let mut refold_creases = Vec::new();
11384
11385 let selections = self.selections.all::<Point>(cx);
11386 let mut selections = selections.iter().peekable();
11387 let mut contiguous_row_selections = Vec::new();
11388 let mut new_selections = Vec::new();
11389
11390 while let Some(selection) = selections.next() {
11391 // Find all the selections that span a contiguous row range
11392 let (start_row, end_row) = consume_contiguous_rows(
11393 &mut contiguous_row_selections,
11394 selection,
11395 &display_map,
11396 &mut selections,
11397 );
11398
11399 // Move the text spanned by the row range to be before the line preceding the row range
11400 if start_row.0 > 0 {
11401 let range_to_move = Point::new(
11402 start_row.previous_row().0,
11403 buffer.line_len(start_row.previous_row()),
11404 )
11405 ..Point::new(
11406 end_row.previous_row().0,
11407 buffer.line_len(end_row.previous_row()),
11408 );
11409 let insertion_point = display_map
11410 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11411 .0;
11412
11413 // Don't move lines across excerpts
11414 if buffer
11415 .excerpt_containing(insertion_point..range_to_move.end)
11416 .is_some()
11417 {
11418 let text = buffer
11419 .text_for_range(range_to_move.clone())
11420 .flat_map(|s| s.chars())
11421 .skip(1)
11422 .chain(['\n'])
11423 .collect::<String>();
11424
11425 edits.push((
11426 buffer.anchor_after(range_to_move.start)
11427 ..buffer.anchor_before(range_to_move.end),
11428 String::new(),
11429 ));
11430 let insertion_anchor = buffer.anchor_after(insertion_point);
11431 edits.push((insertion_anchor..insertion_anchor, text));
11432
11433 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11434
11435 // Move selections up
11436 new_selections.extend(contiguous_row_selections.drain(..).map(
11437 |mut selection| {
11438 selection.start.row -= row_delta;
11439 selection.end.row -= row_delta;
11440 selection
11441 },
11442 ));
11443
11444 // Move folds up
11445 unfold_ranges.push(range_to_move.clone());
11446 for fold in display_map.folds_in_range(
11447 buffer.anchor_before(range_to_move.start)
11448 ..buffer.anchor_after(range_to_move.end),
11449 ) {
11450 let mut start = fold.range.start.to_point(&buffer);
11451 let mut end = fold.range.end.to_point(&buffer);
11452 start.row -= row_delta;
11453 end.row -= row_delta;
11454 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11455 }
11456 }
11457 }
11458
11459 // If we didn't move line(s), preserve the existing selections
11460 new_selections.append(&mut contiguous_row_selections);
11461 }
11462
11463 self.transact(window, cx, |this, window, cx| {
11464 this.unfold_ranges(&unfold_ranges, true, true, cx);
11465 this.buffer.update(cx, |buffer, cx| {
11466 for (range, text) in edits {
11467 buffer.edit([(range, text)], None, cx);
11468 }
11469 });
11470 this.fold_creases(refold_creases, true, window, cx);
11471 this.change_selections(Default::default(), window, cx, |s| {
11472 s.select(new_selections);
11473 })
11474 });
11475 }
11476
11477 pub fn move_line_down(
11478 &mut self,
11479 _: &MoveLineDown,
11480 window: &mut Window,
11481 cx: &mut Context<Self>,
11482 ) {
11483 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11484 if self.mode.is_single_line() {
11485 cx.propagate();
11486 return;
11487 }
11488
11489 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11490 let buffer = self.buffer.read(cx).snapshot(cx);
11491
11492 let mut edits = Vec::new();
11493 let mut unfold_ranges = Vec::new();
11494 let mut refold_creases = Vec::new();
11495
11496 let selections = self.selections.all::<Point>(cx);
11497 let mut selections = selections.iter().peekable();
11498 let mut contiguous_row_selections = Vec::new();
11499 let mut new_selections = Vec::new();
11500
11501 while let Some(selection) = selections.next() {
11502 // Find all the selections that span a contiguous row range
11503 let (start_row, end_row) = consume_contiguous_rows(
11504 &mut contiguous_row_selections,
11505 selection,
11506 &display_map,
11507 &mut selections,
11508 );
11509
11510 // Move the text spanned by the row range to be after the last line of the row range
11511 if end_row.0 <= buffer.max_point().row {
11512 let range_to_move =
11513 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11514 let insertion_point = display_map
11515 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11516 .0;
11517
11518 // Don't move lines across excerpt boundaries
11519 if buffer
11520 .excerpt_containing(range_to_move.start..insertion_point)
11521 .is_some()
11522 {
11523 let mut text = String::from("\n");
11524 text.extend(buffer.text_for_range(range_to_move.clone()));
11525 text.pop(); // Drop trailing newline
11526 edits.push((
11527 buffer.anchor_after(range_to_move.start)
11528 ..buffer.anchor_before(range_to_move.end),
11529 String::new(),
11530 ));
11531 let insertion_anchor = buffer.anchor_after(insertion_point);
11532 edits.push((insertion_anchor..insertion_anchor, text));
11533
11534 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11535
11536 // Move selections down
11537 new_selections.extend(contiguous_row_selections.drain(..).map(
11538 |mut selection| {
11539 selection.start.row += row_delta;
11540 selection.end.row += row_delta;
11541 selection
11542 },
11543 ));
11544
11545 // Move folds down
11546 unfold_ranges.push(range_to_move.clone());
11547 for fold in display_map.folds_in_range(
11548 buffer.anchor_before(range_to_move.start)
11549 ..buffer.anchor_after(range_to_move.end),
11550 ) {
11551 let mut start = fold.range.start.to_point(&buffer);
11552 let mut end = fold.range.end.to_point(&buffer);
11553 start.row += row_delta;
11554 end.row += row_delta;
11555 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11556 }
11557 }
11558 }
11559
11560 // If we didn't move line(s), preserve the existing selections
11561 new_selections.append(&mut contiguous_row_selections);
11562 }
11563
11564 self.transact(window, cx, |this, window, cx| {
11565 this.unfold_ranges(&unfold_ranges, true, true, cx);
11566 this.buffer.update(cx, |buffer, cx| {
11567 for (range, text) in edits {
11568 buffer.edit([(range, text)], None, cx);
11569 }
11570 });
11571 this.fold_creases(refold_creases, true, window, cx);
11572 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
11573 });
11574 }
11575
11576 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11577 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11578 let text_layout_details = &self.text_layout_details(window);
11579 self.transact(window, cx, |this, window, cx| {
11580 let edits = this.change_selections(Default::default(), window, cx, |s| {
11581 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11582 s.move_with(|display_map, selection| {
11583 if !selection.is_empty() {
11584 return;
11585 }
11586
11587 let mut head = selection.head();
11588 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11589 if head.column() == display_map.line_len(head.row()) {
11590 transpose_offset = display_map
11591 .buffer_snapshot
11592 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11593 }
11594
11595 if transpose_offset == 0 {
11596 return;
11597 }
11598
11599 *head.column_mut() += 1;
11600 head = display_map.clip_point(head, Bias::Right);
11601 let goal = SelectionGoal::HorizontalPosition(
11602 display_map
11603 .x_for_display_point(head, text_layout_details)
11604 .into(),
11605 );
11606 selection.collapse_to(head, goal);
11607
11608 let transpose_start = display_map
11609 .buffer_snapshot
11610 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11611 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
11612 let transpose_end = display_map
11613 .buffer_snapshot
11614 .clip_offset(transpose_offset + 1, Bias::Right);
11615 if let Some(ch) =
11616 display_map.buffer_snapshot.chars_at(transpose_start).next()
11617 {
11618 edits.push((transpose_start..transpose_offset, String::new()));
11619 edits.push((transpose_end..transpose_end, ch.to_string()));
11620 }
11621 }
11622 });
11623 edits
11624 });
11625 this.buffer
11626 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11627 let selections = this.selections.all::<usize>(cx);
11628 this.change_selections(Default::default(), window, cx, |s| {
11629 s.select(selections);
11630 });
11631 });
11632 }
11633
11634 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
11635 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11636 if self.mode.is_single_line() {
11637 cx.propagate();
11638 return;
11639 }
11640
11641 self.rewrap_impl(RewrapOptions::default(), cx)
11642 }
11643
11644 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
11645 let buffer = self.buffer.read(cx).snapshot(cx);
11646 let selections = self.selections.all::<Point>(cx);
11647
11648 // Split selections to respect paragraph, indent, and comment prefix boundaries.
11649 let wrap_ranges = selections.into_iter().flat_map(|selection| {
11650 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
11651 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11652 .peekable();
11653
11654 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
11655 row
11656 } else {
11657 return Vec::new();
11658 };
11659
11660 let language_settings = buffer.language_settings_at(selection.head(), cx);
11661 let language_scope = buffer.language_scope_at(selection.head());
11662
11663 let indent_and_prefix_for_row =
11664 |row: u32| -> (IndentSize, Option<String>, Option<String>) {
11665 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
11666 let (comment_prefix, rewrap_prefix) =
11667 if let Some(language_scope) = &language_scope {
11668 let indent_end = Point::new(row, indent.len);
11669 let comment_prefix = language_scope
11670 .line_comment_prefixes()
11671 .iter()
11672 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11673 .map(|prefix| prefix.to_string());
11674 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11675 let line_text_after_indent = buffer
11676 .text_for_range(indent_end..line_end)
11677 .collect::<String>();
11678 let rewrap_prefix = language_scope
11679 .rewrap_prefixes()
11680 .iter()
11681 .find_map(|prefix_regex| {
11682 prefix_regex.find(&line_text_after_indent).map(|mat| {
11683 if mat.start() == 0 {
11684 Some(mat.as_str().to_string())
11685 } else {
11686 None
11687 }
11688 })
11689 })
11690 .flatten();
11691 (comment_prefix, rewrap_prefix)
11692 } else {
11693 (None, None)
11694 };
11695 (indent, comment_prefix, rewrap_prefix)
11696 };
11697
11698 let mut ranges = Vec::new();
11699 let from_empty_selection = selection.is_empty();
11700
11701 let mut current_range_start = first_row;
11702 let mut prev_row = first_row;
11703 let (
11704 mut current_range_indent,
11705 mut current_range_comment_prefix,
11706 mut current_range_rewrap_prefix,
11707 ) = indent_and_prefix_for_row(first_row);
11708
11709 for row in non_blank_rows_iter.skip(1) {
11710 let has_paragraph_break = row > prev_row + 1;
11711
11712 let (row_indent, row_comment_prefix, row_rewrap_prefix) =
11713 indent_and_prefix_for_row(row);
11714
11715 let has_indent_change = row_indent != current_range_indent;
11716 let has_comment_change = row_comment_prefix != current_range_comment_prefix;
11717
11718 let has_boundary_change = has_comment_change
11719 || row_rewrap_prefix.is_some()
11720 || (has_indent_change && current_range_comment_prefix.is_some());
11721
11722 if has_paragraph_break || has_boundary_change {
11723 ranges.push((
11724 language_settings.clone(),
11725 Point::new(current_range_start, 0)
11726 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
11727 current_range_indent,
11728 current_range_comment_prefix.clone(),
11729 current_range_rewrap_prefix.clone(),
11730 from_empty_selection,
11731 ));
11732 current_range_start = row;
11733 current_range_indent = row_indent;
11734 current_range_comment_prefix = row_comment_prefix;
11735 current_range_rewrap_prefix = row_rewrap_prefix;
11736 }
11737 prev_row = row;
11738 }
11739
11740 ranges.push((
11741 language_settings.clone(),
11742 Point::new(current_range_start, 0)
11743 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
11744 current_range_indent,
11745 current_range_comment_prefix,
11746 current_range_rewrap_prefix,
11747 from_empty_selection,
11748 ));
11749
11750 ranges
11751 });
11752
11753 let mut edits = Vec::new();
11754 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
11755
11756 for (
11757 language_settings,
11758 wrap_range,
11759 indent_size,
11760 comment_prefix,
11761 rewrap_prefix,
11762 from_empty_selection,
11763 ) in wrap_ranges
11764 {
11765 let mut start_row = wrap_range.start.row;
11766 let mut end_row = wrap_range.end.row;
11767
11768 // Skip selections that overlap with a range that has already been rewrapped.
11769 let selection_range = start_row..end_row;
11770 if rewrapped_row_ranges
11771 .iter()
11772 .any(|range| range.overlaps(&selection_range))
11773 {
11774 continue;
11775 }
11776
11777 let tab_size = language_settings.tab_size;
11778
11779 let indent_prefix = indent_size.chars().collect::<String>();
11780 let mut line_prefix = indent_prefix.clone();
11781 let mut inside_comment = false;
11782 if let Some(prefix) = &comment_prefix {
11783 line_prefix.push_str(prefix);
11784 inside_comment = true;
11785 }
11786 if let Some(prefix) = &rewrap_prefix {
11787 line_prefix.push_str(prefix);
11788 }
11789
11790 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
11791 RewrapBehavior::InComments => inside_comment,
11792 RewrapBehavior::InSelections => !wrap_range.is_empty(),
11793 RewrapBehavior::Anywhere => true,
11794 };
11795
11796 let should_rewrap = options.override_language_settings
11797 || allow_rewrap_based_on_language
11798 || self.hard_wrap.is_some();
11799 if !should_rewrap {
11800 continue;
11801 }
11802
11803 if from_empty_selection {
11804 'expand_upwards: while start_row > 0 {
11805 let prev_row = start_row - 1;
11806 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
11807 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
11808 && !buffer.is_line_blank(MultiBufferRow(prev_row))
11809 {
11810 start_row = prev_row;
11811 } else {
11812 break 'expand_upwards;
11813 }
11814 }
11815
11816 'expand_downwards: while end_row < buffer.max_point().row {
11817 let next_row = end_row + 1;
11818 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
11819 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
11820 && !buffer.is_line_blank(MultiBufferRow(next_row))
11821 {
11822 end_row = next_row;
11823 } else {
11824 break 'expand_downwards;
11825 }
11826 }
11827 }
11828
11829 let start = Point::new(start_row, 0);
11830 let start_offset = start.to_offset(&buffer);
11831 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
11832 let selection_text = buffer.text_for_range(start..end).collect::<String>();
11833 let Some(lines_without_prefixes) = selection_text
11834 .lines()
11835 .enumerate()
11836 .map(|(ix, line)| {
11837 let line_trimmed = line.trim_start();
11838 if rewrap_prefix.is_some() && ix > 0 {
11839 Ok(line_trimmed)
11840 } else {
11841 line_trimmed
11842 .strip_prefix(&line_prefix.trim_start())
11843 .with_context(|| {
11844 format!("line did not start with prefix {line_prefix:?}: {line:?}")
11845 })
11846 }
11847 })
11848 .collect::<Result<Vec<_>, _>>()
11849 .log_err()
11850 else {
11851 continue;
11852 };
11853
11854 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
11855 buffer
11856 .language_settings_at(Point::new(start_row, 0), cx)
11857 .preferred_line_length as usize
11858 });
11859
11860 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
11861 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
11862 } else {
11863 line_prefix.clone()
11864 };
11865
11866 let wrapped_text = wrap_with_prefix(
11867 line_prefix,
11868 subsequent_lines_prefix,
11869 lines_without_prefixes.join("\n"),
11870 wrap_column,
11871 tab_size,
11872 options.preserve_existing_whitespace,
11873 );
11874
11875 // TODO: should always use char-based diff while still supporting cursor behavior that
11876 // matches vim.
11877 let mut diff_options = DiffOptions::default();
11878 if options.override_language_settings {
11879 diff_options.max_word_diff_len = 0;
11880 diff_options.max_word_diff_line_count = 0;
11881 } else {
11882 diff_options.max_word_diff_len = usize::MAX;
11883 diff_options.max_word_diff_line_count = usize::MAX;
11884 }
11885
11886 for (old_range, new_text) in
11887 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
11888 {
11889 let edit_start = buffer.anchor_after(start_offset + old_range.start);
11890 let edit_end = buffer.anchor_after(start_offset + old_range.end);
11891 edits.push((edit_start..edit_end, new_text));
11892 }
11893
11894 rewrapped_row_ranges.push(start_row..=end_row);
11895 }
11896
11897 self.buffer
11898 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11899 }
11900
11901 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
11902 let mut text = String::new();
11903 let buffer = self.buffer.read(cx).snapshot(cx);
11904 let mut selections = self.selections.all::<Point>(cx);
11905 let mut clipboard_selections = Vec::with_capacity(selections.len());
11906 {
11907 let max_point = buffer.max_point();
11908 let mut is_first = true;
11909 for selection in &mut selections {
11910 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11911 if is_entire_line {
11912 selection.start = Point::new(selection.start.row, 0);
11913 if !selection.is_empty() && selection.end.column == 0 {
11914 selection.end = cmp::min(max_point, selection.end);
11915 } else {
11916 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
11917 }
11918 selection.goal = SelectionGoal::None;
11919 }
11920 if is_first {
11921 is_first = false;
11922 } else {
11923 text += "\n";
11924 }
11925 let mut len = 0;
11926 for chunk in buffer.text_for_range(selection.start..selection.end) {
11927 text.push_str(chunk);
11928 len += chunk.len();
11929 }
11930 clipboard_selections.push(ClipboardSelection {
11931 len,
11932 is_entire_line,
11933 first_line_indent: buffer
11934 .indent_size_for_line(MultiBufferRow(selection.start.row))
11935 .len,
11936 });
11937 }
11938 }
11939
11940 self.transact(window, cx, |this, window, cx| {
11941 this.change_selections(Default::default(), window, cx, |s| {
11942 s.select(selections);
11943 });
11944 this.insert("", window, cx);
11945 });
11946 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
11947 }
11948
11949 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
11950 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11951 let item = self.cut_common(window, cx);
11952 cx.write_to_clipboard(item);
11953 }
11954
11955 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
11956 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11957 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11958 s.move_with(|snapshot, sel| {
11959 if sel.is_empty() {
11960 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
11961 }
11962 });
11963 });
11964 let item = self.cut_common(window, cx);
11965 cx.set_global(KillRing(item))
11966 }
11967
11968 pub fn kill_ring_yank(
11969 &mut self,
11970 _: &KillRingYank,
11971 window: &mut Window,
11972 cx: &mut Context<Self>,
11973 ) {
11974 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11975 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
11976 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
11977 (kill_ring.text().to_string(), kill_ring.metadata_json())
11978 } else {
11979 return;
11980 }
11981 } else {
11982 return;
11983 };
11984 self.do_paste(&text, metadata, false, window, cx);
11985 }
11986
11987 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
11988 self.do_copy(true, cx);
11989 }
11990
11991 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
11992 self.do_copy(false, cx);
11993 }
11994
11995 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
11996 let selections = self.selections.all::<Point>(cx);
11997 let buffer = self.buffer.read(cx).read(cx);
11998 let mut text = String::new();
11999
12000 let mut clipboard_selections = Vec::with_capacity(selections.len());
12001 {
12002 let max_point = buffer.max_point();
12003 let mut is_first = true;
12004 for selection in &selections {
12005 let mut start = selection.start;
12006 let mut end = selection.end;
12007 let is_entire_line = selection.is_empty() || self.selections.line_mode;
12008 if is_entire_line {
12009 start = Point::new(start.row, 0);
12010 end = cmp::min(max_point, Point::new(end.row + 1, 0));
12011 }
12012
12013 let mut trimmed_selections = Vec::new();
12014 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12015 let row = MultiBufferRow(start.row);
12016 let first_indent = buffer.indent_size_for_line(row);
12017 if first_indent.len == 0 || start.column > first_indent.len {
12018 trimmed_selections.push(start..end);
12019 } else {
12020 trimmed_selections.push(
12021 Point::new(row.0, first_indent.len)
12022 ..Point::new(row.0, buffer.line_len(row)),
12023 );
12024 for row in start.row + 1..=end.row {
12025 let mut line_len = buffer.line_len(MultiBufferRow(row));
12026 if row == end.row {
12027 line_len = end.column;
12028 }
12029 if line_len == 0 {
12030 trimmed_selections
12031 .push(Point::new(row, 0)..Point::new(row, line_len));
12032 continue;
12033 }
12034 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12035 if row_indent_size.len >= first_indent.len {
12036 trimmed_selections.push(
12037 Point::new(row, first_indent.len)..Point::new(row, line_len),
12038 );
12039 } else {
12040 trimmed_selections.clear();
12041 trimmed_selections.push(start..end);
12042 break;
12043 }
12044 }
12045 }
12046 } else {
12047 trimmed_selections.push(start..end);
12048 }
12049
12050 for trimmed_range in trimmed_selections {
12051 if is_first {
12052 is_first = false;
12053 } else {
12054 text += "\n";
12055 }
12056 let mut len = 0;
12057 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12058 text.push_str(chunk);
12059 len += chunk.len();
12060 }
12061 clipboard_selections.push(ClipboardSelection {
12062 len,
12063 is_entire_line,
12064 first_line_indent: buffer
12065 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12066 .len,
12067 });
12068 }
12069 }
12070 }
12071
12072 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12073 text,
12074 clipboard_selections,
12075 ));
12076 }
12077
12078 pub fn do_paste(
12079 &mut self,
12080 text: &String,
12081 clipboard_selections: Option<Vec<ClipboardSelection>>,
12082 handle_entire_lines: bool,
12083 window: &mut Window,
12084 cx: &mut Context<Self>,
12085 ) {
12086 if self.read_only(cx) {
12087 return;
12088 }
12089
12090 let clipboard_text = Cow::Borrowed(text);
12091
12092 self.transact(window, cx, |this, window, cx| {
12093 if let Some(mut clipboard_selections) = clipboard_selections {
12094 let old_selections = this.selections.all::<usize>(cx);
12095 let all_selections_were_entire_line =
12096 clipboard_selections.iter().all(|s| s.is_entire_line);
12097 let first_selection_indent_column =
12098 clipboard_selections.first().map(|s| s.first_line_indent);
12099 if clipboard_selections.len() != old_selections.len() {
12100 clipboard_selections.drain(..);
12101 }
12102 let cursor_offset = this.selections.last::<usize>(cx).head();
12103 let mut auto_indent_on_paste = true;
12104
12105 this.buffer.update(cx, |buffer, cx| {
12106 let snapshot = buffer.read(cx);
12107 auto_indent_on_paste = snapshot
12108 .language_settings_at(cursor_offset, cx)
12109 .auto_indent_on_paste;
12110
12111 let mut start_offset = 0;
12112 let mut edits = Vec::new();
12113 let mut original_indent_columns = Vec::new();
12114 for (ix, selection) in old_selections.iter().enumerate() {
12115 let to_insert;
12116 let entire_line;
12117 let original_indent_column;
12118 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12119 let end_offset = start_offset + clipboard_selection.len;
12120 to_insert = &clipboard_text[start_offset..end_offset];
12121 entire_line = clipboard_selection.is_entire_line;
12122 start_offset = end_offset + 1;
12123 original_indent_column = Some(clipboard_selection.first_line_indent);
12124 } else {
12125 to_insert = clipboard_text.as_str();
12126 entire_line = all_selections_were_entire_line;
12127 original_indent_column = first_selection_indent_column
12128 }
12129
12130 // If the corresponding selection was empty when this slice of the
12131 // clipboard text was written, then the entire line containing the
12132 // selection was copied. If this selection is also currently empty,
12133 // then paste the line before the current line of the buffer.
12134 let range = if selection.is_empty() && handle_entire_lines && entire_line {
12135 let column = selection.start.to_point(&snapshot).column as usize;
12136 let line_start = selection.start - column;
12137 line_start..line_start
12138 } else {
12139 selection.range()
12140 };
12141
12142 edits.push((range, to_insert));
12143 original_indent_columns.push(original_indent_column);
12144 }
12145 drop(snapshot);
12146
12147 buffer.edit(
12148 edits,
12149 if auto_indent_on_paste {
12150 Some(AutoindentMode::Block {
12151 original_indent_columns,
12152 })
12153 } else {
12154 None
12155 },
12156 cx,
12157 );
12158 });
12159
12160 let selections = this.selections.all::<usize>(cx);
12161 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12162 } else {
12163 this.insert(&clipboard_text, window, cx);
12164 }
12165 });
12166 }
12167
12168 pub fn diff_clipboard_with_selection(
12169 &mut self,
12170 _: &DiffClipboardWithSelection,
12171 window: &mut Window,
12172 cx: &mut Context<Self>,
12173 ) {
12174 let selections = self.selections.all::<usize>(cx);
12175
12176 if selections.is_empty() {
12177 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12178 return;
12179 };
12180
12181 let clipboard_text = match cx.read_from_clipboard() {
12182 Some(item) => match item.entries().first() {
12183 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
12184 _ => None,
12185 },
12186 None => None,
12187 };
12188
12189 let Some(clipboard_text) = clipboard_text else {
12190 log::warn!("Clipboard doesn't contain text.");
12191 return;
12192 };
12193
12194 window.dispatch_action(
12195 Box::new(DiffClipboardWithSelectionData {
12196 clipboard_text,
12197 editor: cx.entity(),
12198 }),
12199 cx,
12200 );
12201 }
12202
12203 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12204 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12205 if let Some(item) = cx.read_from_clipboard() {
12206 let entries = item.entries();
12207
12208 match entries.first() {
12209 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12210 // of all the pasted entries.
12211 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12212 .do_paste(
12213 clipboard_string.text(),
12214 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12215 true,
12216 window,
12217 cx,
12218 ),
12219 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12220 }
12221 }
12222 }
12223
12224 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12225 if self.read_only(cx) {
12226 return;
12227 }
12228
12229 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12230
12231 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12232 if let Some((selections, _)) =
12233 self.selection_history.transaction(transaction_id).cloned()
12234 {
12235 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12236 s.select_anchors(selections.to_vec());
12237 });
12238 } else {
12239 log::error!(
12240 "No entry in selection_history found for undo. \
12241 This may correspond to a bug where undo does not update the selection. \
12242 If this is occurring, please add details to \
12243 https://github.com/zed-industries/zed/issues/22692"
12244 );
12245 }
12246 self.request_autoscroll(Autoscroll::fit(), cx);
12247 self.unmark_text(window, cx);
12248 self.refresh_inline_completion(true, false, window, cx);
12249 cx.emit(EditorEvent::Edited { transaction_id });
12250 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12251 }
12252 }
12253
12254 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12255 if self.read_only(cx) {
12256 return;
12257 }
12258
12259 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12260
12261 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12262 if let Some((_, Some(selections))) =
12263 self.selection_history.transaction(transaction_id).cloned()
12264 {
12265 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12266 s.select_anchors(selections.to_vec());
12267 });
12268 } else {
12269 log::error!(
12270 "No entry in selection_history found for redo. \
12271 This may correspond to a bug where undo does not update the selection. \
12272 If this is occurring, please add details to \
12273 https://github.com/zed-industries/zed/issues/22692"
12274 );
12275 }
12276 self.request_autoscroll(Autoscroll::fit(), cx);
12277 self.unmark_text(window, cx);
12278 self.refresh_inline_completion(true, false, window, cx);
12279 cx.emit(EditorEvent::Edited { transaction_id });
12280 }
12281 }
12282
12283 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12284 self.buffer
12285 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12286 }
12287
12288 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12289 self.buffer
12290 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12291 }
12292
12293 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
12294 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12295 self.change_selections(Default::default(), window, cx, |s| {
12296 s.move_with(|map, selection| {
12297 let cursor = if selection.is_empty() {
12298 movement::left(map, selection.start)
12299 } else {
12300 selection.start
12301 };
12302 selection.collapse_to(cursor, SelectionGoal::None);
12303 });
12304 })
12305 }
12306
12307 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12308 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12309 self.change_selections(Default::default(), window, cx, |s| {
12310 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12311 })
12312 }
12313
12314 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
12315 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12316 self.change_selections(Default::default(), window, cx, |s| {
12317 s.move_with(|map, selection| {
12318 let cursor = if selection.is_empty() {
12319 movement::right(map, selection.end)
12320 } else {
12321 selection.end
12322 };
12323 selection.collapse_to(cursor, SelectionGoal::None)
12324 });
12325 })
12326 }
12327
12328 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12329 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12330 self.change_selections(Default::default(), window, cx, |s| {
12331 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
12332 })
12333 }
12334
12335 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
12336 if self.take_rename(true, window, cx).is_some() {
12337 return;
12338 }
12339
12340 if self.mode.is_single_line() {
12341 cx.propagate();
12342 return;
12343 }
12344
12345 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12346
12347 let text_layout_details = &self.text_layout_details(window);
12348 let selection_count = self.selections.count();
12349 let first_selection = self.selections.first_anchor();
12350
12351 self.change_selections(Default::default(), window, cx, |s| {
12352 s.move_with(|map, selection| {
12353 if !selection.is_empty() {
12354 selection.goal = SelectionGoal::None;
12355 }
12356 let (cursor, goal) = movement::up(
12357 map,
12358 selection.start,
12359 selection.goal,
12360 false,
12361 text_layout_details,
12362 );
12363 selection.collapse_to(cursor, goal);
12364 });
12365 });
12366
12367 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12368 {
12369 cx.propagate();
12370 }
12371 }
12372
12373 pub fn move_up_by_lines(
12374 &mut self,
12375 action: &MoveUpByLines,
12376 window: &mut Window,
12377 cx: &mut Context<Self>,
12378 ) {
12379 if self.take_rename(true, window, cx).is_some() {
12380 return;
12381 }
12382
12383 if self.mode.is_single_line() {
12384 cx.propagate();
12385 return;
12386 }
12387
12388 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12389
12390 let text_layout_details = &self.text_layout_details(window);
12391
12392 self.change_selections(Default::default(), window, cx, |s| {
12393 s.move_with(|map, selection| {
12394 if !selection.is_empty() {
12395 selection.goal = SelectionGoal::None;
12396 }
12397 let (cursor, goal) = movement::up_by_rows(
12398 map,
12399 selection.start,
12400 action.lines,
12401 selection.goal,
12402 false,
12403 text_layout_details,
12404 );
12405 selection.collapse_to(cursor, goal);
12406 });
12407 })
12408 }
12409
12410 pub fn move_down_by_lines(
12411 &mut self,
12412 action: &MoveDownByLines,
12413 window: &mut Window,
12414 cx: &mut Context<Self>,
12415 ) {
12416 if self.take_rename(true, window, cx).is_some() {
12417 return;
12418 }
12419
12420 if self.mode.is_single_line() {
12421 cx.propagate();
12422 return;
12423 }
12424
12425 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12426
12427 let text_layout_details = &self.text_layout_details(window);
12428
12429 self.change_selections(Default::default(), window, cx, |s| {
12430 s.move_with(|map, selection| {
12431 if !selection.is_empty() {
12432 selection.goal = SelectionGoal::None;
12433 }
12434 let (cursor, goal) = movement::down_by_rows(
12435 map,
12436 selection.start,
12437 action.lines,
12438 selection.goal,
12439 false,
12440 text_layout_details,
12441 );
12442 selection.collapse_to(cursor, goal);
12443 });
12444 })
12445 }
12446
12447 pub fn select_down_by_lines(
12448 &mut self,
12449 action: &SelectDownByLines,
12450 window: &mut Window,
12451 cx: &mut Context<Self>,
12452 ) {
12453 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12454 let text_layout_details = &self.text_layout_details(window);
12455 self.change_selections(Default::default(), window, cx, |s| {
12456 s.move_heads_with(|map, head, goal| {
12457 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
12458 })
12459 })
12460 }
12461
12462 pub fn select_up_by_lines(
12463 &mut self,
12464 action: &SelectUpByLines,
12465 window: &mut Window,
12466 cx: &mut Context<Self>,
12467 ) {
12468 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12469 let text_layout_details = &self.text_layout_details(window);
12470 self.change_selections(Default::default(), window, cx, |s| {
12471 s.move_heads_with(|map, head, goal| {
12472 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
12473 })
12474 })
12475 }
12476
12477 pub fn select_page_up(
12478 &mut self,
12479 _: &SelectPageUp,
12480 window: &mut Window,
12481 cx: &mut Context<Self>,
12482 ) {
12483 let Some(row_count) = self.visible_row_count() else {
12484 return;
12485 };
12486
12487 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12488
12489 let text_layout_details = &self.text_layout_details(window);
12490
12491 self.change_selections(Default::default(), window, cx, |s| {
12492 s.move_heads_with(|map, head, goal| {
12493 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
12494 })
12495 })
12496 }
12497
12498 pub fn move_page_up(
12499 &mut self,
12500 action: &MovePageUp,
12501 window: &mut Window,
12502 cx: &mut Context<Self>,
12503 ) {
12504 if self.take_rename(true, window, cx).is_some() {
12505 return;
12506 }
12507
12508 if self
12509 .context_menu
12510 .borrow_mut()
12511 .as_mut()
12512 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
12513 .unwrap_or(false)
12514 {
12515 return;
12516 }
12517
12518 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12519 cx.propagate();
12520 return;
12521 }
12522
12523 let Some(row_count) = self.visible_row_count() else {
12524 return;
12525 };
12526
12527 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12528
12529 let effects = if action.center_cursor {
12530 SelectionEffects::scroll(Autoscroll::center())
12531 } else {
12532 SelectionEffects::default()
12533 };
12534
12535 let text_layout_details = &self.text_layout_details(window);
12536
12537 self.change_selections(effects, window, cx, |s| {
12538 s.move_with(|map, selection| {
12539 if !selection.is_empty() {
12540 selection.goal = SelectionGoal::None;
12541 }
12542 let (cursor, goal) = movement::up_by_rows(
12543 map,
12544 selection.end,
12545 row_count,
12546 selection.goal,
12547 false,
12548 text_layout_details,
12549 );
12550 selection.collapse_to(cursor, goal);
12551 });
12552 });
12553 }
12554
12555 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
12556 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12557 let text_layout_details = &self.text_layout_details(window);
12558 self.change_selections(Default::default(), window, cx, |s| {
12559 s.move_heads_with(|map, head, goal| {
12560 movement::up(map, head, goal, false, text_layout_details)
12561 })
12562 })
12563 }
12564
12565 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
12566 self.take_rename(true, window, cx);
12567
12568 if self.mode.is_single_line() {
12569 cx.propagate();
12570 return;
12571 }
12572
12573 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12574
12575 let text_layout_details = &self.text_layout_details(window);
12576 let selection_count = self.selections.count();
12577 let first_selection = self.selections.first_anchor();
12578
12579 self.change_selections(Default::default(), window, cx, |s| {
12580 s.move_with(|map, selection| {
12581 if !selection.is_empty() {
12582 selection.goal = SelectionGoal::None;
12583 }
12584 let (cursor, goal) = movement::down(
12585 map,
12586 selection.end,
12587 selection.goal,
12588 false,
12589 text_layout_details,
12590 );
12591 selection.collapse_to(cursor, goal);
12592 });
12593 });
12594
12595 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12596 {
12597 cx.propagate();
12598 }
12599 }
12600
12601 pub fn select_page_down(
12602 &mut self,
12603 _: &SelectPageDown,
12604 window: &mut Window,
12605 cx: &mut Context<Self>,
12606 ) {
12607 let Some(row_count) = self.visible_row_count() else {
12608 return;
12609 };
12610
12611 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12612
12613 let text_layout_details = &self.text_layout_details(window);
12614
12615 self.change_selections(Default::default(), window, cx, |s| {
12616 s.move_heads_with(|map, head, goal| {
12617 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
12618 })
12619 })
12620 }
12621
12622 pub fn move_page_down(
12623 &mut self,
12624 action: &MovePageDown,
12625 window: &mut Window,
12626 cx: &mut Context<Self>,
12627 ) {
12628 if self.take_rename(true, window, cx).is_some() {
12629 return;
12630 }
12631
12632 if self
12633 .context_menu
12634 .borrow_mut()
12635 .as_mut()
12636 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
12637 .unwrap_or(false)
12638 {
12639 return;
12640 }
12641
12642 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12643 cx.propagate();
12644 return;
12645 }
12646
12647 let Some(row_count) = self.visible_row_count() else {
12648 return;
12649 };
12650
12651 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12652
12653 let effects = if action.center_cursor {
12654 SelectionEffects::scroll(Autoscroll::center())
12655 } else {
12656 SelectionEffects::default()
12657 };
12658
12659 let text_layout_details = &self.text_layout_details(window);
12660 self.change_selections(effects, window, cx, |s| {
12661 s.move_with(|map, selection| {
12662 if !selection.is_empty() {
12663 selection.goal = SelectionGoal::None;
12664 }
12665 let (cursor, goal) = movement::down_by_rows(
12666 map,
12667 selection.end,
12668 row_count,
12669 selection.goal,
12670 false,
12671 text_layout_details,
12672 );
12673 selection.collapse_to(cursor, goal);
12674 });
12675 });
12676 }
12677
12678 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
12679 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12680 let text_layout_details = &self.text_layout_details(window);
12681 self.change_selections(Default::default(), window, cx, |s| {
12682 s.move_heads_with(|map, head, goal| {
12683 movement::down(map, head, goal, false, text_layout_details)
12684 })
12685 });
12686 }
12687
12688 pub fn context_menu_first(
12689 &mut self,
12690 _: &ContextMenuFirst,
12691 window: &mut Window,
12692 cx: &mut Context<Self>,
12693 ) {
12694 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12695 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
12696 }
12697 }
12698
12699 pub fn context_menu_prev(
12700 &mut self,
12701 _: &ContextMenuPrevious,
12702 window: &mut Window,
12703 cx: &mut Context<Self>,
12704 ) {
12705 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12706 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
12707 }
12708 }
12709
12710 pub fn context_menu_next(
12711 &mut self,
12712 _: &ContextMenuNext,
12713 window: &mut Window,
12714 cx: &mut Context<Self>,
12715 ) {
12716 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12717 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
12718 }
12719 }
12720
12721 pub fn context_menu_last(
12722 &mut self,
12723 _: &ContextMenuLast,
12724 window: &mut Window,
12725 cx: &mut Context<Self>,
12726 ) {
12727 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12728 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
12729 }
12730 }
12731
12732 pub fn signature_help_prev(
12733 &mut self,
12734 _: &SignatureHelpPrevious,
12735 _: &mut Window,
12736 cx: &mut Context<Self>,
12737 ) {
12738 if let Some(popover) = self.signature_help_state.popover_mut() {
12739 if popover.current_signature == 0 {
12740 popover.current_signature = popover.signatures.len() - 1;
12741 } else {
12742 popover.current_signature -= 1;
12743 }
12744 cx.notify();
12745 }
12746 }
12747
12748 pub fn signature_help_next(
12749 &mut self,
12750 _: &SignatureHelpNext,
12751 _: &mut Window,
12752 cx: &mut Context<Self>,
12753 ) {
12754 if let Some(popover) = self.signature_help_state.popover_mut() {
12755 if popover.current_signature + 1 == popover.signatures.len() {
12756 popover.current_signature = 0;
12757 } else {
12758 popover.current_signature += 1;
12759 }
12760 cx.notify();
12761 }
12762 }
12763
12764 pub fn move_to_previous_word_start(
12765 &mut self,
12766 _: &MoveToPreviousWordStart,
12767 window: &mut Window,
12768 cx: &mut Context<Self>,
12769 ) {
12770 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12771 self.change_selections(Default::default(), window, cx, |s| {
12772 s.move_cursors_with(|map, head, _| {
12773 (
12774 movement::previous_word_start(map, head),
12775 SelectionGoal::None,
12776 )
12777 });
12778 })
12779 }
12780
12781 pub fn move_to_previous_subword_start(
12782 &mut self,
12783 _: &MoveToPreviousSubwordStart,
12784 window: &mut Window,
12785 cx: &mut Context<Self>,
12786 ) {
12787 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12788 self.change_selections(Default::default(), window, cx, |s| {
12789 s.move_cursors_with(|map, head, _| {
12790 (
12791 movement::previous_subword_start(map, head),
12792 SelectionGoal::None,
12793 )
12794 });
12795 })
12796 }
12797
12798 pub fn select_to_previous_word_start(
12799 &mut self,
12800 _: &SelectToPreviousWordStart,
12801 window: &mut Window,
12802 cx: &mut Context<Self>,
12803 ) {
12804 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12805 self.change_selections(Default::default(), window, cx, |s| {
12806 s.move_heads_with(|map, head, _| {
12807 (
12808 movement::previous_word_start(map, head),
12809 SelectionGoal::None,
12810 )
12811 });
12812 })
12813 }
12814
12815 pub fn select_to_previous_subword_start(
12816 &mut self,
12817 _: &SelectToPreviousSubwordStart,
12818 window: &mut Window,
12819 cx: &mut Context<Self>,
12820 ) {
12821 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12822 self.change_selections(Default::default(), window, cx, |s| {
12823 s.move_heads_with(|map, head, _| {
12824 (
12825 movement::previous_subword_start(map, head),
12826 SelectionGoal::None,
12827 )
12828 });
12829 })
12830 }
12831
12832 pub fn delete_to_previous_word_start(
12833 &mut self,
12834 action: &DeleteToPreviousWordStart,
12835 window: &mut Window,
12836 cx: &mut Context<Self>,
12837 ) {
12838 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12839 self.transact(window, cx, |this, window, cx| {
12840 this.select_autoclose_pair(window, cx);
12841 this.change_selections(Default::default(), window, cx, |s| {
12842 s.move_with(|map, selection| {
12843 if selection.is_empty() {
12844 let cursor = if action.ignore_newlines {
12845 movement::previous_word_start(map, selection.head())
12846 } else {
12847 movement::previous_word_start_or_newline(map, selection.head())
12848 };
12849 selection.set_head(cursor, SelectionGoal::None);
12850 }
12851 });
12852 });
12853 this.insert("", window, cx);
12854 });
12855 }
12856
12857 pub fn delete_to_previous_subword_start(
12858 &mut self,
12859 _: &DeleteToPreviousSubwordStart,
12860 window: &mut Window,
12861 cx: &mut Context<Self>,
12862 ) {
12863 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12864 self.transact(window, cx, |this, window, cx| {
12865 this.select_autoclose_pair(window, cx);
12866 this.change_selections(Default::default(), window, cx, |s| {
12867 s.move_with(|map, selection| {
12868 if selection.is_empty() {
12869 let cursor = movement::previous_subword_start(map, selection.head());
12870 selection.set_head(cursor, SelectionGoal::None);
12871 }
12872 });
12873 });
12874 this.insert("", window, cx);
12875 });
12876 }
12877
12878 pub fn move_to_next_word_end(
12879 &mut self,
12880 _: &MoveToNextWordEnd,
12881 window: &mut Window,
12882 cx: &mut Context<Self>,
12883 ) {
12884 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12885 self.change_selections(Default::default(), window, cx, |s| {
12886 s.move_cursors_with(|map, head, _| {
12887 (movement::next_word_end(map, head), SelectionGoal::None)
12888 });
12889 })
12890 }
12891
12892 pub fn move_to_next_subword_end(
12893 &mut self,
12894 _: &MoveToNextSubwordEnd,
12895 window: &mut Window,
12896 cx: &mut Context<Self>,
12897 ) {
12898 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12899 self.change_selections(Default::default(), window, cx, |s| {
12900 s.move_cursors_with(|map, head, _| {
12901 (movement::next_subword_end(map, head), SelectionGoal::None)
12902 });
12903 })
12904 }
12905
12906 pub fn select_to_next_word_end(
12907 &mut self,
12908 _: &SelectToNextWordEnd,
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_heads_with(|map, head, _| {
12915 (movement::next_word_end(map, head), SelectionGoal::None)
12916 });
12917 })
12918 }
12919
12920 pub fn select_to_next_subword_end(
12921 &mut self,
12922 _: &SelectToNextSubwordEnd,
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_heads_with(|map, head, _| {
12929 (movement::next_subword_end(map, head), SelectionGoal::None)
12930 });
12931 })
12932 }
12933
12934 pub fn delete_to_next_word_end(
12935 &mut self,
12936 action: &DeleteToNextWordEnd,
12937 window: &mut Window,
12938 cx: &mut Context<Self>,
12939 ) {
12940 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12941 self.transact(window, cx, |this, window, cx| {
12942 this.change_selections(Default::default(), window, cx, |s| {
12943 s.move_with(|map, selection| {
12944 if selection.is_empty() {
12945 let cursor = if action.ignore_newlines {
12946 movement::next_word_end(map, selection.head())
12947 } else {
12948 movement::next_word_end_or_newline(map, selection.head())
12949 };
12950 selection.set_head(cursor, SelectionGoal::None);
12951 }
12952 });
12953 });
12954 this.insert("", window, cx);
12955 });
12956 }
12957
12958 pub fn delete_to_next_subword_end(
12959 &mut self,
12960 _: &DeleteToNextSubwordEnd,
12961 window: &mut Window,
12962 cx: &mut Context<Self>,
12963 ) {
12964 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12965 self.transact(window, cx, |this, window, cx| {
12966 this.change_selections(Default::default(), window, cx, |s| {
12967 s.move_with(|map, selection| {
12968 if selection.is_empty() {
12969 let cursor = movement::next_subword_end(map, selection.head());
12970 selection.set_head(cursor, SelectionGoal::None);
12971 }
12972 });
12973 });
12974 this.insert("", window, cx);
12975 });
12976 }
12977
12978 pub fn move_to_beginning_of_line(
12979 &mut self,
12980 action: &MoveToBeginningOfLine,
12981 window: &mut Window,
12982 cx: &mut Context<Self>,
12983 ) {
12984 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12985 self.change_selections(Default::default(), window, cx, |s| {
12986 s.move_cursors_with(|map, head, _| {
12987 (
12988 movement::indented_line_beginning(
12989 map,
12990 head,
12991 action.stop_at_soft_wraps,
12992 action.stop_at_indent,
12993 ),
12994 SelectionGoal::None,
12995 )
12996 });
12997 })
12998 }
12999
13000 pub fn select_to_beginning_of_line(
13001 &mut self,
13002 action: &SelectToBeginningOfLine,
13003 window: &mut Window,
13004 cx: &mut Context<Self>,
13005 ) {
13006 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13007 self.change_selections(Default::default(), window, cx, |s| {
13008 s.move_heads_with(|map, head, _| {
13009 (
13010 movement::indented_line_beginning(
13011 map,
13012 head,
13013 action.stop_at_soft_wraps,
13014 action.stop_at_indent,
13015 ),
13016 SelectionGoal::None,
13017 )
13018 });
13019 });
13020 }
13021
13022 pub fn delete_to_beginning_of_line(
13023 &mut self,
13024 action: &DeleteToBeginningOfLine,
13025 window: &mut Window,
13026 cx: &mut Context<Self>,
13027 ) {
13028 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13029 self.transact(window, cx, |this, window, cx| {
13030 this.change_selections(Default::default(), window, cx, |s| {
13031 s.move_with(|_, selection| {
13032 selection.reversed = true;
13033 });
13034 });
13035
13036 this.select_to_beginning_of_line(
13037 &SelectToBeginningOfLine {
13038 stop_at_soft_wraps: false,
13039 stop_at_indent: action.stop_at_indent,
13040 },
13041 window,
13042 cx,
13043 );
13044 this.backspace(&Backspace, window, cx);
13045 });
13046 }
13047
13048 pub fn move_to_end_of_line(
13049 &mut self,
13050 action: &MoveToEndOfLine,
13051 window: &mut Window,
13052 cx: &mut Context<Self>,
13053 ) {
13054 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13055 self.change_selections(Default::default(), window, cx, |s| {
13056 s.move_cursors_with(|map, head, _| {
13057 (
13058 movement::line_end(map, head, action.stop_at_soft_wraps),
13059 SelectionGoal::None,
13060 )
13061 });
13062 })
13063 }
13064
13065 pub fn select_to_end_of_line(
13066 &mut self,
13067 action: &SelectToEndOfLine,
13068 window: &mut Window,
13069 cx: &mut Context<Self>,
13070 ) {
13071 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13072 self.change_selections(Default::default(), window, cx, |s| {
13073 s.move_heads_with(|map, head, _| {
13074 (
13075 movement::line_end(map, head, action.stop_at_soft_wraps),
13076 SelectionGoal::None,
13077 )
13078 });
13079 })
13080 }
13081
13082 pub fn delete_to_end_of_line(
13083 &mut self,
13084 _: &DeleteToEndOfLine,
13085 window: &mut Window,
13086 cx: &mut Context<Self>,
13087 ) {
13088 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13089 self.transact(window, cx, |this, window, cx| {
13090 this.select_to_end_of_line(
13091 &SelectToEndOfLine {
13092 stop_at_soft_wraps: false,
13093 },
13094 window,
13095 cx,
13096 );
13097 this.delete(&Delete, window, cx);
13098 });
13099 }
13100
13101 pub fn cut_to_end_of_line(
13102 &mut self,
13103 _: &CutToEndOfLine,
13104 window: &mut Window,
13105 cx: &mut Context<Self>,
13106 ) {
13107 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13108 self.transact(window, cx, |this, window, cx| {
13109 this.select_to_end_of_line(
13110 &SelectToEndOfLine {
13111 stop_at_soft_wraps: false,
13112 },
13113 window,
13114 cx,
13115 );
13116 this.cut(&Cut, window, cx);
13117 });
13118 }
13119
13120 pub fn move_to_start_of_paragraph(
13121 &mut self,
13122 _: &MoveToStartOfParagraph,
13123 window: &mut Window,
13124 cx: &mut Context<Self>,
13125 ) {
13126 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13127 cx.propagate();
13128 return;
13129 }
13130 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13131 self.change_selections(Default::default(), window, cx, |s| {
13132 s.move_with(|map, selection| {
13133 selection.collapse_to(
13134 movement::start_of_paragraph(map, selection.head(), 1),
13135 SelectionGoal::None,
13136 )
13137 });
13138 })
13139 }
13140
13141 pub fn move_to_end_of_paragraph(
13142 &mut self,
13143 _: &MoveToEndOfParagraph,
13144 window: &mut Window,
13145 cx: &mut Context<Self>,
13146 ) {
13147 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13148 cx.propagate();
13149 return;
13150 }
13151 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13152 self.change_selections(Default::default(), window, cx, |s| {
13153 s.move_with(|map, selection| {
13154 selection.collapse_to(
13155 movement::end_of_paragraph(map, selection.head(), 1),
13156 SelectionGoal::None,
13157 )
13158 });
13159 })
13160 }
13161
13162 pub fn select_to_start_of_paragraph(
13163 &mut self,
13164 _: &SelectToStartOfParagraph,
13165 window: &mut Window,
13166 cx: &mut Context<Self>,
13167 ) {
13168 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13169 cx.propagate();
13170 return;
13171 }
13172 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13173 self.change_selections(Default::default(), window, cx, |s| {
13174 s.move_heads_with(|map, head, _| {
13175 (
13176 movement::start_of_paragraph(map, head, 1),
13177 SelectionGoal::None,
13178 )
13179 });
13180 })
13181 }
13182
13183 pub fn select_to_end_of_paragraph(
13184 &mut self,
13185 _: &SelectToEndOfParagraph,
13186 window: &mut Window,
13187 cx: &mut Context<Self>,
13188 ) {
13189 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13190 cx.propagate();
13191 return;
13192 }
13193 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13194 self.change_selections(Default::default(), window, cx, |s| {
13195 s.move_heads_with(|map, head, _| {
13196 (
13197 movement::end_of_paragraph(map, head, 1),
13198 SelectionGoal::None,
13199 )
13200 });
13201 })
13202 }
13203
13204 pub fn move_to_start_of_excerpt(
13205 &mut self,
13206 _: &MoveToStartOfExcerpt,
13207 window: &mut Window,
13208 cx: &mut Context<Self>,
13209 ) {
13210 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13211 cx.propagate();
13212 return;
13213 }
13214 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13215 self.change_selections(Default::default(), window, cx, |s| {
13216 s.move_with(|map, selection| {
13217 selection.collapse_to(
13218 movement::start_of_excerpt(
13219 map,
13220 selection.head(),
13221 workspace::searchable::Direction::Prev,
13222 ),
13223 SelectionGoal::None,
13224 )
13225 });
13226 })
13227 }
13228
13229 pub fn move_to_start_of_next_excerpt(
13230 &mut self,
13231 _: &MoveToStartOfNextExcerpt,
13232 window: &mut Window,
13233 cx: &mut Context<Self>,
13234 ) {
13235 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13236 cx.propagate();
13237 return;
13238 }
13239
13240 self.change_selections(Default::default(), window, cx, |s| {
13241 s.move_with(|map, selection| {
13242 selection.collapse_to(
13243 movement::start_of_excerpt(
13244 map,
13245 selection.head(),
13246 workspace::searchable::Direction::Next,
13247 ),
13248 SelectionGoal::None,
13249 )
13250 });
13251 })
13252 }
13253
13254 pub fn move_to_end_of_excerpt(
13255 &mut self,
13256 _: &MoveToEndOfExcerpt,
13257 window: &mut Window,
13258 cx: &mut Context<Self>,
13259 ) {
13260 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13261 cx.propagate();
13262 return;
13263 }
13264 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13265 self.change_selections(Default::default(), window, cx, |s| {
13266 s.move_with(|map, selection| {
13267 selection.collapse_to(
13268 movement::end_of_excerpt(
13269 map,
13270 selection.head(),
13271 workspace::searchable::Direction::Next,
13272 ),
13273 SelectionGoal::None,
13274 )
13275 });
13276 })
13277 }
13278
13279 pub fn move_to_end_of_previous_excerpt(
13280 &mut self,
13281 _: &MoveToEndOfPreviousExcerpt,
13282 window: &mut Window,
13283 cx: &mut Context<Self>,
13284 ) {
13285 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13286 cx.propagate();
13287 return;
13288 }
13289 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13290 self.change_selections(Default::default(), window, cx, |s| {
13291 s.move_with(|map, selection| {
13292 selection.collapse_to(
13293 movement::end_of_excerpt(
13294 map,
13295 selection.head(),
13296 workspace::searchable::Direction::Prev,
13297 ),
13298 SelectionGoal::None,
13299 )
13300 });
13301 })
13302 }
13303
13304 pub fn select_to_start_of_excerpt(
13305 &mut self,
13306 _: &SelectToStartOfExcerpt,
13307 window: &mut Window,
13308 cx: &mut Context<Self>,
13309 ) {
13310 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13311 cx.propagate();
13312 return;
13313 }
13314 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13315 self.change_selections(Default::default(), window, cx, |s| {
13316 s.move_heads_with(|map, head, _| {
13317 (
13318 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13319 SelectionGoal::None,
13320 )
13321 });
13322 })
13323 }
13324
13325 pub fn select_to_start_of_next_excerpt(
13326 &mut self,
13327 _: &SelectToStartOfNextExcerpt,
13328 window: &mut Window,
13329 cx: &mut Context<Self>,
13330 ) {
13331 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13332 cx.propagate();
13333 return;
13334 }
13335 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13336 self.change_selections(Default::default(), window, cx, |s| {
13337 s.move_heads_with(|map, head, _| {
13338 (
13339 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
13340 SelectionGoal::None,
13341 )
13342 });
13343 })
13344 }
13345
13346 pub fn select_to_end_of_excerpt(
13347 &mut self,
13348 _: &SelectToEndOfExcerpt,
13349 window: &mut Window,
13350 cx: &mut Context<Self>,
13351 ) {
13352 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13353 cx.propagate();
13354 return;
13355 }
13356 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13357 self.change_selections(Default::default(), window, cx, |s| {
13358 s.move_heads_with(|map, head, _| {
13359 (
13360 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
13361 SelectionGoal::None,
13362 )
13363 });
13364 })
13365 }
13366
13367 pub fn select_to_end_of_previous_excerpt(
13368 &mut self,
13369 _: &SelectToEndOfPreviousExcerpt,
13370 window: &mut Window,
13371 cx: &mut Context<Self>,
13372 ) {
13373 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13374 cx.propagate();
13375 return;
13376 }
13377 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13378 self.change_selections(Default::default(), window, cx, |s| {
13379 s.move_heads_with(|map, head, _| {
13380 (
13381 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13382 SelectionGoal::None,
13383 )
13384 });
13385 })
13386 }
13387
13388 pub fn move_to_beginning(
13389 &mut self,
13390 _: &MoveToBeginning,
13391 window: &mut Window,
13392 cx: &mut Context<Self>,
13393 ) {
13394 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13395 cx.propagate();
13396 return;
13397 }
13398 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13399 self.change_selections(Default::default(), window, cx, |s| {
13400 s.select_ranges(vec![0..0]);
13401 });
13402 }
13403
13404 pub fn select_to_beginning(
13405 &mut self,
13406 _: &SelectToBeginning,
13407 window: &mut Window,
13408 cx: &mut Context<Self>,
13409 ) {
13410 let mut selection = self.selections.last::<Point>(cx);
13411 selection.set_head(Point::zero(), SelectionGoal::None);
13412 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13413 self.change_selections(Default::default(), window, cx, |s| {
13414 s.select(vec![selection]);
13415 });
13416 }
13417
13418 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
13419 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13420 cx.propagate();
13421 return;
13422 }
13423 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13424 let cursor = self.buffer.read(cx).read(cx).len();
13425 self.change_selections(Default::default(), window, cx, |s| {
13426 s.select_ranges(vec![cursor..cursor])
13427 });
13428 }
13429
13430 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
13431 self.nav_history = nav_history;
13432 }
13433
13434 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
13435 self.nav_history.as_ref()
13436 }
13437
13438 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
13439 self.push_to_nav_history(
13440 self.selections.newest_anchor().head(),
13441 None,
13442 false,
13443 true,
13444 cx,
13445 );
13446 }
13447
13448 fn push_to_nav_history(
13449 &mut self,
13450 cursor_anchor: Anchor,
13451 new_position: Option<Point>,
13452 is_deactivate: bool,
13453 always: bool,
13454 cx: &mut Context<Self>,
13455 ) {
13456 if let Some(nav_history) = self.nav_history.as_mut() {
13457 let buffer = self.buffer.read(cx).read(cx);
13458 let cursor_position = cursor_anchor.to_point(&buffer);
13459 let scroll_state = self.scroll_manager.anchor();
13460 let scroll_top_row = scroll_state.top_row(&buffer);
13461 drop(buffer);
13462
13463 if let Some(new_position) = new_position {
13464 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
13465 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
13466 return;
13467 }
13468 }
13469
13470 nav_history.push(
13471 Some(NavigationData {
13472 cursor_anchor,
13473 cursor_position,
13474 scroll_anchor: scroll_state,
13475 scroll_top_row,
13476 }),
13477 cx,
13478 );
13479 cx.emit(EditorEvent::PushedToNavHistory {
13480 anchor: cursor_anchor,
13481 is_deactivate,
13482 })
13483 }
13484 }
13485
13486 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
13487 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13488 let buffer = self.buffer.read(cx).snapshot(cx);
13489 let mut selection = self.selections.first::<usize>(cx);
13490 selection.set_head(buffer.len(), SelectionGoal::None);
13491 self.change_selections(Default::default(), window, cx, |s| {
13492 s.select(vec![selection]);
13493 });
13494 }
13495
13496 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
13497 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13498 let end = self.buffer.read(cx).read(cx).len();
13499 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13500 s.select_ranges(vec![0..end]);
13501 });
13502 }
13503
13504 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
13505 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13506 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13507 let mut selections = self.selections.all::<Point>(cx);
13508 let max_point = display_map.buffer_snapshot.max_point();
13509 for selection in &mut selections {
13510 let rows = selection.spanned_rows(true, &display_map);
13511 selection.start = Point::new(rows.start.0, 0);
13512 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
13513 selection.reversed = false;
13514 }
13515 self.change_selections(Default::default(), window, cx, |s| {
13516 s.select(selections);
13517 });
13518 }
13519
13520 pub fn split_selection_into_lines(
13521 &mut self,
13522 _: &SplitSelectionIntoLines,
13523 window: &mut Window,
13524 cx: &mut Context<Self>,
13525 ) {
13526 let selections = self
13527 .selections
13528 .all::<Point>(cx)
13529 .into_iter()
13530 .map(|selection| selection.start..selection.end)
13531 .collect::<Vec<_>>();
13532 self.unfold_ranges(&selections, true, true, cx);
13533
13534 let mut new_selection_ranges = Vec::new();
13535 {
13536 let buffer = self.buffer.read(cx).read(cx);
13537 for selection in selections {
13538 for row in selection.start.row..selection.end.row {
13539 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
13540 new_selection_ranges.push(cursor..cursor);
13541 }
13542
13543 let is_multiline_selection = selection.start.row != selection.end.row;
13544 // Don't insert last one if it's a multi-line selection ending at the start of a line,
13545 // so this action feels more ergonomic when paired with other selection operations
13546 let should_skip_last = is_multiline_selection && selection.end.column == 0;
13547 if !should_skip_last {
13548 new_selection_ranges.push(selection.end..selection.end);
13549 }
13550 }
13551 }
13552 self.change_selections(Default::default(), window, cx, |s| {
13553 s.select_ranges(new_selection_ranges);
13554 });
13555 }
13556
13557 pub fn add_selection_above(
13558 &mut self,
13559 _: &AddSelectionAbove,
13560 window: &mut Window,
13561 cx: &mut Context<Self>,
13562 ) {
13563 self.add_selection(true, window, cx);
13564 }
13565
13566 pub fn add_selection_below(
13567 &mut self,
13568 _: &AddSelectionBelow,
13569 window: &mut Window,
13570 cx: &mut Context<Self>,
13571 ) {
13572 self.add_selection(false, window, cx);
13573 }
13574
13575 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
13576 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13577
13578 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13579 let all_selections = self.selections.all::<Point>(cx);
13580 let text_layout_details = self.text_layout_details(window);
13581
13582 let (mut columnar_selections, new_selections_to_columnarize) = {
13583 if let Some(state) = self.add_selections_state.as_ref() {
13584 let columnar_selection_ids: HashSet<_> = state
13585 .groups
13586 .iter()
13587 .flat_map(|group| group.stack.iter())
13588 .copied()
13589 .collect();
13590
13591 all_selections
13592 .into_iter()
13593 .partition(|s| columnar_selection_ids.contains(&s.id))
13594 } else {
13595 (Vec::new(), all_selections)
13596 }
13597 };
13598
13599 let mut state = self
13600 .add_selections_state
13601 .take()
13602 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
13603
13604 for selection in new_selections_to_columnarize {
13605 let range = selection.display_range(&display_map).sorted();
13606 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
13607 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
13608 let positions = start_x.min(end_x)..start_x.max(end_x);
13609 let mut stack = Vec::new();
13610 for row in range.start.row().0..=range.end.row().0 {
13611 if let Some(selection) = self.selections.build_columnar_selection(
13612 &display_map,
13613 DisplayRow(row),
13614 &positions,
13615 selection.reversed,
13616 &text_layout_details,
13617 ) {
13618 stack.push(selection.id);
13619 columnar_selections.push(selection);
13620 }
13621 }
13622 if !stack.is_empty() {
13623 if above {
13624 stack.reverse();
13625 }
13626 state.groups.push(AddSelectionsGroup { above, stack });
13627 }
13628 }
13629
13630 let mut final_selections = Vec::new();
13631 let end_row = if above {
13632 DisplayRow(0)
13633 } else {
13634 display_map.max_point().row()
13635 };
13636
13637 let mut last_added_item_per_group = HashMap::default();
13638 for group in state.groups.iter_mut() {
13639 if let Some(last_id) = group.stack.last() {
13640 last_added_item_per_group.insert(*last_id, group);
13641 }
13642 }
13643
13644 for selection in columnar_selections {
13645 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
13646 if above == group.above {
13647 let range = selection.display_range(&display_map).sorted();
13648 debug_assert_eq!(range.start.row(), range.end.row());
13649 let mut row = range.start.row();
13650 let positions =
13651 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
13652 px(start)..px(end)
13653 } else {
13654 let start_x =
13655 display_map.x_for_display_point(range.start, &text_layout_details);
13656 let end_x =
13657 display_map.x_for_display_point(range.end, &text_layout_details);
13658 start_x.min(end_x)..start_x.max(end_x)
13659 };
13660
13661 let mut maybe_new_selection = None;
13662 while row != end_row {
13663 if above {
13664 row.0 -= 1;
13665 } else {
13666 row.0 += 1;
13667 }
13668 if let Some(new_selection) = self.selections.build_columnar_selection(
13669 &display_map,
13670 row,
13671 &positions,
13672 selection.reversed,
13673 &text_layout_details,
13674 ) {
13675 maybe_new_selection = Some(new_selection);
13676 break;
13677 }
13678 }
13679
13680 if let Some(new_selection) = maybe_new_selection {
13681 group.stack.push(new_selection.id);
13682 if above {
13683 final_selections.push(new_selection);
13684 final_selections.push(selection);
13685 } else {
13686 final_selections.push(selection);
13687 final_selections.push(new_selection);
13688 }
13689 } else {
13690 final_selections.push(selection);
13691 }
13692 } else {
13693 group.stack.pop();
13694 }
13695 } else {
13696 final_selections.push(selection);
13697 }
13698 }
13699
13700 self.change_selections(Default::default(), window, cx, |s| {
13701 s.select(final_selections);
13702 });
13703
13704 let final_selection_ids: HashSet<_> = self
13705 .selections
13706 .all::<Point>(cx)
13707 .iter()
13708 .map(|s| s.id)
13709 .collect();
13710 state.groups.retain_mut(|group| {
13711 // selections might get merged above so we remove invalid items from stacks
13712 group.stack.retain(|id| final_selection_ids.contains(id));
13713
13714 // single selection in stack can be treated as initial state
13715 group.stack.len() > 1
13716 });
13717
13718 if !state.groups.is_empty() {
13719 self.add_selections_state = Some(state);
13720 }
13721 }
13722
13723 fn select_match_ranges(
13724 &mut self,
13725 range: Range<usize>,
13726 reversed: bool,
13727 replace_newest: bool,
13728 auto_scroll: Option<Autoscroll>,
13729 window: &mut Window,
13730 cx: &mut Context<Editor>,
13731 ) {
13732 self.unfold_ranges(
13733 std::slice::from_ref(&range),
13734 false,
13735 auto_scroll.is_some(),
13736 cx,
13737 );
13738 let effects = if let Some(scroll) = auto_scroll {
13739 SelectionEffects::scroll(scroll)
13740 } else {
13741 SelectionEffects::no_scroll()
13742 };
13743 self.change_selections(effects, window, cx, |s| {
13744 if replace_newest {
13745 s.delete(s.newest_anchor().id);
13746 }
13747 if reversed {
13748 s.insert_range(range.end..range.start);
13749 } else {
13750 s.insert_range(range);
13751 }
13752 });
13753 }
13754
13755 pub fn select_next_match_internal(
13756 &mut self,
13757 display_map: &DisplaySnapshot,
13758 replace_newest: bool,
13759 autoscroll: Option<Autoscroll>,
13760 window: &mut Window,
13761 cx: &mut Context<Self>,
13762 ) -> Result<()> {
13763 let buffer = &display_map.buffer_snapshot;
13764 let mut selections = self.selections.all::<usize>(cx);
13765 if let Some(mut select_next_state) = self.select_next_state.take() {
13766 let query = &select_next_state.query;
13767 if !select_next_state.done {
13768 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13769 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13770 let mut next_selected_range = None;
13771
13772 let bytes_after_last_selection =
13773 buffer.bytes_in_range(last_selection.end..buffer.len());
13774 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
13775 let query_matches = query
13776 .stream_find_iter(bytes_after_last_selection)
13777 .map(|result| (last_selection.end, result))
13778 .chain(
13779 query
13780 .stream_find_iter(bytes_before_first_selection)
13781 .map(|result| (0, result)),
13782 );
13783
13784 for (start_offset, query_match) in query_matches {
13785 let query_match = query_match.unwrap(); // can only fail due to I/O
13786 let offset_range =
13787 start_offset + query_match.start()..start_offset + query_match.end();
13788
13789 if !select_next_state.wordwise
13790 || (!buffer.is_inside_word(offset_range.start, false)
13791 && !buffer.is_inside_word(offset_range.end, false))
13792 {
13793 // TODO: This is n^2, because we might check all the selections
13794 if !selections
13795 .iter()
13796 .any(|selection| selection.range().overlaps(&offset_range))
13797 {
13798 next_selected_range = Some(offset_range);
13799 break;
13800 }
13801 }
13802 }
13803
13804 if let Some(next_selected_range) = next_selected_range {
13805 self.select_match_ranges(
13806 next_selected_range,
13807 last_selection.reversed,
13808 replace_newest,
13809 autoscroll,
13810 window,
13811 cx,
13812 );
13813 } else {
13814 select_next_state.done = true;
13815 }
13816 }
13817
13818 self.select_next_state = Some(select_next_state);
13819 } else {
13820 let mut only_carets = true;
13821 let mut same_text_selected = true;
13822 let mut selected_text = None;
13823
13824 let mut selections_iter = selections.iter().peekable();
13825 while let Some(selection) = selections_iter.next() {
13826 if selection.start != selection.end {
13827 only_carets = false;
13828 }
13829
13830 if same_text_selected {
13831 if selected_text.is_none() {
13832 selected_text =
13833 Some(buffer.text_for_range(selection.range()).collect::<String>());
13834 }
13835
13836 if let Some(next_selection) = selections_iter.peek() {
13837 if next_selection.range().len() == selection.range().len() {
13838 let next_selected_text = buffer
13839 .text_for_range(next_selection.range())
13840 .collect::<String>();
13841 if Some(next_selected_text) != selected_text {
13842 same_text_selected = false;
13843 selected_text = None;
13844 }
13845 } else {
13846 same_text_selected = false;
13847 selected_text = None;
13848 }
13849 }
13850 }
13851 }
13852
13853 if only_carets {
13854 for selection in &mut selections {
13855 let (word_range, _) = buffer.surrounding_word(selection.start, false);
13856 selection.start = word_range.start;
13857 selection.end = word_range.end;
13858 selection.goal = SelectionGoal::None;
13859 selection.reversed = false;
13860 self.select_match_ranges(
13861 selection.start..selection.end,
13862 selection.reversed,
13863 replace_newest,
13864 autoscroll,
13865 window,
13866 cx,
13867 );
13868 }
13869
13870 if selections.len() == 1 {
13871 let selection = selections
13872 .last()
13873 .expect("ensured that there's only one selection");
13874 let query = buffer
13875 .text_for_range(selection.start..selection.end)
13876 .collect::<String>();
13877 let is_empty = query.is_empty();
13878 let select_state = SelectNextState {
13879 query: AhoCorasick::new(&[query])?,
13880 wordwise: true,
13881 done: is_empty,
13882 };
13883 self.select_next_state = Some(select_state);
13884 } else {
13885 self.select_next_state = None;
13886 }
13887 } else if let Some(selected_text) = selected_text {
13888 self.select_next_state = Some(SelectNextState {
13889 query: AhoCorasick::new(&[selected_text])?,
13890 wordwise: false,
13891 done: false,
13892 });
13893 self.select_next_match_internal(
13894 display_map,
13895 replace_newest,
13896 autoscroll,
13897 window,
13898 cx,
13899 )?;
13900 }
13901 }
13902 Ok(())
13903 }
13904
13905 pub fn select_all_matches(
13906 &mut self,
13907 _action: &SelectAllMatches,
13908 window: &mut Window,
13909 cx: &mut Context<Self>,
13910 ) -> Result<()> {
13911 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13912
13913 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13914
13915 self.select_next_match_internal(&display_map, false, None, window, cx)?;
13916 let Some(select_next_state) = self.select_next_state.as_mut() else {
13917 return Ok(());
13918 };
13919 if select_next_state.done {
13920 return Ok(());
13921 }
13922
13923 let mut new_selections = Vec::new();
13924
13925 let reversed = self.selections.oldest::<usize>(cx).reversed;
13926 let buffer = &display_map.buffer_snapshot;
13927 let query_matches = select_next_state
13928 .query
13929 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
13930
13931 for query_match in query_matches.into_iter() {
13932 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
13933 let offset_range = if reversed {
13934 query_match.end()..query_match.start()
13935 } else {
13936 query_match.start()..query_match.end()
13937 };
13938
13939 if !select_next_state.wordwise
13940 || (!buffer.is_inside_word(offset_range.start, false)
13941 && !buffer.is_inside_word(offset_range.end, false))
13942 {
13943 new_selections.push(offset_range.start..offset_range.end);
13944 }
13945 }
13946
13947 select_next_state.done = true;
13948
13949 if new_selections.is_empty() {
13950 log::error!("bug: new_selections is empty in select_all_matches");
13951 return Ok(());
13952 }
13953
13954 self.unfold_ranges(&new_selections.clone(), false, false, cx);
13955 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
13956 selections.select_ranges(new_selections)
13957 });
13958
13959 Ok(())
13960 }
13961
13962 pub fn select_next(
13963 &mut self,
13964 action: &SelectNext,
13965 window: &mut Window,
13966 cx: &mut Context<Self>,
13967 ) -> Result<()> {
13968 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13969 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13970 self.select_next_match_internal(
13971 &display_map,
13972 action.replace_newest,
13973 Some(Autoscroll::newest()),
13974 window,
13975 cx,
13976 )?;
13977 Ok(())
13978 }
13979
13980 pub fn select_previous(
13981 &mut self,
13982 action: &SelectPrevious,
13983 window: &mut Window,
13984 cx: &mut Context<Self>,
13985 ) -> Result<()> {
13986 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13987 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13988 let buffer = &display_map.buffer_snapshot;
13989 let mut selections = self.selections.all::<usize>(cx);
13990 if let Some(mut select_prev_state) = self.select_prev_state.take() {
13991 let query = &select_prev_state.query;
13992 if !select_prev_state.done {
13993 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13994 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13995 let mut next_selected_range = None;
13996 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
13997 let bytes_before_last_selection =
13998 buffer.reversed_bytes_in_range(0..last_selection.start);
13999 let bytes_after_first_selection =
14000 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14001 let query_matches = query
14002 .stream_find_iter(bytes_before_last_selection)
14003 .map(|result| (last_selection.start, result))
14004 .chain(
14005 query
14006 .stream_find_iter(bytes_after_first_selection)
14007 .map(|result| (buffer.len(), result)),
14008 );
14009 for (end_offset, query_match) in query_matches {
14010 let query_match = query_match.unwrap(); // can only fail due to I/O
14011 let offset_range =
14012 end_offset - query_match.end()..end_offset - query_match.start();
14013
14014 if !select_prev_state.wordwise
14015 || (!buffer.is_inside_word(offset_range.start, false)
14016 && !buffer.is_inside_word(offset_range.end, false))
14017 {
14018 next_selected_range = Some(offset_range);
14019 break;
14020 }
14021 }
14022
14023 if let Some(next_selected_range) = next_selected_range {
14024 self.select_match_ranges(
14025 next_selected_range,
14026 last_selection.reversed,
14027 action.replace_newest,
14028 Some(Autoscroll::newest()),
14029 window,
14030 cx,
14031 );
14032 } else {
14033 select_prev_state.done = true;
14034 }
14035 }
14036
14037 self.select_prev_state = Some(select_prev_state);
14038 } else {
14039 let mut only_carets = true;
14040 let mut same_text_selected = true;
14041 let mut selected_text = None;
14042
14043 let mut selections_iter = selections.iter().peekable();
14044 while let Some(selection) = selections_iter.next() {
14045 if selection.start != selection.end {
14046 only_carets = false;
14047 }
14048
14049 if same_text_selected {
14050 if selected_text.is_none() {
14051 selected_text =
14052 Some(buffer.text_for_range(selection.range()).collect::<String>());
14053 }
14054
14055 if let Some(next_selection) = selections_iter.peek() {
14056 if next_selection.range().len() == selection.range().len() {
14057 let next_selected_text = buffer
14058 .text_for_range(next_selection.range())
14059 .collect::<String>();
14060 if Some(next_selected_text) != selected_text {
14061 same_text_selected = false;
14062 selected_text = None;
14063 }
14064 } else {
14065 same_text_selected = false;
14066 selected_text = None;
14067 }
14068 }
14069 }
14070 }
14071
14072 if only_carets {
14073 for selection in &mut selections {
14074 let (word_range, _) = buffer.surrounding_word(selection.start, false);
14075 selection.start = word_range.start;
14076 selection.end = word_range.end;
14077 selection.goal = SelectionGoal::None;
14078 selection.reversed = false;
14079 self.select_match_ranges(
14080 selection.start..selection.end,
14081 selection.reversed,
14082 action.replace_newest,
14083 Some(Autoscroll::newest()),
14084 window,
14085 cx,
14086 );
14087 }
14088 if selections.len() == 1 {
14089 let selection = selections
14090 .last()
14091 .expect("ensured that there's only one selection");
14092 let query = buffer
14093 .text_for_range(selection.start..selection.end)
14094 .collect::<String>();
14095 let is_empty = query.is_empty();
14096 let select_state = SelectNextState {
14097 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
14098 wordwise: true,
14099 done: is_empty,
14100 };
14101 self.select_prev_state = Some(select_state);
14102 } else {
14103 self.select_prev_state = None;
14104 }
14105 } else if let Some(selected_text) = selected_text {
14106 self.select_prev_state = Some(SelectNextState {
14107 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
14108 wordwise: false,
14109 done: false,
14110 });
14111 self.select_previous(action, window, cx)?;
14112 }
14113 }
14114 Ok(())
14115 }
14116
14117 pub fn find_next_match(
14118 &mut self,
14119 _: &FindNextMatch,
14120 window: &mut Window,
14121 cx: &mut Context<Self>,
14122 ) -> Result<()> {
14123 let selections = self.selections.disjoint_anchors();
14124 match selections.first() {
14125 Some(first) if selections.len() >= 2 => {
14126 self.change_selections(Default::default(), window, cx, |s| {
14127 s.select_ranges([first.range()]);
14128 });
14129 }
14130 _ => self.select_next(
14131 &SelectNext {
14132 replace_newest: true,
14133 },
14134 window,
14135 cx,
14136 )?,
14137 }
14138 Ok(())
14139 }
14140
14141 pub fn find_previous_match(
14142 &mut self,
14143 _: &FindPreviousMatch,
14144 window: &mut Window,
14145 cx: &mut Context<Self>,
14146 ) -> Result<()> {
14147 let selections = self.selections.disjoint_anchors();
14148 match selections.last() {
14149 Some(last) if selections.len() >= 2 => {
14150 self.change_selections(Default::default(), window, cx, |s| {
14151 s.select_ranges([last.range()]);
14152 });
14153 }
14154 _ => self.select_previous(
14155 &SelectPrevious {
14156 replace_newest: true,
14157 },
14158 window,
14159 cx,
14160 )?,
14161 }
14162 Ok(())
14163 }
14164
14165 pub fn toggle_comments(
14166 &mut self,
14167 action: &ToggleComments,
14168 window: &mut Window,
14169 cx: &mut Context<Self>,
14170 ) {
14171 if self.read_only(cx) {
14172 return;
14173 }
14174 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14175 let text_layout_details = &self.text_layout_details(window);
14176 self.transact(window, cx, |this, window, cx| {
14177 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
14178 let mut edits = Vec::new();
14179 let mut selection_edit_ranges = Vec::new();
14180 let mut last_toggled_row = None;
14181 let snapshot = this.buffer.read(cx).read(cx);
14182 let empty_str: Arc<str> = Arc::default();
14183 let mut suffixes_inserted = Vec::new();
14184 let ignore_indent = action.ignore_indent;
14185
14186 fn comment_prefix_range(
14187 snapshot: &MultiBufferSnapshot,
14188 row: MultiBufferRow,
14189 comment_prefix: &str,
14190 comment_prefix_whitespace: &str,
14191 ignore_indent: bool,
14192 ) -> Range<Point> {
14193 let indent_size = if ignore_indent {
14194 0
14195 } else {
14196 snapshot.indent_size_for_line(row).len
14197 };
14198
14199 let start = Point::new(row.0, indent_size);
14200
14201 let mut line_bytes = snapshot
14202 .bytes_in_range(start..snapshot.max_point())
14203 .flatten()
14204 .copied();
14205
14206 // If this line currently begins with the line comment prefix, then record
14207 // the range containing the prefix.
14208 if line_bytes
14209 .by_ref()
14210 .take(comment_prefix.len())
14211 .eq(comment_prefix.bytes())
14212 {
14213 // Include any whitespace that matches the comment prefix.
14214 let matching_whitespace_len = line_bytes
14215 .zip(comment_prefix_whitespace.bytes())
14216 .take_while(|(a, b)| a == b)
14217 .count() as u32;
14218 let end = Point::new(
14219 start.row,
14220 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14221 );
14222 start..end
14223 } else {
14224 start..start
14225 }
14226 }
14227
14228 fn comment_suffix_range(
14229 snapshot: &MultiBufferSnapshot,
14230 row: MultiBufferRow,
14231 comment_suffix: &str,
14232 comment_suffix_has_leading_space: bool,
14233 ) -> Range<Point> {
14234 let end = Point::new(row.0, snapshot.line_len(row));
14235 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14236
14237 let mut line_end_bytes = snapshot
14238 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14239 .flatten()
14240 .copied();
14241
14242 let leading_space_len = if suffix_start_column > 0
14243 && line_end_bytes.next() == Some(b' ')
14244 && comment_suffix_has_leading_space
14245 {
14246 1
14247 } else {
14248 0
14249 };
14250
14251 // If this line currently begins with the line comment prefix, then record
14252 // the range containing the prefix.
14253 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
14254 let start = Point::new(end.row, suffix_start_column - leading_space_len);
14255 start..end
14256 } else {
14257 end..end
14258 }
14259 }
14260
14261 // TODO: Handle selections that cross excerpts
14262 for selection in &mut selections {
14263 let start_column = snapshot
14264 .indent_size_for_line(MultiBufferRow(selection.start.row))
14265 .len;
14266 let language = if let Some(language) =
14267 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
14268 {
14269 language
14270 } else {
14271 continue;
14272 };
14273
14274 selection_edit_ranges.clear();
14275
14276 // If multiple selections contain a given row, avoid processing that
14277 // row more than once.
14278 let mut start_row = MultiBufferRow(selection.start.row);
14279 if last_toggled_row == Some(start_row) {
14280 start_row = start_row.next_row();
14281 }
14282 let end_row =
14283 if selection.end.row > selection.start.row && selection.end.column == 0 {
14284 MultiBufferRow(selection.end.row - 1)
14285 } else {
14286 MultiBufferRow(selection.end.row)
14287 };
14288 last_toggled_row = Some(end_row);
14289
14290 if start_row > end_row {
14291 continue;
14292 }
14293
14294 // If the language has line comments, toggle those.
14295 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
14296
14297 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
14298 if ignore_indent {
14299 full_comment_prefixes = full_comment_prefixes
14300 .into_iter()
14301 .map(|s| Arc::from(s.trim_end()))
14302 .collect();
14303 }
14304
14305 if !full_comment_prefixes.is_empty() {
14306 let first_prefix = full_comment_prefixes
14307 .first()
14308 .expect("prefixes is non-empty");
14309 let prefix_trimmed_lengths = full_comment_prefixes
14310 .iter()
14311 .map(|p| p.trim_end_matches(' ').len())
14312 .collect::<SmallVec<[usize; 4]>>();
14313
14314 let mut all_selection_lines_are_comments = true;
14315
14316 for row in start_row.0..=end_row.0 {
14317 let row = MultiBufferRow(row);
14318 if start_row < end_row && snapshot.is_line_blank(row) {
14319 continue;
14320 }
14321
14322 let prefix_range = full_comment_prefixes
14323 .iter()
14324 .zip(prefix_trimmed_lengths.iter().copied())
14325 .map(|(prefix, trimmed_prefix_len)| {
14326 comment_prefix_range(
14327 snapshot.deref(),
14328 row,
14329 &prefix[..trimmed_prefix_len],
14330 &prefix[trimmed_prefix_len..],
14331 ignore_indent,
14332 )
14333 })
14334 .max_by_key(|range| range.end.column - range.start.column)
14335 .expect("prefixes is non-empty");
14336
14337 if prefix_range.is_empty() {
14338 all_selection_lines_are_comments = false;
14339 }
14340
14341 selection_edit_ranges.push(prefix_range);
14342 }
14343
14344 if all_selection_lines_are_comments {
14345 edits.extend(
14346 selection_edit_ranges
14347 .iter()
14348 .cloned()
14349 .map(|range| (range, empty_str.clone())),
14350 );
14351 } else {
14352 let min_column = selection_edit_ranges
14353 .iter()
14354 .map(|range| range.start.column)
14355 .min()
14356 .unwrap_or(0);
14357 edits.extend(selection_edit_ranges.iter().map(|range| {
14358 let position = Point::new(range.start.row, min_column);
14359 (position..position, first_prefix.clone())
14360 }));
14361 }
14362 } else if let Some(BlockCommentConfig {
14363 start: full_comment_prefix,
14364 end: comment_suffix,
14365 ..
14366 }) = language.block_comment()
14367 {
14368 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
14369 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
14370 let prefix_range = comment_prefix_range(
14371 snapshot.deref(),
14372 start_row,
14373 comment_prefix,
14374 comment_prefix_whitespace,
14375 ignore_indent,
14376 );
14377 let suffix_range = comment_suffix_range(
14378 snapshot.deref(),
14379 end_row,
14380 comment_suffix.trim_start_matches(' '),
14381 comment_suffix.starts_with(' '),
14382 );
14383
14384 if prefix_range.is_empty() || suffix_range.is_empty() {
14385 edits.push((
14386 prefix_range.start..prefix_range.start,
14387 full_comment_prefix.clone(),
14388 ));
14389 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
14390 suffixes_inserted.push((end_row, comment_suffix.len()));
14391 } else {
14392 edits.push((prefix_range, empty_str.clone()));
14393 edits.push((suffix_range, empty_str.clone()));
14394 }
14395 } else {
14396 continue;
14397 }
14398 }
14399
14400 drop(snapshot);
14401 this.buffer.update(cx, |buffer, cx| {
14402 buffer.edit(edits, None, cx);
14403 });
14404
14405 // Adjust selections so that they end before any comment suffixes that
14406 // were inserted.
14407 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
14408 let mut selections = this.selections.all::<Point>(cx);
14409 let snapshot = this.buffer.read(cx).read(cx);
14410 for selection in &mut selections {
14411 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
14412 match row.cmp(&MultiBufferRow(selection.end.row)) {
14413 Ordering::Less => {
14414 suffixes_inserted.next();
14415 continue;
14416 }
14417 Ordering::Greater => break,
14418 Ordering::Equal => {
14419 if selection.end.column == snapshot.line_len(row) {
14420 if selection.is_empty() {
14421 selection.start.column -= suffix_len as u32;
14422 }
14423 selection.end.column -= suffix_len as u32;
14424 }
14425 break;
14426 }
14427 }
14428 }
14429 }
14430
14431 drop(snapshot);
14432 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
14433
14434 let selections = this.selections.all::<Point>(cx);
14435 let selections_on_single_row = selections.windows(2).all(|selections| {
14436 selections[0].start.row == selections[1].start.row
14437 && selections[0].end.row == selections[1].end.row
14438 && selections[0].start.row == selections[0].end.row
14439 });
14440 let selections_selecting = selections
14441 .iter()
14442 .any(|selection| selection.start != selection.end);
14443 let advance_downwards = action.advance_downwards
14444 && selections_on_single_row
14445 && !selections_selecting
14446 && !matches!(this.mode, EditorMode::SingleLine { .. });
14447
14448 if advance_downwards {
14449 let snapshot = this.buffer.read(cx).snapshot(cx);
14450
14451 this.change_selections(Default::default(), window, cx, |s| {
14452 s.move_cursors_with(|display_snapshot, display_point, _| {
14453 let mut point = display_point.to_point(display_snapshot);
14454 point.row += 1;
14455 point = snapshot.clip_point(point, Bias::Left);
14456 let display_point = point.to_display_point(display_snapshot);
14457 let goal = SelectionGoal::HorizontalPosition(
14458 display_snapshot
14459 .x_for_display_point(display_point, text_layout_details)
14460 .into(),
14461 );
14462 (display_point, goal)
14463 })
14464 });
14465 }
14466 });
14467 }
14468
14469 pub fn select_enclosing_symbol(
14470 &mut self,
14471 _: &SelectEnclosingSymbol,
14472 window: &mut Window,
14473 cx: &mut Context<Self>,
14474 ) {
14475 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14476
14477 let buffer = self.buffer.read(cx).snapshot(cx);
14478 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
14479
14480 fn update_selection(
14481 selection: &Selection<usize>,
14482 buffer_snap: &MultiBufferSnapshot,
14483 ) -> Option<Selection<usize>> {
14484 let cursor = selection.head();
14485 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
14486 for symbol in symbols.iter().rev() {
14487 let start = symbol.range.start.to_offset(buffer_snap);
14488 let end = symbol.range.end.to_offset(buffer_snap);
14489 let new_range = start..end;
14490 if start < selection.start || end > selection.end {
14491 return Some(Selection {
14492 id: selection.id,
14493 start: new_range.start,
14494 end: new_range.end,
14495 goal: SelectionGoal::None,
14496 reversed: selection.reversed,
14497 });
14498 }
14499 }
14500 None
14501 }
14502
14503 let mut selected_larger_symbol = false;
14504 let new_selections = old_selections
14505 .iter()
14506 .map(|selection| match update_selection(selection, &buffer) {
14507 Some(new_selection) => {
14508 if new_selection.range() != selection.range() {
14509 selected_larger_symbol = true;
14510 }
14511 new_selection
14512 }
14513 None => selection.clone(),
14514 })
14515 .collect::<Vec<_>>();
14516
14517 if selected_larger_symbol {
14518 self.change_selections(Default::default(), window, cx, |s| {
14519 s.select(new_selections);
14520 });
14521 }
14522 }
14523
14524 pub fn select_larger_syntax_node(
14525 &mut self,
14526 _: &SelectLargerSyntaxNode,
14527 window: &mut Window,
14528 cx: &mut Context<Self>,
14529 ) {
14530 let Some(visible_row_count) = self.visible_row_count() else {
14531 return;
14532 };
14533 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
14534 if old_selections.is_empty() {
14535 return;
14536 }
14537
14538 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14539
14540 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14541 let buffer = self.buffer.read(cx).snapshot(cx);
14542
14543 let mut selected_larger_node = false;
14544 let mut new_selections = old_selections
14545 .iter()
14546 .map(|selection| {
14547 let old_range = selection.start..selection.end;
14548
14549 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
14550 // manually select word at selection
14551 if ["string_content", "inline"].contains(&node.kind()) {
14552 let (word_range, _) = buffer.surrounding_word(old_range.start, false);
14553 // ignore if word is already selected
14554 if !word_range.is_empty() && old_range != word_range {
14555 let (last_word_range, _) =
14556 buffer.surrounding_word(old_range.end, false);
14557 // only select word if start and end point belongs to same word
14558 if word_range == last_word_range {
14559 selected_larger_node = true;
14560 return Selection {
14561 id: selection.id,
14562 start: word_range.start,
14563 end: word_range.end,
14564 goal: SelectionGoal::None,
14565 reversed: selection.reversed,
14566 };
14567 }
14568 }
14569 }
14570 }
14571
14572 let mut new_range = old_range.clone();
14573 while let Some((_node, containing_range)) =
14574 buffer.syntax_ancestor(new_range.clone())
14575 {
14576 new_range = match containing_range {
14577 MultiOrSingleBufferOffsetRange::Single(_) => break,
14578 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14579 };
14580 if !display_map.intersects_fold(new_range.start)
14581 && !display_map.intersects_fold(new_range.end)
14582 {
14583 break;
14584 }
14585 }
14586
14587 selected_larger_node |= new_range != old_range;
14588 Selection {
14589 id: selection.id,
14590 start: new_range.start,
14591 end: new_range.end,
14592 goal: SelectionGoal::None,
14593 reversed: selection.reversed,
14594 }
14595 })
14596 .collect::<Vec<_>>();
14597
14598 if !selected_larger_node {
14599 return; // don't put this call in the history
14600 }
14601
14602 // scroll based on transformation done to the last selection created by the user
14603 let (last_old, last_new) = old_selections
14604 .last()
14605 .zip(new_selections.last().cloned())
14606 .expect("old_selections isn't empty");
14607
14608 // revert selection
14609 let is_selection_reversed = {
14610 let should_newest_selection_be_reversed = last_old.start != last_new.start;
14611 new_selections.last_mut().expect("checked above").reversed =
14612 should_newest_selection_be_reversed;
14613 should_newest_selection_be_reversed
14614 };
14615
14616 if selected_larger_node {
14617 self.select_syntax_node_history.disable_clearing = true;
14618 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14619 s.select(new_selections.clone());
14620 });
14621 self.select_syntax_node_history.disable_clearing = false;
14622 }
14623
14624 let start_row = last_new.start.to_display_point(&display_map).row().0;
14625 let end_row = last_new.end.to_display_point(&display_map).row().0;
14626 let selection_height = end_row - start_row + 1;
14627 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
14628
14629 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
14630 let scroll_behavior = if fits_on_the_screen {
14631 self.request_autoscroll(Autoscroll::fit(), cx);
14632 SelectSyntaxNodeScrollBehavior::FitSelection
14633 } else if is_selection_reversed {
14634 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14635 SelectSyntaxNodeScrollBehavior::CursorTop
14636 } else {
14637 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14638 SelectSyntaxNodeScrollBehavior::CursorBottom
14639 };
14640
14641 self.select_syntax_node_history.push((
14642 old_selections,
14643 scroll_behavior,
14644 is_selection_reversed,
14645 ));
14646 }
14647
14648 pub fn select_smaller_syntax_node(
14649 &mut self,
14650 _: &SelectSmallerSyntaxNode,
14651 window: &mut Window,
14652 cx: &mut Context<Self>,
14653 ) {
14654 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14655
14656 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
14657 self.select_syntax_node_history.pop()
14658 {
14659 if let Some(selection) = selections.last_mut() {
14660 selection.reversed = is_selection_reversed;
14661 }
14662
14663 self.select_syntax_node_history.disable_clearing = true;
14664 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14665 s.select(selections.to_vec());
14666 });
14667 self.select_syntax_node_history.disable_clearing = false;
14668
14669 match scroll_behavior {
14670 SelectSyntaxNodeScrollBehavior::CursorTop => {
14671 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14672 }
14673 SelectSyntaxNodeScrollBehavior::FitSelection => {
14674 self.request_autoscroll(Autoscroll::fit(), cx);
14675 }
14676 SelectSyntaxNodeScrollBehavior::CursorBottom => {
14677 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14678 }
14679 }
14680 }
14681 }
14682
14683 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
14684 if !EditorSettings::get_global(cx).gutter.runnables {
14685 self.clear_tasks();
14686 return Task::ready(());
14687 }
14688 let project = self.project.as_ref().map(Entity::downgrade);
14689 let task_sources = self.lsp_task_sources(cx);
14690 let multi_buffer = self.buffer.downgrade();
14691 cx.spawn_in(window, async move |editor, cx| {
14692 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
14693 let Some(project) = project.and_then(|p| p.upgrade()) else {
14694 return;
14695 };
14696 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
14697 this.display_map.update(cx, |map, cx| map.snapshot(cx))
14698 }) else {
14699 return;
14700 };
14701
14702 let hide_runnables = project
14703 .update(cx, |project, cx| {
14704 // Do not display any test indicators in non-dev server remote projects.
14705 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
14706 })
14707 .unwrap_or(true);
14708 if hide_runnables {
14709 return;
14710 }
14711 let new_rows =
14712 cx.background_spawn({
14713 let snapshot = display_snapshot.clone();
14714 async move {
14715 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
14716 }
14717 })
14718 .await;
14719 let Ok(lsp_tasks) =
14720 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
14721 else {
14722 return;
14723 };
14724 let lsp_tasks = lsp_tasks.await;
14725
14726 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
14727 lsp_tasks
14728 .into_iter()
14729 .flat_map(|(kind, tasks)| {
14730 tasks.into_iter().filter_map(move |(location, task)| {
14731 Some((kind.clone(), location?, task))
14732 })
14733 })
14734 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
14735 let buffer = location.target.buffer;
14736 let buffer_snapshot = buffer.read(cx).snapshot();
14737 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
14738 |(excerpt_id, snapshot, _)| {
14739 if snapshot.remote_id() == buffer_snapshot.remote_id() {
14740 display_snapshot
14741 .buffer_snapshot
14742 .anchor_in_excerpt(excerpt_id, location.target.range.start)
14743 } else {
14744 None
14745 }
14746 },
14747 );
14748 if let Some(offset) = offset {
14749 let task_buffer_range =
14750 location.target.range.to_point(&buffer_snapshot);
14751 let context_buffer_range =
14752 task_buffer_range.to_offset(&buffer_snapshot);
14753 let context_range = BufferOffset(context_buffer_range.start)
14754 ..BufferOffset(context_buffer_range.end);
14755
14756 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
14757 .or_insert_with(|| RunnableTasks {
14758 templates: Vec::new(),
14759 offset,
14760 column: task_buffer_range.start.column,
14761 extra_variables: HashMap::default(),
14762 context_range,
14763 })
14764 .templates
14765 .push((kind, task.original_task().clone()));
14766 }
14767
14768 acc
14769 })
14770 }) else {
14771 return;
14772 };
14773
14774 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
14775 buffer.language_settings(cx).tasks.prefer_lsp
14776 }) else {
14777 return;
14778 };
14779
14780 let rows = Self::runnable_rows(
14781 project,
14782 display_snapshot,
14783 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
14784 new_rows,
14785 cx.clone(),
14786 )
14787 .await;
14788 editor
14789 .update(cx, |editor, _| {
14790 editor.clear_tasks();
14791 for (key, mut value) in rows {
14792 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
14793 value.templates.extend(lsp_tasks.templates);
14794 }
14795
14796 editor.insert_tasks(key, value);
14797 }
14798 for (key, value) in lsp_tasks_by_rows {
14799 editor.insert_tasks(key, value);
14800 }
14801 })
14802 .ok();
14803 })
14804 }
14805 fn fetch_runnable_ranges(
14806 snapshot: &DisplaySnapshot,
14807 range: Range<Anchor>,
14808 ) -> Vec<language::RunnableRange> {
14809 snapshot.buffer_snapshot.runnable_ranges(range).collect()
14810 }
14811
14812 fn runnable_rows(
14813 project: Entity<Project>,
14814 snapshot: DisplaySnapshot,
14815 prefer_lsp: bool,
14816 runnable_ranges: Vec<RunnableRange>,
14817 cx: AsyncWindowContext,
14818 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
14819 cx.spawn(async move |cx| {
14820 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
14821 for mut runnable in runnable_ranges {
14822 let Some(tasks) = cx
14823 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
14824 .ok()
14825 else {
14826 continue;
14827 };
14828 let mut tasks = tasks.await;
14829
14830 if prefer_lsp {
14831 tasks.retain(|(task_kind, _)| {
14832 !matches!(task_kind, TaskSourceKind::Language { .. })
14833 });
14834 }
14835 if tasks.is_empty() {
14836 continue;
14837 }
14838
14839 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
14840 let Some(row) = snapshot
14841 .buffer_snapshot
14842 .buffer_line_for_row(MultiBufferRow(point.row))
14843 .map(|(_, range)| range.start.row)
14844 else {
14845 continue;
14846 };
14847
14848 let context_range =
14849 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
14850 runnable_rows.push((
14851 (runnable.buffer_id, row),
14852 RunnableTasks {
14853 templates: tasks,
14854 offset: snapshot
14855 .buffer_snapshot
14856 .anchor_before(runnable.run_range.start),
14857 context_range,
14858 column: point.column,
14859 extra_variables: runnable.extra_captures,
14860 },
14861 ));
14862 }
14863 runnable_rows
14864 })
14865 }
14866
14867 fn templates_with_tags(
14868 project: &Entity<Project>,
14869 runnable: &mut Runnable,
14870 cx: &mut App,
14871 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
14872 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
14873 let (worktree_id, file) = project
14874 .buffer_for_id(runnable.buffer, cx)
14875 .and_then(|buffer| buffer.read(cx).file())
14876 .map(|file| (file.worktree_id(cx), file.clone()))
14877 .unzip();
14878
14879 (
14880 project.task_store().read(cx).task_inventory().cloned(),
14881 worktree_id,
14882 file,
14883 )
14884 });
14885
14886 let tags = mem::take(&mut runnable.tags);
14887 let language = runnable.language.clone();
14888 cx.spawn(async move |cx| {
14889 let mut templates_with_tags = Vec::new();
14890 if let Some(inventory) = inventory {
14891 for RunnableTag(tag) in tags {
14892 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
14893 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
14894 }) else {
14895 return templates_with_tags;
14896 };
14897 templates_with_tags.extend(new_tasks.await.into_iter().filter(
14898 move |(_, template)| {
14899 template.tags.iter().any(|source_tag| source_tag == &tag)
14900 },
14901 ));
14902 }
14903 }
14904 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
14905
14906 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
14907 // Strongest source wins; if we have worktree tag binding, prefer that to
14908 // global and language bindings;
14909 // if we have a global binding, prefer that to language binding.
14910 let first_mismatch = templates_with_tags
14911 .iter()
14912 .position(|(tag_source, _)| tag_source != leading_tag_source);
14913 if let Some(index) = first_mismatch {
14914 templates_with_tags.truncate(index);
14915 }
14916 }
14917
14918 templates_with_tags
14919 })
14920 }
14921
14922 pub fn move_to_enclosing_bracket(
14923 &mut self,
14924 _: &MoveToEnclosingBracket,
14925 window: &mut Window,
14926 cx: &mut Context<Self>,
14927 ) {
14928 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14929 self.change_selections(Default::default(), window, cx, |s| {
14930 s.move_offsets_with(|snapshot, selection| {
14931 let Some(enclosing_bracket_ranges) =
14932 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
14933 else {
14934 return;
14935 };
14936
14937 let mut best_length = usize::MAX;
14938 let mut best_inside = false;
14939 let mut best_in_bracket_range = false;
14940 let mut best_destination = None;
14941 for (open, close) in enclosing_bracket_ranges {
14942 let close = close.to_inclusive();
14943 let length = close.end() - open.start;
14944 let inside = selection.start >= open.end && selection.end <= *close.start();
14945 let in_bracket_range = open.to_inclusive().contains(&selection.head())
14946 || close.contains(&selection.head());
14947
14948 // If best is next to a bracket and current isn't, skip
14949 if !in_bracket_range && best_in_bracket_range {
14950 continue;
14951 }
14952
14953 // Prefer smaller lengths unless best is inside and current isn't
14954 if length > best_length && (best_inside || !inside) {
14955 continue;
14956 }
14957
14958 best_length = length;
14959 best_inside = inside;
14960 best_in_bracket_range = in_bracket_range;
14961 best_destination = Some(
14962 if close.contains(&selection.start) && close.contains(&selection.end) {
14963 if inside { open.end } else { open.start }
14964 } else if inside {
14965 *close.start()
14966 } else {
14967 *close.end()
14968 },
14969 );
14970 }
14971
14972 if let Some(destination) = best_destination {
14973 selection.collapse_to(destination, SelectionGoal::None);
14974 }
14975 })
14976 });
14977 }
14978
14979 pub fn undo_selection(
14980 &mut self,
14981 _: &UndoSelection,
14982 window: &mut Window,
14983 cx: &mut Context<Self>,
14984 ) {
14985 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14986 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
14987 self.selection_history.mode = SelectionHistoryMode::Undoing;
14988 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
14989 this.end_selection(window, cx);
14990 this.change_selections(
14991 SelectionEffects::scroll(Autoscroll::newest()),
14992 window,
14993 cx,
14994 |s| s.select_anchors(entry.selections.to_vec()),
14995 );
14996 });
14997 self.selection_history.mode = SelectionHistoryMode::Normal;
14998
14999 self.select_next_state = entry.select_next_state;
15000 self.select_prev_state = entry.select_prev_state;
15001 self.add_selections_state = entry.add_selections_state;
15002 }
15003 }
15004
15005 pub fn redo_selection(
15006 &mut self,
15007 _: &RedoSelection,
15008 window: &mut Window,
15009 cx: &mut Context<Self>,
15010 ) {
15011 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15012 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
15013 self.selection_history.mode = SelectionHistoryMode::Redoing;
15014 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15015 this.end_selection(window, cx);
15016 this.change_selections(
15017 SelectionEffects::scroll(Autoscroll::newest()),
15018 window,
15019 cx,
15020 |s| s.select_anchors(entry.selections.to_vec()),
15021 );
15022 });
15023 self.selection_history.mode = SelectionHistoryMode::Normal;
15024
15025 self.select_next_state = entry.select_next_state;
15026 self.select_prev_state = entry.select_prev_state;
15027 self.add_selections_state = entry.add_selections_state;
15028 }
15029 }
15030
15031 pub fn expand_excerpts(
15032 &mut self,
15033 action: &ExpandExcerpts,
15034 _: &mut Window,
15035 cx: &mut Context<Self>,
15036 ) {
15037 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
15038 }
15039
15040 pub fn expand_excerpts_down(
15041 &mut self,
15042 action: &ExpandExcerptsDown,
15043 _: &mut Window,
15044 cx: &mut Context<Self>,
15045 ) {
15046 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
15047 }
15048
15049 pub fn expand_excerpts_up(
15050 &mut self,
15051 action: &ExpandExcerptsUp,
15052 _: &mut Window,
15053 cx: &mut Context<Self>,
15054 ) {
15055 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
15056 }
15057
15058 pub fn expand_excerpts_for_direction(
15059 &mut self,
15060 lines: u32,
15061 direction: ExpandExcerptDirection,
15062
15063 cx: &mut Context<Self>,
15064 ) {
15065 let selections = self.selections.disjoint_anchors();
15066
15067 let lines = if lines == 0 {
15068 EditorSettings::get_global(cx).expand_excerpt_lines
15069 } else {
15070 lines
15071 };
15072
15073 self.buffer.update(cx, |buffer, cx| {
15074 let snapshot = buffer.snapshot(cx);
15075 let mut excerpt_ids = selections
15076 .iter()
15077 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
15078 .collect::<Vec<_>>();
15079 excerpt_ids.sort();
15080 excerpt_ids.dedup();
15081 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
15082 })
15083 }
15084
15085 pub fn expand_excerpt(
15086 &mut self,
15087 excerpt: ExcerptId,
15088 direction: ExpandExcerptDirection,
15089 window: &mut Window,
15090 cx: &mut Context<Self>,
15091 ) {
15092 let current_scroll_position = self.scroll_position(cx);
15093 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
15094 let mut should_scroll_up = false;
15095
15096 if direction == ExpandExcerptDirection::Down {
15097 let multi_buffer = self.buffer.read(cx);
15098 let snapshot = multi_buffer.snapshot(cx);
15099 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
15100 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
15101 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
15102 let buffer_snapshot = buffer.read(cx).snapshot();
15103 let excerpt_end_row =
15104 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
15105 let last_row = buffer_snapshot.max_point().row;
15106 let lines_below = last_row.saturating_sub(excerpt_end_row);
15107 should_scroll_up = lines_below >= lines_to_expand;
15108 }
15109 }
15110 }
15111 }
15112
15113 self.buffer.update(cx, |buffer, cx| {
15114 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
15115 });
15116
15117 if should_scroll_up {
15118 let new_scroll_position =
15119 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
15120 self.set_scroll_position(new_scroll_position, window, cx);
15121 }
15122 }
15123
15124 pub fn go_to_singleton_buffer_point(
15125 &mut self,
15126 point: Point,
15127 window: &mut Window,
15128 cx: &mut Context<Self>,
15129 ) {
15130 self.go_to_singleton_buffer_range(point..point, window, cx);
15131 }
15132
15133 pub fn go_to_singleton_buffer_range(
15134 &mut self,
15135 range: Range<Point>,
15136 window: &mut Window,
15137 cx: &mut Context<Self>,
15138 ) {
15139 let multibuffer = self.buffer().read(cx);
15140 let Some(buffer) = multibuffer.as_singleton() else {
15141 return;
15142 };
15143 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
15144 return;
15145 };
15146 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
15147 return;
15148 };
15149 self.change_selections(
15150 SelectionEffects::default().nav_history(true),
15151 window,
15152 cx,
15153 |s| s.select_anchor_ranges([start..end]),
15154 );
15155 }
15156
15157 pub fn go_to_diagnostic(
15158 &mut self,
15159 action: &GoToDiagnostic,
15160 window: &mut Window,
15161 cx: &mut Context<Self>,
15162 ) {
15163 if !self.diagnostics_enabled() {
15164 return;
15165 }
15166 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15167 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
15168 }
15169
15170 pub fn go_to_prev_diagnostic(
15171 &mut self,
15172 action: &GoToPreviousDiagnostic,
15173 window: &mut Window,
15174 cx: &mut Context<Self>,
15175 ) {
15176 if !self.diagnostics_enabled() {
15177 return;
15178 }
15179 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15180 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
15181 }
15182
15183 pub fn go_to_diagnostic_impl(
15184 &mut self,
15185 direction: Direction,
15186 severity: GoToDiagnosticSeverityFilter,
15187 window: &mut Window,
15188 cx: &mut Context<Self>,
15189 ) {
15190 let buffer = self.buffer.read(cx).snapshot(cx);
15191 let selection = self.selections.newest::<usize>(cx);
15192
15193 let mut active_group_id = None;
15194 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
15195 if active_group.active_range.start.to_offset(&buffer) == selection.start {
15196 active_group_id = Some(active_group.group_id);
15197 }
15198 }
15199
15200 fn filtered(
15201 snapshot: EditorSnapshot,
15202 severity: GoToDiagnosticSeverityFilter,
15203 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
15204 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
15205 diagnostics
15206 .filter(move |entry| severity.matches(entry.diagnostic.severity))
15207 .filter(|entry| entry.range.start != entry.range.end)
15208 .filter(|entry| !entry.diagnostic.is_unnecessary)
15209 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
15210 }
15211
15212 let snapshot = self.snapshot(window, cx);
15213 let before = filtered(
15214 snapshot.clone(),
15215 severity,
15216 buffer
15217 .diagnostics_in_range(0..selection.start)
15218 .filter(|entry| entry.range.start <= selection.start),
15219 );
15220 let after = filtered(
15221 snapshot,
15222 severity,
15223 buffer
15224 .diagnostics_in_range(selection.start..buffer.len())
15225 .filter(|entry| entry.range.start >= selection.start),
15226 );
15227
15228 let mut found: Option<DiagnosticEntry<usize>> = None;
15229 if direction == Direction::Prev {
15230 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
15231 {
15232 for diagnostic in prev_diagnostics.into_iter().rev() {
15233 if diagnostic.range.start != selection.start
15234 || active_group_id
15235 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
15236 {
15237 found = Some(diagnostic);
15238 break 'outer;
15239 }
15240 }
15241 }
15242 } else {
15243 for diagnostic in after.chain(before) {
15244 if diagnostic.range.start != selection.start
15245 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
15246 {
15247 found = Some(diagnostic);
15248 break;
15249 }
15250 }
15251 }
15252 let Some(next_diagnostic) = found else {
15253 return;
15254 };
15255
15256 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
15257 return;
15258 };
15259 self.change_selections(Default::default(), window, cx, |s| {
15260 s.select_ranges(vec![
15261 next_diagnostic.range.start..next_diagnostic.range.start,
15262 ])
15263 });
15264 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
15265 self.refresh_inline_completion(false, true, window, cx);
15266 }
15267
15268 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
15269 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15270 let snapshot = self.snapshot(window, cx);
15271 let selection = self.selections.newest::<Point>(cx);
15272 self.go_to_hunk_before_or_after_position(
15273 &snapshot,
15274 selection.head(),
15275 Direction::Next,
15276 window,
15277 cx,
15278 );
15279 }
15280
15281 pub fn go_to_hunk_before_or_after_position(
15282 &mut self,
15283 snapshot: &EditorSnapshot,
15284 position: Point,
15285 direction: Direction,
15286 window: &mut Window,
15287 cx: &mut Context<Editor>,
15288 ) {
15289 let row = if direction == Direction::Next {
15290 self.hunk_after_position(snapshot, position)
15291 .map(|hunk| hunk.row_range.start)
15292 } else {
15293 self.hunk_before_position(snapshot, position)
15294 };
15295
15296 if let Some(row) = row {
15297 let destination = Point::new(row.0, 0);
15298 let autoscroll = Autoscroll::center();
15299
15300 self.unfold_ranges(&[destination..destination], false, false, cx);
15301 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
15302 s.select_ranges([destination..destination]);
15303 });
15304 }
15305 }
15306
15307 fn hunk_after_position(
15308 &mut self,
15309 snapshot: &EditorSnapshot,
15310 position: Point,
15311 ) -> Option<MultiBufferDiffHunk> {
15312 snapshot
15313 .buffer_snapshot
15314 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
15315 .find(|hunk| hunk.row_range.start.0 > position.row)
15316 .or_else(|| {
15317 snapshot
15318 .buffer_snapshot
15319 .diff_hunks_in_range(Point::zero()..position)
15320 .find(|hunk| hunk.row_range.end.0 < position.row)
15321 })
15322 }
15323
15324 fn go_to_prev_hunk(
15325 &mut self,
15326 _: &GoToPreviousHunk,
15327 window: &mut Window,
15328 cx: &mut Context<Self>,
15329 ) {
15330 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15331 let snapshot = self.snapshot(window, cx);
15332 let selection = self.selections.newest::<Point>(cx);
15333 self.go_to_hunk_before_or_after_position(
15334 &snapshot,
15335 selection.head(),
15336 Direction::Prev,
15337 window,
15338 cx,
15339 );
15340 }
15341
15342 fn hunk_before_position(
15343 &mut self,
15344 snapshot: &EditorSnapshot,
15345 position: Point,
15346 ) -> Option<MultiBufferRow> {
15347 snapshot
15348 .buffer_snapshot
15349 .diff_hunk_before(position)
15350 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
15351 }
15352
15353 fn go_to_next_change(
15354 &mut self,
15355 _: &GoToNextChange,
15356 window: &mut Window,
15357 cx: &mut Context<Self>,
15358 ) {
15359 if let Some(selections) = self
15360 .change_list
15361 .next_change(1, Direction::Next)
15362 .map(|s| s.to_vec())
15363 {
15364 self.change_selections(Default::default(), window, cx, |s| {
15365 let map = s.display_map();
15366 s.select_display_ranges(selections.iter().map(|a| {
15367 let point = a.to_display_point(&map);
15368 point..point
15369 }))
15370 })
15371 }
15372 }
15373
15374 fn go_to_previous_change(
15375 &mut self,
15376 _: &GoToPreviousChange,
15377 window: &mut Window,
15378 cx: &mut Context<Self>,
15379 ) {
15380 if let Some(selections) = self
15381 .change_list
15382 .next_change(1, Direction::Prev)
15383 .map(|s| s.to_vec())
15384 {
15385 self.change_selections(Default::default(), window, cx, |s| {
15386 let map = s.display_map();
15387 s.select_display_ranges(selections.iter().map(|a| {
15388 let point = a.to_display_point(&map);
15389 point..point
15390 }))
15391 })
15392 }
15393 }
15394
15395 fn go_to_line<T: 'static>(
15396 &mut self,
15397 position: Anchor,
15398 highlight_color: Option<Hsla>,
15399 window: &mut Window,
15400 cx: &mut Context<Self>,
15401 ) {
15402 let snapshot = self.snapshot(window, cx).display_snapshot;
15403 let position = position.to_point(&snapshot.buffer_snapshot);
15404 let start = snapshot
15405 .buffer_snapshot
15406 .clip_point(Point::new(position.row, 0), Bias::Left);
15407 let end = start + Point::new(1, 0);
15408 let start = snapshot.buffer_snapshot.anchor_before(start);
15409 let end = snapshot.buffer_snapshot.anchor_before(end);
15410
15411 self.highlight_rows::<T>(
15412 start..end,
15413 highlight_color
15414 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
15415 Default::default(),
15416 cx,
15417 );
15418
15419 if self.buffer.read(cx).is_singleton() {
15420 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
15421 }
15422 }
15423
15424 pub fn go_to_definition(
15425 &mut self,
15426 _: &GoToDefinition,
15427 window: &mut Window,
15428 cx: &mut Context<Self>,
15429 ) -> Task<Result<Navigated>> {
15430 let definition =
15431 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
15432 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
15433 cx.spawn_in(window, async move |editor, cx| {
15434 if definition.await? == Navigated::Yes {
15435 return Ok(Navigated::Yes);
15436 }
15437 match fallback_strategy {
15438 GoToDefinitionFallback::None => Ok(Navigated::No),
15439 GoToDefinitionFallback::FindAllReferences => {
15440 match editor.update_in(cx, |editor, window, cx| {
15441 editor.find_all_references(&FindAllReferences, window, cx)
15442 })? {
15443 Some(references) => references.await,
15444 None => Ok(Navigated::No),
15445 }
15446 }
15447 }
15448 })
15449 }
15450
15451 pub fn go_to_declaration(
15452 &mut self,
15453 _: &GoToDeclaration,
15454 window: &mut Window,
15455 cx: &mut Context<Self>,
15456 ) -> Task<Result<Navigated>> {
15457 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
15458 }
15459
15460 pub fn go_to_declaration_split(
15461 &mut self,
15462 _: &GoToDeclaration,
15463 window: &mut Window,
15464 cx: &mut Context<Self>,
15465 ) -> Task<Result<Navigated>> {
15466 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
15467 }
15468
15469 pub fn go_to_implementation(
15470 &mut self,
15471 _: &GoToImplementation,
15472 window: &mut Window,
15473 cx: &mut Context<Self>,
15474 ) -> Task<Result<Navigated>> {
15475 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
15476 }
15477
15478 pub fn go_to_implementation_split(
15479 &mut self,
15480 _: &GoToImplementationSplit,
15481 window: &mut Window,
15482 cx: &mut Context<Self>,
15483 ) -> Task<Result<Navigated>> {
15484 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
15485 }
15486
15487 pub fn go_to_type_definition(
15488 &mut self,
15489 _: &GoToTypeDefinition,
15490 window: &mut Window,
15491 cx: &mut Context<Self>,
15492 ) -> Task<Result<Navigated>> {
15493 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
15494 }
15495
15496 pub fn go_to_definition_split(
15497 &mut self,
15498 _: &GoToDefinitionSplit,
15499 window: &mut Window,
15500 cx: &mut Context<Self>,
15501 ) -> Task<Result<Navigated>> {
15502 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
15503 }
15504
15505 pub fn go_to_type_definition_split(
15506 &mut self,
15507 _: &GoToTypeDefinitionSplit,
15508 window: &mut Window,
15509 cx: &mut Context<Self>,
15510 ) -> Task<Result<Navigated>> {
15511 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
15512 }
15513
15514 fn go_to_definition_of_kind(
15515 &mut self,
15516 kind: GotoDefinitionKind,
15517 split: bool,
15518 window: &mut Window,
15519 cx: &mut Context<Self>,
15520 ) -> Task<Result<Navigated>> {
15521 let Some(provider) = self.semantics_provider.clone() else {
15522 return Task::ready(Ok(Navigated::No));
15523 };
15524 let head = self.selections.newest::<usize>(cx).head();
15525 let buffer = self.buffer.read(cx);
15526 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
15527 text_anchor
15528 } else {
15529 return Task::ready(Ok(Navigated::No));
15530 };
15531
15532 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
15533 return Task::ready(Ok(Navigated::No));
15534 };
15535
15536 cx.spawn_in(window, async move |editor, cx| {
15537 let definitions = definitions.await?;
15538 let navigated = editor
15539 .update_in(cx, |editor, window, cx| {
15540 editor.navigate_to_hover_links(
15541 Some(kind),
15542 definitions
15543 .into_iter()
15544 .filter(|location| {
15545 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
15546 })
15547 .map(HoverLink::Text)
15548 .collect::<Vec<_>>(),
15549 split,
15550 window,
15551 cx,
15552 )
15553 })?
15554 .await?;
15555 anyhow::Ok(navigated)
15556 })
15557 }
15558
15559 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
15560 let selection = self.selections.newest_anchor();
15561 let head = selection.head();
15562 let tail = selection.tail();
15563
15564 let Some((buffer, start_position)) =
15565 self.buffer.read(cx).text_anchor_for_position(head, cx)
15566 else {
15567 return;
15568 };
15569
15570 let end_position = if head != tail {
15571 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
15572 return;
15573 };
15574 Some(pos)
15575 } else {
15576 None
15577 };
15578
15579 let url_finder = cx.spawn_in(window, async move |editor, cx| {
15580 let url = if let Some(end_pos) = end_position {
15581 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
15582 } else {
15583 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
15584 };
15585
15586 if let Some(url) = url {
15587 editor.update(cx, |_, cx| {
15588 cx.open_url(&url);
15589 })
15590 } else {
15591 Ok(())
15592 }
15593 });
15594
15595 url_finder.detach();
15596 }
15597
15598 pub fn open_selected_filename(
15599 &mut self,
15600 _: &OpenSelectedFilename,
15601 window: &mut Window,
15602 cx: &mut Context<Self>,
15603 ) {
15604 let Some(workspace) = self.workspace() else {
15605 return;
15606 };
15607
15608 let position = self.selections.newest_anchor().head();
15609
15610 let Some((buffer, buffer_position)) =
15611 self.buffer.read(cx).text_anchor_for_position(position, cx)
15612 else {
15613 return;
15614 };
15615
15616 let project = self.project.clone();
15617
15618 cx.spawn_in(window, async move |_, cx| {
15619 let result = find_file(&buffer, project, buffer_position, cx).await;
15620
15621 if let Some((_, path)) = result {
15622 workspace
15623 .update_in(cx, |workspace, window, cx| {
15624 workspace.open_resolved_path(path, window, cx)
15625 })?
15626 .await?;
15627 }
15628 anyhow::Ok(())
15629 })
15630 .detach();
15631 }
15632
15633 pub(crate) fn navigate_to_hover_links(
15634 &mut self,
15635 kind: Option<GotoDefinitionKind>,
15636 mut definitions: Vec<HoverLink>,
15637 split: bool,
15638 window: &mut Window,
15639 cx: &mut Context<Editor>,
15640 ) -> Task<Result<Navigated>> {
15641 // If there is one definition, just open it directly
15642 if definitions.len() == 1 {
15643 let definition = definitions.pop().unwrap();
15644
15645 enum TargetTaskResult {
15646 Location(Option<Location>),
15647 AlreadyNavigated,
15648 }
15649
15650 let target_task = match definition {
15651 HoverLink::Text(link) => {
15652 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
15653 }
15654 HoverLink::InlayHint(lsp_location, server_id) => {
15655 let computation =
15656 self.compute_target_location(lsp_location, server_id, window, cx);
15657 cx.background_spawn(async move {
15658 let location = computation.await?;
15659 Ok(TargetTaskResult::Location(location))
15660 })
15661 }
15662 HoverLink::Url(url) => {
15663 cx.open_url(&url);
15664 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
15665 }
15666 HoverLink::File(path) => {
15667 if let Some(workspace) = self.workspace() {
15668 cx.spawn_in(window, async move |_, cx| {
15669 workspace
15670 .update_in(cx, |workspace, window, cx| {
15671 workspace.open_resolved_path(path, window, cx)
15672 })?
15673 .await
15674 .map(|_| TargetTaskResult::AlreadyNavigated)
15675 })
15676 } else {
15677 Task::ready(Ok(TargetTaskResult::Location(None)))
15678 }
15679 }
15680 };
15681 cx.spawn_in(window, async move |editor, cx| {
15682 let target = match target_task.await.context("target resolution task")? {
15683 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
15684 TargetTaskResult::Location(None) => return Ok(Navigated::No),
15685 TargetTaskResult::Location(Some(target)) => target,
15686 };
15687
15688 editor.update_in(cx, |editor, window, cx| {
15689 let Some(workspace) = editor.workspace() else {
15690 return Navigated::No;
15691 };
15692 let pane = workspace.read(cx).active_pane().clone();
15693
15694 let range = target.range.to_point(target.buffer.read(cx));
15695 let range = editor.range_for_match(&range);
15696 let range = collapse_multiline_range(range);
15697
15698 if !split
15699 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
15700 {
15701 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
15702 } else {
15703 window.defer(cx, move |window, cx| {
15704 let target_editor: Entity<Self> =
15705 workspace.update(cx, |workspace, cx| {
15706 let pane = if split {
15707 workspace.adjacent_pane(window, cx)
15708 } else {
15709 workspace.active_pane().clone()
15710 };
15711
15712 workspace.open_project_item(
15713 pane,
15714 target.buffer.clone(),
15715 true,
15716 true,
15717 window,
15718 cx,
15719 )
15720 });
15721 target_editor.update(cx, |target_editor, cx| {
15722 // When selecting a definition in a different buffer, disable the nav history
15723 // to avoid creating a history entry at the previous cursor location.
15724 pane.update(cx, |pane, _| pane.disable_history());
15725 target_editor.go_to_singleton_buffer_range(range, window, cx);
15726 pane.update(cx, |pane, _| pane.enable_history());
15727 });
15728 });
15729 }
15730 Navigated::Yes
15731 })
15732 })
15733 } else if !definitions.is_empty() {
15734 cx.spawn_in(window, async move |editor, cx| {
15735 let (title, location_tasks, workspace) = editor
15736 .update_in(cx, |editor, window, cx| {
15737 let tab_kind = match kind {
15738 Some(GotoDefinitionKind::Implementation) => "Implementations",
15739 _ => "Definitions",
15740 };
15741 let title = definitions
15742 .iter()
15743 .find_map(|definition| match definition {
15744 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
15745 let buffer = origin.buffer.read(cx);
15746 format!(
15747 "{} for {}",
15748 tab_kind,
15749 buffer
15750 .text_for_range(origin.range.clone())
15751 .collect::<String>()
15752 )
15753 }),
15754 HoverLink::InlayHint(_, _) => None,
15755 HoverLink::Url(_) => None,
15756 HoverLink::File(_) => None,
15757 })
15758 .unwrap_or(tab_kind.to_string());
15759 let location_tasks = definitions
15760 .into_iter()
15761 .map(|definition| match definition {
15762 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
15763 HoverLink::InlayHint(lsp_location, server_id) => editor
15764 .compute_target_location(lsp_location, server_id, window, cx),
15765 HoverLink::Url(_) => Task::ready(Ok(None)),
15766 HoverLink::File(_) => Task::ready(Ok(None)),
15767 })
15768 .collect::<Vec<_>>();
15769 (title, location_tasks, editor.workspace().clone())
15770 })
15771 .context("location tasks preparation")?;
15772
15773 let locations: Vec<Location> = future::join_all(location_tasks)
15774 .await
15775 .into_iter()
15776 .filter_map(|location| location.transpose())
15777 .collect::<Result<_>>()
15778 .context("location tasks")?;
15779
15780 if locations.is_empty() {
15781 return Ok(Navigated::No);
15782 }
15783
15784 let Some(workspace) = workspace else {
15785 return Ok(Navigated::No);
15786 };
15787
15788 let opened = workspace
15789 .update_in(cx, |workspace, window, cx| {
15790 Self::open_locations_in_multibuffer(
15791 workspace,
15792 locations,
15793 title,
15794 split,
15795 MultibufferSelectionMode::First,
15796 window,
15797 cx,
15798 )
15799 })
15800 .ok();
15801
15802 anyhow::Ok(Navigated::from_bool(opened.is_some()))
15803 })
15804 } else {
15805 Task::ready(Ok(Navigated::No))
15806 }
15807 }
15808
15809 fn compute_target_location(
15810 &self,
15811 lsp_location: lsp::Location,
15812 server_id: LanguageServerId,
15813 window: &mut Window,
15814 cx: &mut Context<Self>,
15815 ) -> Task<anyhow::Result<Option<Location>>> {
15816 let Some(project) = self.project.clone() else {
15817 return Task::ready(Ok(None));
15818 };
15819
15820 cx.spawn_in(window, async move |editor, cx| {
15821 let location_task = editor.update(cx, |_, cx| {
15822 project.update(cx, |project, cx| {
15823 let language_server_name = project
15824 .language_server_statuses(cx)
15825 .find(|(id, _)| server_id == *id)
15826 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
15827 language_server_name.map(|language_server_name| {
15828 project.open_local_buffer_via_lsp(
15829 lsp_location.uri.clone(),
15830 server_id,
15831 language_server_name,
15832 cx,
15833 )
15834 })
15835 })
15836 })?;
15837 let location = match location_task {
15838 Some(task) => Some({
15839 let target_buffer_handle = task.await.context("open local buffer")?;
15840 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
15841 let target_start = target_buffer
15842 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
15843 let target_end = target_buffer
15844 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
15845 target_buffer.anchor_after(target_start)
15846 ..target_buffer.anchor_before(target_end)
15847 })?;
15848 Location {
15849 buffer: target_buffer_handle,
15850 range,
15851 }
15852 }),
15853 None => None,
15854 };
15855 Ok(location)
15856 })
15857 }
15858
15859 pub fn find_all_references(
15860 &mut self,
15861 _: &FindAllReferences,
15862 window: &mut Window,
15863 cx: &mut Context<Self>,
15864 ) -> Option<Task<Result<Navigated>>> {
15865 let selection = self.selections.newest::<usize>(cx);
15866 let multi_buffer = self.buffer.read(cx);
15867 let head = selection.head();
15868
15869 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
15870 let head_anchor = multi_buffer_snapshot.anchor_at(
15871 head,
15872 if head < selection.tail() {
15873 Bias::Right
15874 } else {
15875 Bias::Left
15876 },
15877 );
15878
15879 match self
15880 .find_all_references_task_sources
15881 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15882 {
15883 Ok(_) => {
15884 log::info!(
15885 "Ignoring repeated FindAllReferences invocation with the position of already running task"
15886 );
15887 return None;
15888 }
15889 Err(i) => {
15890 self.find_all_references_task_sources.insert(i, head_anchor);
15891 }
15892 }
15893
15894 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
15895 let workspace = self.workspace()?;
15896 let project = workspace.read(cx).project().clone();
15897 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
15898 Some(cx.spawn_in(window, async move |editor, cx| {
15899 let _cleanup = cx.on_drop(&editor, move |editor, _| {
15900 if let Ok(i) = editor
15901 .find_all_references_task_sources
15902 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15903 {
15904 editor.find_all_references_task_sources.remove(i);
15905 }
15906 });
15907
15908 let locations = references.await?;
15909 if locations.is_empty() {
15910 return anyhow::Ok(Navigated::No);
15911 }
15912
15913 workspace.update_in(cx, |workspace, window, cx| {
15914 let title = locations
15915 .first()
15916 .as_ref()
15917 .map(|location| {
15918 let buffer = location.buffer.read(cx);
15919 format!(
15920 "References to `{}`",
15921 buffer
15922 .text_for_range(location.range.clone())
15923 .collect::<String>()
15924 )
15925 })
15926 .unwrap();
15927 Self::open_locations_in_multibuffer(
15928 workspace,
15929 locations,
15930 title,
15931 false,
15932 MultibufferSelectionMode::First,
15933 window,
15934 cx,
15935 );
15936 Navigated::Yes
15937 })
15938 }))
15939 }
15940
15941 /// Opens a multibuffer with the given project locations in it
15942 pub fn open_locations_in_multibuffer(
15943 workspace: &mut Workspace,
15944 mut locations: Vec<Location>,
15945 title: String,
15946 split: bool,
15947 multibuffer_selection_mode: MultibufferSelectionMode,
15948 window: &mut Window,
15949 cx: &mut Context<Workspace>,
15950 ) {
15951 if locations.is_empty() {
15952 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
15953 return;
15954 }
15955
15956 // If there are multiple definitions, open them in a multibuffer
15957 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
15958 let mut locations = locations.into_iter().peekable();
15959 let mut ranges: Vec<Range<Anchor>> = Vec::new();
15960 let capability = workspace.project().read(cx).capability();
15961
15962 let excerpt_buffer = cx.new(|cx| {
15963 let mut multibuffer = MultiBuffer::new(capability);
15964 while let Some(location) = locations.next() {
15965 let buffer = location.buffer.read(cx);
15966 let mut ranges_for_buffer = Vec::new();
15967 let range = location.range.to_point(buffer);
15968 ranges_for_buffer.push(range.clone());
15969
15970 while let Some(next_location) = locations.peek() {
15971 if next_location.buffer == location.buffer {
15972 ranges_for_buffer.push(next_location.range.to_point(buffer));
15973 locations.next();
15974 } else {
15975 break;
15976 }
15977 }
15978
15979 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
15980 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
15981 PathKey::for_buffer(&location.buffer, cx),
15982 location.buffer.clone(),
15983 ranges_for_buffer,
15984 DEFAULT_MULTIBUFFER_CONTEXT,
15985 cx,
15986 );
15987 ranges.extend(new_ranges)
15988 }
15989
15990 multibuffer.with_title(title)
15991 });
15992
15993 let editor = cx.new(|cx| {
15994 Editor::for_multibuffer(
15995 excerpt_buffer,
15996 Some(workspace.project().clone()),
15997 window,
15998 cx,
15999 )
16000 });
16001 editor.update(cx, |editor, cx| {
16002 match multibuffer_selection_mode {
16003 MultibufferSelectionMode::First => {
16004 if let Some(first_range) = ranges.first() {
16005 editor.change_selections(
16006 SelectionEffects::no_scroll(),
16007 window,
16008 cx,
16009 |selections| {
16010 selections.clear_disjoint();
16011 selections
16012 .select_anchor_ranges(std::iter::once(first_range.clone()));
16013 },
16014 );
16015 }
16016 editor.highlight_background::<Self>(
16017 &ranges,
16018 |theme| theme.colors().editor_highlighted_line_background,
16019 cx,
16020 );
16021 }
16022 MultibufferSelectionMode::All => {
16023 editor.change_selections(
16024 SelectionEffects::no_scroll(),
16025 window,
16026 cx,
16027 |selections| {
16028 selections.clear_disjoint();
16029 selections.select_anchor_ranges(ranges);
16030 },
16031 );
16032 }
16033 }
16034 editor.register_buffers_with_language_servers(cx);
16035 });
16036
16037 let item = Box::new(editor);
16038 let item_id = item.item_id();
16039
16040 if split {
16041 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
16042 } else {
16043 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
16044 let (preview_item_id, preview_item_idx) =
16045 workspace.active_pane().read_with(cx, |pane, _| {
16046 (pane.preview_item_id(), pane.preview_item_idx())
16047 });
16048
16049 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
16050
16051 if let Some(preview_item_id) = preview_item_id {
16052 workspace.active_pane().update(cx, |pane, cx| {
16053 pane.remove_item(preview_item_id, false, false, window, cx);
16054 });
16055 }
16056 } else {
16057 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
16058 }
16059 }
16060 workspace.active_pane().update(cx, |pane, cx| {
16061 pane.set_preview_item_id(Some(item_id), cx);
16062 });
16063 }
16064
16065 pub fn rename(
16066 &mut self,
16067 _: &Rename,
16068 window: &mut Window,
16069 cx: &mut Context<Self>,
16070 ) -> Option<Task<Result<()>>> {
16071 use language::ToOffset as _;
16072
16073 let provider = self.semantics_provider.clone()?;
16074 let selection = self.selections.newest_anchor().clone();
16075 let (cursor_buffer, cursor_buffer_position) = self
16076 .buffer
16077 .read(cx)
16078 .text_anchor_for_position(selection.head(), cx)?;
16079 let (tail_buffer, cursor_buffer_position_end) = self
16080 .buffer
16081 .read(cx)
16082 .text_anchor_for_position(selection.tail(), cx)?;
16083 if tail_buffer != cursor_buffer {
16084 return None;
16085 }
16086
16087 let snapshot = cursor_buffer.read(cx).snapshot();
16088 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
16089 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
16090 let prepare_rename = provider
16091 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
16092 .unwrap_or_else(|| Task::ready(Ok(None)));
16093 drop(snapshot);
16094
16095 Some(cx.spawn_in(window, async move |this, cx| {
16096 let rename_range = if let Some(range) = prepare_rename.await? {
16097 Some(range)
16098 } else {
16099 this.update(cx, |this, cx| {
16100 let buffer = this.buffer.read(cx).snapshot(cx);
16101 let mut buffer_highlights = this
16102 .document_highlights_for_position(selection.head(), &buffer)
16103 .filter(|highlight| {
16104 highlight.start.excerpt_id == selection.head().excerpt_id
16105 && highlight.end.excerpt_id == selection.head().excerpt_id
16106 });
16107 buffer_highlights
16108 .next()
16109 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
16110 })?
16111 };
16112 if let Some(rename_range) = rename_range {
16113 this.update_in(cx, |this, window, cx| {
16114 let snapshot = cursor_buffer.read(cx).snapshot();
16115 let rename_buffer_range = rename_range.to_offset(&snapshot);
16116 let cursor_offset_in_rename_range =
16117 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
16118 let cursor_offset_in_rename_range_end =
16119 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
16120
16121 this.take_rename(false, window, cx);
16122 let buffer = this.buffer.read(cx).read(cx);
16123 let cursor_offset = selection.head().to_offset(&buffer);
16124 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
16125 let rename_end = rename_start + rename_buffer_range.len();
16126 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
16127 let mut old_highlight_id = None;
16128 let old_name: Arc<str> = buffer
16129 .chunks(rename_start..rename_end, true)
16130 .map(|chunk| {
16131 if old_highlight_id.is_none() {
16132 old_highlight_id = chunk.syntax_highlight_id;
16133 }
16134 chunk.text
16135 })
16136 .collect::<String>()
16137 .into();
16138
16139 drop(buffer);
16140
16141 // Position the selection in the rename editor so that it matches the current selection.
16142 this.show_local_selections = false;
16143 let rename_editor = cx.new(|cx| {
16144 let mut editor = Editor::single_line(window, cx);
16145 editor.buffer.update(cx, |buffer, cx| {
16146 buffer.edit([(0..0, old_name.clone())], None, cx)
16147 });
16148 let rename_selection_range = match cursor_offset_in_rename_range
16149 .cmp(&cursor_offset_in_rename_range_end)
16150 {
16151 Ordering::Equal => {
16152 editor.select_all(&SelectAll, window, cx);
16153 return editor;
16154 }
16155 Ordering::Less => {
16156 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
16157 }
16158 Ordering::Greater => {
16159 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
16160 }
16161 };
16162 if rename_selection_range.end > old_name.len() {
16163 editor.select_all(&SelectAll, window, cx);
16164 } else {
16165 editor.change_selections(Default::default(), window, cx, |s| {
16166 s.select_ranges([rename_selection_range]);
16167 });
16168 }
16169 editor
16170 });
16171 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
16172 if e == &EditorEvent::Focused {
16173 cx.emit(EditorEvent::FocusedIn)
16174 }
16175 })
16176 .detach();
16177
16178 let write_highlights =
16179 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
16180 let read_highlights =
16181 this.clear_background_highlights::<DocumentHighlightRead>(cx);
16182 let ranges = write_highlights
16183 .iter()
16184 .flat_map(|(_, ranges)| ranges.iter())
16185 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
16186 .cloned()
16187 .collect();
16188
16189 this.highlight_text::<Rename>(
16190 ranges,
16191 HighlightStyle {
16192 fade_out: Some(0.6),
16193 ..Default::default()
16194 },
16195 cx,
16196 );
16197 let rename_focus_handle = rename_editor.focus_handle(cx);
16198 window.focus(&rename_focus_handle);
16199 let block_id = this.insert_blocks(
16200 [BlockProperties {
16201 style: BlockStyle::Flex,
16202 placement: BlockPlacement::Below(range.start),
16203 height: Some(1),
16204 render: Arc::new({
16205 let rename_editor = rename_editor.clone();
16206 move |cx: &mut BlockContext| {
16207 let mut text_style = cx.editor_style.text.clone();
16208 if let Some(highlight_style) = old_highlight_id
16209 .and_then(|h| h.style(&cx.editor_style.syntax))
16210 {
16211 text_style = text_style.highlight(highlight_style);
16212 }
16213 div()
16214 .block_mouse_except_scroll()
16215 .pl(cx.anchor_x)
16216 .child(EditorElement::new(
16217 &rename_editor,
16218 EditorStyle {
16219 background: cx.theme().system().transparent,
16220 local_player: cx.editor_style.local_player,
16221 text: text_style,
16222 scrollbar_width: cx.editor_style.scrollbar_width,
16223 syntax: cx.editor_style.syntax.clone(),
16224 status: cx.editor_style.status.clone(),
16225 inlay_hints_style: HighlightStyle {
16226 font_weight: Some(FontWeight::BOLD),
16227 ..make_inlay_hints_style(cx.app)
16228 },
16229 inline_completion_styles: make_suggestion_styles(
16230 cx.app,
16231 ),
16232 ..EditorStyle::default()
16233 },
16234 ))
16235 .into_any_element()
16236 }
16237 }),
16238 priority: 0,
16239 }],
16240 Some(Autoscroll::fit()),
16241 cx,
16242 )[0];
16243 this.pending_rename = Some(RenameState {
16244 range,
16245 old_name,
16246 editor: rename_editor,
16247 block_id,
16248 });
16249 })?;
16250 }
16251
16252 Ok(())
16253 }))
16254 }
16255
16256 pub fn confirm_rename(
16257 &mut self,
16258 _: &ConfirmRename,
16259 window: &mut Window,
16260 cx: &mut Context<Self>,
16261 ) -> Option<Task<Result<()>>> {
16262 let rename = self.take_rename(false, window, cx)?;
16263 let workspace = self.workspace()?.downgrade();
16264 let (buffer, start) = self
16265 .buffer
16266 .read(cx)
16267 .text_anchor_for_position(rename.range.start, cx)?;
16268 let (end_buffer, _) = self
16269 .buffer
16270 .read(cx)
16271 .text_anchor_for_position(rename.range.end, cx)?;
16272 if buffer != end_buffer {
16273 return None;
16274 }
16275
16276 let old_name = rename.old_name;
16277 let new_name = rename.editor.read(cx).text(cx);
16278
16279 let rename = self.semantics_provider.as_ref()?.perform_rename(
16280 &buffer,
16281 start,
16282 new_name.clone(),
16283 cx,
16284 )?;
16285
16286 Some(cx.spawn_in(window, async move |editor, cx| {
16287 let project_transaction = rename.await?;
16288 Self::open_project_transaction(
16289 &editor,
16290 workspace,
16291 project_transaction,
16292 format!("Rename: {} → {}", old_name, new_name),
16293 cx,
16294 )
16295 .await?;
16296
16297 editor.update(cx, |editor, cx| {
16298 editor.refresh_document_highlights(cx);
16299 })?;
16300 Ok(())
16301 }))
16302 }
16303
16304 fn take_rename(
16305 &mut self,
16306 moving_cursor: bool,
16307 window: &mut Window,
16308 cx: &mut Context<Self>,
16309 ) -> Option<RenameState> {
16310 let rename = self.pending_rename.take()?;
16311 if rename.editor.focus_handle(cx).is_focused(window) {
16312 window.focus(&self.focus_handle);
16313 }
16314
16315 self.remove_blocks(
16316 [rename.block_id].into_iter().collect(),
16317 Some(Autoscroll::fit()),
16318 cx,
16319 );
16320 self.clear_highlights::<Rename>(cx);
16321 self.show_local_selections = true;
16322
16323 if moving_cursor {
16324 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
16325 editor.selections.newest::<usize>(cx).head()
16326 });
16327
16328 // Update the selection to match the position of the selection inside
16329 // the rename editor.
16330 let snapshot = self.buffer.read(cx).read(cx);
16331 let rename_range = rename.range.to_offset(&snapshot);
16332 let cursor_in_editor = snapshot
16333 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
16334 .min(rename_range.end);
16335 drop(snapshot);
16336
16337 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16338 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
16339 });
16340 } else {
16341 self.refresh_document_highlights(cx);
16342 }
16343
16344 Some(rename)
16345 }
16346
16347 pub fn pending_rename(&self) -> Option<&RenameState> {
16348 self.pending_rename.as_ref()
16349 }
16350
16351 fn format(
16352 &mut self,
16353 _: &Format,
16354 window: &mut Window,
16355 cx: &mut Context<Self>,
16356 ) -> Option<Task<Result<()>>> {
16357 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16358
16359 let project = match &self.project {
16360 Some(project) => project.clone(),
16361 None => return None,
16362 };
16363
16364 Some(self.perform_format(
16365 project,
16366 FormatTrigger::Manual,
16367 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
16368 window,
16369 cx,
16370 ))
16371 }
16372
16373 fn format_selections(
16374 &mut self,
16375 _: &FormatSelections,
16376 window: &mut Window,
16377 cx: &mut Context<Self>,
16378 ) -> Option<Task<Result<()>>> {
16379 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16380
16381 let project = match &self.project {
16382 Some(project) => project.clone(),
16383 None => return None,
16384 };
16385
16386 let ranges = self
16387 .selections
16388 .all_adjusted(cx)
16389 .into_iter()
16390 .map(|selection| selection.range())
16391 .collect_vec();
16392
16393 Some(self.perform_format(
16394 project,
16395 FormatTrigger::Manual,
16396 FormatTarget::Ranges(ranges),
16397 window,
16398 cx,
16399 ))
16400 }
16401
16402 fn perform_format(
16403 &mut self,
16404 project: Entity<Project>,
16405 trigger: FormatTrigger,
16406 target: FormatTarget,
16407 window: &mut Window,
16408 cx: &mut Context<Self>,
16409 ) -> Task<Result<()>> {
16410 let buffer = self.buffer.clone();
16411 let (buffers, target) = match target {
16412 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
16413 FormatTarget::Ranges(selection_ranges) => {
16414 let multi_buffer = buffer.read(cx);
16415 let snapshot = multi_buffer.read(cx);
16416 let mut buffers = HashSet::default();
16417 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
16418 BTreeMap::new();
16419 for selection_range in selection_ranges {
16420 for (buffer, buffer_range, _) in
16421 snapshot.range_to_buffer_ranges(selection_range)
16422 {
16423 let buffer_id = buffer.remote_id();
16424 let start = buffer.anchor_before(buffer_range.start);
16425 let end = buffer.anchor_after(buffer_range.end);
16426 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
16427 buffer_id_to_ranges
16428 .entry(buffer_id)
16429 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
16430 .or_insert_with(|| vec![start..end]);
16431 }
16432 }
16433 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
16434 }
16435 };
16436
16437 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
16438 let selections_prev = transaction_id_prev
16439 .and_then(|transaction_id_prev| {
16440 // default to selections as they were after the last edit, if we have them,
16441 // instead of how they are now.
16442 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
16443 // will take you back to where you made the last edit, instead of staying where you scrolled
16444 self.selection_history
16445 .transaction(transaction_id_prev)
16446 .map(|t| t.0.clone())
16447 })
16448 .unwrap_or_else(|| {
16449 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
16450 self.selections.disjoint_anchors()
16451 });
16452
16453 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
16454 let format = project.update(cx, |project, cx| {
16455 project.format(buffers, target, true, trigger, cx)
16456 });
16457
16458 cx.spawn_in(window, async move |editor, cx| {
16459 let transaction = futures::select_biased! {
16460 transaction = format.log_err().fuse() => transaction,
16461 () = timeout => {
16462 log::warn!("timed out waiting for formatting");
16463 None
16464 }
16465 };
16466
16467 buffer
16468 .update(cx, |buffer, cx| {
16469 if let Some(transaction) = transaction {
16470 if !buffer.is_singleton() {
16471 buffer.push_transaction(&transaction.0, cx);
16472 }
16473 }
16474 cx.notify();
16475 })
16476 .ok();
16477
16478 if let Some(transaction_id_now) =
16479 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
16480 {
16481 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
16482 if has_new_transaction {
16483 _ = editor.update(cx, |editor, _| {
16484 editor
16485 .selection_history
16486 .insert_transaction(transaction_id_now, selections_prev);
16487 });
16488 }
16489 }
16490
16491 Ok(())
16492 })
16493 }
16494
16495 fn organize_imports(
16496 &mut self,
16497 _: &OrganizeImports,
16498 window: &mut Window,
16499 cx: &mut Context<Self>,
16500 ) -> Option<Task<Result<()>>> {
16501 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16502 let project = match &self.project {
16503 Some(project) => project.clone(),
16504 None => return None,
16505 };
16506 Some(self.perform_code_action_kind(
16507 project,
16508 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
16509 window,
16510 cx,
16511 ))
16512 }
16513
16514 fn perform_code_action_kind(
16515 &mut self,
16516 project: Entity<Project>,
16517 kind: CodeActionKind,
16518 window: &mut Window,
16519 cx: &mut Context<Self>,
16520 ) -> Task<Result<()>> {
16521 let buffer = self.buffer.clone();
16522 let buffers = buffer.read(cx).all_buffers();
16523 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
16524 let apply_action = project.update(cx, |project, cx| {
16525 project.apply_code_action_kind(buffers, kind, true, cx)
16526 });
16527 cx.spawn_in(window, async move |_, cx| {
16528 let transaction = futures::select_biased! {
16529 () = timeout => {
16530 log::warn!("timed out waiting for executing code action");
16531 None
16532 }
16533 transaction = apply_action.log_err().fuse() => transaction,
16534 };
16535 buffer
16536 .update(cx, |buffer, cx| {
16537 // check if we need this
16538 if let Some(transaction) = transaction {
16539 if !buffer.is_singleton() {
16540 buffer.push_transaction(&transaction.0, cx);
16541 }
16542 }
16543 cx.notify();
16544 })
16545 .ok();
16546 Ok(())
16547 })
16548 }
16549
16550 pub fn restart_language_server(
16551 &mut self,
16552 _: &RestartLanguageServer,
16553 _: &mut Window,
16554 cx: &mut Context<Self>,
16555 ) {
16556 if let Some(project) = self.project.clone() {
16557 self.buffer.update(cx, |multi_buffer, cx| {
16558 project.update(cx, |project, cx| {
16559 project.restart_language_servers_for_buffers(
16560 multi_buffer.all_buffers().into_iter().collect(),
16561 HashSet::default(),
16562 cx,
16563 );
16564 });
16565 })
16566 }
16567 }
16568
16569 pub fn stop_language_server(
16570 &mut self,
16571 _: &StopLanguageServer,
16572 _: &mut Window,
16573 cx: &mut Context<Self>,
16574 ) {
16575 if let Some(project) = self.project.clone() {
16576 self.buffer.update(cx, |multi_buffer, cx| {
16577 project.update(cx, |project, cx| {
16578 project.stop_language_servers_for_buffers(
16579 multi_buffer.all_buffers().into_iter().collect(),
16580 HashSet::default(),
16581 cx,
16582 );
16583 cx.emit(project::Event::RefreshInlayHints);
16584 });
16585 });
16586 }
16587 }
16588
16589 fn cancel_language_server_work(
16590 workspace: &mut Workspace,
16591 _: &actions::CancelLanguageServerWork,
16592 _: &mut Window,
16593 cx: &mut Context<Workspace>,
16594 ) {
16595 let project = workspace.project();
16596 let buffers = workspace
16597 .active_item(cx)
16598 .and_then(|item| item.act_as::<Editor>(cx))
16599 .map_or(HashSet::default(), |editor| {
16600 editor.read(cx).buffer.read(cx).all_buffers()
16601 });
16602 project.update(cx, |project, cx| {
16603 project.cancel_language_server_work_for_buffers(buffers, cx);
16604 });
16605 }
16606
16607 fn show_character_palette(
16608 &mut self,
16609 _: &ShowCharacterPalette,
16610 window: &mut Window,
16611 _: &mut Context<Self>,
16612 ) {
16613 window.show_character_palette();
16614 }
16615
16616 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
16617 if !self.diagnostics_enabled() {
16618 return;
16619 }
16620
16621 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
16622 let buffer = self.buffer.read(cx).snapshot(cx);
16623 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
16624 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
16625 let is_valid = buffer
16626 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
16627 .any(|entry| {
16628 entry.diagnostic.is_primary
16629 && !entry.range.is_empty()
16630 && entry.range.start == primary_range_start
16631 && entry.diagnostic.message == active_diagnostics.active_message
16632 });
16633
16634 if !is_valid {
16635 self.dismiss_diagnostics(cx);
16636 }
16637 }
16638 }
16639
16640 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
16641 match &self.active_diagnostics {
16642 ActiveDiagnostic::Group(group) => Some(group),
16643 _ => None,
16644 }
16645 }
16646
16647 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
16648 if !self.diagnostics_enabled() {
16649 return;
16650 }
16651 self.dismiss_diagnostics(cx);
16652 self.active_diagnostics = ActiveDiagnostic::All;
16653 }
16654
16655 fn activate_diagnostics(
16656 &mut self,
16657 buffer_id: BufferId,
16658 diagnostic: DiagnosticEntry<usize>,
16659 window: &mut Window,
16660 cx: &mut Context<Self>,
16661 ) {
16662 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16663 return;
16664 }
16665 self.dismiss_diagnostics(cx);
16666 let snapshot = self.snapshot(window, cx);
16667 let buffer = self.buffer.read(cx).snapshot(cx);
16668 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
16669 return;
16670 };
16671
16672 let diagnostic_group = buffer
16673 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
16674 .collect::<Vec<_>>();
16675
16676 let blocks =
16677 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
16678
16679 let blocks = self.display_map.update(cx, |display_map, cx| {
16680 display_map.insert_blocks(blocks, cx).into_iter().collect()
16681 });
16682 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
16683 active_range: buffer.anchor_before(diagnostic.range.start)
16684 ..buffer.anchor_after(diagnostic.range.end),
16685 active_message: diagnostic.diagnostic.message.clone(),
16686 group_id: diagnostic.diagnostic.group_id,
16687 blocks,
16688 });
16689 cx.notify();
16690 }
16691
16692 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
16693 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16694 return;
16695 };
16696
16697 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
16698 if let ActiveDiagnostic::Group(group) = prev {
16699 self.display_map.update(cx, |display_map, cx| {
16700 display_map.remove_blocks(group.blocks, cx);
16701 });
16702 cx.notify();
16703 }
16704 }
16705
16706 /// Disable inline diagnostics rendering for this editor.
16707 pub fn disable_inline_diagnostics(&mut self) {
16708 self.inline_diagnostics_enabled = false;
16709 self.inline_diagnostics_update = Task::ready(());
16710 self.inline_diagnostics.clear();
16711 }
16712
16713 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
16714 self.diagnostics_enabled = false;
16715 self.dismiss_diagnostics(cx);
16716 self.inline_diagnostics_update = Task::ready(());
16717 self.inline_diagnostics.clear();
16718 }
16719
16720 pub fn diagnostics_enabled(&self) -> bool {
16721 self.diagnostics_enabled && self.mode.is_full()
16722 }
16723
16724 pub fn inline_diagnostics_enabled(&self) -> bool {
16725 self.inline_diagnostics_enabled && self.diagnostics_enabled()
16726 }
16727
16728 pub fn show_inline_diagnostics(&self) -> bool {
16729 self.show_inline_diagnostics
16730 }
16731
16732 pub fn toggle_inline_diagnostics(
16733 &mut self,
16734 _: &ToggleInlineDiagnostics,
16735 window: &mut Window,
16736 cx: &mut Context<Editor>,
16737 ) {
16738 self.show_inline_diagnostics = !self.show_inline_diagnostics;
16739 self.refresh_inline_diagnostics(false, window, cx);
16740 }
16741
16742 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
16743 self.diagnostics_max_severity = severity;
16744 self.display_map.update(cx, |display_map, _| {
16745 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
16746 });
16747 }
16748
16749 pub fn toggle_diagnostics(
16750 &mut self,
16751 _: &ToggleDiagnostics,
16752 window: &mut Window,
16753 cx: &mut Context<Editor>,
16754 ) {
16755 if !self.diagnostics_enabled() {
16756 return;
16757 }
16758
16759 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16760 EditorSettings::get_global(cx)
16761 .diagnostics_max_severity
16762 .filter(|severity| severity != &DiagnosticSeverity::Off)
16763 .unwrap_or(DiagnosticSeverity::Hint)
16764 } else {
16765 DiagnosticSeverity::Off
16766 };
16767 self.set_max_diagnostics_severity(new_severity, cx);
16768 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16769 self.active_diagnostics = ActiveDiagnostic::None;
16770 self.inline_diagnostics_update = Task::ready(());
16771 self.inline_diagnostics.clear();
16772 } else {
16773 self.refresh_inline_diagnostics(false, window, cx);
16774 }
16775
16776 cx.notify();
16777 }
16778
16779 pub fn toggle_minimap(
16780 &mut self,
16781 _: &ToggleMinimap,
16782 window: &mut Window,
16783 cx: &mut Context<Editor>,
16784 ) {
16785 if self.supports_minimap(cx) {
16786 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
16787 }
16788 }
16789
16790 fn refresh_inline_diagnostics(
16791 &mut self,
16792 debounce: bool,
16793 window: &mut Window,
16794 cx: &mut Context<Self>,
16795 ) {
16796 let max_severity = ProjectSettings::get_global(cx)
16797 .diagnostics
16798 .inline
16799 .max_severity
16800 .unwrap_or(self.diagnostics_max_severity);
16801
16802 if !self.inline_diagnostics_enabled()
16803 || !self.show_inline_diagnostics
16804 || max_severity == DiagnosticSeverity::Off
16805 {
16806 self.inline_diagnostics_update = Task::ready(());
16807 self.inline_diagnostics.clear();
16808 return;
16809 }
16810
16811 let debounce_ms = ProjectSettings::get_global(cx)
16812 .diagnostics
16813 .inline
16814 .update_debounce_ms;
16815 let debounce = if debounce && debounce_ms > 0 {
16816 Some(Duration::from_millis(debounce_ms))
16817 } else {
16818 None
16819 };
16820 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
16821 if let Some(debounce) = debounce {
16822 cx.background_executor().timer(debounce).await;
16823 }
16824 let Some(snapshot) = editor.upgrade().and_then(|editor| {
16825 editor
16826 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
16827 .ok()
16828 }) else {
16829 return;
16830 };
16831
16832 let new_inline_diagnostics = cx
16833 .background_spawn(async move {
16834 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
16835 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
16836 let message = diagnostic_entry
16837 .diagnostic
16838 .message
16839 .split_once('\n')
16840 .map(|(line, _)| line)
16841 .map(SharedString::new)
16842 .unwrap_or_else(|| {
16843 SharedString::from(diagnostic_entry.diagnostic.message)
16844 });
16845 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
16846 let (Ok(i) | Err(i)) = inline_diagnostics
16847 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
16848 inline_diagnostics.insert(
16849 i,
16850 (
16851 start_anchor,
16852 InlineDiagnostic {
16853 message,
16854 group_id: diagnostic_entry.diagnostic.group_id,
16855 start: diagnostic_entry.range.start.to_point(&snapshot),
16856 is_primary: diagnostic_entry.diagnostic.is_primary,
16857 severity: diagnostic_entry.diagnostic.severity,
16858 },
16859 ),
16860 );
16861 }
16862 inline_diagnostics
16863 })
16864 .await;
16865
16866 editor
16867 .update(cx, |editor, cx| {
16868 editor.inline_diagnostics = new_inline_diagnostics;
16869 cx.notify();
16870 })
16871 .ok();
16872 });
16873 }
16874
16875 fn pull_diagnostics(
16876 &mut self,
16877 buffer_id: Option<BufferId>,
16878 window: &Window,
16879 cx: &mut Context<Self>,
16880 ) -> Option<()> {
16881 if !self.mode().is_full() {
16882 return None;
16883 }
16884 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
16885 .diagnostics
16886 .lsp_pull_diagnostics;
16887 if !pull_diagnostics_settings.enabled {
16888 return None;
16889 }
16890 let project = self.project.as_ref()?.downgrade();
16891 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
16892 let mut buffers = self.buffer.read(cx).all_buffers();
16893 if let Some(buffer_id) = buffer_id {
16894 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
16895 }
16896
16897 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
16898 cx.background_executor().timer(debounce).await;
16899
16900 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
16901 buffers
16902 .into_iter()
16903 .filter_map(|buffer| {
16904 project
16905 .update(cx, |project, cx| {
16906 project.lsp_store().update(cx, |lsp_store, cx| {
16907 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
16908 })
16909 })
16910 .ok()
16911 })
16912 .collect::<FuturesUnordered<_>>()
16913 }) else {
16914 return;
16915 };
16916
16917 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
16918 match pull_task {
16919 Ok(()) => {
16920 if editor
16921 .update_in(cx, |editor, window, cx| {
16922 editor.update_diagnostics_state(window, cx);
16923 })
16924 .is_err()
16925 {
16926 return;
16927 }
16928 }
16929 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
16930 }
16931 }
16932 });
16933
16934 Some(())
16935 }
16936
16937 pub fn set_selections_from_remote(
16938 &mut self,
16939 selections: Vec<Selection<Anchor>>,
16940 pending_selection: Option<Selection<Anchor>>,
16941 window: &mut Window,
16942 cx: &mut Context<Self>,
16943 ) {
16944 let old_cursor_position = self.selections.newest_anchor().head();
16945 self.selections.change_with(cx, |s| {
16946 s.select_anchors(selections);
16947 if let Some(pending_selection) = pending_selection {
16948 s.set_pending(pending_selection, SelectMode::Character);
16949 } else {
16950 s.clear_pending();
16951 }
16952 });
16953 self.selections_did_change(
16954 false,
16955 &old_cursor_position,
16956 SelectionEffects::default(),
16957 window,
16958 cx,
16959 );
16960 }
16961
16962 pub fn transact(
16963 &mut self,
16964 window: &mut Window,
16965 cx: &mut Context<Self>,
16966 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
16967 ) -> Option<TransactionId> {
16968 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16969 this.start_transaction_at(Instant::now(), window, cx);
16970 update(this, window, cx);
16971 this.end_transaction_at(Instant::now(), cx)
16972 })
16973 }
16974
16975 pub fn start_transaction_at(
16976 &mut self,
16977 now: Instant,
16978 window: &mut Window,
16979 cx: &mut Context<Self>,
16980 ) -> Option<TransactionId> {
16981 self.end_selection(window, cx);
16982 if let Some(tx_id) = self
16983 .buffer
16984 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
16985 {
16986 self.selection_history
16987 .insert_transaction(tx_id, self.selections.disjoint_anchors());
16988 cx.emit(EditorEvent::TransactionBegun {
16989 transaction_id: tx_id,
16990 });
16991 Some(tx_id)
16992 } else {
16993 None
16994 }
16995 }
16996
16997 pub fn end_transaction_at(
16998 &mut self,
16999 now: Instant,
17000 cx: &mut Context<Self>,
17001 ) -> Option<TransactionId> {
17002 if let Some(transaction_id) = self
17003 .buffer
17004 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
17005 {
17006 if let Some((_, end_selections)) =
17007 self.selection_history.transaction_mut(transaction_id)
17008 {
17009 *end_selections = Some(self.selections.disjoint_anchors());
17010 } else {
17011 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
17012 }
17013
17014 cx.emit(EditorEvent::Edited { transaction_id });
17015 Some(transaction_id)
17016 } else {
17017 None
17018 }
17019 }
17020
17021 pub fn modify_transaction_selection_history(
17022 &mut self,
17023 transaction_id: TransactionId,
17024 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
17025 ) -> bool {
17026 self.selection_history
17027 .transaction_mut(transaction_id)
17028 .map(modify)
17029 .is_some()
17030 }
17031
17032 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
17033 if self.selection_mark_mode {
17034 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17035 s.move_with(|_, sel| {
17036 sel.collapse_to(sel.head(), SelectionGoal::None);
17037 });
17038 })
17039 }
17040 self.selection_mark_mode = true;
17041 cx.notify();
17042 }
17043
17044 pub fn swap_selection_ends(
17045 &mut self,
17046 _: &actions::SwapSelectionEnds,
17047 window: &mut Window,
17048 cx: &mut Context<Self>,
17049 ) {
17050 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17051 s.move_with(|_, sel| {
17052 if sel.start != sel.end {
17053 sel.reversed = !sel.reversed
17054 }
17055 });
17056 });
17057 self.request_autoscroll(Autoscroll::newest(), cx);
17058 cx.notify();
17059 }
17060
17061 pub fn toggle_focus(
17062 workspace: &mut Workspace,
17063 _: &actions::ToggleFocus,
17064 window: &mut Window,
17065 cx: &mut Context<Workspace>,
17066 ) {
17067 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
17068 return;
17069 };
17070 workspace.activate_item(&item, true, true, window, cx);
17071 }
17072
17073 pub fn toggle_fold(
17074 &mut self,
17075 _: &actions::ToggleFold,
17076 window: &mut Window,
17077 cx: &mut Context<Self>,
17078 ) {
17079 if self.is_singleton(cx) {
17080 let selection = self.selections.newest::<Point>(cx);
17081
17082 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17083 let range = if selection.is_empty() {
17084 let point = selection.head().to_display_point(&display_map);
17085 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17086 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17087 .to_point(&display_map);
17088 start..end
17089 } else {
17090 selection.range()
17091 };
17092 if display_map.folds_in_range(range).next().is_some() {
17093 self.unfold_lines(&Default::default(), window, cx)
17094 } else {
17095 self.fold(&Default::default(), window, cx)
17096 }
17097 } else {
17098 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17099 let buffer_ids: HashSet<_> = self
17100 .selections
17101 .disjoint_anchor_ranges()
17102 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17103 .collect();
17104
17105 let should_unfold = buffer_ids
17106 .iter()
17107 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17108
17109 for buffer_id in buffer_ids {
17110 if should_unfold {
17111 self.unfold_buffer(buffer_id, cx);
17112 } else {
17113 self.fold_buffer(buffer_id, cx);
17114 }
17115 }
17116 }
17117 }
17118
17119 pub fn toggle_fold_recursive(
17120 &mut self,
17121 _: &actions::ToggleFoldRecursive,
17122 window: &mut Window,
17123 cx: &mut Context<Self>,
17124 ) {
17125 let selection = self.selections.newest::<Point>(cx);
17126
17127 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17128 let range = if selection.is_empty() {
17129 let point = selection.head().to_display_point(&display_map);
17130 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17131 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17132 .to_point(&display_map);
17133 start..end
17134 } else {
17135 selection.range()
17136 };
17137 if display_map.folds_in_range(range).next().is_some() {
17138 self.unfold_recursive(&Default::default(), window, cx)
17139 } else {
17140 self.fold_recursive(&Default::default(), window, cx)
17141 }
17142 }
17143
17144 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
17145 if self.is_singleton(cx) {
17146 let mut to_fold = Vec::new();
17147 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17148 let selections = self.selections.all_adjusted(cx);
17149
17150 for selection in selections {
17151 let range = selection.range().sorted();
17152 let buffer_start_row = range.start.row;
17153
17154 if range.start.row != range.end.row {
17155 let mut found = false;
17156 let mut row = range.start.row;
17157 while row <= range.end.row {
17158 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
17159 {
17160 found = true;
17161 row = crease.range().end.row + 1;
17162 to_fold.push(crease);
17163 } else {
17164 row += 1
17165 }
17166 }
17167 if found {
17168 continue;
17169 }
17170 }
17171
17172 for row in (0..=range.start.row).rev() {
17173 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17174 if crease.range().end.row >= buffer_start_row {
17175 to_fold.push(crease);
17176 if row <= range.start.row {
17177 break;
17178 }
17179 }
17180 }
17181 }
17182 }
17183
17184 self.fold_creases(to_fold, true, window, cx);
17185 } else {
17186 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17187 let buffer_ids = self
17188 .selections
17189 .disjoint_anchor_ranges()
17190 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17191 .collect::<HashSet<_>>();
17192 for buffer_id in buffer_ids {
17193 self.fold_buffer(buffer_id, cx);
17194 }
17195 }
17196 }
17197
17198 pub fn toggle_fold_all(
17199 &mut self,
17200 _: &actions::ToggleFoldAll,
17201 window: &mut Window,
17202 cx: &mut Context<Self>,
17203 ) {
17204 if self.buffer.read(cx).is_singleton() {
17205 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17206 let has_folds = display_map
17207 .folds_in_range(0..display_map.buffer_snapshot.len())
17208 .next()
17209 .is_some();
17210
17211 if has_folds {
17212 self.unfold_all(&actions::UnfoldAll, window, cx);
17213 } else {
17214 self.fold_all(&actions::FoldAll, window, cx);
17215 }
17216 } else {
17217 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
17218 let should_unfold = buffer_ids
17219 .iter()
17220 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17221
17222 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17223 editor
17224 .update_in(cx, |editor, _, cx| {
17225 for buffer_id in buffer_ids {
17226 if should_unfold {
17227 editor.unfold_buffer(buffer_id, cx);
17228 } else {
17229 editor.fold_buffer(buffer_id, cx);
17230 }
17231 }
17232 })
17233 .ok();
17234 });
17235 }
17236 }
17237
17238 fn fold_at_level(
17239 &mut self,
17240 fold_at: &FoldAtLevel,
17241 window: &mut Window,
17242 cx: &mut Context<Self>,
17243 ) {
17244 if !self.buffer.read(cx).is_singleton() {
17245 return;
17246 }
17247
17248 let fold_at_level = fold_at.0;
17249 let snapshot = self.buffer.read(cx).snapshot(cx);
17250 let mut to_fold = Vec::new();
17251 let mut stack = vec![(0, snapshot.max_row().0, 1)];
17252
17253 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
17254 while start_row < end_row {
17255 match self
17256 .snapshot(window, cx)
17257 .crease_for_buffer_row(MultiBufferRow(start_row))
17258 {
17259 Some(crease) => {
17260 let nested_start_row = crease.range().start.row + 1;
17261 let nested_end_row = crease.range().end.row;
17262
17263 if current_level < fold_at_level {
17264 stack.push((nested_start_row, nested_end_row, current_level + 1));
17265 } else if current_level == fold_at_level {
17266 to_fold.push(crease);
17267 }
17268
17269 start_row = nested_end_row + 1;
17270 }
17271 None => start_row += 1,
17272 }
17273 }
17274 }
17275
17276 self.fold_creases(to_fold, true, window, cx);
17277 }
17278
17279 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
17280 if self.buffer.read(cx).is_singleton() {
17281 let mut fold_ranges = Vec::new();
17282 let snapshot = self.buffer.read(cx).snapshot(cx);
17283
17284 for row in 0..snapshot.max_row().0 {
17285 if let Some(foldable_range) = self
17286 .snapshot(window, cx)
17287 .crease_for_buffer_row(MultiBufferRow(row))
17288 {
17289 fold_ranges.push(foldable_range);
17290 }
17291 }
17292
17293 self.fold_creases(fold_ranges, true, window, cx);
17294 } else {
17295 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17296 editor
17297 .update_in(cx, |editor, _, cx| {
17298 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17299 editor.fold_buffer(buffer_id, cx);
17300 }
17301 })
17302 .ok();
17303 });
17304 }
17305 }
17306
17307 pub fn fold_function_bodies(
17308 &mut self,
17309 _: &actions::FoldFunctionBodies,
17310 window: &mut Window,
17311 cx: &mut Context<Self>,
17312 ) {
17313 let snapshot = self.buffer.read(cx).snapshot(cx);
17314
17315 let ranges = snapshot
17316 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
17317 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
17318 .collect::<Vec<_>>();
17319
17320 let creases = ranges
17321 .into_iter()
17322 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
17323 .collect();
17324
17325 self.fold_creases(creases, true, window, cx);
17326 }
17327
17328 pub fn fold_recursive(
17329 &mut self,
17330 _: &actions::FoldRecursive,
17331 window: &mut Window,
17332 cx: &mut Context<Self>,
17333 ) {
17334 let mut to_fold = Vec::new();
17335 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17336 let selections = self.selections.all_adjusted(cx);
17337
17338 for selection in selections {
17339 let range = selection.range().sorted();
17340 let buffer_start_row = range.start.row;
17341
17342 if range.start.row != range.end.row {
17343 let mut found = false;
17344 for row in range.start.row..=range.end.row {
17345 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17346 found = true;
17347 to_fold.push(crease);
17348 }
17349 }
17350 if found {
17351 continue;
17352 }
17353 }
17354
17355 for row in (0..=range.start.row).rev() {
17356 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17357 if crease.range().end.row >= buffer_start_row {
17358 to_fold.push(crease);
17359 } else {
17360 break;
17361 }
17362 }
17363 }
17364 }
17365
17366 self.fold_creases(to_fold, true, window, cx);
17367 }
17368
17369 pub fn fold_at(
17370 &mut self,
17371 buffer_row: MultiBufferRow,
17372 window: &mut Window,
17373 cx: &mut Context<Self>,
17374 ) {
17375 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17376
17377 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
17378 let autoscroll = self
17379 .selections
17380 .all::<Point>(cx)
17381 .iter()
17382 .any(|selection| crease.range().overlaps(&selection.range()));
17383
17384 self.fold_creases(vec![crease], autoscroll, window, cx);
17385 }
17386 }
17387
17388 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
17389 if self.is_singleton(cx) {
17390 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17391 let buffer = &display_map.buffer_snapshot;
17392 let selections = self.selections.all::<Point>(cx);
17393 let ranges = selections
17394 .iter()
17395 .map(|s| {
17396 let range = s.display_range(&display_map).sorted();
17397 let mut start = range.start.to_point(&display_map);
17398 let mut end = range.end.to_point(&display_map);
17399 start.column = 0;
17400 end.column = buffer.line_len(MultiBufferRow(end.row));
17401 start..end
17402 })
17403 .collect::<Vec<_>>();
17404
17405 self.unfold_ranges(&ranges, true, true, cx);
17406 } else {
17407 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17408 let buffer_ids = self
17409 .selections
17410 .disjoint_anchor_ranges()
17411 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17412 .collect::<HashSet<_>>();
17413 for buffer_id in buffer_ids {
17414 self.unfold_buffer(buffer_id, cx);
17415 }
17416 }
17417 }
17418
17419 pub fn unfold_recursive(
17420 &mut self,
17421 _: &UnfoldRecursive,
17422 _window: &mut Window,
17423 cx: &mut Context<Self>,
17424 ) {
17425 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17426 let selections = self.selections.all::<Point>(cx);
17427 let ranges = selections
17428 .iter()
17429 .map(|s| {
17430 let mut range = s.display_range(&display_map).sorted();
17431 *range.start.column_mut() = 0;
17432 *range.end.column_mut() = display_map.line_len(range.end.row());
17433 let start = range.start.to_point(&display_map);
17434 let end = range.end.to_point(&display_map);
17435 start..end
17436 })
17437 .collect::<Vec<_>>();
17438
17439 self.unfold_ranges(&ranges, true, true, cx);
17440 }
17441
17442 pub fn unfold_at(
17443 &mut self,
17444 buffer_row: MultiBufferRow,
17445 _window: &mut Window,
17446 cx: &mut Context<Self>,
17447 ) {
17448 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17449
17450 let intersection_range = Point::new(buffer_row.0, 0)
17451 ..Point::new(
17452 buffer_row.0,
17453 display_map.buffer_snapshot.line_len(buffer_row),
17454 );
17455
17456 let autoscroll = self
17457 .selections
17458 .all::<Point>(cx)
17459 .iter()
17460 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
17461
17462 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
17463 }
17464
17465 pub fn unfold_all(
17466 &mut self,
17467 _: &actions::UnfoldAll,
17468 _window: &mut Window,
17469 cx: &mut Context<Self>,
17470 ) {
17471 if self.buffer.read(cx).is_singleton() {
17472 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17473 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
17474 } else {
17475 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
17476 editor
17477 .update(cx, |editor, cx| {
17478 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17479 editor.unfold_buffer(buffer_id, cx);
17480 }
17481 })
17482 .ok();
17483 });
17484 }
17485 }
17486
17487 pub fn fold_selected_ranges(
17488 &mut self,
17489 _: &FoldSelectedRanges,
17490 window: &mut Window,
17491 cx: &mut Context<Self>,
17492 ) {
17493 let selections = self.selections.all_adjusted(cx);
17494 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17495 let ranges = selections
17496 .into_iter()
17497 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
17498 .collect::<Vec<_>>();
17499 self.fold_creases(ranges, true, window, cx);
17500 }
17501
17502 pub fn fold_ranges<T: ToOffset + Clone>(
17503 &mut self,
17504 ranges: Vec<Range<T>>,
17505 auto_scroll: bool,
17506 window: &mut Window,
17507 cx: &mut Context<Self>,
17508 ) {
17509 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17510 let ranges = ranges
17511 .into_iter()
17512 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
17513 .collect::<Vec<_>>();
17514 self.fold_creases(ranges, auto_scroll, window, cx);
17515 }
17516
17517 pub fn fold_creases<T: ToOffset + Clone>(
17518 &mut self,
17519 creases: Vec<Crease<T>>,
17520 auto_scroll: bool,
17521 _window: &mut Window,
17522 cx: &mut Context<Self>,
17523 ) {
17524 if creases.is_empty() {
17525 return;
17526 }
17527
17528 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
17529
17530 if auto_scroll {
17531 self.request_autoscroll(Autoscroll::fit(), cx);
17532 }
17533
17534 cx.notify();
17535
17536 self.scrollbar_marker_state.dirty = true;
17537 self.folds_did_change(cx);
17538 }
17539
17540 /// Removes any folds whose ranges intersect any of the given ranges.
17541 pub fn unfold_ranges<T: ToOffset + Clone>(
17542 &mut self,
17543 ranges: &[Range<T>],
17544 inclusive: bool,
17545 auto_scroll: bool,
17546 cx: &mut Context<Self>,
17547 ) {
17548 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17549 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
17550 });
17551 self.folds_did_change(cx);
17552 }
17553
17554 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17555 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
17556 return;
17557 }
17558 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17559 self.display_map.update(cx, |display_map, cx| {
17560 display_map.fold_buffers([buffer_id], cx)
17561 });
17562 cx.emit(EditorEvent::BufferFoldToggled {
17563 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
17564 folded: true,
17565 });
17566 cx.notify();
17567 }
17568
17569 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17570 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
17571 return;
17572 }
17573 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17574 self.display_map.update(cx, |display_map, cx| {
17575 display_map.unfold_buffers([buffer_id], cx);
17576 });
17577 cx.emit(EditorEvent::BufferFoldToggled {
17578 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
17579 folded: false,
17580 });
17581 cx.notify();
17582 }
17583
17584 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
17585 self.display_map.read(cx).is_buffer_folded(buffer)
17586 }
17587
17588 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
17589 self.display_map.read(cx).folded_buffers()
17590 }
17591
17592 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17593 self.display_map.update(cx, |display_map, cx| {
17594 display_map.disable_header_for_buffer(buffer_id, cx);
17595 });
17596 cx.notify();
17597 }
17598
17599 /// Removes any folds with the given ranges.
17600 pub fn remove_folds_with_type<T: ToOffset + Clone>(
17601 &mut self,
17602 ranges: &[Range<T>],
17603 type_id: TypeId,
17604 auto_scroll: bool,
17605 cx: &mut Context<Self>,
17606 ) {
17607 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17608 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
17609 });
17610 self.folds_did_change(cx);
17611 }
17612
17613 fn remove_folds_with<T: ToOffset + Clone>(
17614 &mut self,
17615 ranges: &[Range<T>],
17616 auto_scroll: bool,
17617 cx: &mut Context<Self>,
17618 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
17619 ) {
17620 if ranges.is_empty() {
17621 return;
17622 }
17623
17624 let mut buffers_affected = HashSet::default();
17625 let multi_buffer = self.buffer().read(cx);
17626 for range in ranges {
17627 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
17628 buffers_affected.insert(buffer.read(cx).remote_id());
17629 };
17630 }
17631
17632 self.display_map.update(cx, update);
17633
17634 if auto_scroll {
17635 self.request_autoscroll(Autoscroll::fit(), cx);
17636 }
17637
17638 cx.notify();
17639 self.scrollbar_marker_state.dirty = true;
17640 self.active_indent_guides_state.dirty = true;
17641 }
17642
17643 pub fn update_renderer_widths(
17644 &mut self,
17645 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
17646 cx: &mut Context<Self>,
17647 ) -> bool {
17648 self.display_map
17649 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
17650 }
17651
17652 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
17653 self.display_map.read(cx).fold_placeholder.clone()
17654 }
17655
17656 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
17657 self.buffer.update(cx, |buffer, cx| {
17658 buffer.set_all_diff_hunks_expanded(cx);
17659 });
17660 }
17661
17662 pub fn expand_all_diff_hunks(
17663 &mut self,
17664 _: &ExpandAllDiffHunks,
17665 _window: &mut Window,
17666 cx: &mut Context<Self>,
17667 ) {
17668 self.buffer.update(cx, |buffer, cx| {
17669 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
17670 });
17671 }
17672
17673 pub fn toggle_selected_diff_hunks(
17674 &mut self,
17675 _: &ToggleSelectedDiffHunks,
17676 _window: &mut Window,
17677 cx: &mut Context<Self>,
17678 ) {
17679 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17680 self.toggle_diff_hunks_in_ranges(ranges, cx);
17681 }
17682
17683 pub fn diff_hunks_in_ranges<'a>(
17684 &'a self,
17685 ranges: &'a [Range<Anchor>],
17686 buffer: &'a MultiBufferSnapshot,
17687 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
17688 ranges.iter().flat_map(move |range| {
17689 let end_excerpt_id = range.end.excerpt_id;
17690 let range = range.to_point(buffer);
17691 let mut peek_end = range.end;
17692 if range.end.row < buffer.max_row().0 {
17693 peek_end = Point::new(range.end.row + 1, 0);
17694 }
17695 buffer
17696 .diff_hunks_in_range(range.start..peek_end)
17697 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
17698 })
17699 }
17700
17701 pub fn has_stageable_diff_hunks_in_ranges(
17702 &self,
17703 ranges: &[Range<Anchor>],
17704 snapshot: &MultiBufferSnapshot,
17705 ) -> bool {
17706 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
17707 hunks.any(|hunk| hunk.status().has_secondary_hunk())
17708 }
17709
17710 pub fn toggle_staged_selected_diff_hunks(
17711 &mut self,
17712 _: &::git::ToggleStaged,
17713 _: &mut Window,
17714 cx: &mut Context<Self>,
17715 ) {
17716 let snapshot = self.buffer.read(cx).snapshot(cx);
17717 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17718 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
17719 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17720 }
17721
17722 pub fn set_render_diff_hunk_controls(
17723 &mut self,
17724 render_diff_hunk_controls: RenderDiffHunkControlsFn,
17725 cx: &mut Context<Self>,
17726 ) {
17727 self.render_diff_hunk_controls = render_diff_hunk_controls;
17728 cx.notify();
17729 }
17730
17731 pub fn stage_and_next(
17732 &mut self,
17733 _: &::git::StageAndNext,
17734 window: &mut Window,
17735 cx: &mut Context<Self>,
17736 ) {
17737 self.do_stage_or_unstage_and_next(true, window, cx);
17738 }
17739
17740 pub fn unstage_and_next(
17741 &mut self,
17742 _: &::git::UnstageAndNext,
17743 window: &mut Window,
17744 cx: &mut Context<Self>,
17745 ) {
17746 self.do_stage_or_unstage_and_next(false, window, cx);
17747 }
17748
17749 pub fn stage_or_unstage_diff_hunks(
17750 &mut self,
17751 stage: bool,
17752 ranges: Vec<Range<Anchor>>,
17753 cx: &mut Context<Self>,
17754 ) {
17755 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
17756 cx.spawn(async move |this, cx| {
17757 task.await?;
17758 this.update(cx, |this, cx| {
17759 let snapshot = this.buffer.read(cx).snapshot(cx);
17760 let chunk_by = this
17761 .diff_hunks_in_ranges(&ranges, &snapshot)
17762 .chunk_by(|hunk| hunk.buffer_id);
17763 for (buffer_id, hunks) in &chunk_by {
17764 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
17765 }
17766 })
17767 })
17768 .detach_and_log_err(cx);
17769 }
17770
17771 fn save_buffers_for_ranges_if_needed(
17772 &mut self,
17773 ranges: &[Range<Anchor>],
17774 cx: &mut Context<Editor>,
17775 ) -> Task<Result<()>> {
17776 let multibuffer = self.buffer.read(cx);
17777 let snapshot = multibuffer.read(cx);
17778 let buffer_ids: HashSet<_> = ranges
17779 .iter()
17780 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
17781 .collect();
17782 drop(snapshot);
17783
17784 let mut buffers = HashSet::default();
17785 for buffer_id in buffer_ids {
17786 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
17787 let buffer = buffer_entity.read(cx);
17788 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
17789 {
17790 buffers.insert(buffer_entity);
17791 }
17792 }
17793 }
17794
17795 if let Some(project) = &self.project {
17796 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
17797 } else {
17798 Task::ready(Ok(()))
17799 }
17800 }
17801
17802 fn do_stage_or_unstage_and_next(
17803 &mut self,
17804 stage: bool,
17805 window: &mut Window,
17806 cx: &mut Context<Self>,
17807 ) {
17808 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
17809
17810 if ranges.iter().any(|range| range.start != range.end) {
17811 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17812 return;
17813 }
17814
17815 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17816 let snapshot = self.snapshot(window, cx);
17817 let position = self.selections.newest::<Point>(cx).head();
17818 let mut row = snapshot
17819 .buffer_snapshot
17820 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
17821 .find(|hunk| hunk.row_range.start.0 > position.row)
17822 .map(|hunk| hunk.row_range.start);
17823
17824 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
17825 // Outside of the project diff editor, wrap around to the beginning.
17826 if !all_diff_hunks_expanded {
17827 row = row.or_else(|| {
17828 snapshot
17829 .buffer_snapshot
17830 .diff_hunks_in_range(Point::zero()..position)
17831 .find(|hunk| hunk.row_range.end.0 < position.row)
17832 .map(|hunk| hunk.row_range.start)
17833 });
17834 }
17835
17836 if let Some(row) = row {
17837 let destination = Point::new(row.0, 0);
17838 let autoscroll = Autoscroll::center();
17839
17840 self.unfold_ranges(&[destination..destination], false, false, cx);
17841 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17842 s.select_ranges([destination..destination]);
17843 });
17844 }
17845 }
17846
17847 fn do_stage_or_unstage(
17848 &self,
17849 stage: bool,
17850 buffer_id: BufferId,
17851 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
17852 cx: &mut App,
17853 ) -> Option<()> {
17854 let project = self.project.as_ref()?;
17855 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
17856 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
17857 let buffer_snapshot = buffer.read(cx).snapshot();
17858 let file_exists = buffer_snapshot
17859 .file()
17860 .is_some_and(|file| file.disk_state().exists());
17861 diff.update(cx, |diff, cx| {
17862 diff.stage_or_unstage_hunks(
17863 stage,
17864 &hunks
17865 .map(|hunk| buffer_diff::DiffHunk {
17866 buffer_range: hunk.buffer_range,
17867 diff_base_byte_range: hunk.diff_base_byte_range,
17868 secondary_status: hunk.secondary_status,
17869 range: Point::zero()..Point::zero(), // unused
17870 })
17871 .collect::<Vec<_>>(),
17872 &buffer_snapshot,
17873 file_exists,
17874 cx,
17875 )
17876 });
17877 None
17878 }
17879
17880 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
17881 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17882 self.buffer
17883 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
17884 }
17885
17886 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
17887 self.buffer.update(cx, |buffer, cx| {
17888 let ranges = vec![Anchor::min()..Anchor::max()];
17889 if !buffer.all_diff_hunks_expanded()
17890 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
17891 {
17892 buffer.collapse_diff_hunks(ranges, cx);
17893 true
17894 } else {
17895 false
17896 }
17897 })
17898 }
17899
17900 fn toggle_diff_hunks_in_ranges(
17901 &mut self,
17902 ranges: Vec<Range<Anchor>>,
17903 cx: &mut Context<Editor>,
17904 ) {
17905 self.buffer.update(cx, |buffer, cx| {
17906 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
17907 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
17908 })
17909 }
17910
17911 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
17912 self.buffer.update(cx, |buffer, cx| {
17913 let snapshot = buffer.snapshot(cx);
17914 let excerpt_id = range.end.excerpt_id;
17915 let point_range = range.to_point(&snapshot);
17916 let expand = !buffer.single_hunk_is_expanded(range, cx);
17917 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
17918 })
17919 }
17920
17921 pub(crate) fn apply_all_diff_hunks(
17922 &mut self,
17923 _: &ApplyAllDiffHunks,
17924 window: &mut Window,
17925 cx: &mut Context<Self>,
17926 ) {
17927 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17928
17929 let buffers = self.buffer.read(cx).all_buffers();
17930 for branch_buffer in buffers {
17931 branch_buffer.update(cx, |branch_buffer, cx| {
17932 branch_buffer.merge_into_base(Vec::new(), cx);
17933 });
17934 }
17935
17936 if let Some(project) = self.project.clone() {
17937 self.save(
17938 SaveOptions {
17939 format: true,
17940 autosave: false,
17941 },
17942 project,
17943 window,
17944 cx,
17945 )
17946 .detach_and_log_err(cx);
17947 }
17948 }
17949
17950 pub(crate) fn apply_selected_diff_hunks(
17951 &mut self,
17952 _: &ApplyDiffHunk,
17953 window: &mut Window,
17954 cx: &mut Context<Self>,
17955 ) {
17956 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17957 let snapshot = self.snapshot(window, cx);
17958 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
17959 let mut ranges_by_buffer = HashMap::default();
17960 self.transact(window, cx, |editor, _window, cx| {
17961 for hunk in hunks {
17962 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
17963 ranges_by_buffer
17964 .entry(buffer.clone())
17965 .or_insert_with(Vec::new)
17966 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
17967 }
17968 }
17969
17970 for (buffer, ranges) in ranges_by_buffer {
17971 buffer.update(cx, |buffer, cx| {
17972 buffer.merge_into_base(ranges, cx);
17973 });
17974 }
17975 });
17976
17977 if let Some(project) = self.project.clone() {
17978 self.save(
17979 SaveOptions {
17980 format: true,
17981 autosave: false,
17982 },
17983 project,
17984 window,
17985 cx,
17986 )
17987 .detach_and_log_err(cx);
17988 }
17989 }
17990
17991 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
17992 if hovered != self.gutter_hovered {
17993 self.gutter_hovered = hovered;
17994 cx.notify();
17995 }
17996 }
17997
17998 pub fn insert_blocks(
17999 &mut self,
18000 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
18001 autoscroll: Option<Autoscroll>,
18002 cx: &mut Context<Self>,
18003 ) -> Vec<CustomBlockId> {
18004 let blocks = self
18005 .display_map
18006 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
18007 if let Some(autoscroll) = autoscroll {
18008 self.request_autoscroll(autoscroll, cx);
18009 }
18010 cx.notify();
18011 blocks
18012 }
18013
18014 pub fn resize_blocks(
18015 &mut self,
18016 heights: HashMap<CustomBlockId, u32>,
18017 autoscroll: Option<Autoscroll>,
18018 cx: &mut Context<Self>,
18019 ) {
18020 self.display_map
18021 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
18022 if let Some(autoscroll) = autoscroll {
18023 self.request_autoscroll(autoscroll, cx);
18024 }
18025 cx.notify();
18026 }
18027
18028 pub fn replace_blocks(
18029 &mut self,
18030 renderers: HashMap<CustomBlockId, RenderBlock>,
18031 autoscroll: Option<Autoscroll>,
18032 cx: &mut Context<Self>,
18033 ) {
18034 self.display_map
18035 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
18036 if let Some(autoscroll) = autoscroll {
18037 self.request_autoscroll(autoscroll, cx);
18038 }
18039 cx.notify();
18040 }
18041
18042 pub fn remove_blocks(
18043 &mut self,
18044 block_ids: HashSet<CustomBlockId>,
18045 autoscroll: Option<Autoscroll>,
18046 cx: &mut Context<Self>,
18047 ) {
18048 self.display_map.update(cx, |display_map, cx| {
18049 display_map.remove_blocks(block_ids, cx)
18050 });
18051 if let Some(autoscroll) = autoscroll {
18052 self.request_autoscroll(autoscroll, cx);
18053 }
18054 cx.notify();
18055 }
18056
18057 pub fn row_for_block(
18058 &self,
18059 block_id: CustomBlockId,
18060 cx: &mut Context<Self>,
18061 ) -> Option<DisplayRow> {
18062 self.display_map
18063 .update(cx, |map, cx| map.row_for_block(block_id, cx))
18064 }
18065
18066 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
18067 self.focused_block = Some(focused_block);
18068 }
18069
18070 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
18071 self.focused_block.take()
18072 }
18073
18074 pub fn insert_creases(
18075 &mut self,
18076 creases: impl IntoIterator<Item = Crease<Anchor>>,
18077 cx: &mut Context<Self>,
18078 ) -> Vec<CreaseId> {
18079 self.display_map
18080 .update(cx, |map, cx| map.insert_creases(creases, cx))
18081 }
18082
18083 pub fn remove_creases(
18084 &mut self,
18085 ids: impl IntoIterator<Item = CreaseId>,
18086 cx: &mut Context<Self>,
18087 ) -> Vec<(CreaseId, Range<Anchor>)> {
18088 self.display_map
18089 .update(cx, |map, cx| map.remove_creases(ids, cx))
18090 }
18091
18092 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
18093 self.display_map
18094 .update(cx, |map, cx| map.snapshot(cx))
18095 .longest_row()
18096 }
18097
18098 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
18099 self.display_map
18100 .update(cx, |map, cx| map.snapshot(cx))
18101 .max_point()
18102 }
18103
18104 pub fn text(&self, cx: &App) -> String {
18105 self.buffer.read(cx).read(cx).text()
18106 }
18107
18108 pub fn is_empty(&self, cx: &App) -> bool {
18109 self.buffer.read(cx).read(cx).is_empty()
18110 }
18111
18112 pub fn text_option(&self, cx: &App) -> Option<String> {
18113 let text = self.text(cx);
18114 let text = text.trim();
18115
18116 if text.is_empty() {
18117 return None;
18118 }
18119
18120 Some(text.to_string())
18121 }
18122
18123 pub fn set_text(
18124 &mut self,
18125 text: impl Into<Arc<str>>,
18126 window: &mut Window,
18127 cx: &mut Context<Self>,
18128 ) {
18129 self.transact(window, cx, |this, _, cx| {
18130 this.buffer
18131 .read(cx)
18132 .as_singleton()
18133 .expect("you can only call set_text on editors for singleton buffers")
18134 .update(cx, |buffer, cx| buffer.set_text(text, cx));
18135 });
18136 }
18137
18138 pub fn display_text(&self, cx: &mut App) -> String {
18139 self.display_map
18140 .update(cx, |map, cx| map.snapshot(cx))
18141 .text()
18142 }
18143
18144 fn create_minimap(
18145 &self,
18146 minimap_settings: MinimapSettings,
18147 window: &mut Window,
18148 cx: &mut Context<Self>,
18149 ) -> Option<Entity<Self>> {
18150 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
18151 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
18152 }
18153
18154 fn initialize_new_minimap(
18155 &self,
18156 minimap_settings: MinimapSettings,
18157 window: &mut Window,
18158 cx: &mut Context<Self>,
18159 ) -> Entity<Self> {
18160 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
18161
18162 let mut minimap = Editor::new_internal(
18163 EditorMode::Minimap {
18164 parent: cx.weak_entity(),
18165 },
18166 self.buffer.clone(),
18167 None,
18168 Some(self.display_map.clone()),
18169 window,
18170 cx,
18171 );
18172 minimap.scroll_manager.clone_state(&self.scroll_manager);
18173 minimap.set_text_style_refinement(TextStyleRefinement {
18174 font_size: Some(MINIMAP_FONT_SIZE),
18175 font_weight: Some(MINIMAP_FONT_WEIGHT),
18176 ..Default::default()
18177 });
18178 minimap.update_minimap_configuration(minimap_settings, cx);
18179 cx.new(|_| minimap)
18180 }
18181
18182 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
18183 let current_line_highlight = minimap_settings
18184 .current_line_highlight
18185 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
18186 self.set_current_line_highlight(Some(current_line_highlight));
18187 }
18188
18189 pub fn minimap(&self) -> Option<&Entity<Self>> {
18190 self.minimap
18191 .as_ref()
18192 .filter(|_| self.minimap_visibility.visible())
18193 }
18194
18195 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
18196 let mut wrap_guides = smallvec![];
18197
18198 if self.show_wrap_guides == Some(false) {
18199 return wrap_guides;
18200 }
18201
18202 let settings = self.buffer.read(cx).language_settings(cx);
18203 if settings.show_wrap_guides {
18204 match self.soft_wrap_mode(cx) {
18205 SoftWrap::Column(soft_wrap) => {
18206 wrap_guides.push((soft_wrap as usize, true));
18207 }
18208 SoftWrap::Bounded(soft_wrap) => {
18209 wrap_guides.push((soft_wrap as usize, true));
18210 }
18211 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
18212 }
18213 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
18214 }
18215
18216 wrap_guides
18217 }
18218
18219 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
18220 let settings = self.buffer.read(cx).language_settings(cx);
18221 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
18222 match mode {
18223 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
18224 SoftWrap::None
18225 }
18226 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
18227 language_settings::SoftWrap::PreferredLineLength => {
18228 SoftWrap::Column(settings.preferred_line_length)
18229 }
18230 language_settings::SoftWrap::Bounded => {
18231 SoftWrap::Bounded(settings.preferred_line_length)
18232 }
18233 }
18234 }
18235
18236 pub fn set_soft_wrap_mode(
18237 &mut self,
18238 mode: language_settings::SoftWrap,
18239
18240 cx: &mut Context<Self>,
18241 ) {
18242 self.soft_wrap_mode_override = Some(mode);
18243 cx.notify();
18244 }
18245
18246 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
18247 self.hard_wrap = hard_wrap;
18248 cx.notify();
18249 }
18250
18251 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
18252 self.text_style_refinement = Some(style);
18253 }
18254
18255 /// called by the Element so we know what style we were most recently rendered with.
18256 pub(crate) fn set_style(
18257 &mut self,
18258 style: EditorStyle,
18259 window: &mut Window,
18260 cx: &mut Context<Self>,
18261 ) {
18262 // We intentionally do not inform the display map about the minimap style
18263 // so that wrapping is not recalculated and stays consistent for the editor
18264 // and its linked minimap.
18265 if !self.mode.is_minimap() {
18266 let rem_size = window.rem_size();
18267 self.display_map.update(cx, |map, cx| {
18268 map.set_font(
18269 style.text.font(),
18270 style.text.font_size.to_pixels(rem_size),
18271 cx,
18272 )
18273 });
18274 }
18275 self.style = Some(style);
18276 }
18277
18278 pub fn style(&self) -> Option<&EditorStyle> {
18279 self.style.as_ref()
18280 }
18281
18282 // Called by the element. This method is not designed to be called outside of the editor
18283 // element's layout code because it does not notify when rewrapping is computed synchronously.
18284 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
18285 self.display_map
18286 .update(cx, |map, cx| map.set_wrap_width(width, cx))
18287 }
18288
18289 pub fn set_soft_wrap(&mut self) {
18290 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
18291 }
18292
18293 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
18294 if self.soft_wrap_mode_override.is_some() {
18295 self.soft_wrap_mode_override.take();
18296 } else {
18297 let soft_wrap = match self.soft_wrap_mode(cx) {
18298 SoftWrap::GitDiff => return,
18299 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
18300 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
18301 language_settings::SoftWrap::None
18302 }
18303 };
18304 self.soft_wrap_mode_override = Some(soft_wrap);
18305 }
18306 cx.notify();
18307 }
18308
18309 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
18310 let Some(workspace) = self.workspace() else {
18311 return;
18312 };
18313 let fs = workspace.read(cx).app_state().fs.clone();
18314 let current_show = TabBarSettings::get_global(cx).show;
18315 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
18316 setting.show = Some(!current_show);
18317 });
18318 }
18319
18320 pub fn toggle_indent_guides(
18321 &mut self,
18322 _: &ToggleIndentGuides,
18323 _: &mut Window,
18324 cx: &mut Context<Self>,
18325 ) {
18326 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
18327 self.buffer
18328 .read(cx)
18329 .language_settings(cx)
18330 .indent_guides
18331 .enabled
18332 });
18333 self.show_indent_guides = Some(!currently_enabled);
18334 cx.notify();
18335 }
18336
18337 fn should_show_indent_guides(&self) -> Option<bool> {
18338 self.show_indent_guides
18339 }
18340
18341 pub fn toggle_line_numbers(
18342 &mut self,
18343 _: &ToggleLineNumbers,
18344 _: &mut Window,
18345 cx: &mut Context<Self>,
18346 ) {
18347 let mut editor_settings = EditorSettings::get_global(cx).clone();
18348 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
18349 EditorSettings::override_global(editor_settings, cx);
18350 }
18351
18352 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
18353 if let Some(show_line_numbers) = self.show_line_numbers {
18354 return show_line_numbers;
18355 }
18356 EditorSettings::get_global(cx).gutter.line_numbers
18357 }
18358
18359 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
18360 self.use_relative_line_numbers
18361 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
18362 }
18363
18364 pub fn toggle_relative_line_numbers(
18365 &mut self,
18366 _: &ToggleRelativeLineNumbers,
18367 _: &mut Window,
18368 cx: &mut Context<Self>,
18369 ) {
18370 let is_relative = self.should_use_relative_line_numbers(cx);
18371 self.set_relative_line_number(Some(!is_relative), cx)
18372 }
18373
18374 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
18375 self.use_relative_line_numbers = is_relative;
18376 cx.notify();
18377 }
18378
18379 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
18380 self.show_gutter = show_gutter;
18381 cx.notify();
18382 }
18383
18384 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
18385 self.show_scrollbars = ScrollbarAxes {
18386 horizontal: show,
18387 vertical: show,
18388 };
18389 cx.notify();
18390 }
18391
18392 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18393 self.show_scrollbars.vertical = show;
18394 cx.notify();
18395 }
18396
18397 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18398 self.show_scrollbars.horizontal = show;
18399 cx.notify();
18400 }
18401
18402 pub fn set_minimap_visibility(
18403 &mut self,
18404 minimap_visibility: MinimapVisibility,
18405 window: &mut Window,
18406 cx: &mut Context<Self>,
18407 ) {
18408 if self.minimap_visibility != minimap_visibility {
18409 if minimap_visibility.visible() && self.minimap.is_none() {
18410 let minimap_settings = EditorSettings::get_global(cx).minimap;
18411 self.minimap =
18412 self.create_minimap(minimap_settings.with_show_override(), window, cx);
18413 }
18414 self.minimap_visibility = minimap_visibility;
18415 cx.notify();
18416 }
18417 }
18418
18419 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18420 self.set_show_scrollbars(false, cx);
18421 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
18422 }
18423
18424 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18425 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
18426 }
18427
18428 /// Normally the text in full mode and auto height editors is padded on the
18429 /// left side by roughly half a character width for improved hit testing.
18430 ///
18431 /// Use this method to disable this for cases where this is not wanted (e.g.
18432 /// if you want to align the editor text with some other text above or below)
18433 /// or if you want to add this padding to single-line editors.
18434 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
18435 self.offset_content = offset_content;
18436 cx.notify();
18437 }
18438
18439 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
18440 self.show_line_numbers = Some(show_line_numbers);
18441 cx.notify();
18442 }
18443
18444 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
18445 self.disable_expand_excerpt_buttons = true;
18446 cx.notify();
18447 }
18448
18449 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
18450 self.show_git_diff_gutter = Some(show_git_diff_gutter);
18451 cx.notify();
18452 }
18453
18454 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
18455 self.show_code_actions = Some(show_code_actions);
18456 cx.notify();
18457 }
18458
18459 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
18460 self.show_runnables = Some(show_runnables);
18461 cx.notify();
18462 }
18463
18464 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
18465 self.show_breakpoints = Some(show_breakpoints);
18466 cx.notify();
18467 }
18468
18469 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
18470 if self.display_map.read(cx).masked != masked {
18471 self.display_map.update(cx, |map, _| map.masked = masked);
18472 }
18473 cx.notify()
18474 }
18475
18476 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
18477 self.show_wrap_guides = Some(show_wrap_guides);
18478 cx.notify();
18479 }
18480
18481 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
18482 self.show_indent_guides = Some(show_indent_guides);
18483 cx.notify();
18484 }
18485
18486 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
18487 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
18488 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
18489 if let Some(dir) = file.abs_path(cx).parent() {
18490 return Some(dir.to_owned());
18491 }
18492 }
18493
18494 if let Some(project_path) = buffer.read(cx).project_path(cx) {
18495 return Some(project_path.path.to_path_buf());
18496 }
18497 }
18498
18499 None
18500 }
18501
18502 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
18503 self.active_excerpt(cx)?
18504 .1
18505 .read(cx)
18506 .file()
18507 .and_then(|f| f.as_local())
18508 }
18509
18510 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18511 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18512 let buffer = buffer.read(cx);
18513 if let Some(project_path) = buffer.project_path(cx) {
18514 let project = self.project.as_ref()?.read(cx);
18515 project.absolute_path(&project_path, cx)
18516 } else {
18517 buffer
18518 .file()
18519 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
18520 }
18521 })
18522 }
18523
18524 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18525 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18526 let project_path = buffer.read(cx).project_path(cx)?;
18527 let project = self.project.as_ref()?.read(cx);
18528 let entry = project.entry_for_path(&project_path, cx)?;
18529 let path = entry.path.to_path_buf();
18530 Some(path)
18531 })
18532 }
18533
18534 pub fn reveal_in_finder(
18535 &mut self,
18536 _: &RevealInFileManager,
18537 _window: &mut Window,
18538 cx: &mut Context<Self>,
18539 ) {
18540 if let Some(target) = self.target_file(cx) {
18541 cx.reveal_path(&target.abs_path(cx));
18542 }
18543 }
18544
18545 pub fn copy_path(
18546 &mut self,
18547 _: &zed_actions::workspace::CopyPath,
18548 _window: &mut Window,
18549 cx: &mut Context<Self>,
18550 ) {
18551 if let Some(path) = self.target_file_abs_path(cx) {
18552 if let Some(path) = path.to_str() {
18553 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18554 }
18555 }
18556 }
18557
18558 pub fn copy_relative_path(
18559 &mut self,
18560 _: &zed_actions::workspace::CopyRelativePath,
18561 _window: &mut Window,
18562 cx: &mut Context<Self>,
18563 ) {
18564 if let Some(path) = self.target_file_path(cx) {
18565 if let Some(path) = path.to_str() {
18566 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18567 }
18568 }
18569 }
18570
18571 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
18572 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
18573 buffer.read(cx).project_path(cx)
18574 } else {
18575 None
18576 }
18577 }
18578
18579 // Returns true if the editor handled a go-to-line request
18580 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
18581 maybe!({
18582 let breakpoint_store = self.breakpoint_store.as_ref()?;
18583
18584 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
18585 else {
18586 self.clear_row_highlights::<ActiveDebugLine>();
18587 return None;
18588 };
18589
18590 let position = active_stack_frame.position;
18591 let buffer_id = position.buffer_id?;
18592 let snapshot = self
18593 .project
18594 .as_ref()?
18595 .read(cx)
18596 .buffer_for_id(buffer_id, cx)?
18597 .read(cx)
18598 .snapshot();
18599
18600 let mut handled = false;
18601 for (id, ExcerptRange { context, .. }) in
18602 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
18603 {
18604 if context.start.cmp(&position, &snapshot).is_ge()
18605 || context.end.cmp(&position, &snapshot).is_lt()
18606 {
18607 continue;
18608 }
18609 let snapshot = self.buffer.read(cx).snapshot(cx);
18610 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
18611
18612 handled = true;
18613 self.clear_row_highlights::<ActiveDebugLine>();
18614
18615 self.go_to_line::<ActiveDebugLine>(
18616 multibuffer_anchor,
18617 Some(cx.theme().colors().editor_debugger_active_line_background),
18618 window,
18619 cx,
18620 );
18621
18622 cx.notify();
18623 }
18624
18625 handled.then_some(())
18626 })
18627 .is_some()
18628 }
18629
18630 pub fn copy_file_name_without_extension(
18631 &mut self,
18632 _: &CopyFileNameWithoutExtension,
18633 _: &mut Window,
18634 cx: &mut Context<Self>,
18635 ) {
18636 if let Some(file) = self.target_file(cx) {
18637 if let Some(file_stem) = file.path().file_stem() {
18638 if let Some(name) = file_stem.to_str() {
18639 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18640 }
18641 }
18642 }
18643 }
18644
18645 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
18646 if let Some(file) = self.target_file(cx) {
18647 if let Some(file_name) = file.path().file_name() {
18648 if let Some(name) = file_name.to_str() {
18649 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18650 }
18651 }
18652 }
18653 }
18654
18655 pub fn toggle_git_blame(
18656 &mut self,
18657 _: &::git::Blame,
18658 window: &mut Window,
18659 cx: &mut Context<Self>,
18660 ) {
18661 self.show_git_blame_gutter = !self.show_git_blame_gutter;
18662
18663 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
18664 self.start_git_blame(true, window, cx);
18665 }
18666
18667 cx.notify();
18668 }
18669
18670 pub fn toggle_git_blame_inline(
18671 &mut self,
18672 _: &ToggleGitBlameInline,
18673 window: &mut Window,
18674 cx: &mut Context<Self>,
18675 ) {
18676 self.toggle_git_blame_inline_internal(true, window, cx);
18677 cx.notify();
18678 }
18679
18680 pub fn open_git_blame_commit(
18681 &mut self,
18682 _: &OpenGitBlameCommit,
18683 window: &mut Window,
18684 cx: &mut Context<Self>,
18685 ) {
18686 self.open_git_blame_commit_internal(window, cx);
18687 }
18688
18689 fn open_git_blame_commit_internal(
18690 &mut self,
18691 window: &mut Window,
18692 cx: &mut Context<Self>,
18693 ) -> Option<()> {
18694 let blame = self.blame.as_ref()?;
18695 let snapshot = self.snapshot(window, cx);
18696 let cursor = self.selections.newest::<Point>(cx).head();
18697 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
18698 let blame_entry = blame
18699 .update(cx, |blame, cx| {
18700 blame
18701 .blame_for_rows(
18702 &[RowInfo {
18703 buffer_id: Some(buffer.remote_id()),
18704 buffer_row: Some(point.row),
18705 ..Default::default()
18706 }],
18707 cx,
18708 )
18709 .next()
18710 })
18711 .flatten()?;
18712 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
18713 let repo = blame.read(cx).repository(cx)?;
18714 let workspace = self.workspace()?.downgrade();
18715 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
18716 None
18717 }
18718
18719 pub fn git_blame_inline_enabled(&self) -> bool {
18720 self.git_blame_inline_enabled
18721 }
18722
18723 pub fn toggle_selection_menu(
18724 &mut self,
18725 _: &ToggleSelectionMenu,
18726 _: &mut Window,
18727 cx: &mut Context<Self>,
18728 ) {
18729 self.show_selection_menu = self
18730 .show_selection_menu
18731 .map(|show_selections_menu| !show_selections_menu)
18732 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
18733
18734 cx.notify();
18735 }
18736
18737 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
18738 self.show_selection_menu
18739 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
18740 }
18741
18742 fn start_git_blame(
18743 &mut self,
18744 user_triggered: bool,
18745 window: &mut Window,
18746 cx: &mut Context<Self>,
18747 ) {
18748 if let Some(project) = self.project.as_ref() {
18749 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
18750 return;
18751 };
18752
18753 if buffer.read(cx).file().is_none() {
18754 return;
18755 }
18756
18757 let focused = self.focus_handle(cx).contains_focused(window, cx);
18758
18759 let project = project.clone();
18760 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
18761 self.blame_subscription =
18762 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
18763 self.blame = Some(blame);
18764 }
18765 }
18766
18767 fn toggle_git_blame_inline_internal(
18768 &mut self,
18769 user_triggered: bool,
18770 window: &mut Window,
18771 cx: &mut Context<Self>,
18772 ) {
18773 if self.git_blame_inline_enabled {
18774 self.git_blame_inline_enabled = false;
18775 self.show_git_blame_inline = false;
18776 self.show_git_blame_inline_delay_task.take();
18777 } else {
18778 self.git_blame_inline_enabled = true;
18779 self.start_git_blame_inline(user_triggered, window, cx);
18780 }
18781
18782 cx.notify();
18783 }
18784
18785 fn start_git_blame_inline(
18786 &mut self,
18787 user_triggered: bool,
18788 window: &mut Window,
18789 cx: &mut Context<Self>,
18790 ) {
18791 self.start_git_blame(user_triggered, window, cx);
18792
18793 if ProjectSettings::get_global(cx)
18794 .git
18795 .inline_blame_delay()
18796 .is_some()
18797 {
18798 self.start_inline_blame_timer(window, cx);
18799 } else {
18800 self.show_git_blame_inline = true
18801 }
18802 }
18803
18804 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
18805 self.blame.as_ref()
18806 }
18807
18808 pub fn show_git_blame_gutter(&self) -> bool {
18809 self.show_git_blame_gutter
18810 }
18811
18812 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
18813 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
18814 }
18815
18816 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
18817 self.show_git_blame_inline
18818 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
18819 && !self.newest_selection_head_on_empty_line(cx)
18820 && self.has_blame_entries(cx)
18821 }
18822
18823 fn has_blame_entries(&self, cx: &App) -> bool {
18824 self.blame()
18825 .map_or(false, |blame| blame.read(cx).has_generated_entries())
18826 }
18827
18828 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
18829 let cursor_anchor = self.selections.newest_anchor().head();
18830
18831 let snapshot = self.buffer.read(cx).snapshot(cx);
18832 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
18833
18834 snapshot.line_len(buffer_row) == 0
18835 }
18836
18837 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
18838 let buffer_and_selection = maybe!({
18839 let selection = self.selections.newest::<Point>(cx);
18840 let selection_range = selection.range();
18841
18842 let multi_buffer = self.buffer().read(cx);
18843 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18844 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
18845
18846 let (buffer, range, _) = if selection.reversed {
18847 buffer_ranges.first()
18848 } else {
18849 buffer_ranges.last()
18850 }?;
18851
18852 let selection = text::ToPoint::to_point(&range.start, &buffer).row
18853 ..text::ToPoint::to_point(&range.end, &buffer).row;
18854 Some((
18855 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
18856 selection,
18857 ))
18858 });
18859
18860 let Some((buffer, selection)) = buffer_and_selection else {
18861 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
18862 };
18863
18864 let Some(project) = self.project.as_ref() else {
18865 return Task::ready(Err(anyhow!("editor does not have project")));
18866 };
18867
18868 project.update(cx, |project, cx| {
18869 project.get_permalink_to_line(&buffer, selection, cx)
18870 })
18871 }
18872
18873 pub fn copy_permalink_to_line(
18874 &mut self,
18875 _: &CopyPermalinkToLine,
18876 window: &mut Window,
18877 cx: &mut Context<Self>,
18878 ) {
18879 let permalink_task = self.get_permalink_to_line(cx);
18880 let workspace = self.workspace();
18881
18882 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18883 Ok(permalink) => {
18884 cx.update(|_, cx| {
18885 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
18886 })
18887 .ok();
18888 }
18889 Err(err) => {
18890 let message = format!("Failed to copy permalink: {err}");
18891
18892 anyhow::Result::<()>::Err(err).log_err();
18893
18894 if let Some(workspace) = workspace {
18895 workspace
18896 .update_in(cx, |workspace, _, cx| {
18897 struct CopyPermalinkToLine;
18898
18899 workspace.show_toast(
18900 Toast::new(
18901 NotificationId::unique::<CopyPermalinkToLine>(),
18902 message,
18903 ),
18904 cx,
18905 )
18906 })
18907 .ok();
18908 }
18909 }
18910 })
18911 .detach();
18912 }
18913
18914 pub fn copy_file_location(
18915 &mut self,
18916 _: &CopyFileLocation,
18917 _: &mut Window,
18918 cx: &mut Context<Self>,
18919 ) {
18920 let selection = self.selections.newest::<Point>(cx).start.row + 1;
18921 if let Some(file) = self.target_file(cx) {
18922 if let Some(path) = file.path().to_str() {
18923 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
18924 }
18925 }
18926 }
18927
18928 pub fn open_permalink_to_line(
18929 &mut self,
18930 _: &OpenPermalinkToLine,
18931 window: &mut Window,
18932 cx: &mut Context<Self>,
18933 ) {
18934 let permalink_task = self.get_permalink_to_line(cx);
18935 let workspace = self.workspace();
18936
18937 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18938 Ok(permalink) => {
18939 cx.update(|_, cx| {
18940 cx.open_url(permalink.as_ref());
18941 })
18942 .ok();
18943 }
18944 Err(err) => {
18945 let message = format!("Failed to open permalink: {err}");
18946
18947 anyhow::Result::<()>::Err(err).log_err();
18948
18949 if let Some(workspace) = workspace {
18950 workspace
18951 .update(cx, |workspace, cx| {
18952 struct OpenPermalinkToLine;
18953
18954 workspace.show_toast(
18955 Toast::new(
18956 NotificationId::unique::<OpenPermalinkToLine>(),
18957 message,
18958 ),
18959 cx,
18960 )
18961 })
18962 .ok();
18963 }
18964 }
18965 })
18966 .detach();
18967 }
18968
18969 pub fn insert_uuid_v4(
18970 &mut self,
18971 _: &InsertUuidV4,
18972 window: &mut Window,
18973 cx: &mut Context<Self>,
18974 ) {
18975 self.insert_uuid(UuidVersion::V4, window, cx);
18976 }
18977
18978 pub fn insert_uuid_v7(
18979 &mut self,
18980 _: &InsertUuidV7,
18981 window: &mut Window,
18982 cx: &mut Context<Self>,
18983 ) {
18984 self.insert_uuid(UuidVersion::V7, window, cx);
18985 }
18986
18987 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
18988 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18989 self.transact(window, cx, |this, window, cx| {
18990 let edits = this
18991 .selections
18992 .all::<Point>(cx)
18993 .into_iter()
18994 .map(|selection| {
18995 let uuid = match version {
18996 UuidVersion::V4 => uuid::Uuid::new_v4(),
18997 UuidVersion::V7 => uuid::Uuid::now_v7(),
18998 };
18999
19000 (selection.range(), uuid.to_string())
19001 });
19002 this.edit(edits, cx);
19003 this.refresh_inline_completion(true, false, window, cx);
19004 });
19005 }
19006
19007 pub fn open_selections_in_multibuffer(
19008 &mut self,
19009 _: &OpenSelectionsInMultibuffer,
19010 window: &mut Window,
19011 cx: &mut Context<Self>,
19012 ) {
19013 let multibuffer = self.buffer.read(cx);
19014
19015 let Some(buffer) = multibuffer.as_singleton() else {
19016 return;
19017 };
19018
19019 let Some(workspace) = self.workspace() else {
19020 return;
19021 };
19022
19023 let title = multibuffer.title(cx).to_string();
19024
19025 let locations = self
19026 .selections
19027 .all_anchors(cx)
19028 .into_iter()
19029 .map(|selection| Location {
19030 buffer: buffer.clone(),
19031 range: selection.start.text_anchor..selection.end.text_anchor,
19032 })
19033 .collect::<Vec<_>>();
19034
19035 cx.spawn_in(window, async move |_, cx| {
19036 workspace.update_in(cx, |workspace, window, cx| {
19037 Self::open_locations_in_multibuffer(
19038 workspace,
19039 locations,
19040 format!("Selections for '{title}'"),
19041 false,
19042 MultibufferSelectionMode::All,
19043 window,
19044 cx,
19045 );
19046 })
19047 })
19048 .detach();
19049 }
19050
19051 /// Adds a row highlight for the given range. If a row has multiple highlights, the
19052 /// last highlight added will be used.
19053 ///
19054 /// If the range ends at the beginning of a line, then that line will not be highlighted.
19055 pub fn highlight_rows<T: 'static>(
19056 &mut self,
19057 range: Range<Anchor>,
19058 color: Hsla,
19059 options: RowHighlightOptions,
19060 cx: &mut Context<Self>,
19061 ) {
19062 let snapshot = self.buffer().read(cx).snapshot(cx);
19063 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19064 let ix = row_highlights.binary_search_by(|highlight| {
19065 Ordering::Equal
19066 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
19067 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
19068 });
19069
19070 if let Err(mut ix) = ix {
19071 let index = post_inc(&mut self.highlight_order);
19072
19073 // If this range intersects with the preceding highlight, then merge it with
19074 // the preceding highlight. Otherwise insert a new highlight.
19075 let mut merged = false;
19076 if ix > 0 {
19077 let prev_highlight = &mut row_highlights[ix - 1];
19078 if prev_highlight
19079 .range
19080 .end
19081 .cmp(&range.start, &snapshot)
19082 .is_ge()
19083 {
19084 ix -= 1;
19085 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
19086 prev_highlight.range.end = range.end;
19087 }
19088 merged = true;
19089 prev_highlight.index = index;
19090 prev_highlight.color = color;
19091 prev_highlight.options = options;
19092 }
19093 }
19094
19095 if !merged {
19096 row_highlights.insert(
19097 ix,
19098 RowHighlight {
19099 range: range.clone(),
19100 index,
19101 color,
19102 options,
19103 type_id: TypeId::of::<T>(),
19104 },
19105 );
19106 }
19107
19108 // If any of the following highlights intersect with this one, merge them.
19109 while let Some(next_highlight) = row_highlights.get(ix + 1) {
19110 let highlight = &row_highlights[ix];
19111 if next_highlight
19112 .range
19113 .start
19114 .cmp(&highlight.range.end, &snapshot)
19115 .is_le()
19116 {
19117 if next_highlight
19118 .range
19119 .end
19120 .cmp(&highlight.range.end, &snapshot)
19121 .is_gt()
19122 {
19123 row_highlights[ix].range.end = next_highlight.range.end;
19124 }
19125 row_highlights.remove(ix + 1);
19126 } else {
19127 break;
19128 }
19129 }
19130 }
19131 }
19132
19133 /// Remove any highlighted row ranges of the given type that intersect the
19134 /// given ranges.
19135 pub fn remove_highlighted_rows<T: 'static>(
19136 &mut self,
19137 ranges_to_remove: Vec<Range<Anchor>>,
19138 cx: &mut Context<Self>,
19139 ) {
19140 let snapshot = self.buffer().read(cx).snapshot(cx);
19141 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19142 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19143 row_highlights.retain(|highlight| {
19144 while let Some(range_to_remove) = ranges_to_remove.peek() {
19145 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
19146 Ordering::Less | Ordering::Equal => {
19147 ranges_to_remove.next();
19148 }
19149 Ordering::Greater => {
19150 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
19151 Ordering::Less | Ordering::Equal => {
19152 return false;
19153 }
19154 Ordering::Greater => break,
19155 }
19156 }
19157 }
19158 }
19159
19160 true
19161 })
19162 }
19163
19164 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
19165 pub fn clear_row_highlights<T: 'static>(&mut self) {
19166 self.highlighted_rows.remove(&TypeId::of::<T>());
19167 }
19168
19169 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
19170 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
19171 self.highlighted_rows
19172 .get(&TypeId::of::<T>())
19173 .map_or(&[] as &[_], |vec| vec.as_slice())
19174 .iter()
19175 .map(|highlight| (highlight.range.clone(), highlight.color))
19176 }
19177
19178 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
19179 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
19180 /// Allows to ignore certain kinds of highlights.
19181 pub fn highlighted_display_rows(
19182 &self,
19183 window: &mut Window,
19184 cx: &mut App,
19185 ) -> BTreeMap<DisplayRow, LineHighlight> {
19186 let snapshot = self.snapshot(window, cx);
19187 let mut used_highlight_orders = HashMap::default();
19188 self.highlighted_rows
19189 .iter()
19190 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
19191 .fold(
19192 BTreeMap::<DisplayRow, LineHighlight>::new(),
19193 |mut unique_rows, highlight| {
19194 let start = highlight.range.start.to_display_point(&snapshot);
19195 let end = highlight.range.end.to_display_point(&snapshot);
19196 let start_row = start.row().0;
19197 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
19198 && end.column() == 0
19199 {
19200 end.row().0.saturating_sub(1)
19201 } else {
19202 end.row().0
19203 };
19204 for row in start_row..=end_row {
19205 let used_index =
19206 used_highlight_orders.entry(row).or_insert(highlight.index);
19207 if highlight.index >= *used_index {
19208 *used_index = highlight.index;
19209 unique_rows.insert(
19210 DisplayRow(row),
19211 LineHighlight {
19212 include_gutter: highlight.options.include_gutter,
19213 border: None,
19214 background: highlight.color.into(),
19215 type_id: Some(highlight.type_id),
19216 },
19217 );
19218 }
19219 }
19220 unique_rows
19221 },
19222 )
19223 }
19224
19225 pub fn highlighted_display_row_for_autoscroll(
19226 &self,
19227 snapshot: &DisplaySnapshot,
19228 ) -> Option<DisplayRow> {
19229 self.highlighted_rows
19230 .values()
19231 .flat_map(|highlighted_rows| highlighted_rows.iter())
19232 .filter_map(|highlight| {
19233 if highlight.options.autoscroll {
19234 Some(highlight.range.start.to_display_point(snapshot).row())
19235 } else {
19236 None
19237 }
19238 })
19239 .min()
19240 }
19241
19242 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
19243 self.highlight_background::<SearchWithinRange>(
19244 ranges,
19245 |colors| colors.colors().editor_document_highlight_read_background,
19246 cx,
19247 )
19248 }
19249
19250 pub fn set_breadcrumb_header(&mut self, new_header: String) {
19251 self.breadcrumb_header = Some(new_header);
19252 }
19253
19254 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
19255 self.clear_background_highlights::<SearchWithinRange>(cx);
19256 }
19257
19258 pub fn highlight_background<T: 'static>(
19259 &mut self,
19260 ranges: &[Range<Anchor>],
19261 color_fetcher: fn(&Theme) -> Hsla,
19262 cx: &mut Context<Self>,
19263 ) {
19264 self.background_highlights.insert(
19265 HighlightKey::Type(TypeId::of::<T>()),
19266 (color_fetcher, Arc::from(ranges)),
19267 );
19268 self.scrollbar_marker_state.dirty = true;
19269 cx.notify();
19270 }
19271
19272 pub fn highlight_background_key<T: 'static>(
19273 &mut self,
19274 key: usize,
19275 ranges: &[Range<Anchor>],
19276 color_fetcher: fn(&Theme) -> Hsla,
19277 cx: &mut Context<Self>,
19278 ) {
19279 self.background_highlights.insert(
19280 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19281 (color_fetcher, Arc::from(ranges)),
19282 );
19283 self.scrollbar_marker_state.dirty = true;
19284 cx.notify();
19285 }
19286
19287 pub fn clear_background_highlights<T: 'static>(
19288 &mut self,
19289 cx: &mut Context<Self>,
19290 ) -> Option<BackgroundHighlight> {
19291 let text_highlights = self
19292 .background_highlights
19293 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
19294 if !text_highlights.1.is_empty() {
19295 self.scrollbar_marker_state.dirty = true;
19296 cx.notify();
19297 }
19298 Some(text_highlights)
19299 }
19300
19301 pub fn highlight_gutter<T: 'static>(
19302 &mut self,
19303 ranges: impl Into<Vec<Range<Anchor>>>,
19304 color_fetcher: fn(&App) -> Hsla,
19305 cx: &mut Context<Self>,
19306 ) {
19307 self.gutter_highlights
19308 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
19309 cx.notify();
19310 }
19311
19312 pub fn clear_gutter_highlights<T: 'static>(
19313 &mut self,
19314 cx: &mut Context<Self>,
19315 ) -> Option<GutterHighlight> {
19316 cx.notify();
19317 self.gutter_highlights.remove(&TypeId::of::<T>())
19318 }
19319
19320 pub fn insert_gutter_highlight<T: 'static>(
19321 &mut self,
19322 range: Range<Anchor>,
19323 color_fetcher: fn(&App) -> Hsla,
19324 cx: &mut Context<Self>,
19325 ) {
19326 let snapshot = self.buffer().read(cx).snapshot(cx);
19327 let mut highlights = self
19328 .gutter_highlights
19329 .remove(&TypeId::of::<T>())
19330 .map(|(_, highlights)| highlights)
19331 .unwrap_or_default();
19332 let ix = highlights.binary_search_by(|highlight| {
19333 Ordering::Equal
19334 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
19335 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
19336 });
19337 if let Err(ix) = ix {
19338 highlights.insert(ix, range);
19339 }
19340 self.gutter_highlights
19341 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
19342 }
19343
19344 pub fn remove_gutter_highlights<T: 'static>(
19345 &mut self,
19346 ranges_to_remove: Vec<Range<Anchor>>,
19347 cx: &mut Context<Self>,
19348 ) {
19349 let snapshot = self.buffer().read(cx).snapshot(cx);
19350 let Some((color_fetcher, mut gutter_highlights)) =
19351 self.gutter_highlights.remove(&TypeId::of::<T>())
19352 else {
19353 return;
19354 };
19355 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19356 gutter_highlights.retain(|highlight| {
19357 while let Some(range_to_remove) = ranges_to_remove.peek() {
19358 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
19359 Ordering::Less | Ordering::Equal => {
19360 ranges_to_remove.next();
19361 }
19362 Ordering::Greater => {
19363 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
19364 Ordering::Less | Ordering::Equal => {
19365 return false;
19366 }
19367 Ordering::Greater => break,
19368 }
19369 }
19370 }
19371 }
19372
19373 true
19374 });
19375 self.gutter_highlights
19376 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
19377 }
19378
19379 #[cfg(feature = "test-support")]
19380 pub fn all_text_highlights(
19381 &self,
19382 window: &mut Window,
19383 cx: &mut Context<Self>,
19384 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
19385 let snapshot = self.snapshot(window, cx);
19386 self.display_map.update(cx, |display_map, _| {
19387 display_map
19388 .all_text_highlights()
19389 .map(|highlight| {
19390 let (style, ranges) = highlight.as_ref();
19391 (
19392 *style,
19393 ranges
19394 .iter()
19395 .map(|range| range.clone().to_display_points(&snapshot))
19396 .collect(),
19397 )
19398 })
19399 .collect()
19400 })
19401 }
19402
19403 #[cfg(feature = "test-support")]
19404 pub fn all_text_background_highlights(
19405 &self,
19406 window: &mut Window,
19407 cx: &mut Context<Self>,
19408 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19409 let snapshot = self.snapshot(window, cx);
19410 let buffer = &snapshot.buffer_snapshot;
19411 let start = buffer.anchor_before(0);
19412 let end = buffer.anchor_after(buffer.len());
19413 self.background_highlights_in_range(start..end, &snapshot, cx.theme())
19414 }
19415
19416 #[cfg(feature = "test-support")]
19417 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
19418 let snapshot = self.buffer().read(cx).snapshot(cx);
19419
19420 let highlights = self
19421 .background_highlights
19422 .get(&HighlightKey::Type(TypeId::of::<
19423 items::BufferSearchHighlights,
19424 >()));
19425
19426 if let Some((_color, ranges)) = highlights {
19427 ranges
19428 .iter()
19429 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
19430 .collect_vec()
19431 } else {
19432 vec![]
19433 }
19434 }
19435
19436 fn document_highlights_for_position<'a>(
19437 &'a self,
19438 position: Anchor,
19439 buffer: &'a MultiBufferSnapshot,
19440 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
19441 let read_highlights = self
19442 .background_highlights
19443 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
19444 .map(|h| &h.1);
19445 let write_highlights = self
19446 .background_highlights
19447 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
19448 .map(|h| &h.1);
19449 let left_position = position.bias_left(buffer);
19450 let right_position = position.bias_right(buffer);
19451 read_highlights
19452 .into_iter()
19453 .chain(write_highlights)
19454 .flat_map(move |ranges| {
19455 let start_ix = match ranges.binary_search_by(|probe| {
19456 let cmp = probe.end.cmp(&left_position, buffer);
19457 if cmp.is_ge() {
19458 Ordering::Greater
19459 } else {
19460 Ordering::Less
19461 }
19462 }) {
19463 Ok(i) | Err(i) => i,
19464 };
19465
19466 ranges[start_ix..]
19467 .iter()
19468 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
19469 })
19470 }
19471
19472 pub fn has_background_highlights<T: 'static>(&self) -> bool {
19473 self.background_highlights
19474 .get(&HighlightKey::Type(TypeId::of::<T>()))
19475 .map_or(false, |(_, highlights)| !highlights.is_empty())
19476 }
19477
19478 pub fn background_highlights_in_range(
19479 &self,
19480 search_range: Range<Anchor>,
19481 display_snapshot: &DisplaySnapshot,
19482 theme: &Theme,
19483 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19484 let mut results = Vec::new();
19485 for (color_fetcher, ranges) in self.background_highlights.values() {
19486 let color = color_fetcher(theme);
19487 let start_ix = match ranges.binary_search_by(|probe| {
19488 let cmp = probe
19489 .end
19490 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19491 if cmp.is_gt() {
19492 Ordering::Greater
19493 } else {
19494 Ordering::Less
19495 }
19496 }) {
19497 Ok(i) | Err(i) => i,
19498 };
19499 for range in &ranges[start_ix..] {
19500 if range
19501 .start
19502 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19503 .is_ge()
19504 {
19505 break;
19506 }
19507
19508 let start = range.start.to_display_point(display_snapshot);
19509 let end = range.end.to_display_point(display_snapshot);
19510 results.push((start..end, color))
19511 }
19512 }
19513 results
19514 }
19515
19516 pub fn background_highlight_row_ranges<T: 'static>(
19517 &self,
19518 search_range: Range<Anchor>,
19519 display_snapshot: &DisplaySnapshot,
19520 count: usize,
19521 ) -> Vec<RangeInclusive<DisplayPoint>> {
19522 let mut results = Vec::new();
19523 let Some((_, ranges)) = self
19524 .background_highlights
19525 .get(&HighlightKey::Type(TypeId::of::<T>()))
19526 else {
19527 return vec![];
19528 };
19529
19530 let start_ix = match ranges.binary_search_by(|probe| {
19531 let cmp = probe
19532 .end
19533 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19534 if cmp.is_gt() {
19535 Ordering::Greater
19536 } else {
19537 Ordering::Less
19538 }
19539 }) {
19540 Ok(i) | Err(i) => i,
19541 };
19542 let mut push_region = |start: Option<Point>, end: Option<Point>| {
19543 if let (Some(start_display), Some(end_display)) = (start, end) {
19544 results.push(
19545 start_display.to_display_point(display_snapshot)
19546 ..=end_display.to_display_point(display_snapshot),
19547 );
19548 }
19549 };
19550 let mut start_row: Option<Point> = None;
19551 let mut end_row: Option<Point> = None;
19552 if ranges.len() > count {
19553 return Vec::new();
19554 }
19555 for range in &ranges[start_ix..] {
19556 if range
19557 .start
19558 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19559 .is_ge()
19560 {
19561 break;
19562 }
19563 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
19564 if let Some(current_row) = &end_row {
19565 if end.row == current_row.row {
19566 continue;
19567 }
19568 }
19569 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
19570 if start_row.is_none() {
19571 assert_eq!(end_row, None);
19572 start_row = Some(start);
19573 end_row = Some(end);
19574 continue;
19575 }
19576 if let Some(current_end) = end_row.as_mut() {
19577 if start.row > current_end.row + 1 {
19578 push_region(start_row, end_row);
19579 start_row = Some(start);
19580 end_row = Some(end);
19581 } else {
19582 // Merge two hunks.
19583 *current_end = end;
19584 }
19585 } else {
19586 unreachable!();
19587 }
19588 }
19589 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
19590 push_region(start_row, end_row);
19591 results
19592 }
19593
19594 pub fn gutter_highlights_in_range(
19595 &self,
19596 search_range: Range<Anchor>,
19597 display_snapshot: &DisplaySnapshot,
19598 cx: &App,
19599 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19600 let mut results = Vec::new();
19601 for (color_fetcher, ranges) in self.gutter_highlights.values() {
19602 let color = color_fetcher(cx);
19603 let start_ix = match ranges.binary_search_by(|probe| {
19604 let cmp = probe
19605 .end
19606 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19607 if cmp.is_gt() {
19608 Ordering::Greater
19609 } else {
19610 Ordering::Less
19611 }
19612 }) {
19613 Ok(i) | Err(i) => i,
19614 };
19615 for range in &ranges[start_ix..] {
19616 if range
19617 .start
19618 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19619 .is_ge()
19620 {
19621 break;
19622 }
19623
19624 let start = range.start.to_display_point(display_snapshot);
19625 let end = range.end.to_display_point(display_snapshot);
19626 results.push((start..end, color))
19627 }
19628 }
19629 results
19630 }
19631
19632 /// Get the text ranges corresponding to the redaction query
19633 pub fn redacted_ranges(
19634 &self,
19635 search_range: Range<Anchor>,
19636 display_snapshot: &DisplaySnapshot,
19637 cx: &App,
19638 ) -> Vec<Range<DisplayPoint>> {
19639 display_snapshot
19640 .buffer_snapshot
19641 .redacted_ranges(search_range, |file| {
19642 if let Some(file) = file {
19643 file.is_private()
19644 && EditorSettings::get(
19645 Some(SettingsLocation {
19646 worktree_id: file.worktree_id(cx),
19647 path: file.path().as_ref(),
19648 }),
19649 cx,
19650 )
19651 .redact_private_values
19652 } else {
19653 false
19654 }
19655 })
19656 .map(|range| {
19657 range.start.to_display_point(display_snapshot)
19658 ..range.end.to_display_point(display_snapshot)
19659 })
19660 .collect()
19661 }
19662
19663 pub fn highlight_text_key<T: 'static>(
19664 &mut self,
19665 key: usize,
19666 ranges: Vec<Range<Anchor>>,
19667 style: HighlightStyle,
19668 cx: &mut Context<Self>,
19669 ) {
19670 self.display_map.update(cx, |map, _| {
19671 map.highlight_text(
19672 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19673 ranges,
19674 style,
19675 );
19676 });
19677 cx.notify();
19678 }
19679
19680 pub fn highlight_text<T: 'static>(
19681 &mut self,
19682 ranges: Vec<Range<Anchor>>,
19683 style: HighlightStyle,
19684 cx: &mut Context<Self>,
19685 ) {
19686 self.display_map.update(cx, |map, _| {
19687 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
19688 });
19689 cx.notify();
19690 }
19691
19692 pub(crate) fn highlight_inlays<T: 'static>(
19693 &mut self,
19694 highlights: Vec<InlayHighlight>,
19695 style: HighlightStyle,
19696 cx: &mut Context<Self>,
19697 ) {
19698 self.display_map.update(cx, |map, _| {
19699 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
19700 });
19701 cx.notify();
19702 }
19703
19704 pub fn text_highlights<'a, T: 'static>(
19705 &'a self,
19706 cx: &'a App,
19707 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
19708 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
19709 }
19710
19711 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
19712 let cleared = self
19713 .display_map
19714 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
19715 if cleared {
19716 cx.notify();
19717 }
19718 }
19719
19720 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
19721 (self.read_only(cx) || self.blink_manager.read(cx).visible())
19722 && self.focus_handle.is_focused(window)
19723 }
19724
19725 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
19726 self.show_cursor_when_unfocused = is_enabled;
19727 cx.notify();
19728 }
19729
19730 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
19731 cx.notify();
19732 }
19733
19734 fn on_debug_session_event(
19735 &mut self,
19736 _session: Entity<Session>,
19737 event: &SessionEvent,
19738 cx: &mut Context<Self>,
19739 ) {
19740 match event {
19741 SessionEvent::InvalidateInlineValue => {
19742 self.refresh_inline_values(cx);
19743 }
19744 _ => {}
19745 }
19746 }
19747
19748 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
19749 let Some(project) = self.project.clone() else {
19750 return;
19751 };
19752
19753 if !self.inline_value_cache.enabled {
19754 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
19755 self.splice_inlays(&inlays, Vec::new(), cx);
19756 return;
19757 }
19758
19759 let current_execution_position = self
19760 .highlighted_rows
19761 .get(&TypeId::of::<ActiveDebugLine>())
19762 .and_then(|lines| lines.last().map(|line| line.range.end));
19763
19764 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
19765 let inline_values = editor
19766 .update(cx, |editor, cx| {
19767 let Some(current_execution_position) = current_execution_position else {
19768 return Some(Task::ready(Ok(Vec::new())));
19769 };
19770
19771 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
19772 let snapshot = buffer.snapshot(cx);
19773
19774 let excerpt = snapshot.excerpt_containing(
19775 current_execution_position..current_execution_position,
19776 )?;
19777
19778 editor.buffer.read(cx).buffer(excerpt.buffer_id())
19779 })?;
19780
19781 let range =
19782 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
19783
19784 project.inline_values(buffer, range, cx)
19785 })
19786 .ok()
19787 .flatten()?
19788 .await
19789 .context("refreshing debugger inlays")
19790 .log_err()?;
19791
19792 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
19793
19794 for (buffer_id, inline_value) in inline_values
19795 .into_iter()
19796 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
19797 {
19798 buffer_inline_values
19799 .entry(buffer_id)
19800 .or_default()
19801 .push(inline_value);
19802 }
19803
19804 editor
19805 .update(cx, |editor, cx| {
19806 let snapshot = editor.buffer.read(cx).snapshot(cx);
19807 let mut new_inlays = Vec::default();
19808
19809 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
19810 let buffer_id = buffer_snapshot.remote_id();
19811 buffer_inline_values
19812 .get(&buffer_id)
19813 .into_iter()
19814 .flatten()
19815 .for_each(|hint| {
19816 let inlay = Inlay::debugger(
19817 post_inc(&mut editor.next_inlay_id),
19818 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
19819 hint.text(),
19820 );
19821 if !inlay.text.chars().contains(&'\n') {
19822 new_inlays.push(inlay);
19823 }
19824 });
19825 }
19826
19827 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
19828 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
19829
19830 editor.splice_inlays(&inlay_ids, new_inlays, cx);
19831 })
19832 .ok()?;
19833 Some(())
19834 });
19835 }
19836
19837 fn on_buffer_event(
19838 &mut self,
19839 multibuffer: &Entity<MultiBuffer>,
19840 event: &multi_buffer::Event,
19841 window: &mut Window,
19842 cx: &mut Context<Self>,
19843 ) {
19844 match event {
19845 multi_buffer::Event::Edited {
19846 singleton_buffer_edited,
19847 edited_buffer,
19848 } => {
19849 self.scrollbar_marker_state.dirty = true;
19850 self.active_indent_guides_state.dirty = true;
19851 self.refresh_active_diagnostics(cx);
19852 self.refresh_code_actions(window, cx);
19853 self.refresh_selected_text_highlights(true, window, cx);
19854 self.refresh_single_line_folds(window, cx);
19855 refresh_matching_bracket_highlights(self, window, cx);
19856 if self.has_active_inline_completion() {
19857 self.update_visible_inline_completion(window, cx);
19858 }
19859 if let Some(project) = self.project.as_ref() {
19860 if let Some(edited_buffer) = edited_buffer {
19861 project.update(cx, |project, cx| {
19862 self.registered_buffers
19863 .entry(edited_buffer.read(cx).remote_id())
19864 .or_insert_with(|| {
19865 project
19866 .register_buffer_with_language_servers(&edited_buffer, cx)
19867 });
19868 });
19869 }
19870 }
19871 cx.emit(EditorEvent::BufferEdited);
19872 cx.emit(SearchEvent::MatchesInvalidated);
19873
19874 if let Some(buffer) = edited_buffer {
19875 self.update_lsp_data(false, Some(buffer.read(cx).remote_id()), window, cx);
19876 }
19877
19878 if *singleton_buffer_edited {
19879 if let Some(buffer) = edited_buffer {
19880 if buffer.read(cx).file().is_none() {
19881 cx.emit(EditorEvent::TitleChanged);
19882 }
19883 }
19884 if let Some(project) = &self.project {
19885 #[allow(clippy::mutable_key_type)]
19886 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
19887 multibuffer
19888 .all_buffers()
19889 .into_iter()
19890 .filter_map(|buffer| {
19891 buffer.update(cx, |buffer, cx| {
19892 let language = buffer.language()?;
19893 let should_discard = project.update(cx, |project, cx| {
19894 project.is_local()
19895 && !project.has_language_servers_for(buffer, cx)
19896 });
19897 should_discard.not().then_some(language.clone())
19898 })
19899 })
19900 .collect::<HashSet<_>>()
19901 });
19902 if !languages_affected.is_empty() {
19903 self.refresh_inlay_hints(
19904 InlayHintRefreshReason::BufferEdited(languages_affected),
19905 cx,
19906 );
19907 }
19908 }
19909 }
19910
19911 let Some(project) = &self.project else { return };
19912 let (telemetry, is_via_ssh) = {
19913 let project = project.read(cx);
19914 let telemetry = project.client().telemetry().clone();
19915 let is_via_ssh = project.is_via_ssh();
19916 (telemetry, is_via_ssh)
19917 };
19918 refresh_linked_ranges(self, window, cx);
19919 telemetry.log_edit_event("editor", is_via_ssh);
19920 }
19921 multi_buffer::Event::ExcerptsAdded {
19922 buffer,
19923 predecessor,
19924 excerpts,
19925 } => {
19926 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19927 let buffer_id = buffer.read(cx).remote_id();
19928 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
19929 if let Some(project) = &self.project {
19930 update_uncommitted_diff_for_buffer(
19931 cx.entity(),
19932 project,
19933 [buffer.clone()],
19934 self.buffer.clone(),
19935 cx,
19936 )
19937 .detach();
19938 }
19939 }
19940 self.update_lsp_data(false, Some(buffer_id), window, cx);
19941 cx.emit(EditorEvent::ExcerptsAdded {
19942 buffer: buffer.clone(),
19943 predecessor: *predecessor,
19944 excerpts: excerpts.clone(),
19945 });
19946 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
19947 }
19948 multi_buffer::Event::ExcerptsRemoved {
19949 ids,
19950 removed_buffer_ids,
19951 } => {
19952 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
19953 let buffer = self.buffer.read(cx);
19954 self.registered_buffers
19955 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
19956 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19957 cx.emit(EditorEvent::ExcerptsRemoved {
19958 ids: ids.clone(),
19959 removed_buffer_ids: removed_buffer_ids.clone(),
19960 });
19961 }
19962 multi_buffer::Event::ExcerptsEdited {
19963 excerpt_ids,
19964 buffer_ids,
19965 } => {
19966 self.display_map.update(cx, |map, cx| {
19967 map.unfold_buffers(buffer_ids.iter().copied(), cx)
19968 });
19969 cx.emit(EditorEvent::ExcerptsEdited {
19970 ids: excerpt_ids.clone(),
19971 });
19972 }
19973 multi_buffer::Event::ExcerptsExpanded { ids } => {
19974 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
19975 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
19976 }
19977 multi_buffer::Event::Reparsed(buffer_id) => {
19978 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19979 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19980
19981 cx.emit(EditorEvent::Reparsed(*buffer_id));
19982 }
19983 multi_buffer::Event::DiffHunksToggled => {
19984 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19985 }
19986 multi_buffer::Event::LanguageChanged(buffer_id) => {
19987 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
19988 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19989 cx.emit(EditorEvent::Reparsed(*buffer_id));
19990 cx.notify();
19991 }
19992 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
19993 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
19994 multi_buffer::Event::FileHandleChanged
19995 | multi_buffer::Event::Reloaded
19996 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
19997 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
19998 multi_buffer::Event::DiagnosticsUpdated => {
19999 self.update_diagnostics_state(window, cx);
20000 }
20001 _ => {}
20002 };
20003 }
20004
20005 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
20006 if !self.diagnostics_enabled() {
20007 return;
20008 }
20009 self.refresh_active_diagnostics(cx);
20010 self.refresh_inline_diagnostics(true, window, cx);
20011 self.scrollbar_marker_state.dirty = true;
20012 cx.notify();
20013 }
20014
20015 pub fn start_temporary_diff_override(&mut self) {
20016 self.load_diff_task.take();
20017 self.temporary_diff_override = true;
20018 }
20019
20020 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
20021 self.temporary_diff_override = false;
20022 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
20023 self.buffer.update(cx, |buffer, cx| {
20024 buffer.set_all_diff_hunks_collapsed(cx);
20025 });
20026
20027 if let Some(project) = self.project.clone() {
20028 self.load_diff_task = Some(
20029 update_uncommitted_diff_for_buffer(
20030 cx.entity(),
20031 &project,
20032 self.buffer.read(cx).all_buffers(),
20033 self.buffer.clone(),
20034 cx,
20035 )
20036 .shared(),
20037 );
20038 }
20039 }
20040
20041 fn on_display_map_changed(
20042 &mut self,
20043 _: Entity<DisplayMap>,
20044 _: &mut Window,
20045 cx: &mut Context<Self>,
20046 ) {
20047 cx.notify();
20048 }
20049
20050 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20051 if self.diagnostics_enabled() {
20052 let new_severity = EditorSettings::get_global(cx)
20053 .diagnostics_max_severity
20054 .unwrap_or(DiagnosticSeverity::Hint);
20055 self.set_max_diagnostics_severity(new_severity, cx);
20056 }
20057 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20058 self.update_edit_prediction_settings(cx);
20059 self.refresh_inline_completion(true, false, window, cx);
20060 self.refresh_inline_values(cx);
20061 self.refresh_inlay_hints(
20062 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
20063 self.selections.newest_anchor().head(),
20064 &self.buffer.read(cx).snapshot(cx),
20065 cx,
20066 )),
20067 cx,
20068 );
20069
20070 let old_cursor_shape = self.cursor_shape;
20071
20072 {
20073 let editor_settings = EditorSettings::get_global(cx);
20074 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
20075 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
20076 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
20077 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
20078 }
20079
20080 if old_cursor_shape != self.cursor_shape {
20081 cx.emit(EditorEvent::CursorShapeChanged);
20082 }
20083
20084 let project_settings = ProjectSettings::get_global(cx);
20085 self.serialize_dirty_buffers =
20086 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
20087
20088 if self.mode.is_full() {
20089 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
20090 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
20091 if self.show_inline_diagnostics != show_inline_diagnostics {
20092 self.show_inline_diagnostics = show_inline_diagnostics;
20093 self.refresh_inline_diagnostics(false, window, cx);
20094 }
20095
20096 if self.git_blame_inline_enabled != inline_blame_enabled {
20097 self.toggle_git_blame_inline_internal(false, window, cx);
20098 }
20099
20100 let minimap_settings = EditorSettings::get_global(cx).minimap;
20101 if self.minimap_visibility != MinimapVisibility::Disabled {
20102 if self.minimap_visibility.settings_visibility()
20103 != minimap_settings.minimap_enabled()
20104 {
20105 self.set_minimap_visibility(
20106 MinimapVisibility::for_mode(self.mode(), cx),
20107 window,
20108 cx,
20109 );
20110 } else if let Some(minimap_entity) = self.minimap.as_ref() {
20111 minimap_entity.update(cx, |minimap_editor, cx| {
20112 minimap_editor.update_minimap_configuration(minimap_settings, cx)
20113 })
20114 }
20115 }
20116 }
20117
20118 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
20119 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
20120 }) {
20121 if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
20122 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
20123 }
20124 self.refresh_colors(false, None, window, cx);
20125 }
20126
20127 cx.notify();
20128 }
20129
20130 pub fn set_searchable(&mut self, searchable: bool) {
20131 self.searchable = searchable;
20132 }
20133
20134 pub fn searchable(&self) -> bool {
20135 self.searchable
20136 }
20137
20138 fn open_proposed_changes_editor(
20139 &mut self,
20140 _: &OpenProposedChangesEditor,
20141 window: &mut Window,
20142 cx: &mut Context<Self>,
20143 ) {
20144 let Some(workspace) = self.workspace() else {
20145 cx.propagate();
20146 return;
20147 };
20148
20149 let selections = self.selections.all::<usize>(cx);
20150 let multi_buffer = self.buffer.read(cx);
20151 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20152 let mut new_selections_by_buffer = HashMap::default();
20153 for selection in selections {
20154 for (buffer, range, _) in
20155 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
20156 {
20157 let mut range = range.to_point(buffer);
20158 range.start.column = 0;
20159 range.end.column = buffer.line_len(range.end.row);
20160 new_selections_by_buffer
20161 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
20162 .or_insert(Vec::new())
20163 .push(range)
20164 }
20165 }
20166
20167 let proposed_changes_buffers = new_selections_by_buffer
20168 .into_iter()
20169 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
20170 .collect::<Vec<_>>();
20171 let proposed_changes_editor = cx.new(|cx| {
20172 ProposedChangesEditor::new(
20173 "Proposed changes",
20174 proposed_changes_buffers,
20175 self.project.clone(),
20176 window,
20177 cx,
20178 )
20179 });
20180
20181 window.defer(cx, move |window, cx| {
20182 workspace.update(cx, |workspace, cx| {
20183 workspace.active_pane().update(cx, |pane, cx| {
20184 pane.add_item(
20185 Box::new(proposed_changes_editor),
20186 true,
20187 true,
20188 None,
20189 window,
20190 cx,
20191 );
20192 });
20193 });
20194 });
20195 }
20196
20197 pub fn open_excerpts_in_split(
20198 &mut self,
20199 _: &OpenExcerptsSplit,
20200 window: &mut Window,
20201 cx: &mut Context<Self>,
20202 ) {
20203 self.open_excerpts_common(None, true, window, cx)
20204 }
20205
20206 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
20207 self.open_excerpts_common(None, false, window, cx)
20208 }
20209
20210 fn open_excerpts_common(
20211 &mut self,
20212 jump_data: Option<JumpData>,
20213 split: bool,
20214 window: &mut Window,
20215 cx: &mut Context<Self>,
20216 ) {
20217 let Some(workspace) = self.workspace() else {
20218 cx.propagate();
20219 return;
20220 };
20221
20222 if self.buffer.read(cx).is_singleton() {
20223 cx.propagate();
20224 return;
20225 }
20226
20227 let mut new_selections_by_buffer = HashMap::default();
20228 match &jump_data {
20229 Some(JumpData::MultiBufferPoint {
20230 excerpt_id,
20231 position,
20232 anchor,
20233 line_offset_from_top,
20234 }) => {
20235 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20236 if let Some(buffer) = multi_buffer_snapshot
20237 .buffer_id_for_excerpt(*excerpt_id)
20238 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
20239 {
20240 let buffer_snapshot = buffer.read(cx).snapshot();
20241 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
20242 language::ToPoint::to_point(anchor, &buffer_snapshot)
20243 } else {
20244 buffer_snapshot.clip_point(*position, Bias::Left)
20245 };
20246 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
20247 new_selections_by_buffer.insert(
20248 buffer,
20249 (
20250 vec![jump_to_offset..jump_to_offset],
20251 Some(*line_offset_from_top),
20252 ),
20253 );
20254 }
20255 }
20256 Some(JumpData::MultiBufferRow {
20257 row,
20258 line_offset_from_top,
20259 }) => {
20260 let point = MultiBufferPoint::new(row.0, 0);
20261 if let Some((buffer, buffer_point, _)) =
20262 self.buffer.read(cx).point_to_buffer_point(point, cx)
20263 {
20264 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
20265 new_selections_by_buffer
20266 .entry(buffer)
20267 .or_insert((Vec::new(), Some(*line_offset_from_top)))
20268 .0
20269 .push(buffer_offset..buffer_offset)
20270 }
20271 }
20272 None => {
20273 let selections = self.selections.all::<usize>(cx);
20274 let multi_buffer = self.buffer.read(cx);
20275 for selection in selections {
20276 for (snapshot, range, _, anchor) in multi_buffer
20277 .snapshot(cx)
20278 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
20279 {
20280 if let Some(anchor) = anchor {
20281 // selection is in a deleted hunk
20282 let Some(buffer_id) = anchor.buffer_id else {
20283 continue;
20284 };
20285 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
20286 continue;
20287 };
20288 let offset = text::ToOffset::to_offset(
20289 &anchor.text_anchor,
20290 &buffer_handle.read(cx).snapshot(),
20291 );
20292 let range = offset..offset;
20293 new_selections_by_buffer
20294 .entry(buffer_handle)
20295 .or_insert((Vec::new(), None))
20296 .0
20297 .push(range)
20298 } else {
20299 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
20300 else {
20301 continue;
20302 };
20303 new_selections_by_buffer
20304 .entry(buffer_handle)
20305 .or_insert((Vec::new(), None))
20306 .0
20307 .push(range)
20308 }
20309 }
20310 }
20311 }
20312 }
20313
20314 new_selections_by_buffer
20315 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
20316
20317 if new_selections_by_buffer.is_empty() {
20318 return;
20319 }
20320
20321 // We defer the pane interaction because we ourselves are a workspace item
20322 // and activating a new item causes the pane to call a method on us reentrantly,
20323 // which panics if we're on the stack.
20324 window.defer(cx, move |window, cx| {
20325 workspace.update(cx, |workspace, cx| {
20326 let pane = if split {
20327 workspace.adjacent_pane(window, cx)
20328 } else {
20329 workspace.active_pane().clone()
20330 };
20331
20332 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
20333 let editor = buffer
20334 .read(cx)
20335 .file()
20336 .is_none()
20337 .then(|| {
20338 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
20339 // so `workspace.open_project_item` will never find them, always opening a new editor.
20340 // Instead, we try to activate the existing editor in the pane first.
20341 let (editor, pane_item_index) =
20342 pane.read(cx).items().enumerate().find_map(|(i, item)| {
20343 let editor = item.downcast::<Editor>()?;
20344 let singleton_buffer =
20345 editor.read(cx).buffer().read(cx).as_singleton()?;
20346 if singleton_buffer == buffer {
20347 Some((editor, i))
20348 } else {
20349 None
20350 }
20351 })?;
20352 pane.update(cx, |pane, cx| {
20353 pane.activate_item(pane_item_index, true, true, window, cx)
20354 });
20355 Some(editor)
20356 })
20357 .flatten()
20358 .unwrap_or_else(|| {
20359 workspace.open_project_item::<Self>(
20360 pane.clone(),
20361 buffer,
20362 true,
20363 true,
20364 window,
20365 cx,
20366 )
20367 });
20368
20369 editor.update(cx, |editor, cx| {
20370 let autoscroll = match scroll_offset {
20371 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
20372 None => Autoscroll::newest(),
20373 };
20374 let nav_history = editor.nav_history.take();
20375 editor.change_selections(
20376 SelectionEffects::scroll(autoscroll),
20377 window,
20378 cx,
20379 |s| {
20380 s.select_ranges(ranges);
20381 },
20382 );
20383 editor.nav_history = nav_history;
20384 });
20385 }
20386 })
20387 });
20388 }
20389
20390 // For now, don't allow opening excerpts in buffers that aren't backed by
20391 // regular project files.
20392 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
20393 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
20394 }
20395
20396 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
20397 let snapshot = self.buffer.read(cx).read(cx);
20398 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
20399 Some(
20400 ranges
20401 .iter()
20402 .map(move |range| {
20403 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
20404 })
20405 .collect(),
20406 )
20407 }
20408
20409 fn selection_replacement_ranges(
20410 &self,
20411 range: Range<OffsetUtf16>,
20412 cx: &mut App,
20413 ) -> Vec<Range<OffsetUtf16>> {
20414 let selections = self.selections.all::<OffsetUtf16>(cx);
20415 let newest_selection = selections
20416 .iter()
20417 .max_by_key(|selection| selection.id)
20418 .unwrap();
20419 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
20420 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
20421 let snapshot = self.buffer.read(cx).read(cx);
20422 selections
20423 .into_iter()
20424 .map(|mut selection| {
20425 selection.start.0 =
20426 (selection.start.0 as isize).saturating_add(start_delta) as usize;
20427 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
20428 snapshot.clip_offset_utf16(selection.start, Bias::Left)
20429 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
20430 })
20431 .collect()
20432 }
20433
20434 fn report_editor_event(
20435 &self,
20436 event_type: &'static str,
20437 file_extension: Option<String>,
20438 cx: &App,
20439 ) {
20440 if cfg!(any(test, feature = "test-support")) {
20441 return;
20442 }
20443
20444 let Some(project) = &self.project else { return };
20445
20446 // If None, we are in a file without an extension
20447 let file = self
20448 .buffer
20449 .read(cx)
20450 .as_singleton()
20451 .and_then(|b| b.read(cx).file());
20452 let file_extension = file_extension.or(file
20453 .as_ref()
20454 .and_then(|file| Path::new(file.file_name(cx)).extension())
20455 .and_then(|e| e.to_str())
20456 .map(|a| a.to_string()));
20457
20458 let vim_mode = vim_enabled(cx);
20459
20460 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
20461 let copilot_enabled = edit_predictions_provider
20462 == language::language_settings::EditPredictionProvider::Copilot;
20463 let copilot_enabled_for_language = self
20464 .buffer
20465 .read(cx)
20466 .language_settings(cx)
20467 .show_edit_predictions;
20468
20469 let project = project.read(cx);
20470 telemetry::event!(
20471 event_type,
20472 file_extension,
20473 vim_mode,
20474 copilot_enabled,
20475 copilot_enabled_for_language,
20476 edit_predictions_provider,
20477 is_via_ssh = project.is_via_ssh(),
20478 );
20479 }
20480
20481 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
20482 /// with each line being an array of {text, highlight} objects.
20483 fn copy_highlight_json(
20484 &mut self,
20485 _: &CopyHighlightJson,
20486 window: &mut Window,
20487 cx: &mut Context<Self>,
20488 ) {
20489 #[derive(Serialize)]
20490 struct Chunk<'a> {
20491 text: String,
20492 highlight: Option<&'a str>,
20493 }
20494
20495 let snapshot = self.buffer.read(cx).snapshot(cx);
20496 let range = self
20497 .selected_text_range(false, window, cx)
20498 .and_then(|selection| {
20499 if selection.range.is_empty() {
20500 None
20501 } else {
20502 Some(selection.range)
20503 }
20504 })
20505 .unwrap_or_else(|| 0..snapshot.len());
20506
20507 let chunks = snapshot.chunks(range, true);
20508 let mut lines = Vec::new();
20509 let mut line: VecDeque<Chunk> = VecDeque::new();
20510
20511 let Some(style) = self.style.as_ref() else {
20512 return;
20513 };
20514
20515 for chunk in chunks {
20516 let highlight = chunk
20517 .syntax_highlight_id
20518 .and_then(|id| id.name(&style.syntax));
20519 let mut chunk_lines = chunk.text.split('\n').peekable();
20520 while let Some(text) = chunk_lines.next() {
20521 let mut merged_with_last_token = false;
20522 if let Some(last_token) = line.back_mut() {
20523 if last_token.highlight == highlight {
20524 last_token.text.push_str(text);
20525 merged_with_last_token = true;
20526 }
20527 }
20528
20529 if !merged_with_last_token {
20530 line.push_back(Chunk {
20531 text: text.into(),
20532 highlight,
20533 });
20534 }
20535
20536 if chunk_lines.peek().is_some() {
20537 if line.len() > 1 && line.front().unwrap().text.is_empty() {
20538 line.pop_front();
20539 }
20540 if line.len() > 1 && line.back().unwrap().text.is_empty() {
20541 line.pop_back();
20542 }
20543
20544 lines.push(mem::take(&mut line));
20545 }
20546 }
20547 }
20548
20549 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
20550 return;
20551 };
20552 cx.write_to_clipboard(ClipboardItem::new_string(lines));
20553 }
20554
20555 pub fn open_context_menu(
20556 &mut self,
20557 _: &OpenContextMenu,
20558 window: &mut Window,
20559 cx: &mut Context<Self>,
20560 ) {
20561 self.request_autoscroll(Autoscroll::newest(), cx);
20562 let position = self.selections.newest_display(cx).start;
20563 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
20564 }
20565
20566 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
20567 &self.inlay_hint_cache
20568 }
20569
20570 pub fn replay_insert_event(
20571 &mut self,
20572 text: &str,
20573 relative_utf16_range: Option<Range<isize>>,
20574 window: &mut Window,
20575 cx: &mut Context<Self>,
20576 ) {
20577 if !self.input_enabled {
20578 cx.emit(EditorEvent::InputIgnored { text: text.into() });
20579 return;
20580 }
20581 if let Some(relative_utf16_range) = relative_utf16_range {
20582 let selections = self.selections.all::<OffsetUtf16>(cx);
20583 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20584 let new_ranges = selections.into_iter().map(|range| {
20585 let start = OffsetUtf16(
20586 range
20587 .head()
20588 .0
20589 .saturating_add_signed(relative_utf16_range.start),
20590 );
20591 let end = OffsetUtf16(
20592 range
20593 .head()
20594 .0
20595 .saturating_add_signed(relative_utf16_range.end),
20596 );
20597 start..end
20598 });
20599 s.select_ranges(new_ranges);
20600 });
20601 }
20602
20603 self.handle_input(text, window, cx);
20604 }
20605
20606 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
20607 let Some(provider) = self.semantics_provider.as_ref() else {
20608 return false;
20609 };
20610
20611 let mut supports = false;
20612 self.buffer().update(cx, |this, cx| {
20613 this.for_each_buffer(|buffer| {
20614 supports |= provider.supports_inlay_hints(buffer, cx);
20615 });
20616 });
20617
20618 supports
20619 }
20620
20621 pub fn is_focused(&self, window: &Window) -> bool {
20622 self.focus_handle.is_focused(window)
20623 }
20624
20625 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20626 cx.emit(EditorEvent::Focused);
20627
20628 if let Some(descendant) = self
20629 .last_focused_descendant
20630 .take()
20631 .and_then(|descendant| descendant.upgrade())
20632 {
20633 window.focus(&descendant);
20634 } else {
20635 if let Some(blame) = self.blame.as_ref() {
20636 blame.update(cx, GitBlame::focus)
20637 }
20638
20639 self.blink_manager.update(cx, BlinkManager::enable);
20640 self.show_cursor_names(window, cx);
20641 self.buffer.update(cx, |buffer, cx| {
20642 buffer.finalize_last_transaction(cx);
20643 if self.leader_id.is_none() {
20644 buffer.set_active_selections(
20645 &self.selections.disjoint_anchors(),
20646 self.selections.line_mode,
20647 self.cursor_shape,
20648 cx,
20649 );
20650 }
20651 });
20652 }
20653 }
20654
20655 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
20656 cx.emit(EditorEvent::FocusedIn)
20657 }
20658
20659 fn handle_focus_out(
20660 &mut self,
20661 event: FocusOutEvent,
20662 _window: &mut Window,
20663 cx: &mut Context<Self>,
20664 ) {
20665 if event.blurred != self.focus_handle {
20666 self.last_focused_descendant = Some(event.blurred);
20667 }
20668 self.selection_drag_state = SelectionDragState::None;
20669 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
20670 }
20671
20672 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20673 self.blink_manager.update(cx, BlinkManager::disable);
20674 self.buffer
20675 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
20676
20677 if let Some(blame) = self.blame.as_ref() {
20678 blame.update(cx, GitBlame::blur)
20679 }
20680 if !self.hover_state.focused(window, cx) {
20681 hide_hover(self, cx);
20682 }
20683 if !self
20684 .context_menu
20685 .borrow()
20686 .as_ref()
20687 .is_some_and(|context_menu| context_menu.focused(window, cx))
20688 {
20689 self.hide_context_menu(window, cx);
20690 }
20691 self.discard_inline_completion(false, cx);
20692 cx.emit(EditorEvent::Blurred);
20693 cx.notify();
20694 }
20695
20696 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20697 let mut pending: String = window
20698 .pending_input_keystrokes()
20699 .into_iter()
20700 .flatten()
20701 .filter_map(|keystroke| {
20702 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
20703 keystroke.key_char.clone()
20704 } else {
20705 None
20706 }
20707 })
20708 .collect();
20709
20710 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
20711 pending = "".to_string();
20712 }
20713
20714 let existing_pending = self
20715 .text_highlights::<PendingInput>(cx)
20716 .map(|(_, ranges)| ranges.iter().cloned().collect::<Vec<_>>());
20717 if existing_pending.is_none() && pending.is_empty() {
20718 return;
20719 }
20720 let transaction =
20721 self.transact(window, cx, |this, window, cx| {
20722 let selections = this.selections.all::<usize>(cx);
20723 let edits = selections
20724 .iter()
20725 .map(|selection| (selection.end..selection.end, pending.clone()));
20726 this.edit(edits, cx);
20727 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20728 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
20729 sel.start + ix * pending.len()..sel.end + ix * pending.len()
20730 }));
20731 });
20732 if let Some(existing_ranges) = existing_pending {
20733 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
20734 this.edit(edits, cx);
20735 }
20736 });
20737
20738 let snapshot = self.snapshot(window, cx);
20739 let ranges = self
20740 .selections
20741 .all::<usize>(cx)
20742 .into_iter()
20743 .map(|selection| {
20744 snapshot.buffer_snapshot.anchor_after(selection.end)
20745 ..snapshot
20746 .buffer_snapshot
20747 .anchor_before(selection.end + pending.len())
20748 })
20749 .collect();
20750
20751 if pending.is_empty() {
20752 self.clear_highlights::<PendingInput>(cx);
20753 } else {
20754 self.highlight_text::<PendingInput>(
20755 ranges,
20756 HighlightStyle {
20757 underline: Some(UnderlineStyle {
20758 thickness: px(1.),
20759 color: None,
20760 wavy: false,
20761 }),
20762 ..Default::default()
20763 },
20764 cx,
20765 );
20766 }
20767
20768 self.ime_transaction = self.ime_transaction.or(transaction);
20769 if let Some(transaction) = self.ime_transaction {
20770 self.buffer.update(cx, |buffer, cx| {
20771 buffer.group_until_transaction(transaction, cx);
20772 });
20773 }
20774
20775 if self.text_highlights::<PendingInput>(cx).is_none() {
20776 self.ime_transaction.take();
20777 }
20778 }
20779
20780 pub fn register_action_renderer(
20781 &mut self,
20782 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
20783 ) -> Subscription {
20784 let id = self.next_editor_action_id.post_inc();
20785 self.editor_actions
20786 .borrow_mut()
20787 .insert(id, Box::new(listener));
20788
20789 let editor_actions = self.editor_actions.clone();
20790 Subscription::new(move || {
20791 editor_actions.borrow_mut().remove(&id);
20792 })
20793 }
20794
20795 pub fn register_action<A: Action>(
20796 &mut self,
20797 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
20798 ) -> Subscription {
20799 let id = self.next_editor_action_id.post_inc();
20800 let listener = Arc::new(listener);
20801 self.editor_actions.borrow_mut().insert(
20802 id,
20803 Box::new(move |_, window, _| {
20804 let listener = listener.clone();
20805 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
20806 let action = action.downcast_ref().unwrap();
20807 if phase == DispatchPhase::Bubble {
20808 listener(action, window, cx)
20809 }
20810 })
20811 }),
20812 );
20813
20814 let editor_actions = self.editor_actions.clone();
20815 Subscription::new(move || {
20816 editor_actions.borrow_mut().remove(&id);
20817 })
20818 }
20819
20820 pub fn file_header_size(&self) -> u32 {
20821 FILE_HEADER_HEIGHT
20822 }
20823
20824 pub fn restore(
20825 &mut self,
20826 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
20827 window: &mut Window,
20828 cx: &mut Context<Self>,
20829 ) {
20830 let workspace = self.workspace();
20831 let project = self.project.as_ref();
20832 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
20833 let mut tasks = Vec::new();
20834 for (buffer_id, changes) in revert_changes {
20835 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
20836 buffer.update(cx, |buffer, cx| {
20837 buffer.edit(
20838 changes
20839 .into_iter()
20840 .map(|(range, text)| (range, text.to_string())),
20841 None,
20842 cx,
20843 );
20844 });
20845
20846 if let Some(project) =
20847 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
20848 {
20849 project.update(cx, |project, cx| {
20850 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
20851 })
20852 }
20853 }
20854 }
20855 tasks
20856 });
20857 cx.spawn_in(window, async move |_, cx| {
20858 for (buffer, task) in save_tasks {
20859 let result = task.await;
20860 if result.is_err() {
20861 let Some(path) = buffer
20862 .read_with(cx, |buffer, cx| buffer.project_path(cx))
20863 .ok()
20864 else {
20865 continue;
20866 };
20867 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
20868 let Some(task) = cx
20869 .update_window_entity(&workspace, |workspace, window, cx| {
20870 workspace
20871 .open_path_preview(path, None, false, false, false, window, cx)
20872 })
20873 .ok()
20874 else {
20875 continue;
20876 };
20877 task.await.log_err();
20878 }
20879 }
20880 }
20881 })
20882 .detach();
20883 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
20884 selections.refresh()
20885 });
20886 }
20887
20888 pub fn to_pixel_point(
20889 &self,
20890 source: multi_buffer::Anchor,
20891 editor_snapshot: &EditorSnapshot,
20892 window: &mut Window,
20893 ) -> Option<gpui::Point<Pixels>> {
20894 let source_point = source.to_display_point(editor_snapshot);
20895 self.display_to_pixel_point(source_point, editor_snapshot, window)
20896 }
20897
20898 pub fn display_to_pixel_point(
20899 &self,
20900 source: DisplayPoint,
20901 editor_snapshot: &EditorSnapshot,
20902 window: &mut Window,
20903 ) -> Option<gpui::Point<Pixels>> {
20904 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
20905 let text_layout_details = self.text_layout_details(window);
20906 let scroll_top = text_layout_details
20907 .scroll_anchor
20908 .scroll_position(editor_snapshot)
20909 .y;
20910
20911 if source.row().as_f32() < scroll_top.floor() {
20912 return None;
20913 }
20914 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
20915 let source_y = line_height * (source.row().as_f32() - scroll_top);
20916 Some(gpui::Point::new(source_x, source_y))
20917 }
20918
20919 pub fn has_visible_completions_menu(&self) -> bool {
20920 !self.edit_prediction_preview_is_active()
20921 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
20922 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
20923 })
20924 }
20925
20926 pub fn register_addon<T: Addon>(&mut self, instance: T) {
20927 if self.mode.is_minimap() {
20928 return;
20929 }
20930 self.addons
20931 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
20932 }
20933
20934 pub fn unregister_addon<T: Addon>(&mut self) {
20935 self.addons.remove(&std::any::TypeId::of::<T>());
20936 }
20937
20938 pub fn addon<T: Addon>(&self) -> Option<&T> {
20939 let type_id = std::any::TypeId::of::<T>();
20940 self.addons
20941 .get(&type_id)
20942 .and_then(|item| item.to_any().downcast_ref::<T>())
20943 }
20944
20945 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
20946 let type_id = std::any::TypeId::of::<T>();
20947 self.addons
20948 .get_mut(&type_id)
20949 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
20950 }
20951
20952 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
20953 let text_layout_details = self.text_layout_details(window);
20954 let style = &text_layout_details.editor_style;
20955 let font_id = window.text_system().resolve_font(&style.text.font());
20956 let font_size = style.text.font_size.to_pixels(window.rem_size());
20957 let line_height = style.text.line_height_in_pixels(window.rem_size());
20958 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
20959 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
20960
20961 CharacterDimensions {
20962 em_width,
20963 em_advance,
20964 line_height,
20965 }
20966 }
20967
20968 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
20969 self.load_diff_task.clone()
20970 }
20971
20972 fn read_metadata_from_db(
20973 &mut self,
20974 item_id: u64,
20975 workspace_id: WorkspaceId,
20976 window: &mut Window,
20977 cx: &mut Context<Editor>,
20978 ) {
20979 if self.is_singleton(cx)
20980 && !self.mode.is_minimap()
20981 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
20982 {
20983 let buffer_snapshot = OnceCell::new();
20984
20985 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
20986 if !folds.is_empty() {
20987 let snapshot =
20988 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
20989 self.fold_ranges(
20990 folds
20991 .into_iter()
20992 .map(|(start, end)| {
20993 snapshot.clip_offset(start, Bias::Left)
20994 ..snapshot.clip_offset(end, Bias::Right)
20995 })
20996 .collect(),
20997 false,
20998 window,
20999 cx,
21000 );
21001 }
21002 }
21003
21004 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
21005 if !selections.is_empty() {
21006 let snapshot =
21007 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21008 // skip adding the initial selection to selection history
21009 self.selection_history.mode = SelectionHistoryMode::Skipping;
21010 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21011 s.select_ranges(selections.into_iter().map(|(start, end)| {
21012 snapshot.clip_offset(start, Bias::Left)
21013 ..snapshot.clip_offset(end, Bias::Right)
21014 }));
21015 });
21016 self.selection_history.mode = SelectionHistoryMode::Normal;
21017 }
21018 };
21019 }
21020
21021 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
21022 }
21023
21024 fn update_lsp_data(
21025 &mut self,
21026 ignore_cache: bool,
21027 for_buffer: Option<BufferId>,
21028 window: &mut Window,
21029 cx: &mut Context<'_, Self>,
21030 ) {
21031 self.pull_diagnostics(for_buffer, window, cx);
21032 self.refresh_colors(ignore_cache, for_buffer, window, cx);
21033 }
21034}
21035
21036fn vim_enabled(cx: &App) -> bool {
21037 cx.global::<SettingsStore>()
21038 .raw_user_settings()
21039 .get("vim_mode")
21040 == Some(&serde_json::Value::Bool(true))
21041}
21042
21043fn process_completion_for_edit(
21044 completion: &Completion,
21045 intent: CompletionIntent,
21046 buffer: &Entity<Buffer>,
21047 cursor_position: &text::Anchor,
21048 cx: &mut Context<Editor>,
21049) -> CompletionEdit {
21050 let buffer = buffer.read(cx);
21051 let buffer_snapshot = buffer.snapshot();
21052 let (snippet, new_text) = if completion.is_snippet() {
21053 // Workaround for typescript language server issues so that methods don't expand within
21054 // strings and functions with type expressions. The previous point is used because the query
21055 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
21056 let mut snippet_source = completion.new_text.clone();
21057 let mut previous_point = text::ToPoint::to_point(cursor_position, buffer);
21058 previous_point.column = previous_point.column.saturating_sub(1);
21059 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point) {
21060 if scope.prefers_label_for_snippet_in_completion() {
21061 if let Some(label) = completion.label() {
21062 if matches!(
21063 completion.kind(),
21064 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
21065 ) {
21066 snippet_source = label;
21067 }
21068 }
21069 }
21070 }
21071 match Snippet::parse(&snippet_source).log_err() {
21072 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
21073 None => (None, completion.new_text.clone()),
21074 }
21075 } else {
21076 (None, completion.new_text.clone())
21077 };
21078
21079 let mut range_to_replace = {
21080 let replace_range = &completion.replace_range;
21081 if let CompletionSource::Lsp {
21082 insert_range: Some(insert_range),
21083 ..
21084 } = &completion.source
21085 {
21086 debug_assert_eq!(
21087 insert_range.start, replace_range.start,
21088 "insert_range and replace_range should start at the same position"
21089 );
21090 debug_assert!(
21091 insert_range
21092 .start
21093 .cmp(&cursor_position, &buffer_snapshot)
21094 .is_le(),
21095 "insert_range should start before or at cursor position"
21096 );
21097 debug_assert!(
21098 replace_range
21099 .start
21100 .cmp(&cursor_position, &buffer_snapshot)
21101 .is_le(),
21102 "replace_range should start before or at cursor position"
21103 );
21104 debug_assert!(
21105 insert_range
21106 .end
21107 .cmp(&cursor_position, &buffer_snapshot)
21108 .is_le(),
21109 "insert_range should end before or at cursor position"
21110 );
21111
21112 let should_replace = match intent {
21113 CompletionIntent::CompleteWithInsert => false,
21114 CompletionIntent::CompleteWithReplace => true,
21115 CompletionIntent::Complete | CompletionIntent::Compose => {
21116 let insert_mode =
21117 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
21118 .completions
21119 .lsp_insert_mode;
21120 match insert_mode {
21121 LspInsertMode::Insert => false,
21122 LspInsertMode::Replace => true,
21123 LspInsertMode::ReplaceSubsequence => {
21124 let mut text_to_replace = buffer.chars_for_range(
21125 buffer.anchor_before(replace_range.start)
21126 ..buffer.anchor_after(replace_range.end),
21127 );
21128 let mut current_needle = text_to_replace.next();
21129 for haystack_ch in completion.label.text.chars() {
21130 if let Some(needle_ch) = current_needle {
21131 if haystack_ch.eq_ignore_ascii_case(&needle_ch) {
21132 current_needle = text_to_replace.next();
21133 }
21134 }
21135 }
21136 current_needle.is_none()
21137 }
21138 LspInsertMode::ReplaceSuffix => {
21139 if replace_range
21140 .end
21141 .cmp(&cursor_position, &buffer_snapshot)
21142 .is_gt()
21143 {
21144 let range_after_cursor = *cursor_position..replace_range.end;
21145 let text_after_cursor = buffer
21146 .text_for_range(
21147 buffer.anchor_before(range_after_cursor.start)
21148 ..buffer.anchor_after(range_after_cursor.end),
21149 )
21150 .collect::<String>()
21151 .to_ascii_lowercase();
21152 completion
21153 .label
21154 .text
21155 .to_ascii_lowercase()
21156 .ends_with(&text_after_cursor)
21157 } else {
21158 true
21159 }
21160 }
21161 }
21162 }
21163 };
21164
21165 if should_replace {
21166 replace_range.clone()
21167 } else {
21168 insert_range.clone()
21169 }
21170 } else {
21171 replace_range.clone()
21172 }
21173 };
21174
21175 if range_to_replace
21176 .end
21177 .cmp(&cursor_position, &buffer_snapshot)
21178 .is_lt()
21179 {
21180 range_to_replace.end = *cursor_position;
21181 }
21182
21183 CompletionEdit {
21184 new_text,
21185 replace_range: range_to_replace.to_offset(&buffer),
21186 snippet,
21187 }
21188}
21189
21190struct CompletionEdit {
21191 new_text: String,
21192 replace_range: Range<usize>,
21193 snippet: Option<Snippet>,
21194}
21195
21196fn insert_extra_newline_brackets(
21197 buffer: &MultiBufferSnapshot,
21198 range: Range<usize>,
21199 language: &language::LanguageScope,
21200) -> bool {
21201 let leading_whitespace_len = buffer
21202 .reversed_chars_at(range.start)
21203 .take_while(|c| c.is_whitespace() && *c != '\n')
21204 .map(|c| c.len_utf8())
21205 .sum::<usize>();
21206 let trailing_whitespace_len = buffer
21207 .chars_at(range.end)
21208 .take_while(|c| c.is_whitespace() && *c != '\n')
21209 .map(|c| c.len_utf8())
21210 .sum::<usize>();
21211 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
21212
21213 language.brackets().any(|(pair, enabled)| {
21214 let pair_start = pair.start.trim_end();
21215 let pair_end = pair.end.trim_start();
21216
21217 enabled
21218 && pair.newline
21219 && buffer.contains_str_at(range.end, pair_end)
21220 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
21221 })
21222}
21223
21224fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
21225 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
21226 [(buffer, range, _)] => (*buffer, range.clone()),
21227 _ => return false,
21228 };
21229 let pair = {
21230 let mut result: Option<BracketMatch> = None;
21231
21232 for pair in buffer
21233 .all_bracket_ranges(range.clone())
21234 .filter(move |pair| {
21235 pair.open_range.start <= range.start && pair.close_range.end >= range.end
21236 })
21237 {
21238 let len = pair.close_range.end - pair.open_range.start;
21239
21240 if let Some(existing) = &result {
21241 let existing_len = existing.close_range.end - existing.open_range.start;
21242 if len > existing_len {
21243 continue;
21244 }
21245 }
21246
21247 result = Some(pair);
21248 }
21249
21250 result
21251 };
21252 let Some(pair) = pair else {
21253 return false;
21254 };
21255 pair.newline_only
21256 && buffer
21257 .chars_for_range(pair.open_range.end..range.start)
21258 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
21259 .all(|c| c.is_whitespace() && c != '\n')
21260}
21261
21262fn update_uncommitted_diff_for_buffer(
21263 editor: Entity<Editor>,
21264 project: &Entity<Project>,
21265 buffers: impl IntoIterator<Item = Entity<Buffer>>,
21266 buffer: Entity<MultiBuffer>,
21267 cx: &mut App,
21268) -> Task<()> {
21269 let mut tasks = Vec::new();
21270 project.update(cx, |project, cx| {
21271 for buffer in buffers {
21272 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
21273 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
21274 }
21275 }
21276 });
21277 cx.spawn(async move |cx| {
21278 let diffs = future::join_all(tasks).await;
21279 if editor
21280 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
21281 .unwrap_or(false)
21282 {
21283 return;
21284 }
21285
21286 buffer
21287 .update(cx, |buffer, cx| {
21288 for diff in diffs.into_iter().flatten() {
21289 buffer.add_diff(diff, cx);
21290 }
21291 })
21292 .ok();
21293 })
21294}
21295
21296fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
21297 let tab_size = tab_size.get() as usize;
21298 let mut width = offset;
21299
21300 for ch in text.chars() {
21301 width += if ch == '\t' {
21302 tab_size - (width % tab_size)
21303 } else {
21304 1
21305 };
21306 }
21307
21308 width - offset
21309}
21310
21311#[cfg(test)]
21312mod tests {
21313 use super::*;
21314
21315 #[test]
21316 fn test_string_size_with_expanded_tabs() {
21317 let nz = |val| NonZeroU32::new(val).unwrap();
21318 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
21319 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
21320 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
21321 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
21322 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
21323 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
21324 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
21325 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
21326 }
21327}
21328
21329/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
21330struct WordBreakingTokenizer<'a> {
21331 input: &'a str,
21332}
21333
21334impl<'a> WordBreakingTokenizer<'a> {
21335 fn new(input: &'a str) -> Self {
21336 Self { input }
21337 }
21338}
21339
21340fn is_char_ideographic(ch: char) -> bool {
21341 use unicode_script::Script::*;
21342 use unicode_script::UnicodeScript;
21343 matches!(ch.script(), Han | Tangut | Yi)
21344}
21345
21346fn is_grapheme_ideographic(text: &str) -> bool {
21347 text.chars().any(is_char_ideographic)
21348}
21349
21350fn is_grapheme_whitespace(text: &str) -> bool {
21351 text.chars().any(|x| x.is_whitespace())
21352}
21353
21354fn should_stay_with_preceding_ideograph(text: &str) -> bool {
21355 text.chars().next().map_or(false, |ch| {
21356 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
21357 })
21358}
21359
21360#[derive(PartialEq, Eq, Debug, Clone, Copy)]
21361enum WordBreakToken<'a> {
21362 Word { token: &'a str, grapheme_len: usize },
21363 InlineWhitespace { token: &'a str, grapheme_len: usize },
21364 Newline,
21365}
21366
21367impl<'a> Iterator for WordBreakingTokenizer<'a> {
21368 /// Yields a span, the count of graphemes in the token, and whether it was
21369 /// whitespace. Note that it also breaks at word boundaries.
21370 type Item = WordBreakToken<'a>;
21371
21372 fn next(&mut self) -> Option<Self::Item> {
21373 use unicode_segmentation::UnicodeSegmentation;
21374 if self.input.is_empty() {
21375 return None;
21376 }
21377
21378 let mut iter = self.input.graphemes(true).peekable();
21379 let mut offset = 0;
21380 let mut grapheme_len = 0;
21381 if let Some(first_grapheme) = iter.next() {
21382 let is_newline = first_grapheme == "\n";
21383 let is_whitespace = is_grapheme_whitespace(first_grapheme);
21384 offset += first_grapheme.len();
21385 grapheme_len += 1;
21386 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
21387 if let Some(grapheme) = iter.peek().copied() {
21388 if should_stay_with_preceding_ideograph(grapheme) {
21389 offset += grapheme.len();
21390 grapheme_len += 1;
21391 }
21392 }
21393 } else {
21394 let mut words = self.input[offset..].split_word_bound_indices().peekable();
21395 let mut next_word_bound = words.peek().copied();
21396 if next_word_bound.map_or(false, |(i, _)| i == 0) {
21397 next_word_bound = words.next();
21398 }
21399 while let Some(grapheme) = iter.peek().copied() {
21400 if next_word_bound.map_or(false, |(i, _)| i == offset) {
21401 break;
21402 };
21403 if is_grapheme_whitespace(grapheme) != is_whitespace
21404 || (grapheme == "\n") != is_newline
21405 {
21406 break;
21407 };
21408 offset += grapheme.len();
21409 grapheme_len += 1;
21410 iter.next();
21411 }
21412 }
21413 let token = &self.input[..offset];
21414 self.input = &self.input[offset..];
21415 if token == "\n" {
21416 Some(WordBreakToken::Newline)
21417 } else if is_whitespace {
21418 Some(WordBreakToken::InlineWhitespace {
21419 token,
21420 grapheme_len,
21421 })
21422 } else {
21423 Some(WordBreakToken::Word {
21424 token,
21425 grapheme_len,
21426 })
21427 }
21428 } else {
21429 None
21430 }
21431 }
21432}
21433
21434#[test]
21435fn test_word_breaking_tokenizer() {
21436 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
21437 ("", &[]),
21438 (" ", &[whitespace(" ", 2)]),
21439 ("Ʒ", &[word("Ʒ", 1)]),
21440 ("Ǽ", &[word("Ǽ", 1)]),
21441 ("⋑", &[word("⋑", 1)]),
21442 ("⋑⋑", &[word("⋑⋑", 2)]),
21443 (
21444 "原理,进而",
21445 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
21446 ),
21447 (
21448 "hello world",
21449 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
21450 ),
21451 (
21452 "hello, world",
21453 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
21454 ),
21455 (
21456 " hello world",
21457 &[
21458 whitespace(" ", 2),
21459 word("hello", 5),
21460 whitespace(" ", 1),
21461 word("world", 5),
21462 ],
21463 ),
21464 (
21465 "这是什么 \n 钢笔",
21466 &[
21467 word("这", 1),
21468 word("是", 1),
21469 word("什", 1),
21470 word("么", 1),
21471 whitespace(" ", 1),
21472 newline(),
21473 whitespace(" ", 1),
21474 word("钢", 1),
21475 word("笔", 1),
21476 ],
21477 ),
21478 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
21479 ];
21480
21481 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21482 WordBreakToken::Word {
21483 token,
21484 grapheme_len,
21485 }
21486 }
21487
21488 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21489 WordBreakToken::InlineWhitespace {
21490 token,
21491 grapheme_len,
21492 }
21493 }
21494
21495 fn newline() -> WordBreakToken<'static> {
21496 WordBreakToken::Newline
21497 }
21498
21499 for (input, result) in tests {
21500 assert_eq!(
21501 WordBreakingTokenizer::new(input)
21502 .collect::<Vec<_>>()
21503 .as_slice(),
21504 *result,
21505 );
21506 }
21507}
21508
21509fn wrap_with_prefix(
21510 first_line_prefix: String,
21511 subsequent_lines_prefix: String,
21512 unwrapped_text: String,
21513 wrap_column: usize,
21514 tab_size: NonZeroU32,
21515 preserve_existing_whitespace: bool,
21516) -> String {
21517 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
21518 let subsequent_lines_prefix_len =
21519 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
21520 let mut wrapped_text = String::new();
21521 let mut current_line = first_line_prefix.clone();
21522 let mut is_first_line = true;
21523
21524 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
21525 let mut current_line_len = first_line_prefix_len;
21526 let mut in_whitespace = false;
21527 for token in tokenizer {
21528 let have_preceding_whitespace = in_whitespace;
21529 match token {
21530 WordBreakToken::Word {
21531 token,
21532 grapheme_len,
21533 } => {
21534 in_whitespace = false;
21535 let current_prefix_len = if is_first_line {
21536 first_line_prefix_len
21537 } else {
21538 subsequent_lines_prefix_len
21539 };
21540 if current_line_len + grapheme_len > wrap_column
21541 && current_line_len != current_prefix_len
21542 {
21543 wrapped_text.push_str(current_line.trim_end());
21544 wrapped_text.push('\n');
21545 is_first_line = false;
21546 current_line = subsequent_lines_prefix.clone();
21547 current_line_len = subsequent_lines_prefix_len;
21548 }
21549 current_line.push_str(token);
21550 current_line_len += grapheme_len;
21551 }
21552 WordBreakToken::InlineWhitespace {
21553 mut token,
21554 mut grapheme_len,
21555 } => {
21556 in_whitespace = true;
21557 if have_preceding_whitespace && !preserve_existing_whitespace {
21558 continue;
21559 }
21560 if !preserve_existing_whitespace {
21561 token = " ";
21562 grapheme_len = 1;
21563 }
21564 let current_prefix_len = if is_first_line {
21565 first_line_prefix_len
21566 } else {
21567 subsequent_lines_prefix_len
21568 };
21569 if current_line_len + grapheme_len > wrap_column {
21570 wrapped_text.push_str(current_line.trim_end());
21571 wrapped_text.push('\n');
21572 is_first_line = false;
21573 current_line = subsequent_lines_prefix.clone();
21574 current_line_len = subsequent_lines_prefix_len;
21575 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
21576 current_line.push_str(token);
21577 current_line_len += grapheme_len;
21578 }
21579 }
21580 WordBreakToken::Newline => {
21581 in_whitespace = true;
21582 let current_prefix_len = if is_first_line {
21583 first_line_prefix_len
21584 } else {
21585 subsequent_lines_prefix_len
21586 };
21587 if preserve_existing_whitespace {
21588 wrapped_text.push_str(current_line.trim_end());
21589 wrapped_text.push('\n');
21590 is_first_line = false;
21591 current_line = subsequent_lines_prefix.clone();
21592 current_line_len = subsequent_lines_prefix_len;
21593 } else if have_preceding_whitespace {
21594 continue;
21595 } else if current_line_len + 1 > wrap_column
21596 && current_line_len != current_prefix_len
21597 {
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 {
21604 current_line.push(' ');
21605 current_line_len += 1;
21606 }
21607 }
21608 }
21609 }
21610
21611 if !current_line.is_empty() {
21612 wrapped_text.push_str(¤t_line);
21613 }
21614 wrapped_text
21615}
21616
21617#[test]
21618fn test_wrap_with_prefix() {
21619 assert_eq!(
21620 wrap_with_prefix(
21621 "# ".to_string(),
21622 "# ".to_string(),
21623 "abcdefg".to_string(),
21624 4,
21625 NonZeroU32::new(4).unwrap(),
21626 false,
21627 ),
21628 "# abcdefg"
21629 );
21630 assert_eq!(
21631 wrap_with_prefix(
21632 "".to_string(),
21633 "".to_string(),
21634 "\thello world".to_string(),
21635 8,
21636 NonZeroU32::new(4).unwrap(),
21637 false,
21638 ),
21639 "hello\nworld"
21640 );
21641 assert_eq!(
21642 wrap_with_prefix(
21643 "// ".to_string(),
21644 "// ".to_string(),
21645 "xx \nyy zz aa bb cc".to_string(),
21646 12,
21647 NonZeroU32::new(4).unwrap(),
21648 false,
21649 ),
21650 "// xx yy zz\n// aa bb cc"
21651 );
21652 assert_eq!(
21653 wrap_with_prefix(
21654 String::new(),
21655 String::new(),
21656 "这是什么 \n 钢笔".to_string(),
21657 3,
21658 NonZeroU32::new(4).unwrap(),
21659 false,
21660 ),
21661 "这是什\n么 钢\n笔"
21662 );
21663}
21664
21665pub trait CollaborationHub {
21666 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
21667 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
21668 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
21669}
21670
21671impl CollaborationHub for Entity<Project> {
21672 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
21673 self.read(cx).collaborators()
21674 }
21675
21676 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
21677 self.read(cx).user_store().read(cx).participant_indices()
21678 }
21679
21680 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
21681 let this = self.read(cx);
21682 let user_ids = this.collaborators().values().map(|c| c.user_id);
21683 this.user_store().read(cx).participant_names(user_ids, cx)
21684 }
21685}
21686
21687pub trait SemanticsProvider {
21688 fn hover(
21689 &self,
21690 buffer: &Entity<Buffer>,
21691 position: text::Anchor,
21692 cx: &mut App,
21693 ) -> Option<Task<Vec<project::Hover>>>;
21694
21695 fn inline_values(
21696 &self,
21697 buffer_handle: Entity<Buffer>,
21698 range: Range<text::Anchor>,
21699 cx: &mut App,
21700 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21701
21702 fn inlay_hints(
21703 &self,
21704 buffer_handle: Entity<Buffer>,
21705 range: Range<text::Anchor>,
21706 cx: &mut App,
21707 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21708
21709 fn resolve_inlay_hint(
21710 &self,
21711 hint: InlayHint,
21712 buffer_handle: Entity<Buffer>,
21713 server_id: LanguageServerId,
21714 cx: &mut App,
21715 ) -> Option<Task<anyhow::Result<InlayHint>>>;
21716
21717 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
21718
21719 fn document_highlights(
21720 &self,
21721 buffer: &Entity<Buffer>,
21722 position: text::Anchor,
21723 cx: &mut App,
21724 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
21725
21726 fn definitions(
21727 &self,
21728 buffer: &Entity<Buffer>,
21729 position: text::Anchor,
21730 kind: GotoDefinitionKind,
21731 cx: &mut App,
21732 ) -> Option<Task<Result<Vec<LocationLink>>>>;
21733
21734 fn range_for_rename(
21735 &self,
21736 buffer: &Entity<Buffer>,
21737 position: text::Anchor,
21738 cx: &mut App,
21739 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
21740
21741 fn perform_rename(
21742 &self,
21743 buffer: &Entity<Buffer>,
21744 position: text::Anchor,
21745 new_name: String,
21746 cx: &mut App,
21747 ) -> Option<Task<Result<ProjectTransaction>>>;
21748}
21749
21750pub trait CompletionProvider {
21751 fn completions(
21752 &self,
21753 excerpt_id: ExcerptId,
21754 buffer: &Entity<Buffer>,
21755 buffer_position: text::Anchor,
21756 trigger: CompletionContext,
21757 window: &mut Window,
21758 cx: &mut Context<Editor>,
21759 ) -> Task<Result<Vec<CompletionResponse>>>;
21760
21761 fn resolve_completions(
21762 &self,
21763 _buffer: Entity<Buffer>,
21764 _completion_indices: Vec<usize>,
21765 _completions: Rc<RefCell<Box<[Completion]>>>,
21766 _cx: &mut Context<Editor>,
21767 ) -> Task<Result<bool>> {
21768 Task::ready(Ok(false))
21769 }
21770
21771 fn apply_additional_edits_for_completion(
21772 &self,
21773 _buffer: Entity<Buffer>,
21774 _completions: Rc<RefCell<Box<[Completion]>>>,
21775 _completion_index: usize,
21776 _push_to_history: bool,
21777 _cx: &mut Context<Editor>,
21778 ) -> Task<Result<Option<language::Transaction>>> {
21779 Task::ready(Ok(None))
21780 }
21781
21782 fn is_completion_trigger(
21783 &self,
21784 buffer: &Entity<Buffer>,
21785 position: language::Anchor,
21786 text: &str,
21787 trigger_in_words: bool,
21788 menu_is_open: bool,
21789 cx: &mut Context<Editor>,
21790 ) -> bool;
21791
21792 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
21793
21794 fn sort_completions(&self) -> bool {
21795 true
21796 }
21797
21798 fn filter_completions(&self) -> bool {
21799 true
21800 }
21801}
21802
21803pub trait CodeActionProvider {
21804 fn id(&self) -> Arc<str>;
21805
21806 fn code_actions(
21807 &self,
21808 buffer: &Entity<Buffer>,
21809 range: Range<text::Anchor>,
21810 window: &mut Window,
21811 cx: &mut App,
21812 ) -> Task<Result<Vec<CodeAction>>>;
21813
21814 fn apply_code_action(
21815 &self,
21816 buffer_handle: Entity<Buffer>,
21817 action: CodeAction,
21818 excerpt_id: ExcerptId,
21819 push_to_history: bool,
21820 window: &mut Window,
21821 cx: &mut App,
21822 ) -> Task<Result<ProjectTransaction>>;
21823}
21824
21825impl CodeActionProvider for Entity<Project> {
21826 fn id(&self) -> Arc<str> {
21827 "project".into()
21828 }
21829
21830 fn code_actions(
21831 &self,
21832 buffer: &Entity<Buffer>,
21833 range: Range<text::Anchor>,
21834 _window: &mut Window,
21835 cx: &mut App,
21836 ) -> Task<Result<Vec<CodeAction>>> {
21837 self.update(cx, |project, cx| {
21838 let code_lens = project.code_lens(buffer, range.clone(), cx);
21839 let code_actions = project.code_actions(buffer, range, None, cx);
21840 cx.background_spawn(async move {
21841 let (code_lens, code_actions) = join(code_lens, code_actions).await;
21842 Ok(code_lens
21843 .context("code lens fetch")?
21844 .into_iter()
21845 .chain(code_actions.context("code action fetch")?)
21846 .collect())
21847 })
21848 })
21849 }
21850
21851 fn apply_code_action(
21852 &self,
21853 buffer_handle: Entity<Buffer>,
21854 action: CodeAction,
21855 _excerpt_id: ExcerptId,
21856 push_to_history: bool,
21857 _window: &mut Window,
21858 cx: &mut App,
21859 ) -> Task<Result<ProjectTransaction>> {
21860 self.update(cx, |project, cx| {
21861 project.apply_code_action(buffer_handle, action, push_to_history, cx)
21862 })
21863 }
21864}
21865
21866fn snippet_completions(
21867 project: &Project,
21868 buffer: &Entity<Buffer>,
21869 buffer_position: text::Anchor,
21870 cx: &mut App,
21871) -> Task<Result<CompletionResponse>> {
21872 let languages = buffer.read(cx).languages_at(buffer_position);
21873 let snippet_store = project.snippets().read(cx);
21874
21875 let scopes: Vec<_> = languages
21876 .iter()
21877 .filter_map(|language| {
21878 let language_name = language.lsp_id();
21879 let snippets = snippet_store.snippets_for(Some(language_name), cx);
21880
21881 if snippets.is_empty() {
21882 None
21883 } else {
21884 Some((language.default_scope(), snippets))
21885 }
21886 })
21887 .collect();
21888
21889 if scopes.is_empty() {
21890 return Task::ready(Ok(CompletionResponse {
21891 completions: vec![],
21892 is_incomplete: false,
21893 }));
21894 }
21895
21896 let snapshot = buffer.read(cx).text_snapshot();
21897 let chars: String = snapshot
21898 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
21899 .collect();
21900 let executor = cx.background_executor().clone();
21901
21902 cx.background_spawn(async move {
21903 let mut is_incomplete = false;
21904 let mut completions: Vec<Completion> = Vec::new();
21905 for (scope, snippets) in scopes.into_iter() {
21906 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
21907 let mut last_word = chars
21908 .chars()
21909 .take_while(|c| classifier.is_word(*c))
21910 .collect::<String>();
21911 last_word = last_word.chars().rev().collect();
21912
21913 if last_word.is_empty() {
21914 return Ok(CompletionResponse {
21915 completions: vec![],
21916 is_incomplete: true,
21917 });
21918 }
21919
21920 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
21921 let to_lsp = |point: &text::Anchor| {
21922 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
21923 point_to_lsp(end)
21924 };
21925 let lsp_end = to_lsp(&buffer_position);
21926
21927 let candidates = snippets
21928 .iter()
21929 .enumerate()
21930 .flat_map(|(ix, snippet)| {
21931 snippet
21932 .prefix
21933 .iter()
21934 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
21935 })
21936 .collect::<Vec<StringMatchCandidate>>();
21937
21938 const MAX_RESULTS: usize = 100;
21939 let mut matches = fuzzy::match_strings(
21940 &candidates,
21941 &last_word,
21942 last_word.chars().any(|c| c.is_uppercase()),
21943 true,
21944 MAX_RESULTS,
21945 &Default::default(),
21946 executor.clone(),
21947 )
21948 .await;
21949
21950 if matches.len() >= MAX_RESULTS {
21951 is_incomplete = true;
21952 }
21953
21954 // Remove all candidates where the query's start does not match the start of any word in the candidate
21955 if let Some(query_start) = last_word.chars().next() {
21956 matches.retain(|string_match| {
21957 split_words(&string_match.string).any(|word| {
21958 // Check that the first codepoint of the word as lowercase matches the first
21959 // codepoint of the query as lowercase
21960 word.chars()
21961 .flat_map(|codepoint| codepoint.to_lowercase())
21962 .zip(query_start.to_lowercase())
21963 .all(|(word_cp, query_cp)| word_cp == query_cp)
21964 })
21965 });
21966 }
21967
21968 let matched_strings = matches
21969 .into_iter()
21970 .map(|m| m.string)
21971 .collect::<HashSet<_>>();
21972
21973 completions.extend(snippets.iter().filter_map(|snippet| {
21974 let matching_prefix = snippet
21975 .prefix
21976 .iter()
21977 .find(|prefix| matched_strings.contains(*prefix))?;
21978 let start = as_offset - last_word.len();
21979 let start = snapshot.anchor_before(start);
21980 let range = start..buffer_position;
21981 let lsp_start = to_lsp(&start);
21982 let lsp_range = lsp::Range {
21983 start: lsp_start,
21984 end: lsp_end,
21985 };
21986 Some(Completion {
21987 replace_range: range,
21988 new_text: snippet.body.clone(),
21989 source: CompletionSource::Lsp {
21990 insert_range: None,
21991 server_id: LanguageServerId(usize::MAX),
21992 resolved: true,
21993 lsp_completion: Box::new(lsp::CompletionItem {
21994 label: snippet.prefix.first().unwrap().clone(),
21995 kind: Some(CompletionItemKind::SNIPPET),
21996 label_details: snippet.description.as_ref().map(|description| {
21997 lsp::CompletionItemLabelDetails {
21998 detail: Some(description.clone()),
21999 description: None,
22000 }
22001 }),
22002 insert_text_format: Some(InsertTextFormat::SNIPPET),
22003 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22004 lsp::InsertReplaceEdit {
22005 new_text: snippet.body.clone(),
22006 insert: lsp_range,
22007 replace: lsp_range,
22008 },
22009 )),
22010 filter_text: Some(snippet.body.clone()),
22011 sort_text: Some(char::MAX.to_string()),
22012 ..lsp::CompletionItem::default()
22013 }),
22014 lsp_defaults: None,
22015 },
22016 label: CodeLabel {
22017 text: matching_prefix.clone(),
22018 runs: Vec::new(),
22019 filter_range: 0..matching_prefix.len(),
22020 },
22021 icon_path: None,
22022 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
22023 single_line: snippet.name.clone().into(),
22024 plain_text: snippet
22025 .description
22026 .clone()
22027 .map(|description| description.into()),
22028 }),
22029 insert_text_mode: None,
22030 confirm: None,
22031 })
22032 }))
22033 }
22034
22035 Ok(CompletionResponse {
22036 completions,
22037 is_incomplete,
22038 })
22039 })
22040}
22041
22042impl CompletionProvider for Entity<Project> {
22043 fn completions(
22044 &self,
22045 _excerpt_id: ExcerptId,
22046 buffer: &Entity<Buffer>,
22047 buffer_position: text::Anchor,
22048 options: CompletionContext,
22049 _window: &mut Window,
22050 cx: &mut Context<Editor>,
22051 ) -> Task<Result<Vec<CompletionResponse>>> {
22052 self.update(cx, |project, cx| {
22053 let snippets = snippet_completions(project, buffer, buffer_position, cx);
22054 let project_completions = project.completions(buffer, buffer_position, options, cx);
22055 cx.background_spawn(async move {
22056 let mut responses = project_completions.await?;
22057 let snippets = snippets.await?;
22058 if !snippets.completions.is_empty() {
22059 responses.push(snippets);
22060 }
22061 Ok(responses)
22062 })
22063 })
22064 }
22065
22066 fn resolve_completions(
22067 &self,
22068 buffer: Entity<Buffer>,
22069 completion_indices: Vec<usize>,
22070 completions: Rc<RefCell<Box<[Completion]>>>,
22071 cx: &mut Context<Editor>,
22072 ) -> Task<Result<bool>> {
22073 self.update(cx, |project, cx| {
22074 project.lsp_store().update(cx, |lsp_store, cx| {
22075 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
22076 })
22077 })
22078 }
22079
22080 fn apply_additional_edits_for_completion(
22081 &self,
22082 buffer: Entity<Buffer>,
22083 completions: Rc<RefCell<Box<[Completion]>>>,
22084 completion_index: usize,
22085 push_to_history: bool,
22086 cx: &mut Context<Editor>,
22087 ) -> Task<Result<Option<language::Transaction>>> {
22088 self.update(cx, |project, cx| {
22089 project.lsp_store().update(cx, |lsp_store, cx| {
22090 lsp_store.apply_additional_edits_for_completion(
22091 buffer,
22092 completions,
22093 completion_index,
22094 push_to_history,
22095 cx,
22096 )
22097 })
22098 })
22099 }
22100
22101 fn is_completion_trigger(
22102 &self,
22103 buffer: &Entity<Buffer>,
22104 position: language::Anchor,
22105 text: &str,
22106 trigger_in_words: bool,
22107 menu_is_open: bool,
22108 cx: &mut Context<Editor>,
22109 ) -> bool {
22110 let mut chars = text.chars();
22111 let char = if let Some(char) = chars.next() {
22112 char
22113 } else {
22114 return false;
22115 };
22116 if chars.next().is_some() {
22117 return false;
22118 }
22119
22120 let buffer = buffer.read(cx);
22121 let snapshot = buffer.snapshot();
22122 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
22123 return false;
22124 }
22125 let classifier = snapshot.char_classifier_at(position).for_completion(true);
22126 if trigger_in_words && classifier.is_word(char) {
22127 return true;
22128 }
22129
22130 buffer.completion_triggers().contains(text)
22131 }
22132}
22133
22134impl SemanticsProvider for Entity<Project> {
22135 fn hover(
22136 &self,
22137 buffer: &Entity<Buffer>,
22138 position: text::Anchor,
22139 cx: &mut App,
22140 ) -> Option<Task<Vec<project::Hover>>> {
22141 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
22142 }
22143
22144 fn document_highlights(
22145 &self,
22146 buffer: &Entity<Buffer>,
22147 position: text::Anchor,
22148 cx: &mut App,
22149 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
22150 Some(self.update(cx, |project, cx| {
22151 project.document_highlights(buffer, position, cx)
22152 }))
22153 }
22154
22155 fn definitions(
22156 &self,
22157 buffer: &Entity<Buffer>,
22158 position: text::Anchor,
22159 kind: GotoDefinitionKind,
22160 cx: &mut App,
22161 ) -> Option<Task<Result<Vec<LocationLink>>>> {
22162 Some(self.update(cx, |project, cx| match kind {
22163 GotoDefinitionKind::Symbol => project.definitions(&buffer, position, cx),
22164 GotoDefinitionKind::Declaration => project.declarations(&buffer, position, cx),
22165 GotoDefinitionKind::Type => project.type_definitions(&buffer, position, cx),
22166 GotoDefinitionKind::Implementation => project.implementations(&buffer, position, cx),
22167 }))
22168 }
22169
22170 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
22171 // TODO: make this work for remote projects
22172 self.update(cx, |project, cx| {
22173 if project
22174 .active_debug_session(cx)
22175 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
22176 {
22177 return true;
22178 }
22179
22180 buffer.update(cx, |buffer, cx| {
22181 project.any_language_server_supports_inlay_hints(buffer, cx)
22182 })
22183 })
22184 }
22185
22186 fn inline_values(
22187 &self,
22188 buffer_handle: Entity<Buffer>,
22189 range: Range<text::Anchor>,
22190 cx: &mut App,
22191 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22192 self.update(cx, |project, cx| {
22193 let (session, active_stack_frame) = project.active_debug_session(cx)?;
22194
22195 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
22196 })
22197 }
22198
22199 fn inlay_hints(
22200 &self,
22201 buffer_handle: Entity<Buffer>,
22202 range: Range<text::Anchor>,
22203 cx: &mut App,
22204 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22205 Some(self.update(cx, |project, cx| {
22206 project.inlay_hints(buffer_handle, range, cx)
22207 }))
22208 }
22209
22210 fn resolve_inlay_hint(
22211 &self,
22212 hint: InlayHint,
22213 buffer_handle: Entity<Buffer>,
22214 server_id: LanguageServerId,
22215 cx: &mut App,
22216 ) -> Option<Task<anyhow::Result<InlayHint>>> {
22217 Some(self.update(cx, |project, cx| {
22218 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
22219 }))
22220 }
22221
22222 fn range_for_rename(
22223 &self,
22224 buffer: &Entity<Buffer>,
22225 position: text::Anchor,
22226 cx: &mut App,
22227 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
22228 Some(self.update(cx, |project, cx| {
22229 let buffer = buffer.clone();
22230 let task = project.prepare_rename(buffer.clone(), position, cx);
22231 cx.spawn(async move |_, cx| {
22232 Ok(match task.await? {
22233 PrepareRenameResponse::Success(range) => Some(range),
22234 PrepareRenameResponse::InvalidPosition => None,
22235 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
22236 // Fallback on using TreeSitter info to determine identifier range
22237 buffer.read_with(cx, |buffer, _| {
22238 let snapshot = buffer.snapshot();
22239 let (range, kind) = snapshot.surrounding_word(position, false);
22240 if kind != Some(CharKind::Word) {
22241 return None;
22242 }
22243 Some(
22244 snapshot.anchor_before(range.start)
22245 ..snapshot.anchor_after(range.end),
22246 )
22247 })?
22248 }
22249 })
22250 })
22251 }))
22252 }
22253
22254 fn perform_rename(
22255 &self,
22256 buffer: &Entity<Buffer>,
22257 position: text::Anchor,
22258 new_name: String,
22259 cx: &mut App,
22260 ) -> Option<Task<Result<ProjectTransaction>>> {
22261 Some(self.update(cx, |project, cx| {
22262 project.perform_rename(buffer.clone(), position, new_name, cx)
22263 }))
22264 }
22265}
22266
22267fn inlay_hint_settings(
22268 location: Anchor,
22269 snapshot: &MultiBufferSnapshot,
22270 cx: &mut Context<Editor>,
22271) -> InlayHintSettings {
22272 let file = snapshot.file_at(location);
22273 let language = snapshot.language_at(location).map(|l| l.name());
22274 language_settings(language, file, cx).inlay_hints
22275}
22276
22277fn consume_contiguous_rows(
22278 contiguous_row_selections: &mut Vec<Selection<Point>>,
22279 selection: &Selection<Point>,
22280 display_map: &DisplaySnapshot,
22281 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
22282) -> (MultiBufferRow, MultiBufferRow) {
22283 contiguous_row_selections.push(selection.clone());
22284 let start_row = starting_row(selection, display_map);
22285 let mut end_row = ending_row(selection, display_map);
22286
22287 while let Some(next_selection) = selections.peek() {
22288 if next_selection.start.row <= end_row.0 {
22289 end_row = ending_row(next_selection, display_map);
22290 contiguous_row_selections.push(selections.next().unwrap().clone());
22291 } else {
22292 break;
22293 }
22294 }
22295 (start_row, end_row)
22296}
22297
22298fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22299 if selection.start.column > 0 {
22300 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
22301 } else {
22302 MultiBufferRow(selection.start.row)
22303 }
22304}
22305
22306fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22307 if next_selection.end.column > 0 || next_selection.is_empty() {
22308 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
22309 } else {
22310 MultiBufferRow(next_selection.end.row)
22311 }
22312}
22313
22314impl EditorSnapshot {
22315 pub fn remote_selections_in_range<'a>(
22316 &'a self,
22317 range: &'a Range<Anchor>,
22318 collaboration_hub: &dyn CollaborationHub,
22319 cx: &'a App,
22320 ) -> impl 'a + Iterator<Item = RemoteSelection> {
22321 let participant_names = collaboration_hub.user_names(cx);
22322 let participant_indices = collaboration_hub.user_participant_indices(cx);
22323 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
22324 let collaborators_by_replica_id = collaborators_by_peer_id
22325 .values()
22326 .map(|collaborator| (collaborator.replica_id, collaborator))
22327 .collect::<HashMap<_, _>>();
22328 self.buffer_snapshot
22329 .selections_in_range(range, false)
22330 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
22331 if replica_id == AGENT_REPLICA_ID {
22332 Some(RemoteSelection {
22333 replica_id,
22334 selection,
22335 cursor_shape,
22336 line_mode,
22337 collaborator_id: CollaboratorId::Agent,
22338 user_name: Some("Agent".into()),
22339 color: cx.theme().players().agent(),
22340 })
22341 } else {
22342 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
22343 let participant_index = participant_indices.get(&collaborator.user_id).copied();
22344 let user_name = participant_names.get(&collaborator.user_id).cloned();
22345 Some(RemoteSelection {
22346 replica_id,
22347 selection,
22348 cursor_shape,
22349 line_mode,
22350 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
22351 user_name,
22352 color: if let Some(index) = participant_index {
22353 cx.theme().players().color_for_participant(index.0)
22354 } else {
22355 cx.theme().players().absent()
22356 },
22357 })
22358 }
22359 })
22360 }
22361
22362 pub fn hunks_for_ranges(
22363 &self,
22364 ranges: impl IntoIterator<Item = Range<Point>>,
22365 ) -> Vec<MultiBufferDiffHunk> {
22366 let mut hunks = Vec::new();
22367 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
22368 HashMap::default();
22369 for query_range in ranges {
22370 let query_rows =
22371 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
22372 for hunk in self.buffer_snapshot.diff_hunks_in_range(
22373 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
22374 ) {
22375 // Include deleted hunks that are adjacent to the query range, because
22376 // otherwise they would be missed.
22377 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
22378 if hunk.status().is_deleted() {
22379 intersects_range |= hunk.row_range.start == query_rows.end;
22380 intersects_range |= hunk.row_range.end == query_rows.start;
22381 }
22382 if intersects_range {
22383 if !processed_buffer_rows
22384 .entry(hunk.buffer_id)
22385 .or_default()
22386 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
22387 {
22388 continue;
22389 }
22390 hunks.push(hunk);
22391 }
22392 }
22393 }
22394
22395 hunks
22396 }
22397
22398 fn display_diff_hunks_for_rows<'a>(
22399 &'a self,
22400 display_rows: Range<DisplayRow>,
22401 folded_buffers: &'a HashSet<BufferId>,
22402 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
22403 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
22404 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
22405
22406 self.buffer_snapshot
22407 .diff_hunks_in_range(buffer_start..buffer_end)
22408 .filter_map(|hunk| {
22409 if folded_buffers.contains(&hunk.buffer_id) {
22410 return None;
22411 }
22412
22413 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
22414 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
22415
22416 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
22417 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
22418
22419 let display_hunk = if hunk_display_start.column() != 0 {
22420 DisplayDiffHunk::Folded {
22421 display_row: hunk_display_start.row(),
22422 }
22423 } else {
22424 let mut end_row = hunk_display_end.row();
22425 if hunk_display_end.column() > 0 {
22426 end_row.0 += 1;
22427 }
22428 let is_created_file = hunk.is_created_file();
22429 DisplayDiffHunk::Unfolded {
22430 status: hunk.status(),
22431 diff_base_byte_range: hunk.diff_base_byte_range,
22432 display_row_range: hunk_display_start.row()..end_row,
22433 multi_buffer_range: Anchor::range_in_buffer(
22434 hunk.excerpt_id,
22435 hunk.buffer_id,
22436 hunk.buffer_range,
22437 ),
22438 is_created_file,
22439 }
22440 };
22441
22442 Some(display_hunk)
22443 })
22444 }
22445
22446 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
22447 self.display_snapshot.buffer_snapshot.language_at(position)
22448 }
22449
22450 pub fn is_focused(&self) -> bool {
22451 self.is_focused
22452 }
22453
22454 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
22455 self.placeholder_text.as_ref()
22456 }
22457
22458 pub fn scroll_position(&self) -> gpui::Point<f32> {
22459 self.scroll_anchor.scroll_position(&self.display_snapshot)
22460 }
22461
22462 fn gutter_dimensions(
22463 &self,
22464 font_id: FontId,
22465 font_size: Pixels,
22466 max_line_number_width: Pixels,
22467 cx: &App,
22468 ) -> Option<GutterDimensions> {
22469 if !self.show_gutter {
22470 return None;
22471 }
22472
22473 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
22474 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
22475
22476 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
22477 matches!(
22478 ProjectSettings::get_global(cx).git.git_gutter,
22479 Some(GitGutterSetting::TrackedFiles)
22480 )
22481 });
22482 let gutter_settings = EditorSettings::get_global(cx).gutter;
22483 let show_line_numbers = self
22484 .show_line_numbers
22485 .unwrap_or(gutter_settings.line_numbers);
22486 let line_gutter_width = if show_line_numbers {
22487 // Avoid flicker-like gutter resizes when the line number gains another digit by
22488 // only resizing the gutter on files with > 10**min_line_number_digits lines.
22489 let min_width_for_number_on_gutter =
22490 ch_advance * gutter_settings.min_line_number_digits as f32;
22491 max_line_number_width.max(min_width_for_number_on_gutter)
22492 } else {
22493 0.0.into()
22494 };
22495
22496 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
22497 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
22498
22499 let git_blame_entries_width =
22500 self.git_blame_gutter_max_author_length
22501 .map(|max_author_length| {
22502 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
22503 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
22504
22505 /// The number of characters to dedicate to gaps and margins.
22506 const SPACING_WIDTH: usize = 4;
22507
22508 let max_char_count = max_author_length.min(renderer.max_author_length())
22509 + ::git::SHORT_SHA_LENGTH
22510 + MAX_RELATIVE_TIMESTAMP.len()
22511 + SPACING_WIDTH;
22512
22513 ch_advance * max_char_count
22514 });
22515
22516 let is_singleton = self.buffer_snapshot.is_singleton();
22517
22518 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
22519 left_padding += if !is_singleton {
22520 ch_width * 4.0
22521 } else if show_runnables || show_breakpoints {
22522 ch_width * 3.0
22523 } else if show_git_gutter && show_line_numbers {
22524 ch_width * 2.0
22525 } else if show_git_gutter || show_line_numbers {
22526 ch_width
22527 } else {
22528 px(0.)
22529 };
22530
22531 let shows_folds = is_singleton && gutter_settings.folds;
22532
22533 let right_padding = if shows_folds && show_line_numbers {
22534 ch_width * 4.0
22535 } else if shows_folds || (!is_singleton && show_line_numbers) {
22536 ch_width * 3.0
22537 } else if show_line_numbers {
22538 ch_width
22539 } else {
22540 px(0.)
22541 };
22542
22543 Some(GutterDimensions {
22544 left_padding,
22545 right_padding,
22546 width: line_gutter_width + left_padding + right_padding,
22547 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
22548 git_blame_entries_width,
22549 })
22550 }
22551
22552 pub fn render_crease_toggle(
22553 &self,
22554 buffer_row: MultiBufferRow,
22555 row_contains_cursor: bool,
22556 editor: Entity<Editor>,
22557 window: &mut Window,
22558 cx: &mut App,
22559 ) -> Option<AnyElement> {
22560 let folded = self.is_line_folded(buffer_row);
22561 let mut is_foldable = false;
22562
22563 if let Some(crease) = self
22564 .crease_snapshot
22565 .query_row(buffer_row, &self.buffer_snapshot)
22566 {
22567 is_foldable = true;
22568 match crease {
22569 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
22570 if let Some(render_toggle) = render_toggle {
22571 let toggle_callback =
22572 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
22573 if folded {
22574 editor.update(cx, |editor, cx| {
22575 editor.fold_at(buffer_row, window, cx)
22576 });
22577 } else {
22578 editor.update(cx, |editor, cx| {
22579 editor.unfold_at(buffer_row, window, cx)
22580 });
22581 }
22582 });
22583 return Some((render_toggle)(
22584 buffer_row,
22585 folded,
22586 toggle_callback,
22587 window,
22588 cx,
22589 ));
22590 }
22591 }
22592 }
22593 }
22594
22595 is_foldable |= self.starts_indent(buffer_row);
22596
22597 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
22598 Some(
22599 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
22600 .toggle_state(folded)
22601 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
22602 if folded {
22603 this.unfold_at(buffer_row, window, cx);
22604 } else {
22605 this.fold_at(buffer_row, window, cx);
22606 }
22607 }))
22608 .into_any_element(),
22609 )
22610 } else {
22611 None
22612 }
22613 }
22614
22615 pub fn render_crease_trailer(
22616 &self,
22617 buffer_row: MultiBufferRow,
22618 window: &mut Window,
22619 cx: &mut App,
22620 ) -> Option<AnyElement> {
22621 let folded = self.is_line_folded(buffer_row);
22622 if let Crease::Inline { render_trailer, .. } = self
22623 .crease_snapshot
22624 .query_row(buffer_row, &self.buffer_snapshot)?
22625 {
22626 let render_trailer = render_trailer.as_ref()?;
22627 Some(render_trailer(buffer_row, folded, window, cx))
22628 } else {
22629 None
22630 }
22631 }
22632}
22633
22634impl Deref for EditorSnapshot {
22635 type Target = DisplaySnapshot;
22636
22637 fn deref(&self) -> &Self::Target {
22638 &self.display_snapshot
22639 }
22640}
22641
22642#[derive(Clone, Debug, PartialEq, Eq)]
22643pub enum EditorEvent {
22644 InputIgnored {
22645 text: Arc<str>,
22646 },
22647 InputHandled {
22648 utf16_range_to_replace: Option<Range<isize>>,
22649 text: Arc<str>,
22650 },
22651 ExcerptsAdded {
22652 buffer: Entity<Buffer>,
22653 predecessor: ExcerptId,
22654 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
22655 },
22656 ExcerptsRemoved {
22657 ids: Vec<ExcerptId>,
22658 removed_buffer_ids: Vec<BufferId>,
22659 },
22660 BufferFoldToggled {
22661 ids: Vec<ExcerptId>,
22662 folded: bool,
22663 },
22664 ExcerptsEdited {
22665 ids: Vec<ExcerptId>,
22666 },
22667 ExcerptsExpanded {
22668 ids: Vec<ExcerptId>,
22669 },
22670 BufferEdited,
22671 Edited {
22672 transaction_id: clock::Lamport,
22673 },
22674 Reparsed(BufferId),
22675 Focused,
22676 FocusedIn,
22677 Blurred,
22678 DirtyChanged,
22679 Saved,
22680 TitleChanged,
22681 DiffBaseChanged,
22682 SelectionsChanged {
22683 local: bool,
22684 },
22685 ScrollPositionChanged {
22686 local: bool,
22687 autoscroll: bool,
22688 },
22689 Closed,
22690 TransactionUndone {
22691 transaction_id: clock::Lamport,
22692 },
22693 TransactionBegun {
22694 transaction_id: clock::Lamport,
22695 },
22696 Reloaded,
22697 CursorShapeChanged,
22698 PushedToNavHistory {
22699 anchor: Anchor,
22700 is_deactivate: bool,
22701 },
22702}
22703
22704impl EventEmitter<EditorEvent> for Editor {}
22705
22706impl Focusable for Editor {
22707 fn focus_handle(&self, _cx: &App) -> FocusHandle {
22708 self.focus_handle.clone()
22709 }
22710}
22711
22712impl Render for Editor {
22713 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22714 let settings = ThemeSettings::get_global(cx);
22715
22716 let mut text_style = match self.mode {
22717 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
22718 color: cx.theme().colors().editor_foreground,
22719 font_family: settings.ui_font.family.clone(),
22720 font_features: settings.ui_font.features.clone(),
22721 font_fallbacks: settings.ui_font.fallbacks.clone(),
22722 font_size: rems(0.875).into(),
22723 font_weight: settings.ui_font.weight,
22724 line_height: relative(settings.buffer_line_height.value()),
22725 ..Default::default()
22726 },
22727 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
22728 color: cx.theme().colors().editor_foreground,
22729 font_family: settings.buffer_font.family.clone(),
22730 font_features: settings.buffer_font.features.clone(),
22731 font_fallbacks: settings.buffer_font.fallbacks.clone(),
22732 font_size: settings.buffer_font_size(cx).into(),
22733 font_weight: settings.buffer_font.weight,
22734 line_height: relative(settings.buffer_line_height.value()),
22735 ..Default::default()
22736 },
22737 };
22738 if let Some(text_style_refinement) = &self.text_style_refinement {
22739 text_style.refine(text_style_refinement)
22740 }
22741
22742 let background = match self.mode {
22743 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
22744 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
22745 EditorMode::Full { .. } => cx.theme().colors().editor_background,
22746 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
22747 };
22748
22749 EditorElement::new(
22750 &cx.entity(),
22751 EditorStyle {
22752 background,
22753 border: cx.theme().colors().border,
22754 local_player: cx.theme().players().local(),
22755 text: text_style,
22756 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
22757 syntax: cx.theme().syntax().clone(),
22758 status: cx.theme().status().clone(),
22759 inlay_hints_style: make_inlay_hints_style(cx),
22760 inline_completion_styles: make_suggestion_styles(cx),
22761 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
22762 show_underlines: self.diagnostics_enabled(),
22763 },
22764 )
22765 }
22766}
22767
22768impl EntityInputHandler for Editor {
22769 fn text_for_range(
22770 &mut self,
22771 range_utf16: Range<usize>,
22772 adjusted_range: &mut Option<Range<usize>>,
22773 _: &mut Window,
22774 cx: &mut Context<Self>,
22775 ) -> Option<String> {
22776 let snapshot = self.buffer.read(cx).read(cx);
22777 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
22778 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
22779 if (start.0..end.0) != range_utf16 {
22780 adjusted_range.replace(start.0..end.0);
22781 }
22782 Some(snapshot.text_for_range(start..end).collect())
22783 }
22784
22785 fn selected_text_range(
22786 &mut self,
22787 ignore_disabled_input: bool,
22788 _: &mut Window,
22789 cx: &mut Context<Self>,
22790 ) -> Option<UTF16Selection> {
22791 // Prevent the IME menu from appearing when holding down an alphabetic key
22792 // while input is disabled.
22793 if !ignore_disabled_input && !self.input_enabled {
22794 return None;
22795 }
22796
22797 let selection = self.selections.newest::<OffsetUtf16>(cx);
22798 let range = selection.range();
22799
22800 Some(UTF16Selection {
22801 range: range.start.0..range.end.0,
22802 reversed: selection.reversed,
22803 })
22804 }
22805
22806 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
22807 let snapshot = self.buffer.read(cx).read(cx);
22808 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
22809 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
22810 }
22811
22812 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
22813 self.clear_highlights::<InputComposition>(cx);
22814 self.ime_transaction.take();
22815 }
22816
22817 fn replace_text_in_range(
22818 &mut self,
22819 range_utf16: Option<Range<usize>>,
22820 text: &str,
22821 window: &mut Window,
22822 cx: &mut Context<Self>,
22823 ) {
22824 if !self.input_enabled {
22825 cx.emit(EditorEvent::InputIgnored { text: text.into() });
22826 return;
22827 }
22828
22829 self.transact(window, cx, |this, window, cx| {
22830 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
22831 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
22832 Some(this.selection_replacement_ranges(range_utf16, cx))
22833 } else {
22834 this.marked_text_ranges(cx)
22835 };
22836
22837 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
22838 let newest_selection_id = this.selections.newest_anchor().id;
22839 this.selections
22840 .all::<OffsetUtf16>(cx)
22841 .iter()
22842 .zip(ranges_to_replace.iter())
22843 .find_map(|(selection, range)| {
22844 if selection.id == newest_selection_id {
22845 Some(
22846 (range.start.0 as isize - selection.head().0 as isize)
22847 ..(range.end.0 as isize - selection.head().0 as isize),
22848 )
22849 } else {
22850 None
22851 }
22852 })
22853 });
22854
22855 cx.emit(EditorEvent::InputHandled {
22856 utf16_range_to_replace: range_to_replace,
22857 text: text.into(),
22858 });
22859
22860 if let Some(new_selected_ranges) = new_selected_ranges {
22861 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22862 selections.select_ranges(new_selected_ranges)
22863 });
22864 this.backspace(&Default::default(), window, cx);
22865 }
22866
22867 this.handle_input(text, window, cx);
22868 });
22869
22870 if let Some(transaction) = self.ime_transaction {
22871 self.buffer.update(cx, |buffer, cx| {
22872 buffer.group_until_transaction(transaction, cx);
22873 });
22874 }
22875
22876 self.unmark_text(window, cx);
22877 }
22878
22879 fn replace_and_mark_text_in_range(
22880 &mut self,
22881 range_utf16: Option<Range<usize>>,
22882 text: &str,
22883 new_selected_range_utf16: Option<Range<usize>>,
22884 window: &mut Window,
22885 cx: &mut Context<Self>,
22886 ) {
22887 if !self.input_enabled {
22888 return;
22889 }
22890
22891 let transaction = self.transact(window, cx, |this, window, cx| {
22892 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
22893 let snapshot = this.buffer.read(cx).read(cx);
22894 if let Some(relative_range_utf16) = range_utf16.as_ref() {
22895 for marked_range in &mut marked_ranges {
22896 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
22897 marked_range.start.0 += relative_range_utf16.start;
22898 marked_range.start =
22899 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
22900 marked_range.end =
22901 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
22902 }
22903 }
22904 Some(marked_ranges)
22905 } else if let Some(range_utf16) = range_utf16 {
22906 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
22907 Some(this.selection_replacement_ranges(range_utf16, cx))
22908 } else {
22909 None
22910 };
22911
22912 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
22913 let newest_selection_id = this.selections.newest_anchor().id;
22914 this.selections
22915 .all::<OffsetUtf16>(cx)
22916 .iter()
22917 .zip(ranges_to_replace.iter())
22918 .find_map(|(selection, range)| {
22919 if selection.id == newest_selection_id {
22920 Some(
22921 (range.start.0 as isize - selection.head().0 as isize)
22922 ..(range.end.0 as isize - selection.head().0 as isize),
22923 )
22924 } else {
22925 None
22926 }
22927 })
22928 });
22929
22930 cx.emit(EditorEvent::InputHandled {
22931 utf16_range_to_replace: range_to_replace,
22932 text: text.into(),
22933 });
22934
22935 if let Some(ranges) = ranges_to_replace {
22936 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22937 s.select_ranges(ranges)
22938 });
22939 }
22940
22941 let marked_ranges = {
22942 let snapshot = this.buffer.read(cx).read(cx);
22943 this.selections
22944 .disjoint_anchors()
22945 .iter()
22946 .map(|selection| {
22947 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
22948 })
22949 .collect::<Vec<_>>()
22950 };
22951
22952 if text.is_empty() {
22953 this.unmark_text(window, cx);
22954 } else {
22955 this.highlight_text::<InputComposition>(
22956 marked_ranges.clone(),
22957 HighlightStyle {
22958 underline: Some(UnderlineStyle {
22959 thickness: px(1.),
22960 color: None,
22961 wavy: false,
22962 }),
22963 ..Default::default()
22964 },
22965 cx,
22966 );
22967 }
22968
22969 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
22970 let use_autoclose = this.use_autoclose;
22971 let use_auto_surround = this.use_auto_surround;
22972 this.set_use_autoclose(false);
22973 this.set_use_auto_surround(false);
22974 this.handle_input(text, window, cx);
22975 this.set_use_autoclose(use_autoclose);
22976 this.set_use_auto_surround(use_auto_surround);
22977
22978 if let Some(new_selected_range) = new_selected_range_utf16 {
22979 let snapshot = this.buffer.read(cx).read(cx);
22980 let new_selected_ranges = marked_ranges
22981 .into_iter()
22982 .map(|marked_range| {
22983 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
22984 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
22985 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
22986 snapshot.clip_offset_utf16(new_start, Bias::Left)
22987 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
22988 })
22989 .collect::<Vec<_>>();
22990
22991 drop(snapshot);
22992 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22993 selections.select_ranges(new_selected_ranges)
22994 });
22995 }
22996 });
22997
22998 self.ime_transaction = self.ime_transaction.or(transaction);
22999 if let Some(transaction) = self.ime_transaction {
23000 self.buffer.update(cx, |buffer, cx| {
23001 buffer.group_until_transaction(transaction, cx);
23002 });
23003 }
23004
23005 if self.text_highlights::<InputComposition>(cx).is_none() {
23006 self.ime_transaction.take();
23007 }
23008 }
23009
23010 fn bounds_for_range(
23011 &mut self,
23012 range_utf16: Range<usize>,
23013 element_bounds: gpui::Bounds<Pixels>,
23014 window: &mut Window,
23015 cx: &mut Context<Self>,
23016 ) -> Option<gpui::Bounds<Pixels>> {
23017 let text_layout_details = self.text_layout_details(window);
23018 let CharacterDimensions {
23019 em_width,
23020 em_advance,
23021 line_height,
23022 } = self.character_dimensions(window);
23023
23024 let snapshot = self.snapshot(window, cx);
23025 let scroll_position = snapshot.scroll_position();
23026 let scroll_left = scroll_position.x * em_advance;
23027
23028 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
23029 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
23030 + self.gutter_dimensions.full_width();
23031 let y = line_height * (start.row().as_f32() - scroll_position.y);
23032
23033 Some(Bounds {
23034 origin: element_bounds.origin + point(x, y),
23035 size: size(em_width, line_height),
23036 })
23037 }
23038
23039 fn character_index_for_point(
23040 &mut self,
23041 point: gpui::Point<Pixels>,
23042 _window: &mut Window,
23043 _cx: &mut Context<Self>,
23044 ) -> Option<usize> {
23045 let position_map = self.last_position_map.as_ref()?;
23046 if !position_map.text_hitbox.contains(&point) {
23047 return None;
23048 }
23049 let display_point = position_map.point_for_position(point).previous_valid;
23050 let anchor = position_map
23051 .snapshot
23052 .display_point_to_anchor(display_point, Bias::Left);
23053 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
23054 Some(utf16_offset.0)
23055 }
23056}
23057
23058trait SelectionExt {
23059 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
23060 fn spanned_rows(
23061 &self,
23062 include_end_if_at_line_start: bool,
23063 map: &DisplaySnapshot,
23064 ) -> Range<MultiBufferRow>;
23065}
23066
23067impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
23068 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
23069 let start = self
23070 .start
23071 .to_point(&map.buffer_snapshot)
23072 .to_display_point(map);
23073 let end = self
23074 .end
23075 .to_point(&map.buffer_snapshot)
23076 .to_display_point(map);
23077 if self.reversed {
23078 end..start
23079 } else {
23080 start..end
23081 }
23082 }
23083
23084 fn spanned_rows(
23085 &self,
23086 include_end_if_at_line_start: bool,
23087 map: &DisplaySnapshot,
23088 ) -> Range<MultiBufferRow> {
23089 let start = self.start.to_point(&map.buffer_snapshot);
23090 let mut end = self.end.to_point(&map.buffer_snapshot);
23091 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
23092 end.row -= 1;
23093 }
23094
23095 let buffer_start = map.prev_line_boundary(start).0;
23096 let buffer_end = map.next_line_boundary(end).0;
23097 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
23098 }
23099}
23100
23101impl<T: InvalidationRegion> InvalidationStack<T> {
23102 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
23103 where
23104 S: Clone + ToOffset,
23105 {
23106 while let Some(region) = self.last() {
23107 let all_selections_inside_invalidation_ranges =
23108 if selections.len() == region.ranges().len() {
23109 selections
23110 .iter()
23111 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
23112 .all(|(selection, invalidation_range)| {
23113 let head = selection.head().to_offset(buffer);
23114 invalidation_range.start <= head && invalidation_range.end >= head
23115 })
23116 } else {
23117 false
23118 };
23119
23120 if all_selections_inside_invalidation_ranges {
23121 break;
23122 } else {
23123 self.pop();
23124 }
23125 }
23126 }
23127}
23128
23129impl<T> Default for InvalidationStack<T> {
23130 fn default() -> Self {
23131 Self(Default::default())
23132 }
23133}
23134
23135impl<T> Deref for InvalidationStack<T> {
23136 type Target = Vec<T>;
23137
23138 fn deref(&self) -> &Self::Target {
23139 &self.0
23140 }
23141}
23142
23143impl<T> DerefMut for InvalidationStack<T> {
23144 fn deref_mut(&mut self) -> &mut Self::Target {
23145 &mut self.0
23146 }
23147}
23148
23149impl InvalidationRegion for SnippetState {
23150 fn ranges(&self) -> &[Range<Anchor>] {
23151 &self.ranges[self.active_index]
23152 }
23153}
23154
23155fn inline_completion_edit_text(
23156 current_snapshot: &BufferSnapshot,
23157 edits: &[(Range<Anchor>, String)],
23158 edit_preview: &EditPreview,
23159 include_deletions: bool,
23160 cx: &App,
23161) -> HighlightedText {
23162 let edits = edits
23163 .iter()
23164 .map(|(anchor, text)| {
23165 (
23166 anchor.start.text_anchor..anchor.end.text_anchor,
23167 text.clone(),
23168 )
23169 })
23170 .collect::<Vec<_>>();
23171
23172 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
23173}
23174
23175pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
23176 match severity {
23177 lsp::DiagnosticSeverity::ERROR => colors.error,
23178 lsp::DiagnosticSeverity::WARNING => colors.warning,
23179 lsp::DiagnosticSeverity::INFORMATION => colors.info,
23180 lsp::DiagnosticSeverity::HINT => colors.info,
23181 _ => colors.ignored,
23182 }
23183}
23184
23185pub fn styled_runs_for_code_label<'a>(
23186 label: &'a CodeLabel,
23187 syntax_theme: &'a theme::SyntaxTheme,
23188) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
23189 let fade_out = HighlightStyle {
23190 fade_out: Some(0.35),
23191 ..Default::default()
23192 };
23193
23194 let mut prev_end = label.filter_range.end;
23195 label
23196 .runs
23197 .iter()
23198 .enumerate()
23199 .flat_map(move |(ix, (range, highlight_id))| {
23200 let style = if let Some(style) = highlight_id.style(syntax_theme) {
23201 style
23202 } else {
23203 return Default::default();
23204 };
23205 let mut muted_style = style;
23206 muted_style.highlight(fade_out);
23207
23208 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
23209 if range.start >= label.filter_range.end {
23210 if range.start > prev_end {
23211 runs.push((prev_end..range.start, fade_out));
23212 }
23213 runs.push((range.clone(), muted_style));
23214 } else if range.end <= label.filter_range.end {
23215 runs.push((range.clone(), style));
23216 } else {
23217 runs.push((range.start..label.filter_range.end, style));
23218 runs.push((label.filter_range.end..range.end, muted_style));
23219 }
23220 prev_end = cmp::max(prev_end, range.end);
23221
23222 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
23223 runs.push((prev_end..label.text.len(), fade_out));
23224 }
23225
23226 runs
23227 })
23228}
23229
23230pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
23231 let mut prev_index = 0;
23232 let mut prev_codepoint: Option<char> = None;
23233 text.char_indices()
23234 .chain([(text.len(), '\0')])
23235 .filter_map(move |(index, codepoint)| {
23236 let prev_codepoint = prev_codepoint.replace(codepoint)?;
23237 let is_boundary = index == text.len()
23238 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
23239 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
23240 if is_boundary {
23241 let chunk = &text[prev_index..index];
23242 prev_index = index;
23243 Some(chunk)
23244 } else {
23245 None
23246 }
23247 })
23248}
23249
23250pub trait RangeToAnchorExt: Sized {
23251 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
23252
23253 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
23254 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
23255 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
23256 }
23257}
23258
23259impl<T: ToOffset> RangeToAnchorExt for Range<T> {
23260 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
23261 let start_offset = self.start.to_offset(snapshot);
23262 let end_offset = self.end.to_offset(snapshot);
23263 if start_offset == end_offset {
23264 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
23265 } else {
23266 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
23267 }
23268 }
23269}
23270
23271pub trait RowExt {
23272 fn as_f32(&self) -> f32;
23273
23274 fn next_row(&self) -> Self;
23275
23276 fn previous_row(&self) -> Self;
23277
23278 fn minus(&self, other: Self) -> u32;
23279}
23280
23281impl RowExt for DisplayRow {
23282 fn as_f32(&self) -> f32 {
23283 self.0 as f32
23284 }
23285
23286 fn next_row(&self) -> Self {
23287 Self(self.0 + 1)
23288 }
23289
23290 fn previous_row(&self) -> Self {
23291 Self(self.0.saturating_sub(1))
23292 }
23293
23294 fn minus(&self, other: Self) -> u32 {
23295 self.0 - other.0
23296 }
23297}
23298
23299impl RowExt for MultiBufferRow {
23300 fn as_f32(&self) -> f32 {
23301 self.0 as f32
23302 }
23303
23304 fn next_row(&self) -> Self {
23305 Self(self.0 + 1)
23306 }
23307
23308 fn previous_row(&self) -> Self {
23309 Self(self.0.saturating_sub(1))
23310 }
23311
23312 fn minus(&self, other: Self) -> u32 {
23313 self.0 - other.0
23314 }
23315}
23316
23317trait RowRangeExt {
23318 type Row;
23319
23320 fn len(&self) -> usize;
23321
23322 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
23323}
23324
23325impl RowRangeExt for Range<MultiBufferRow> {
23326 type Row = MultiBufferRow;
23327
23328 fn len(&self) -> usize {
23329 (self.end.0 - self.start.0) as usize
23330 }
23331
23332 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
23333 (self.start.0..self.end.0).map(MultiBufferRow)
23334 }
23335}
23336
23337impl RowRangeExt for Range<DisplayRow> {
23338 type Row = DisplayRow;
23339
23340 fn len(&self) -> usize {
23341 (self.end.0 - self.start.0) as usize
23342 }
23343
23344 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
23345 (self.start.0..self.end.0).map(DisplayRow)
23346 }
23347}
23348
23349/// If select range has more than one line, we
23350/// just point the cursor to range.start.
23351fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
23352 if range.start.row == range.end.row {
23353 range
23354 } else {
23355 range.start..range.start
23356 }
23357}
23358pub struct KillRing(ClipboardItem);
23359impl Global for KillRing {}
23360
23361const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
23362
23363enum BreakpointPromptEditAction {
23364 Log,
23365 Condition,
23366 HitCondition,
23367}
23368
23369struct BreakpointPromptEditor {
23370 pub(crate) prompt: Entity<Editor>,
23371 editor: WeakEntity<Editor>,
23372 breakpoint_anchor: Anchor,
23373 breakpoint: Breakpoint,
23374 edit_action: BreakpointPromptEditAction,
23375 block_ids: HashSet<CustomBlockId>,
23376 editor_margins: Arc<Mutex<EditorMargins>>,
23377 _subscriptions: Vec<Subscription>,
23378}
23379
23380impl BreakpointPromptEditor {
23381 const MAX_LINES: u8 = 4;
23382
23383 fn new(
23384 editor: WeakEntity<Editor>,
23385 breakpoint_anchor: Anchor,
23386 breakpoint: Breakpoint,
23387 edit_action: BreakpointPromptEditAction,
23388 window: &mut Window,
23389 cx: &mut Context<Self>,
23390 ) -> Self {
23391 let base_text = match edit_action {
23392 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
23393 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
23394 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
23395 }
23396 .map(|msg| msg.to_string())
23397 .unwrap_or_default();
23398
23399 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
23400 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
23401
23402 let prompt = cx.new(|cx| {
23403 let mut prompt = Editor::new(
23404 EditorMode::AutoHeight {
23405 min_lines: 1,
23406 max_lines: Some(Self::MAX_LINES as usize),
23407 },
23408 buffer,
23409 None,
23410 window,
23411 cx,
23412 );
23413 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
23414 prompt.set_show_cursor_when_unfocused(false, cx);
23415 prompt.set_placeholder_text(
23416 match edit_action {
23417 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
23418 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
23419 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
23420 },
23421 cx,
23422 );
23423
23424 prompt
23425 });
23426
23427 Self {
23428 prompt,
23429 editor,
23430 breakpoint_anchor,
23431 breakpoint,
23432 edit_action,
23433 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
23434 block_ids: Default::default(),
23435 _subscriptions: vec![],
23436 }
23437 }
23438
23439 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
23440 self.block_ids.extend(block_ids)
23441 }
23442
23443 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
23444 if let Some(editor) = self.editor.upgrade() {
23445 let message = self
23446 .prompt
23447 .read(cx)
23448 .buffer
23449 .read(cx)
23450 .as_singleton()
23451 .expect("A multi buffer in breakpoint prompt isn't possible")
23452 .read(cx)
23453 .as_rope()
23454 .to_string();
23455
23456 editor.update(cx, |editor, cx| {
23457 editor.edit_breakpoint_at_anchor(
23458 self.breakpoint_anchor,
23459 self.breakpoint.clone(),
23460 match self.edit_action {
23461 BreakpointPromptEditAction::Log => {
23462 BreakpointEditAction::EditLogMessage(message.into())
23463 }
23464 BreakpointPromptEditAction::Condition => {
23465 BreakpointEditAction::EditCondition(message.into())
23466 }
23467 BreakpointPromptEditAction::HitCondition => {
23468 BreakpointEditAction::EditHitCondition(message.into())
23469 }
23470 },
23471 cx,
23472 );
23473
23474 editor.remove_blocks(self.block_ids.clone(), None, cx);
23475 cx.focus_self(window);
23476 });
23477 }
23478 }
23479
23480 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
23481 self.editor
23482 .update(cx, |editor, cx| {
23483 editor.remove_blocks(self.block_ids.clone(), None, cx);
23484 window.focus(&editor.focus_handle);
23485 })
23486 .log_err();
23487 }
23488
23489 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
23490 let settings = ThemeSettings::get_global(cx);
23491 let text_style = TextStyle {
23492 color: if self.prompt.read(cx).read_only(cx) {
23493 cx.theme().colors().text_disabled
23494 } else {
23495 cx.theme().colors().text
23496 },
23497 font_family: settings.buffer_font.family.clone(),
23498 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23499 font_size: settings.buffer_font_size(cx).into(),
23500 font_weight: settings.buffer_font.weight,
23501 line_height: relative(settings.buffer_line_height.value()),
23502 ..Default::default()
23503 };
23504 EditorElement::new(
23505 &self.prompt,
23506 EditorStyle {
23507 background: cx.theme().colors().editor_background,
23508 local_player: cx.theme().players().local(),
23509 text: text_style,
23510 ..Default::default()
23511 },
23512 )
23513 }
23514}
23515
23516impl Render for BreakpointPromptEditor {
23517 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23518 let editor_margins = *self.editor_margins.lock();
23519 let gutter_dimensions = editor_margins.gutter;
23520 h_flex()
23521 .key_context("Editor")
23522 .bg(cx.theme().colors().editor_background)
23523 .border_y_1()
23524 .border_color(cx.theme().status().info_border)
23525 .size_full()
23526 .py(window.line_height() / 2.5)
23527 .on_action(cx.listener(Self::confirm))
23528 .on_action(cx.listener(Self::cancel))
23529 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
23530 .child(div().flex_1().child(self.render_prompt_editor(cx)))
23531 }
23532}
23533
23534impl Focusable for BreakpointPromptEditor {
23535 fn focus_handle(&self, cx: &App) -> FocusHandle {
23536 self.prompt.focus_handle(cx)
23537 }
23538}
23539
23540fn all_edits_insertions_or_deletions(
23541 edits: &Vec<(Range<Anchor>, String)>,
23542 snapshot: &MultiBufferSnapshot,
23543) -> bool {
23544 let mut all_insertions = true;
23545 let mut all_deletions = true;
23546
23547 for (range, new_text) in edits.iter() {
23548 let range_is_empty = range.to_offset(&snapshot).is_empty();
23549 let text_is_empty = new_text.is_empty();
23550
23551 if range_is_empty != text_is_empty {
23552 if range_is_empty {
23553 all_deletions = false;
23554 } else {
23555 all_insertions = false;
23556 }
23557 } else {
23558 return false;
23559 }
23560
23561 if !all_insertions && !all_deletions {
23562 return false;
23563 }
23564 }
23565 all_insertions || all_deletions
23566}
23567
23568struct MissingEditPredictionKeybindingTooltip;
23569
23570impl Render for MissingEditPredictionKeybindingTooltip {
23571 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23572 ui::tooltip_container(window, cx, |container, _, cx| {
23573 container
23574 .flex_shrink_0()
23575 .max_w_80()
23576 .min_h(rems_from_px(124.))
23577 .justify_between()
23578 .child(
23579 v_flex()
23580 .flex_1()
23581 .text_ui_sm(cx)
23582 .child(Label::new("Conflict with Accept Keybinding"))
23583 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
23584 )
23585 .child(
23586 h_flex()
23587 .pb_1()
23588 .gap_1()
23589 .items_end()
23590 .w_full()
23591 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
23592 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
23593 }))
23594 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
23595 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
23596 })),
23597 )
23598 })
23599 }
23600}
23601
23602#[derive(Debug, Clone, Copy, PartialEq)]
23603pub struct LineHighlight {
23604 pub background: Background,
23605 pub border: Option<gpui::Hsla>,
23606 pub include_gutter: bool,
23607 pub type_id: Option<TypeId>,
23608}
23609
23610struct LineManipulationResult {
23611 pub new_text: String,
23612 pub line_count_before: usize,
23613 pub line_count_after: usize,
23614}
23615
23616fn render_diff_hunk_controls(
23617 row: u32,
23618 status: &DiffHunkStatus,
23619 hunk_range: Range<Anchor>,
23620 is_created_file: bool,
23621 line_height: Pixels,
23622 editor: &Entity<Editor>,
23623 _window: &mut Window,
23624 cx: &mut App,
23625) -> AnyElement {
23626 h_flex()
23627 .h(line_height)
23628 .mr_1()
23629 .gap_1()
23630 .px_0p5()
23631 .pb_1()
23632 .border_x_1()
23633 .border_b_1()
23634 .border_color(cx.theme().colors().border_variant)
23635 .rounded_b_lg()
23636 .bg(cx.theme().colors().editor_background)
23637 .gap_1()
23638 .block_mouse_except_scroll()
23639 .shadow_md()
23640 .child(if status.has_secondary_hunk() {
23641 Button::new(("stage", row as u64), "Stage")
23642 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
23643 .tooltip({
23644 let focus_handle = editor.focus_handle(cx);
23645 move |window, cx| {
23646 Tooltip::for_action_in(
23647 "Stage Hunk",
23648 &::git::ToggleStaged,
23649 &focus_handle,
23650 window,
23651 cx,
23652 )
23653 }
23654 })
23655 .on_click({
23656 let editor = editor.clone();
23657 move |_event, _window, cx| {
23658 editor.update(cx, |editor, cx| {
23659 editor.stage_or_unstage_diff_hunks(
23660 true,
23661 vec![hunk_range.start..hunk_range.start],
23662 cx,
23663 );
23664 });
23665 }
23666 })
23667 } else {
23668 Button::new(("unstage", row as u64), "Unstage")
23669 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
23670 .tooltip({
23671 let focus_handle = editor.focus_handle(cx);
23672 move |window, cx| {
23673 Tooltip::for_action_in(
23674 "Unstage Hunk",
23675 &::git::ToggleStaged,
23676 &focus_handle,
23677 window,
23678 cx,
23679 )
23680 }
23681 })
23682 .on_click({
23683 let editor = editor.clone();
23684 move |_event, _window, cx| {
23685 editor.update(cx, |editor, cx| {
23686 editor.stage_or_unstage_diff_hunks(
23687 false,
23688 vec![hunk_range.start..hunk_range.start],
23689 cx,
23690 );
23691 });
23692 }
23693 })
23694 })
23695 .child(
23696 Button::new(("restore", row as u64), "Restore")
23697 .tooltip({
23698 let focus_handle = editor.focus_handle(cx);
23699 move |window, cx| {
23700 Tooltip::for_action_in(
23701 "Restore Hunk",
23702 &::git::Restore,
23703 &focus_handle,
23704 window,
23705 cx,
23706 )
23707 }
23708 })
23709 .on_click({
23710 let editor = editor.clone();
23711 move |_event, window, cx| {
23712 editor.update(cx, |editor, cx| {
23713 let snapshot = editor.snapshot(window, cx);
23714 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
23715 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
23716 });
23717 }
23718 })
23719 .disabled(is_created_file),
23720 )
23721 .when(
23722 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
23723 |el| {
23724 el.child(
23725 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
23726 .shape(IconButtonShape::Square)
23727 .icon_size(IconSize::Small)
23728 // .disabled(!has_multiple_hunks)
23729 .tooltip({
23730 let focus_handle = editor.focus_handle(cx);
23731 move |window, cx| {
23732 Tooltip::for_action_in(
23733 "Next Hunk",
23734 &GoToHunk,
23735 &focus_handle,
23736 window,
23737 cx,
23738 )
23739 }
23740 })
23741 .on_click({
23742 let editor = editor.clone();
23743 move |_event, window, cx| {
23744 editor.update(cx, |editor, cx| {
23745 let snapshot = editor.snapshot(window, cx);
23746 let position =
23747 hunk_range.end.to_point(&snapshot.buffer_snapshot);
23748 editor.go_to_hunk_before_or_after_position(
23749 &snapshot,
23750 position,
23751 Direction::Next,
23752 window,
23753 cx,
23754 );
23755 editor.expand_selected_diff_hunks(cx);
23756 });
23757 }
23758 }),
23759 )
23760 .child(
23761 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
23762 .shape(IconButtonShape::Square)
23763 .icon_size(IconSize::Small)
23764 // .disabled(!has_multiple_hunks)
23765 .tooltip({
23766 let focus_handle = editor.focus_handle(cx);
23767 move |window, cx| {
23768 Tooltip::for_action_in(
23769 "Previous Hunk",
23770 &GoToPreviousHunk,
23771 &focus_handle,
23772 window,
23773 cx,
23774 )
23775 }
23776 })
23777 .on_click({
23778 let editor = editor.clone();
23779 move |_event, window, cx| {
23780 editor.update(cx, |editor, cx| {
23781 let snapshot = editor.snapshot(window, cx);
23782 let point =
23783 hunk_range.start.to_point(&snapshot.buffer_snapshot);
23784 editor.go_to_hunk_before_or_after_position(
23785 &snapshot,
23786 point,
23787 Direction::Prev,
23788 window,
23789 cx,
23790 );
23791 editor.expand_selected_diff_hunks(cx);
23792 });
23793 }
23794 }),
23795 )
23796 },
23797 )
23798 .into_any_element()
23799}