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 unintended 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 task
8239 let Some((buffer, buffer_row, tasks)) = self
8240 .find_enclosing_node_task(cx)
8241 // Or find the task that's closest in row-distance.
8242 .or_else(|| self.find_closest_task(cx))
8243 else {
8244 return;
8245 };
8246
8247 let reveal_strategy = action.reveal;
8248 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8249 cx.spawn_in(window, async move |_, cx| {
8250 let context = task_context.await?;
8251 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8252
8253 let resolved = &mut resolved_task.resolved;
8254 resolved.reveal = reveal_strategy;
8255
8256 workspace
8257 .update_in(cx, |workspace, window, cx| {
8258 workspace.schedule_resolved_task(
8259 task_source_kind,
8260 resolved_task,
8261 false,
8262 window,
8263 cx,
8264 );
8265 })
8266 .ok()
8267 })
8268 .detach();
8269 }
8270
8271 fn find_closest_task(
8272 &mut self,
8273 cx: &mut Context<Self>,
8274 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8275 let cursor_row = self.selections.newest_adjusted(cx).head().row;
8276
8277 let ((buffer_id, row), tasks) = self
8278 .tasks
8279 .iter()
8280 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8281
8282 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8283 let tasks = Arc::new(tasks.to_owned());
8284 Some((buffer, *row, tasks))
8285 }
8286
8287 fn find_enclosing_node_task(
8288 &mut self,
8289 cx: &mut Context<Self>,
8290 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8291 let snapshot = self.buffer.read(cx).snapshot(cx);
8292 let offset = self.selections.newest::<usize>(cx).head();
8293 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8294 let buffer_id = excerpt.buffer().remote_id();
8295
8296 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8297 let mut cursor = layer.node().walk();
8298
8299 while cursor.goto_first_child_for_byte(offset).is_some() {
8300 if cursor.node().end_byte() == offset {
8301 cursor.goto_next_sibling();
8302 }
8303 }
8304
8305 // Ascend to the smallest ancestor that contains the range and has a task.
8306 loop {
8307 let node = cursor.node();
8308 let node_range = node.byte_range();
8309 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8310
8311 // Check if this node contains our offset
8312 if node_range.start <= offset && node_range.end >= offset {
8313 // If it contains offset, check for task
8314 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8315 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8316 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8317 }
8318 }
8319
8320 if !cursor.goto_parent() {
8321 break;
8322 }
8323 }
8324 None
8325 }
8326
8327 fn render_run_indicator(
8328 &self,
8329 _style: &EditorStyle,
8330 is_active: bool,
8331 row: DisplayRow,
8332 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8333 cx: &mut Context<Self>,
8334 ) -> IconButton {
8335 let color = Color::Muted;
8336 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8337
8338 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
8339 .shape(ui::IconButtonShape::Square)
8340 .icon_size(IconSize::XSmall)
8341 .icon_color(color)
8342 .toggle_state(is_active)
8343 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8344 let quick_launch = e.down.button == MouseButton::Left;
8345 window.focus(&editor.focus_handle(cx));
8346 editor.toggle_code_actions(
8347 &ToggleCodeActions {
8348 deployed_from: Some(CodeActionSource::RunMenu(row)),
8349 quick_launch,
8350 },
8351 window,
8352 cx,
8353 );
8354 }))
8355 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8356 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
8357 }))
8358 }
8359
8360 pub fn context_menu_visible(&self) -> bool {
8361 !self.edit_prediction_preview_is_active()
8362 && self
8363 .context_menu
8364 .borrow()
8365 .as_ref()
8366 .map_or(false, |menu| menu.visible())
8367 }
8368
8369 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8370 self.context_menu
8371 .borrow()
8372 .as_ref()
8373 .map(|menu| menu.origin())
8374 }
8375
8376 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8377 self.context_menu_options = Some(options);
8378 }
8379
8380 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
8381 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
8382
8383 fn render_edit_prediction_popover(
8384 &mut self,
8385 text_bounds: &Bounds<Pixels>,
8386 content_origin: gpui::Point<Pixels>,
8387 right_margin: Pixels,
8388 editor_snapshot: &EditorSnapshot,
8389 visible_row_range: Range<DisplayRow>,
8390 scroll_top: f32,
8391 scroll_bottom: f32,
8392 line_layouts: &[LineWithInvisibles],
8393 line_height: Pixels,
8394 scroll_pixel_position: gpui::Point<Pixels>,
8395 newest_selection_head: Option<DisplayPoint>,
8396 editor_width: Pixels,
8397 style: &EditorStyle,
8398 window: &mut Window,
8399 cx: &mut App,
8400 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8401 if self.mode().is_minimap() {
8402 return None;
8403 }
8404 let active_inline_completion = self.active_inline_completion.as_ref()?;
8405
8406 if self.edit_prediction_visible_in_cursor_popover(true) {
8407 return None;
8408 }
8409
8410 match &active_inline_completion.completion {
8411 InlineCompletion::Move { target, .. } => {
8412 let target_display_point = target.to_display_point(editor_snapshot);
8413
8414 if self.edit_prediction_requires_modifier() {
8415 if !self.edit_prediction_preview_is_active() {
8416 return None;
8417 }
8418
8419 self.render_edit_prediction_modifier_jump_popover(
8420 text_bounds,
8421 content_origin,
8422 visible_row_range,
8423 line_layouts,
8424 line_height,
8425 scroll_pixel_position,
8426 newest_selection_head,
8427 target_display_point,
8428 window,
8429 cx,
8430 )
8431 } else {
8432 self.render_edit_prediction_eager_jump_popover(
8433 text_bounds,
8434 content_origin,
8435 editor_snapshot,
8436 visible_row_range,
8437 scroll_top,
8438 scroll_bottom,
8439 line_height,
8440 scroll_pixel_position,
8441 target_display_point,
8442 editor_width,
8443 window,
8444 cx,
8445 )
8446 }
8447 }
8448 InlineCompletion::Edit {
8449 display_mode: EditDisplayMode::Inline,
8450 ..
8451 } => None,
8452 InlineCompletion::Edit {
8453 display_mode: EditDisplayMode::TabAccept,
8454 edits,
8455 ..
8456 } => {
8457 let range = &edits.first()?.0;
8458 let target_display_point = range.end.to_display_point(editor_snapshot);
8459
8460 self.render_edit_prediction_end_of_line_popover(
8461 "Accept",
8462 editor_snapshot,
8463 visible_row_range,
8464 target_display_point,
8465 line_height,
8466 scroll_pixel_position,
8467 content_origin,
8468 editor_width,
8469 window,
8470 cx,
8471 )
8472 }
8473 InlineCompletion::Edit {
8474 edits,
8475 edit_preview,
8476 display_mode: EditDisplayMode::DiffPopover,
8477 snapshot,
8478 } => self.render_edit_prediction_diff_popover(
8479 text_bounds,
8480 content_origin,
8481 right_margin,
8482 editor_snapshot,
8483 visible_row_range,
8484 line_layouts,
8485 line_height,
8486 scroll_pixel_position,
8487 newest_selection_head,
8488 editor_width,
8489 style,
8490 edits,
8491 edit_preview,
8492 snapshot,
8493 window,
8494 cx,
8495 ),
8496 }
8497 }
8498
8499 fn render_edit_prediction_modifier_jump_popover(
8500 &mut self,
8501 text_bounds: &Bounds<Pixels>,
8502 content_origin: gpui::Point<Pixels>,
8503 visible_row_range: Range<DisplayRow>,
8504 line_layouts: &[LineWithInvisibles],
8505 line_height: Pixels,
8506 scroll_pixel_position: gpui::Point<Pixels>,
8507 newest_selection_head: Option<DisplayPoint>,
8508 target_display_point: DisplayPoint,
8509 window: &mut Window,
8510 cx: &mut App,
8511 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8512 let scrolled_content_origin =
8513 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
8514
8515 const SCROLL_PADDING_Y: Pixels = px(12.);
8516
8517 if target_display_point.row() < visible_row_range.start {
8518 return self.render_edit_prediction_scroll_popover(
8519 |_| SCROLL_PADDING_Y,
8520 IconName::ArrowUp,
8521 visible_row_range,
8522 line_layouts,
8523 newest_selection_head,
8524 scrolled_content_origin,
8525 window,
8526 cx,
8527 );
8528 } else if target_display_point.row() >= visible_row_range.end {
8529 return self.render_edit_prediction_scroll_popover(
8530 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8531 IconName::ArrowDown,
8532 visible_row_range,
8533 line_layouts,
8534 newest_selection_head,
8535 scrolled_content_origin,
8536 window,
8537 cx,
8538 );
8539 }
8540
8541 const POLE_WIDTH: Pixels = px(2.);
8542
8543 let line_layout =
8544 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8545 let target_column = target_display_point.column() as usize;
8546
8547 let target_x = line_layout.x_for_index(target_column);
8548 let target_y =
8549 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
8550
8551 let flag_on_right = target_x < text_bounds.size.width / 2.;
8552
8553 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8554 border_color.l += 0.001;
8555
8556 let mut element = v_flex()
8557 .items_end()
8558 .when(flag_on_right, |el| el.items_start())
8559 .child(if flag_on_right {
8560 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8561 .rounded_bl(px(0.))
8562 .rounded_tl(px(0.))
8563 .border_l_2()
8564 .border_color(border_color)
8565 } else {
8566 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8567 .rounded_br(px(0.))
8568 .rounded_tr(px(0.))
8569 .border_r_2()
8570 .border_color(border_color)
8571 })
8572 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8573 .into_any();
8574
8575 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8576
8577 let mut origin = scrolled_content_origin + point(target_x, target_y)
8578 - point(
8579 if flag_on_right {
8580 POLE_WIDTH
8581 } else {
8582 size.width - POLE_WIDTH
8583 },
8584 size.height - line_height,
8585 );
8586
8587 origin.x = origin.x.max(content_origin.x);
8588
8589 element.prepaint_at(origin, window, cx);
8590
8591 Some((element, origin))
8592 }
8593
8594 fn render_edit_prediction_scroll_popover(
8595 &mut self,
8596 to_y: impl Fn(Size<Pixels>) -> Pixels,
8597 scroll_icon: IconName,
8598 visible_row_range: Range<DisplayRow>,
8599 line_layouts: &[LineWithInvisibles],
8600 newest_selection_head: Option<DisplayPoint>,
8601 scrolled_content_origin: gpui::Point<Pixels>,
8602 window: &mut Window,
8603 cx: &mut App,
8604 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8605 let mut element = self
8606 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
8607 .into_any();
8608
8609 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8610
8611 let cursor = newest_selection_head?;
8612 let cursor_row_layout =
8613 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8614 let cursor_column = cursor.column() as usize;
8615
8616 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8617
8618 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8619
8620 element.prepaint_at(origin, window, cx);
8621 Some((element, origin))
8622 }
8623
8624 fn render_edit_prediction_eager_jump_popover(
8625 &mut self,
8626 text_bounds: &Bounds<Pixels>,
8627 content_origin: gpui::Point<Pixels>,
8628 editor_snapshot: &EditorSnapshot,
8629 visible_row_range: Range<DisplayRow>,
8630 scroll_top: f32,
8631 scroll_bottom: f32,
8632 line_height: Pixels,
8633 scroll_pixel_position: gpui::Point<Pixels>,
8634 target_display_point: DisplayPoint,
8635 editor_width: Pixels,
8636 window: &mut Window,
8637 cx: &mut App,
8638 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8639 if target_display_point.row().as_f32() < scroll_top {
8640 let mut element = self
8641 .render_edit_prediction_line_popover(
8642 "Jump to Edit",
8643 Some(IconName::ArrowUp),
8644 window,
8645 cx,
8646 )?
8647 .into_any();
8648
8649 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8650 let offset = point(
8651 (text_bounds.size.width - size.width) / 2.,
8652 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8653 );
8654
8655 let origin = text_bounds.origin + offset;
8656 element.prepaint_at(origin, window, cx);
8657 Some((element, origin))
8658 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
8659 let mut element = self
8660 .render_edit_prediction_line_popover(
8661 "Jump to Edit",
8662 Some(IconName::ArrowDown),
8663 window,
8664 cx,
8665 )?
8666 .into_any();
8667
8668 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8669 let offset = point(
8670 (text_bounds.size.width - size.width) / 2.,
8671 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8672 );
8673
8674 let origin = text_bounds.origin + offset;
8675 element.prepaint_at(origin, window, cx);
8676 Some((element, origin))
8677 } else {
8678 self.render_edit_prediction_end_of_line_popover(
8679 "Jump to Edit",
8680 editor_snapshot,
8681 visible_row_range,
8682 target_display_point,
8683 line_height,
8684 scroll_pixel_position,
8685 content_origin,
8686 editor_width,
8687 window,
8688 cx,
8689 )
8690 }
8691 }
8692
8693 fn render_edit_prediction_end_of_line_popover(
8694 self: &mut Editor,
8695 label: &'static str,
8696 editor_snapshot: &EditorSnapshot,
8697 visible_row_range: Range<DisplayRow>,
8698 target_display_point: DisplayPoint,
8699 line_height: Pixels,
8700 scroll_pixel_position: gpui::Point<Pixels>,
8701 content_origin: gpui::Point<Pixels>,
8702 editor_width: Pixels,
8703 window: &mut Window,
8704 cx: &mut App,
8705 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8706 let target_line_end = DisplayPoint::new(
8707 target_display_point.row(),
8708 editor_snapshot.line_len(target_display_point.row()),
8709 );
8710
8711 let mut element = self
8712 .render_edit_prediction_line_popover(label, None, window, cx)?
8713 .into_any();
8714
8715 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8716
8717 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8718
8719 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
8720 let mut origin = start_point
8721 + line_origin
8722 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8723 origin.x = origin.x.max(content_origin.x);
8724
8725 let max_x = content_origin.x + editor_width - size.width;
8726
8727 if origin.x > max_x {
8728 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8729
8730 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8731 origin.y += offset;
8732 IconName::ArrowUp
8733 } else {
8734 origin.y -= offset;
8735 IconName::ArrowDown
8736 };
8737
8738 element = self
8739 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
8740 .into_any();
8741
8742 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8743
8744 origin.x = content_origin.x + editor_width - size.width - px(2.);
8745 }
8746
8747 element.prepaint_at(origin, window, cx);
8748 Some((element, origin))
8749 }
8750
8751 fn render_edit_prediction_diff_popover(
8752 self: &Editor,
8753 text_bounds: &Bounds<Pixels>,
8754 content_origin: gpui::Point<Pixels>,
8755 right_margin: Pixels,
8756 editor_snapshot: &EditorSnapshot,
8757 visible_row_range: Range<DisplayRow>,
8758 line_layouts: &[LineWithInvisibles],
8759 line_height: Pixels,
8760 scroll_pixel_position: gpui::Point<Pixels>,
8761 newest_selection_head: Option<DisplayPoint>,
8762 editor_width: Pixels,
8763 style: &EditorStyle,
8764 edits: &Vec<(Range<Anchor>, String)>,
8765 edit_preview: &Option<language::EditPreview>,
8766 snapshot: &language::BufferSnapshot,
8767 window: &mut Window,
8768 cx: &mut App,
8769 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8770 let edit_start = edits
8771 .first()
8772 .unwrap()
8773 .0
8774 .start
8775 .to_display_point(editor_snapshot);
8776 let edit_end = edits
8777 .last()
8778 .unwrap()
8779 .0
8780 .end
8781 .to_display_point(editor_snapshot);
8782
8783 let is_visible = visible_row_range.contains(&edit_start.row())
8784 || visible_row_range.contains(&edit_end.row());
8785 if !is_visible {
8786 return None;
8787 }
8788
8789 let highlighted_edits =
8790 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
8791
8792 let styled_text = highlighted_edits.to_styled_text(&style.text);
8793 let line_count = highlighted_edits.text.lines().count();
8794
8795 const BORDER_WIDTH: Pixels = px(1.);
8796
8797 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8798 let has_keybind = keybind.is_some();
8799
8800 let mut element = h_flex()
8801 .items_start()
8802 .child(
8803 h_flex()
8804 .bg(cx.theme().colors().editor_background)
8805 .border(BORDER_WIDTH)
8806 .shadow_xs()
8807 .border_color(cx.theme().colors().border)
8808 .rounded_l_lg()
8809 .when(line_count > 1, |el| el.rounded_br_lg())
8810 .pr_1()
8811 .child(styled_text),
8812 )
8813 .child(
8814 h_flex()
8815 .h(line_height + BORDER_WIDTH * 2.)
8816 .px_1p5()
8817 .gap_1()
8818 // Workaround: For some reason, there's a gap if we don't do this
8819 .ml(-BORDER_WIDTH)
8820 .shadow(vec![gpui::BoxShadow {
8821 color: gpui::black().opacity(0.05),
8822 offset: point(px(1.), px(1.)),
8823 blur_radius: px(2.),
8824 spread_radius: px(0.),
8825 }])
8826 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8827 .border(BORDER_WIDTH)
8828 .border_color(cx.theme().colors().border)
8829 .rounded_r_lg()
8830 .id("edit_prediction_diff_popover_keybind")
8831 .when(!has_keybind, |el| {
8832 let status_colors = cx.theme().status();
8833
8834 el.bg(status_colors.error_background)
8835 .border_color(status_colors.error.opacity(0.6))
8836 .child(Icon::new(IconName::Info).color(Color::Error))
8837 .cursor_default()
8838 .hoverable_tooltip(move |_window, cx| {
8839 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8840 })
8841 })
8842 .children(keybind),
8843 )
8844 .into_any();
8845
8846 let longest_row =
8847 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8848 let longest_line_width = if visible_row_range.contains(&longest_row) {
8849 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8850 } else {
8851 layout_line(
8852 longest_row,
8853 editor_snapshot,
8854 style,
8855 editor_width,
8856 |_| false,
8857 window,
8858 cx,
8859 )
8860 .width
8861 };
8862
8863 let viewport_bounds =
8864 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8865 right: -right_margin,
8866 ..Default::default()
8867 });
8868
8869 let x_after_longest =
8870 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
8871 - scroll_pixel_position.x;
8872
8873 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8874
8875 // Fully visible if it can be displayed within the window (allow overlapping other
8876 // panes). However, this is only allowed if the popover starts within text_bounds.
8877 let can_position_to_the_right = x_after_longest < text_bounds.right()
8878 && x_after_longest + element_bounds.width < viewport_bounds.right();
8879
8880 let mut origin = if can_position_to_the_right {
8881 point(
8882 x_after_longest,
8883 text_bounds.origin.y + edit_start.row().as_f32() * line_height
8884 - scroll_pixel_position.y,
8885 )
8886 } else {
8887 let cursor_row = newest_selection_head.map(|head| head.row());
8888 let above_edit = edit_start
8889 .row()
8890 .0
8891 .checked_sub(line_count as u32)
8892 .map(DisplayRow);
8893 let below_edit = Some(edit_end.row() + 1);
8894 let above_cursor =
8895 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
8896 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
8897
8898 // Place the edit popover adjacent to the edit if there is a location
8899 // available that is onscreen and does not obscure the cursor. Otherwise,
8900 // place it adjacent to the cursor.
8901 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
8902 .into_iter()
8903 .flatten()
8904 .find(|&start_row| {
8905 let end_row = start_row + line_count as u32;
8906 visible_row_range.contains(&start_row)
8907 && visible_row_range.contains(&end_row)
8908 && cursor_row.map_or(true, |cursor_row| {
8909 !((start_row..end_row).contains(&cursor_row))
8910 })
8911 })?;
8912
8913 content_origin
8914 + point(
8915 -scroll_pixel_position.x,
8916 row_target.as_f32() * line_height - scroll_pixel_position.y,
8917 )
8918 };
8919
8920 origin.x -= BORDER_WIDTH;
8921
8922 window.defer_draw(element, origin, 1);
8923
8924 // Do not return an element, since it will already be drawn due to defer_draw.
8925 None
8926 }
8927
8928 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
8929 px(30.)
8930 }
8931
8932 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
8933 if self.read_only(cx) {
8934 cx.theme().players().read_only()
8935 } else {
8936 self.style.as_ref().unwrap().local_player
8937 }
8938 }
8939
8940 fn render_edit_prediction_accept_keybind(
8941 &self,
8942 window: &mut Window,
8943 cx: &App,
8944 ) -> Option<AnyElement> {
8945 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
8946 let accept_keystroke = accept_binding.keystroke()?;
8947
8948 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8949
8950 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
8951 Color::Accent
8952 } else {
8953 Color::Muted
8954 };
8955
8956 h_flex()
8957 .px_0p5()
8958 .when(is_platform_style_mac, |parent| parent.gap_0p5())
8959 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8960 .text_size(TextSize::XSmall.rems(cx))
8961 .child(h_flex().children(ui::render_modifiers(
8962 &accept_keystroke.modifiers,
8963 PlatformStyle::platform(),
8964 Some(modifiers_color),
8965 Some(IconSize::XSmall.rems().into()),
8966 true,
8967 )))
8968 .when(is_platform_style_mac, |parent| {
8969 parent.child(accept_keystroke.key.clone())
8970 })
8971 .when(!is_platform_style_mac, |parent| {
8972 parent.child(
8973 Key::new(
8974 util::capitalize(&accept_keystroke.key),
8975 Some(Color::Default),
8976 )
8977 .size(Some(IconSize::XSmall.rems().into())),
8978 )
8979 })
8980 .into_any()
8981 .into()
8982 }
8983
8984 fn render_edit_prediction_line_popover(
8985 &self,
8986 label: impl Into<SharedString>,
8987 icon: Option<IconName>,
8988 window: &mut Window,
8989 cx: &App,
8990 ) -> Option<Stateful<Div>> {
8991 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
8992
8993 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8994 let has_keybind = keybind.is_some();
8995
8996 let result = h_flex()
8997 .id("ep-line-popover")
8998 .py_0p5()
8999 .pl_1()
9000 .pr(padding_right)
9001 .gap_1()
9002 .rounded_md()
9003 .border_1()
9004 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9005 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9006 .shadow_xs()
9007 .when(!has_keybind, |el| {
9008 let status_colors = cx.theme().status();
9009
9010 el.bg(status_colors.error_background)
9011 .border_color(status_colors.error.opacity(0.6))
9012 .pl_2()
9013 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9014 .cursor_default()
9015 .hoverable_tooltip(move |_window, cx| {
9016 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9017 })
9018 })
9019 .children(keybind)
9020 .child(
9021 Label::new(label)
9022 .size(LabelSize::Small)
9023 .when(!has_keybind, |el| {
9024 el.color(cx.theme().status().error.into()).strikethrough()
9025 }),
9026 )
9027 .when(!has_keybind, |el| {
9028 el.child(
9029 h_flex().ml_1().child(
9030 Icon::new(IconName::Info)
9031 .size(IconSize::Small)
9032 .color(cx.theme().status().error.into()),
9033 ),
9034 )
9035 })
9036 .when_some(icon, |element, icon| {
9037 element.child(
9038 div()
9039 .mt(px(1.5))
9040 .child(Icon::new(icon).size(IconSize::Small)),
9041 )
9042 });
9043
9044 Some(result)
9045 }
9046
9047 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9048 let accent_color = cx.theme().colors().text_accent;
9049 let editor_bg_color = cx.theme().colors().editor_background;
9050 editor_bg_color.blend(accent_color.opacity(0.1))
9051 }
9052
9053 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9054 let accent_color = cx.theme().colors().text_accent;
9055 let editor_bg_color = cx.theme().colors().editor_background;
9056 editor_bg_color.blend(accent_color.opacity(0.6))
9057 }
9058
9059 fn render_edit_prediction_cursor_popover(
9060 &self,
9061 min_width: Pixels,
9062 max_width: Pixels,
9063 cursor_point: Point,
9064 style: &EditorStyle,
9065 accept_keystroke: Option<&gpui::Keystroke>,
9066 _window: &Window,
9067 cx: &mut Context<Editor>,
9068 ) -> Option<AnyElement> {
9069 let provider = self.edit_prediction_provider.as_ref()?;
9070
9071 if provider.provider.needs_terms_acceptance(cx) {
9072 return Some(
9073 h_flex()
9074 .min_w(min_width)
9075 .flex_1()
9076 .px_2()
9077 .py_1()
9078 .gap_3()
9079 .elevation_2(cx)
9080 .hover(|style| style.bg(cx.theme().colors().element_hover))
9081 .id("accept-terms")
9082 .cursor_pointer()
9083 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
9084 .on_click(cx.listener(|this, _event, window, cx| {
9085 cx.stop_propagation();
9086 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
9087 window.dispatch_action(
9088 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
9089 cx,
9090 );
9091 }))
9092 .child(
9093 h_flex()
9094 .flex_1()
9095 .gap_2()
9096 .child(Icon::new(IconName::ZedPredict))
9097 .child(Label::new("Accept Terms of Service"))
9098 .child(div().w_full())
9099 .child(
9100 Icon::new(IconName::ArrowUpRight)
9101 .color(Color::Muted)
9102 .size(IconSize::Small),
9103 )
9104 .into_any_element(),
9105 )
9106 .into_any(),
9107 );
9108 }
9109
9110 let is_refreshing = provider.provider.is_refreshing(cx);
9111
9112 fn pending_completion_container() -> Div {
9113 h_flex()
9114 .h_full()
9115 .flex_1()
9116 .gap_2()
9117 .child(Icon::new(IconName::ZedPredict))
9118 }
9119
9120 let completion = match &self.active_inline_completion {
9121 Some(prediction) => {
9122 if !self.has_visible_completions_menu() {
9123 const RADIUS: Pixels = px(6.);
9124 const BORDER_WIDTH: Pixels = px(1.);
9125
9126 return Some(
9127 h_flex()
9128 .elevation_2(cx)
9129 .border(BORDER_WIDTH)
9130 .border_color(cx.theme().colors().border)
9131 .when(accept_keystroke.is_none(), |el| {
9132 el.border_color(cx.theme().status().error)
9133 })
9134 .rounded(RADIUS)
9135 .rounded_tl(px(0.))
9136 .overflow_hidden()
9137 .child(div().px_1p5().child(match &prediction.completion {
9138 InlineCompletion::Move { target, snapshot } => {
9139 use text::ToPoint as _;
9140 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
9141 {
9142 Icon::new(IconName::ZedPredictDown)
9143 } else {
9144 Icon::new(IconName::ZedPredictUp)
9145 }
9146 }
9147 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
9148 }))
9149 .child(
9150 h_flex()
9151 .gap_1()
9152 .py_1()
9153 .px_2()
9154 .rounded_r(RADIUS - BORDER_WIDTH)
9155 .border_l_1()
9156 .border_color(cx.theme().colors().border)
9157 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9158 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9159 el.child(
9160 Label::new("Hold")
9161 .size(LabelSize::Small)
9162 .when(accept_keystroke.is_none(), |el| {
9163 el.strikethrough()
9164 })
9165 .line_height_style(LineHeightStyle::UiLabel),
9166 )
9167 })
9168 .id("edit_prediction_cursor_popover_keybind")
9169 .when(accept_keystroke.is_none(), |el| {
9170 let status_colors = cx.theme().status();
9171
9172 el.bg(status_colors.error_background)
9173 .border_color(status_colors.error.opacity(0.6))
9174 .child(Icon::new(IconName::Info).color(Color::Error))
9175 .cursor_default()
9176 .hoverable_tooltip(move |_window, cx| {
9177 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9178 .into()
9179 })
9180 })
9181 .when_some(
9182 accept_keystroke.as_ref(),
9183 |el, accept_keystroke| {
9184 el.child(h_flex().children(ui::render_modifiers(
9185 &accept_keystroke.modifiers,
9186 PlatformStyle::platform(),
9187 Some(Color::Default),
9188 Some(IconSize::XSmall.rems().into()),
9189 false,
9190 )))
9191 },
9192 ),
9193 )
9194 .into_any(),
9195 );
9196 }
9197
9198 self.render_edit_prediction_cursor_popover_preview(
9199 prediction,
9200 cursor_point,
9201 style,
9202 cx,
9203 )?
9204 }
9205
9206 None if is_refreshing => match &self.stale_inline_completion_in_menu {
9207 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9208 stale_completion,
9209 cursor_point,
9210 style,
9211 cx,
9212 )?,
9213
9214 None => {
9215 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
9216 }
9217 },
9218
9219 None => pending_completion_container().child(Label::new("No Prediction")),
9220 };
9221
9222 let completion = if is_refreshing {
9223 completion
9224 .with_animation(
9225 "loading-completion",
9226 Animation::new(Duration::from_secs(2))
9227 .repeat()
9228 .with_easing(pulsating_between(0.4, 0.8)),
9229 |label, delta| label.opacity(delta),
9230 )
9231 .into_any_element()
9232 } else {
9233 completion.into_any_element()
9234 };
9235
9236 let has_completion = self.active_inline_completion.is_some();
9237
9238 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9239 Some(
9240 h_flex()
9241 .min_w(min_width)
9242 .max_w(max_width)
9243 .flex_1()
9244 .elevation_2(cx)
9245 .border_color(cx.theme().colors().border)
9246 .child(
9247 div()
9248 .flex_1()
9249 .py_1()
9250 .px_2()
9251 .overflow_hidden()
9252 .child(completion),
9253 )
9254 .when_some(accept_keystroke, |el, accept_keystroke| {
9255 if !accept_keystroke.modifiers.modified() {
9256 return el;
9257 }
9258
9259 el.child(
9260 h_flex()
9261 .h_full()
9262 .border_l_1()
9263 .rounded_r_lg()
9264 .border_color(cx.theme().colors().border)
9265 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9266 .gap_1()
9267 .py_1()
9268 .px_2()
9269 .child(
9270 h_flex()
9271 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9272 .when(is_platform_style_mac, |parent| parent.gap_1())
9273 .child(h_flex().children(ui::render_modifiers(
9274 &accept_keystroke.modifiers,
9275 PlatformStyle::platform(),
9276 Some(if !has_completion {
9277 Color::Muted
9278 } else {
9279 Color::Default
9280 }),
9281 None,
9282 false,
9283 ))),
9284 )
9285 .child(Label::new("Preview").into_any_element())
9286 .opacity(if has_completion { 1.0 } else { 0.4 }),
9287 )
9288 })
9289 .into_any(),
9290 )
9291 }
9292
9293 fn render_edit_prediction_cursor_popover_preview(
9294 &self,
9295 completion: &InlineCompletionState,
9296 cursor_point: Point,
9297 style: &EditorStyle,
9298 cx: &mut Context<Editor>,
9299 ) -> Option<Div> {
9300 use text::ToPoint as _;
9301
9302 fn render_relative_row_jump(
9303 prefix: impl Into<String>,
9304 current_row: u32,
9305 target_row: u32,
9306 ) -> Div {
9307 let (row_diff, arrow) = if target_row < current_row {
9308 (current_row - target_row, IconName::ArrowUp)
9309 } else {
9310 (target_row - current_row, IconName::ArrowDown)
9311 };
9312
9313 h_flex()
9314 .child(
9315 Label::new(format!("{}{}", prefix.into(), row_diff))
9316 .color(Color::Muted)
9317 .size(LabelSize::Small),
9318 )
9319 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9320 }
9321
9322 match &completion.completion {
9323 InlineCompletion::Move {
9324 target, snapshot, ..
9325 } => Some(
9326 h_flex()
9327 .px_2()
9328 .gap_2()
9329 .flex_1()
9330 .child(
9331 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
9332 Icon::new(IconName::ZedPredictDown)
9333 } else {
9334 Icon::new(IconName::ZedPredictUp)
9335 },
9336 )
9337 .child(Label::new("Jump to Edit")),
9338 ),
9339
9340 InlineCompletion::Edit {
9341 edits,
9342 edit_preview,
9343 snapshot,
9344 display_mode: _,
9345 } => {
9346 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
9347
9348 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
9349 &snapshot,
9350 &edits,
9351 edit_preview.as_ref()?,
9352 true,
9353 cx,
9354 )
9355 .first_line_preview();
9356
9357 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9358 .with_default_highlights(&style.text, highlighted_edits.highlights);
9359
9360 let preview = h_flex()
9361 .gap_1()
9362 .min_w_16()
9363 .child(styled_text)
9364 .when(has_more_lines, |parent| parent.child("…"));
9365
9366 let left = if first_edit_row != cursor_point.row {
9367 render_relative_row_jump("", cursor_point.row, first_edit_row)
9368 .into_any_element()
9369 } else {
9370 Icon::new(IconName::ZedPredict).into_any_element()
9371 };
9372
9373 Some(
9374 h_flex()
9375 .h_full()
9376 .flex_1()
9377 .gap_2()
9378 .pr_1()
9379 .overflow_x_hidden()
9380 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9381 .child(left)
9382 .child(preview),
9383 )
9384 }
9385 }
9386 }
9387
9388 pub fn render_context_menu(
9389 &self,
9390 style: &EditorStyle,
9391 max_height_in_lines: u32,
9392 window: &mut Window,
9393 cx: &mut Context<Editor>,
9394 ) -> Option<AnyElement> {
9395 let menu = self.context_menu.borrow();
9396 let menu = menu.as_ref()?;
9397 if !menu.visible() {
9398 return None;
9399 };
9400 Some(menu.render(style, max_height_in_lines, window, cx))
9401 }
9402
9403 fn render_context_menu_aside(
9404 &mut self,
9405 max_size: Size<Pixels>,
9406 window: &mut Window,
9407 cx: &mut Context<Editor>,
9408 ) -> Option<AnyElement> {
9409 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9410 if menu.visible() {
9411 menu.render_aside(max_size, window, cx)
9412 } else {
9413 None
9414 }
9415 })
9416 }
9417
9418 fn hide_context_menu(
9419 &mut self,
9420 window: &mut Window,
9421 cx: &mut Context<Self>,
9422 ) -> Option<CodeContextMenu> {
9423 cx.notify();
9424 self.completion_tasks.clear();
9425 let context_menu = self.context_menu.borrow_mut().take();
9426 self.stale_inline_completion_in_menu.take();
9427 self.update_visible_inline_completion(window, cx);
9428 if let Some(CodeContextMenu::Completions(_)) = &context_menu {
9429 if let Some(completion_provider) = &self.completion_provider {
9430 completion_provider.selection_changed(None, window, cx);
9431 }
9432 }
9433 context_menu
9434 }
9435
9436 fn show_snippet_choices(
9437 &mut self,
9438 choices: &Vec<String>,
9439 selection: Range<Anchor>,
9440 cx: &mut Context<Self>,
9441 ) {
9442 let buffer_id = match (&selection.start.buffer_id, &selection.end.buffer_id) {
9443 (Some(a), Some(b)) if a == b => a,
9444 _ => {
9445 log::error!("expected anchor range to have matching buffer IDs");
9446 return;
9447 }
9448 };
9449 let multi_buffer = self.buffer().read(cx);
9450 let Some(buffer) = multi_buffer.buffer(*buffer_id) else {
9451 return;
9452 };
9453
9454 let id = post_inc(&mut self.next_completion_id);
9455 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9456 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9457 CompletionsMenu::new_snippet_choices(
9458 id,
9459 true,
9460 choices,
9461 selection,
9462 buffer,
9463 snippet_sort_order,
9464 ),
9465 ));
9466 }
9467
9468 pub fn insert_snippet(
9469 &mut self,
9470 insertion_ranges: &[Range<usize>],
9471 snippet: Snippet,
9472 window: &mut Window,
9473 cx: &mut Context<Self>,
9474 ) -> Result<()> {
9475 struct Tabstop<T> {
9476 is_end_tabstop: bool,
9477 ranges: Vec<Range<T>>,
9478 choices: Option<Vec<String>>,
9479 }
9480
9481 let tabstops = self.buffer.update(cx, |buffer, cx| {
9482 let snippet_text: Arc<str> = snippet.text.clone().into();
9483 let edits = insertion_ranges
9484 .iter()
9485 .cloned()
9486 .map(|range| (range, snippet_text.clone()));
9487 let autoindent_mode = AutoindentMode::Block {
9488 original_indent_columns: Vec::new(),
9489 };
9490 buffer.edit(edits, Some(autoindent_mode), cx);
9491
9492 let snapshot = &*buffer.read(cx);
9493 let snippet = &snippet;
9494 snippet
9495 .tabstops
9496 .iter()
9497 .map(|tabstop| {
9498 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
9499 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9500 });
9501 let mut tabstop_ranges = tabstop
9502 .ranges
9503 .iter()
9504 .flat_map(|tabstop_range| {
9505 let mut delta = 0_isize;
9506 insertion_ranges.iter().map(move |insertion_range| {
9507 let insertion_start = insertion_range.start as isize + delta;
9508 delta +=
9509 snippet.text.len() as isize - insertion_range.len() as isize;
9510
9511 let start = ((insertion_start + tabstop_range.start) as usize)
9512 .min(snapshot.len());
9513 let end = ((insertion_start + tabstop_range.end) as usize)
9514 .min(snapshot.len());
9515 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9516 })
9517 })
9518 .collect::<Vec<_>>();
9519 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9520
9521 Tabstop {
9522 is_end_tabstop,
9523 ranges: tabstop_ranges,
9524 choices: tabstop.choices.clone(),
9525 }
9526 })
9527 .collect::<Vec<_>>()
9528 });
9529 if let Some(tabstop) = tabstops.first() {
9530 self.change_selections(Default::default(), window, cx, |s| {
9531 // Reverse order so that the first range is the newest created selection.
9532 // Completions will use it and autoscroll will prioritize it.
9533 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9534 });
9535
9536 if let Some(choices) = &tabstop.choices {
9537 if let Some(selection) = tabstop.ranges.first() {
9538 self.show_snippet_choices(choices, selection.clone(), cx)
9539 }
9540 }
9541
9542 // If we're already at the last tabstop and it's at the end of the snippet,
9543 // we're done, we don't need to keep the state around.
9544 if !tabstop.is_end_tabstop {
9545 let choices = tabstops
9546 .iter()
9547 .map(|tabstop| tabstop.choices.clone())
9548 .collect();
9549
9550 let ranges = tabstops
9551 .into_iter()
9552 .map(|tabstop| tabstop.ranges)
9553 .collect::<Vec<_>>();
9554
9555 self.snippet_stack.push(SnippetState {
9556 active_index: 0,
9557 ranges,
9558 choices,
9559 });
9560 }
9561
9562 // Check whether the just-entered snippet ends with an auto-closable bracket.
9563 if self.autoclose_regions.is_empty() {
9564 let snapshot = self.buffer.read(cx).snapshot(cx);
9565 for selection in &mut self.selections.all::<Point>(cx) {
9566 let selection_head = selection.head();
9567 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9568 continue;
9569 };
9570
9571 let mut bracket_pair = None;
9572 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
9573 let prev_chars = snapshot
9574 .reversed_chars_at(selection_head)
9575 .collect::<String>();
9576 for (pair, enabled) in scope.brackets() {
9577 if enabled
9578 && pair.close
9579 && prev_chars.starts_with(pair.start.as_str())
9580 && next_chars.starts_with(pair.end.as_str())
9581 {
9582 bracket_pair = Some(pair.clone());
9583 break;
9584 }
9585 }
9586 if let Some(pair) = bracket_pair {
9587 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9588 let autoclose_enabled =
9589 self.use_autoclose && snapshot_settings.use_autoclose;
9590 if autoclose_enabled {
9591 let start = snapshot.anchor_after(selection_head);
9592 let end = snapshot.anchor_after(selection_head);
9593 self.autoclose_regions.push(AutocloseRegion {
9594 selection_id: selection.id,
9595 range: start..end,
9596 pair,
9597 });
9598 }
9599 }
9600 }
9601 }
9602 }
9603 Ok(())
9604 }
9605
9606 pub fn move_to_next_snippet_tabstop(
9607 &mut self,
9608 window: &mut Window,
9609 cx: &mut Context<Self>,
9610 ) -> bool {
9611 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9612 }
9613
9614 pub fn move_to_prev_snippet_tabstop(
9615 &mut self,
9616 window: &mut Window,
9617 cx: &mut Context<Self>,
9618 ) -> bool {
9619 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9620 }
9621
9622 pub fn move_to_snippet_tabstop(
9623 &mut self,
9624 bias: Bias,
9625 window: &mut Window,
9626 cx: &mut Context<Self>,
9627 ) -> bool {
9628 if let Some(mut snippet) = self.snippet_stack.pop() {
9629 match bias {
9630 Bias::Left => {
9631 if snippet.active_index > 0 {
9632 snippet.active_index -= 1;
9633 } else {
9634 self.snippet_stack.push(snippet);
9635 return false;
9636 }
9637 }
9638 Bias::Right => {
9639 if snippet.active_index + 1 < snippet.ranges.len() {
9640 snippet.active_index += 1;
9641 } else {
9642 self.snippet_stack.push(snippet);
9643 return false;
9644 }
9645 }
9646 }
9647 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9648 self.change_selections(Default::default(), window, cx, |s| {
9649 // Reverse order so that the first range is the newest created selection.
9650 // Completions will use it and autoscroll will prioritize it.
9651 s.select_ranges(current_ranges.iter().rev().cloned())
9652 });
9653
9654 if let Some(choices) = &snippet.choices[snippet.active_index] {
9655 if let Some(selection) = current_ranges.first() {
9656 self.show_snippet_choices(&choices, selection.clone(), cx);
9657 }
9658 }
9659
9660 // If snippet state is not at the last tabstop, push it back on the stack
9661 if snippet.active_index + 1 < snippet.ranges.len() {
9662 self.snippet_stack.push(snippet);
9663 }
9664 return true;
9665 }
9666 }
9667
9668 false
9669 }
9670
9671 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9672 self.transact(window, cx, |this, window, cx| {
9673 this.select_all(&SelectAll, window, cx);
9674 this.insert("", window, cx);
9675 });
9676 }
9677
9678 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9679 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9680 self.transact(window, cx, |this, window, cx| {
9681 this.select_autoclose_pair(window, cx);
9682 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9683 if !this.linked_edit_ranges.is_empty() {
9684 let selections = this.selections.all::<MultiBufferPoint>(cx);
9685 let snapshot = this.buffer.read(cx).snapshot(cx);
9686
9687 for selection in selections.iter() {
9688 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9689 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9690 if selection_start.buffer_id != selection_end.buffer_id {
9691 continue;
9692 }
9693 if let Some(ranges) =
9694 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9695 {
9696 for (buffer, entries) in ranges {
9697 linked_ranges.entry(buffer).or_default().extend(entries);
9698 }
9699 }
9700 }
9701 }
9702
9703 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
9704 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9705 for selection in &mut selections {
9706 if selection.is_empty() {
9707 let old_head = selection.head();
9708 let mut new_head =
9709 movement::left(&display_map, old_head.to_display_point(&display_map))
9710 .to_point(&display_map);
9711 if let Some((buffer, line_buffer_range)) = display_map
9712 .buffer_snapshot
9713 .buffer_line_for_row(MultiBufferRow(old_head.row))
9714 {
9715 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9716 let indent_len = match indent_size.kind {
9717 IndentKind::Space => {
9718 buffer.settings_at(line_buffer_range.start, cx).tab_size
9719 }
9720 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9721 };
9722 if old_head.column <= indent_size.len && old_head.column > 0 {
9723 let indent_len = indent_len.get();
9724 new_head = cmp::min(
9725 new_head,
9726 MultiBufferPoint::new(
9727 old_head.row,
9728 ((old_head.column - 1) / indent_len) * indent_len,
9729 ),
9730 );
9731 }
9732 }
9733
9734 selection.set_head(new_head, SelectionGoal::None);
9735 }
9736 }
9737
9738 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9739 this.insert("", window, cx);
9740 let empty_str: Arc<str> = Arc::from("");
9741 for (buffer, edits) in linked_ranges {
9742 let snapshot = buffer.read(cx).snapshot();
9743 use text::ToPoint as TP;
9744
9745 let edits = edits
9746 .into_iter()
9747 .map(|range| {
9748 let end_point = TP::to_point(&range.end, &snapshot);
9749 let mut start_point = TP::to_point(&range.start, &snapshot);
9750
9751 if end_point == start_point {
9752 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9753 .saturating_sub(1);
9754 start_point =
9755 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9756 };
9757
9758 (start_point..end_point, empty_str.clone())
9759 })
9760 .sorted_by_key(|(range, _)| range.start)
9761 .collect::<Vec<_>>();
9762 buffer.update(cx, |this, cx| {
9763 this.edit(edits, None, cx);
9764 })
9765 }
9766 this.refresh_inline_completion(true, false, window, cx);
9767 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
9768 });
9769 }
9770
9771 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9772 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9773 self.transact(window, cx, |this, window, cx| {
9774 this.change_selections(Default::default(), window, cx, |s| {
9775 s.move_with(|map, selection| {
9776 if selection.is_empty() {
9777 let cursor = movement::right(map, selection.head());
9778 selection.end = cursor;
9779 selection.reversed = true;
9780 selection.goal = SelectionGoal::None;
9781 }
9782 })
9783 });
9784 this.insert("", window, cx);
9785 this.refresh_inline_completion(true, false, window, cx);
9786 });
9787 }
9788
9789 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9790 if self.mode.is_single_line() {
9791 cx.propagate();
9792 return;
9793 }
9794
9795 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9796 if self.move_to_prev_snippet_tabstop(window, cx) {
9797 return;
9798 }
9799 self.outdent(&Outdent, window, cx);
9800 }
9801
9802 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9803 if self.mode.is_single_line() {
9804 cx.propagate();
9805 return;
9806 }
9807
9808 if self.move_to_next_snippet_tabstop(window, cx) {
9809 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9810 return;
9811 }
9812 if self.read_only(cx) {
9813 return;
9814 }
9815 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9816 let mut selections = self.selections.all_adjusted(cx);
9817 let buffer = self.buffer.read(cx);
9818 let snapshot = buffer.snapshot(cx);
9819 let rows_iter = selections.iter().map(|s| s.head().row);
9820 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9821
9822 let has_some_cursor_in_whitespace = selections
9823 .iter()
9824 .filter(|selection| selection.is_empty())
9825 .any(|selection| {
9826 let cursor = selection.head();
9827 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9828 cursor.column < current_indent.len
9829 });
9830
9831 let mut edits = Vec::new();
9832 let mut prev_edited_row = 0;
9833 let mut row_delta = 0;
9834 for selection in &mut selections {
9835 if selection.start.row != prev_edited_row {
9836 row_delta = 0;
9837 }
9838 prev_edited_row = selection.end.row;
9839
9840 // If the selection is non-empty, then increase the indentation of the selected lines.
9841 if !selection.is_empty() {
9842 row_delta =
9843 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9844 continue;
9845 }
9846
9847 let cursor = selection.head();
9848 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9849 if let Some(suggested_indent) =
9850 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
9851 {
9852 // Don't do anything if already at suggested indent
9853 // and there is any other cursor which is not
9854 if has_some_cursor_in_whitespace
9855 && cursor.column == current_indent.len
9856 && current_indent.len == suggested_indent.len
9857 {
9858 continue;
9859 }
9860
9861 // Adjust line and move cursor to suggested indent
9862 // if cursor is not at suggested indent
9863 if cursor.column < suggested_indent.len
9864 && cursor.column <= current_indent.len
9865 && current_indent.len <= suggested_indent.len
9866 {
9867 selection.start = Point::new(cursor.row, suggested_indent.len);
9868 selection.end = selection.start;
9869 if row_delta == 0 {
9870 edits.extend(Buffer::edit_for_indent_size_adjustment(
9871 cursor.row,
9872 current_indent,
9873 suggested_indent,
9874 ));
9875 row_delta = suggested_indent.len - current_indent.len;
9876 }
9877 continue;
9878 }
9879
9880 // If current indent is more than suggested indent
9881 // only move cursor to current indent and skip indent
9882 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
9883 selection.start = Point::new(cursor.row, current_indent.len);
9884 selection.end = selection.start;
9885 continue;
9886 }
9887 }
9888
9889 // Otherwise, insert a hard or soft tab.
9890 let settings = buffer.language_settings_at(cursor, cx);
9891 let tab_size = if settings.hard_tabs {
9892 IndentSize::tab()
9893 } else {
9894 let tab_size = settings.tab_size.get();
9895 let indent_remainder = snapshot
9896 .text_for_range(Point::new(cursor.row, 0)..cursor)
9897 .flat_map(str::chars)
9898 .fold(row_delta % tab_size, |counter: u32, c| {
9899 if c == '\t' {
9900 0
9901 } else {
9902 (counter + 1) % tab_size
9903 }
9904 });
9905
9906 let chars_to_next_tab_stop = tab_size - indent_remainder;
9907 IndentSize::spaces(chars_to_next_tab_stop)
9908 };
9909 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
9910 selection.end = selection.start;
9911 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
9912 row_delta += tab_size.len;
9913 }
9914
9915 self.transact(window, cx, |this, window, cx| {
9916 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9917 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9918 this.refresh_inline_completion(true, false, window, cx);
9919 });
9920 }
9921
9922 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
9923 if self.read_only(cx) {
9924 return;
9925 }
9926 if self.mode.is_single_line() {
9927 cx.propagate();
9928 return;
9929 }
9930
9931 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9932 let mut selections = self.selections.all::<Point>(cx);
9933 let mut prev_edited_row = 0;
9934 let mut row_delta = 0;
9935 let mut edits = Vec::new();
9936 let buffer = self.buffer.read(cx);
9937 let snapshot = buffer.snapshot(cx);
9938 for selection in &mut selections {
9939 if selection.start.row != prev_edited_row {
9940 row_delta = 0;
9941 }
9942 prev_edited_row = selection.end.row;
9943
9944 row_delta =
9945 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9946 }
9947
9948 self.transact(window, cx, |this, window, cx| {
9949 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9950 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9951 });
9952 }
9953
9954 fn indent_selection(
9955 buffer: &MultiBuffer,
9956 snapshot: &MultiBufferSnapshot,
9957 selection: &mut Selection<Point>,
9958 edits: &mut Vec<(Range<Point>, String)>,
9959 delta_for_start_row: u32,
9960 cx: &App,
9961 ) -> u32 {
9962 let settings = buffer.language_settings_at(selection.start, cx);
9963 let tab_size = settings.tab_size.get();
9964 let indent_kind = if settings.hard_tabs {
9965 IndentKind::Tab
9966 } else {
9967 IndentKind::Space
9968 };
9969 let mut start_row = selection.start.row;
9970 let mut end_row = selection.end.row + 1;
9971
9972 // If a selection ends at the beginning of a line, don't indent
9973 // that last line.
9974 if selection.end.column == 0 && selection.end.row > selection.start.row {
9975 end_row -= 1;
9976 }
9977
9978 // Avoid re-indenting a row that has already been indented by a
9979 // previous selection, but still update this selection's column
9980 // to reflect that indentation.
9981 if delta_for_start_row > 0 {
9982 start_row += 1;
9983 selection.start.column += delta_for_start_row;
9984 if selection.end.row == selection.start.row {
9985 selection.end.column += delta_for_start_row;
9986 }
9987 }
9988
9989 let mut delta_for_end_row = 0;
9990 let has_multiple_rows = start_row + 1 != end_row;
9991 for row in start_row..end_row {
9992 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
9993 let indent_delta = match (current_indent.kind, indent_kind) {
9994 (IndentKind::Space, IndentKind::Space) => {
9995 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
9996 IndentSize::spaces(columns_to_next_tab_stop)
9997 }
9998 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
9999 (_, IndentKind::Tab) => IndentSize::tab(),
10000 };
10001
10002 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10003 0
10004 } else {
10005 selection.start.column
10006 };
10007 let row_start = Point::new(row, start);
10008 edits.push((
10009 row_start..row_start,
10010 indent_delta.chars().collect::<String>(),
10011 ));
10012
10013 // Update this selection's endpoints to reflect the indentation.
10014 if row == selection.start.row {
10015 selection.start.column += indent_delta.len;
10016 }
10017 if row == selection.end.row {
10018 selection.end.column += indent_delta.len;
10019 delta_for_end_row = indent_delta.len;
10020 }
10021 }
10022
10023 if selection.start.row == selection.end.row {
10024 delta_for_start_row + delta_for_end_row
10025 } else {
10026 delta_for_end_row
10027 }
10028 }
10029
10030 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10031 if self.read_only(cx) {
10032 return;
10033 }
10034 if self.mode.is_single_line() {
10035 cx.propagate();
10036 return;
10037 }
10038
10039 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10040 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10041 let selections = self.selections.all::<Point>(cx);
10042 let mut deletion_ranges = Vec::new();
10043 let mut last_outdent = None;
10044 {
10045 let buffer = self.buffer.read(cx);
10046 let snapshot = buffer.snapshot(cx);
10047 for selection in &selections {
10048 let settings = buffer.language_settings_at(selection.start, cx);
10049 let tab_size = settings.tab_size.get();
10050 let mut rows = selection.spanned_rows(false, &display_map);
10051
10052 // Avoid re-outdenting a row that has already been outdented by a
10053 // previous selection.
10054 if let Some(last_row) = last_outdent {
10055 if last_row == rows.start {
10056 rows.start = rows.start.next_row();
10057 }
10058 }
10059 let has_multiple_rows = rows.len() > 1;
10060 for row in rows.iter_rows() {
10061 let indent_size = snapshot.indent_size_for_line(row);
10062 if indent_size.len > 0 {
10063 let deletion_len = match indent_size.kind {
10064 IndentKind::Space => {
10065 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10066 if columns_to_prev_tab_stop == 0 {
10067 tab_size
10068 } else {
10069 columns_to_prev_tab_stop
10070 }
10071 }
10072 IndentKind::Tab => 1,
10073 };
10074 let start = if has_multiple_rows
10075 || deletion_len > selection.start.column
10076 || indent_size.len < selection.start.column
10077 {
10078 0
10079 } else {
10080 selection.start.column - deletion_len
10081 };
10082 deletion_ranges.push(
10083 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10084 );
10085 last_outdent = Some(row);
10086 }
10087 }
10088 }
10089 }
10090
10091 self.transact(window, cx, |this, window, cx| {
10092 this.buffer.update(cx, |buffer, cx| {
10093 let empty_str: Arc<str> = Arc::default();
10094 buffer.edit(
10095 deletion_ranges
10096 .into_iter()
10097 .map(|range| (range, empty_str.clone())),
10098 None,
10099 cx,
10100 );
10101 });
10102 let selections = this.selections.all::<usize>(cx);
10103 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10104 });
10105 }
10106
10107 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10108 if self.read_only(cx) {
10109 return;
10110 }
10111 if self.mode.is_single_line() {
10112 cx.propagate();
10113 return;
10114 }
10115
10116 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10117 let selections = self
10118 .selections
10119 .all::<usize>(cx)
10120 .into_iter()
10121 .map(|s| s.range());
10122
10123 self.transact(window, cx, |this, window, cx| {
10124 this.buffer.update(cx, |buffer, cx| {
10125 buffer.autoindent_ranges(selections, cx);
10126 });
10127 let selections = this.selections.all::<usize>(cx);
10128 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10129 });
10130 }
10131
10132 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10133 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10134 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10135 let selections = self.selections.all::<Point>(cx);
10136
10137 let mut new_cursors = Vec::new();
10138 let mut edit_ranges = Vec::new();
10139 let mut selections = selections.iter().peekable();
10140 while let Some(selection) = selections.next() {
10141 let mut rows = selection.spanned_rows(false, &display_map);
10142 let goal_display_column = selection.head().to_display_point(&display_map).column();
10143
10144 // Accumulate contiguous regions of rows that we want to delete.
10145 while let Some(next_selection) = selections.peek() {
10146 let next_rows = next_selection.spanned_rows(false, &display_map);
10147 if next_rows.start <= rows.end {
10148 rows.end = next_rows.end;
10149 selections.next().unwrap();
10150 } else {
10151 break;
10152 }
10153 }
10154
10155 let buffer = &display_map.buffer_snapshot;
10156 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
10157 let edit_end;
10158 let cursor_buffer_row;
10159 if buffer.max_point().row >= rows.end.0 {
10160 // If there's a line after the range, delete the \n from the end of the row range
10161 // and position the cursor on the next line.
10162 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
10163 cursor_buffer_row = rows.end;
10164 } else {
10165 // If there isn't a line after the range, delete the \n from the line before the
10166 // start of the row range and position the cursor there.
10167 edit_start = edit_start.saturating_sub(1);
10168 edit_end = buffer.len();
10169 cursor_buffer_row = rows.start.previous_row();
10170 }
10171
10172 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
10173 *cursor.column_mut() =
10174 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
10175
10176 new_cursors.push((
10177 selection.id,
10178 buffer.anchor_after(cursor.to_point(&display_map)),
10179 ));
10180 edit_ranges.push(edit_start..edit_end);
10181 }
10182
10183 self.transact(window, cx, |this, window, cx| {
10184 let buffer = this.buffer.update(cx, |buffer, cx| {
10185 let empty_str: Arc<str> = Arc::default();
10186 buffer.edit(
10187 edit_ranges
10188 .into_iter()
10189 .map(|range| (range, empty_str.clone())),
10190 None,
10191 cx,
10192 );
10193 buffer.snapshot(cx)
10194 });
10195 let new_selections = new_cursors
10196 .into_iter()
10197 .map(|(id, cursor)| {
10198 let cursor = cursor.to_point(&buffer);
10199 Selection {
10200 id,
10201 start: cursor,
10202 end: cursor,
10203 reversed: false,
10204 goal: SelectionGoal::None,
10205 }
10206 })
10207 .collect();
10208
10209 this.change_selections(Default::default(), window, cx, |s| {
10210 s.select(new_selections);
10211 });
10212 });
10213 }
10214
10215 pub fn join_lines_impl(
10216 &mut self,
10217 insert_whitespace: bool,
10218 window: &mut Window,
10219 cx: &mut Context<Self>,
10220 ) {
10221 if self.read_only(cx) {
10222 return;
10223 }
10224 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10225 for selection in self.selections.all::<Point>(cx) {
10226 let start = MultiBufferRow(selection.start.row);
10227 // Treat single line selections as if they include the next line. Otherwise this action
10228 // would do nothing for single line selections individual cursors.
10229 let end = if selection.start.row == selection.end.row {
10230 MultiBufferRow(selection.start.row + 1)
10231 } else {
10232 MultiBufferRow(selection.end.row)
10233 };
10234
10235 if let Some(last_row_range) = row_ranges.last_mut() {
10236 if start <= last_row_range.end {
10237 last_row_range.end = end;
10238 continue;
10239 }
10240 }
10241 row_ranges.push(start..end);
10242 }
10243
10244 let snapshot = self.buffer.read(cx).snapshot(cx);
10245 let mut cursor_positions = Vec::new();
10246 for row_range in &row_ranges {
10247 let anchor = snapshot.anchor_before(Point::new(
10248 row_range.end.previous_row().0,
10249 snapshot.line_len(row_range.end.previous_row()),
10250 ));
10251 cursor_positions.push(anchor..anchor);
10252 }
10253
10254 self.transact(window, cx, |this, window, cx| {
10255 for row_range in row_ranges.into_iter().rev() {
10256 for row in row_range.iter_rows().rev() {
10257 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10258 let next_line_row = row.next_row();
10259 let indent = snapshot.indent_size_for_line(next_line_row);
10260 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10261
10262 let replace =
10263 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10264 " "
10265 } else {
10266 ""
10267 };
10268
10269 this.buffer.update(cx, |buffer, cx| {
10270 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10271 });
10272 }
10273 }
10274
10275 this.change_selections(Default::default(), window, cx, |s| {
10276 s.select_anchor_ranges(cursor_positions)
10277 });
10278 });
10279 }
10280
10281 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10282 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10283 self.join_lines_impl(true, window, cx);
10284 }
10285
10286 pub fn sort_lines_case_sensitive(
10287 &mut self,
10288 _: &SortLinesCaseSensitive,
10289 window: &mut Window,
10290 cx: &mut Context<Self>,
10291 ) {
10292 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10293 }
10294
10295 pub fn sort_lines_by_length(
10296 &mut self,
10297 _: &SortLinesByLength,
10298 window: &mut Window,
10299 cx: &mut Context<Self>,
10300 ) {
10301 self.manipulate_immutable_lines(window, cx, |lines| {
10302 lines.sort_by_key(|&line| line.chars().count())
10303 })
10304 }
10305
10306 pub fn sort_lines_case_insensitive(
10307 &mut self,
10308 _: &SortLinesCaseInsensitive,
10309 window: &mut Window,
10310 cx: &mut Context<Self>,
10311 ) {
10312 self.manipulate_immutable_lines(window, cx, |lines| {
10313 lines.sort_by_key(|line| line.to_lowercase())
10314 })
10315 }
10316
10317 pub fn unique_lines_case_insensitive(
10318 &mut self,
10319 _: &UniqueLinesCaseInsensitive,
10320 window: &mut Window,
10321 cx: &mut Context<Self>,
10322 ) {
10323 self.manipulate_immutable_lines(window, cx, |lines| {
10324 let mut seen = HashSet::default();
10325 lines.retain(|line| seen.insert(line.to_lowercase()));
10326 })
10327 }
10328
10329 pub fn unique_lines_case_sensitive(
10330 &mut self,
10331 _: &UniqueLinesCaseSensitive,
10332 window: &mut Window,
10333 cx: &mut Context<Self>,
10334 ) {
10335 self.manipulate_immutable_lines(window, cx, |lines| {
10336 let mut seen = HashSet::default();
10337 lines.retain(|line| seen.insert(*line));
10338 })
10339 }
10340
10341 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10342 let Some(project) = self.project.clone() else {
10343 return;
10344 };
10345 self.reload(project, window, cx)
10346 .detach_and_notify_err(window, cx);
10347 }
10348
10349 pub fn restore_file(
10350 &mut self,
10351 _: &::git::RestoreFile,
10352 window: &mut Window,
10353 cx: &mut Context<Self>,
10354 ) {
10355 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10356 let mut buffer_ids = HashSet::default();
10357 let snapshot = self.buffer().read(cx).snapshot(cx);
10358 for selection in self.selections.all::<usize>(cx) {
10359 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10360 }
10361
10362 let buffer = self.buffer().read(cx);
10363 let ranges = buffer_ids
10364 .into_iter()
10365 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10366 .collect::<Vec<_>>();
10367
10368 self.restore_hunks_in_ranges(ranges, window, cx);
10369 }
10370
10371 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10372 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10373 let selections = self
10374 .selections
10375 .all(cx)
10376 .into_iter()
10377 .map(|s| s.range())
10378 .collect();
10379 self.restore_hunks_in_ranges(selections, window, cx);
10380 }
10381
10382 pub fn restore_hunks_in_ranges(
10383 &mut self,
10384 ranges: Vec<Range<Point>>,
10385 window: &mut Window,
10386 cx: &mut Context<Editor>,
10387 ) {
10388 let mut revert_changes = HashMap::default();
10389 let chunk_by = self
10390 .snapshot(window, cx)
10391 .hunks_for_ranges(ranges)
10392 .into_iter()
10393 .chunk_by(|hunk| hunk.buffer_id);
10394 for (buffer_id, hunks) in &chunk_by {
10395 let hunks = hunks.collect::<Vec<_>>();
10396 for hunk in &hunks {
10397 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10398 }
10399 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10400 }
10401 drop(chunk_by);
10402 if !revert_changes.is_empty() {
10403 self.transact(window, cx, |editor, window, cx| {
10404 editor.restore(revert_changes, window, cx);
10405 });
10406 }
10407 }
10408
10409 pub fn open_active_item_in_terminal(
10410 &mut self,
10411 _: &OpenInTerminal,
10412 window: &mut Window,
10413 cx: &mut Context<Self>,
10414 ) {
10415 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10416 let project_path = buffer.read(cx).project_path(cx)?;
10417 let project = self.project.as_ref()?.read(cx);
10418 let entry = project.entry_for_path(&project_path, cx)?;
10419 let parent = match &entry.canonical_path {
10420 Some(canonical_path) => canonical_path.to_path_buf(),
10421 None => project.absolute_path(&project_path, cx)?,
10422 }
10423 .parent()?
10424 .to_path_buf();
10425 Some(parent)
10426 }) {
10427 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10428 }
10429 }
10430
10431 fn set_breakpoint_context_menu(
10432 &mut self,
10433 display_row: DisplayRow,
10434 position: Option<Anchor>,
10435 clicked_point: gpui::Point<Pixels>,
10436 window: &mut Window,
10437 cx: &mut Context<Self>,
10438 ) {
10439 let source = self
10440 .buffer
10441 .read(cx)
10442 .snapshot(cx)
10443 .anchor_before(Point::new(display_row.0, 0u32));
10444
10445 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10446
10447 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10448 self,
10449 source,
10450 clicked_point,
10451 context_menu,
10452 window,
10453 cx,
10454 );
10455 }
10456
10457 fn add_edit_breakpoint_block(
10458 &mut self,
10459 anchor: Anchor,
10460 breakpoint: &Breakpoint,
10461 edit_action: BreakpointPromptEditAction,
10462 window: &mut Window,
10463 cx: &mut Context<Self>,
10464 ) {
10465 let weak_editor = cx.weak_entity();
10466 let bp_prompt = cx.new(|cx| {
10467 BreakpointPromptEditor::new(
10468 weak_editor,
10469 anchor,
10470 breakpoint.clone(),
10471 edit_action,
10472 window,
10473 cx,
10474 )
10475 });
10476
10477 let height = bp_prompt.update(cx, |this, cx| {
10478 this.prompt
10479 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10480 });
10481 let cloned_prompt = bp_prompt.clone();
10482 let blocks = vec![BlockProperties {
10483 style: BlockStyle::Sticky,
10484 placement: BlockPlacement::Above(anchor),
10485 height: Some(height),
10486 render: Arc::new(move |cx| {
10487 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10488 cloned_prompt.clone().into_any_element()
10489 }),
10490 priority: 0,
10491 }];
10492
10493 let focus_handle = bp_prompt.focus_handle(cx);
10494 window.focus(&focus_handle);
10495
10496 let block_ids = self.insert_blocks(blocks, None, cx);
10497 bp_prompt.update(cx, |prompt, _| {
10498 prompt.add_block_ids(block_ids);
10499 });
10500 }
10501
10502 pub(crate) fn breakpoint_at_row(
10503 &self,
10504 row: u32,
10505 window: &mut Window,
10506 cx: &mut Context<Self>,
10507 ) -> Option<(Anchor, Breakpoint)> {
10508 let snapshot = self.snapshot(window, cx);
10509 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
10510
10511 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10512 }
10513
10514 pub(crate) fn breakpoint_at_anchor(
10515 &self,
10516 breakpoint_position: Anchor,
10517 snapshot: &EditorSnapshot,
10518 cx: &mut Context<Self>,
10519 ) -> Option<(Anchor, Breakpoint)> {
10520 let project = self.project.clone()?;
10521
10522 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
10523 snapshot
10524 .buffer_snapshot
10525 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
10526 })?;
10527
10528 let enclosing_excerpt = breakpoint_position.excerpt_id;
10529 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
10530 let buffer_snapshot = buffer.read(cx).snapshot();
10531
10532 let row = buffer_snapshot
10533 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10534 .row;
10535
10536 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
10537 let anchor_end = snapshot
10538 .buffer_snapshot
10539 .anchor_after(Point::new(row, line_len));
10540
10541 let bp = self
10542 .breakpoint_store
10543 .as_ref()?
10544 .read_with(cx, |breakpoint_store, cx| {
10545 breakpoint_store
10546 .breakpoints(
10547 &buffer,
10548 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10549 &buffer_snapshot,
10550 cx,
10551 )
10552 .next()
10553 .and_then(|(bp, _)| {
10554 let breakpoint_row = buffer_snapshot
10555 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10556 .row;
10557
10558 if breakpoint_row == row {
10559 snapshot
10560 .buffer_snapshot
10561 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10562 .map(|position| (position, bp.bp.clone()))
10563 } else {
10564 None
10565 }
10566 })
10567 });
10568 bp
10569 }
10570
10571 pub fn edit_log_breakpoint(
10572 &mut self,
10573 _: &EditLogBreakpoint,
10574 window: &mut Window,
10575 cx: &mut Context<Self>,
10576 ) {
10577 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10578 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10579 message: None,
10580 state: BreakpointState::Enabled,
10581 condition: None,
10582 hit_condition: None,
10583 });
10584
10585 self.add_edit_breakpoint_block(
10586 anchor,
10587 &breakpoint,
10588 BreakpointPromptEditAction::Log,
10589 window,
10590 cx,
10591 );
10592 }
10593 }
10594
10595 fn breakpoints_at_cursors(
10596 &self,
10597 window: &mut Window,
10598 cx: &mut Context<Self>,
10599 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10600 let snapshot = self.snapshot(window, cx);
10601 let cursors = self
10602 .selections
10603 .disjoint_anchors()
10604 .into_iter()
10605 .map(|selection| {
10606 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
10607
10608 let breakpoint_position = self
10609 .breakpoint_at_row(cursor_position.row, window, cx)
10610 .map(|bp| bp.0)
10611 .unwrap_or_else(|| {
10612 snapshot
10613 .display_snapshot
10614 .buffer_snapshot
10615 .anchor_after(Point::new(cursor_position.row, 0))
10616 });
10617
10618 let breakpoint = self
10619 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10620 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10621
10622 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10623 })
10624 // 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.
10625 .collect::<HashMap<Anchor, _>>();
10626
10627 cursors.into_iter().collect()
10628 }
10629
10630 pub fn enable_breakpoint(
10631 &mut self,
10632 _: &crate::actions::EnableBreakpoint,
10633 window: &mut Window,
10634 cx: &mut Context<Self>,
10635 ) {
10636 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10637 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10638 continue;
10639 };
10640 self.edit_breakpoint_at_anchor(
10641 anchor,
10642 breakpoint,
10643 BreakpointEditAction::InvertState,
10644 cx,
10645 );
10646 }
10647 }
10648
10649 pub fn disable_breakpoint(
10650 &mut self,
10651 _: &crate::actions::DisableBreakpoint,
10652 window: &mut Window,
10653 cx: &mut Context<Self>,
10654 ) {
10655 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10656 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10657 continue;
10658 };
10659 self.edit_breakpoint_at_anchor(
10660 anchor,
10661 breakpoint,
10662 BreakpointEditAction::InvertState,
10663 cx,
10664 );
10665 }
10666 }
10667
10668 pub fn toggle_breakpoint(
10669 &mut self,
10670 _: &crate::actions::ToggleBreakpoint,
10671 window: &mut Window,
10672 cx: &mut Context<Self>,
10673 ) {
10674 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10675 if let Some(breakpoint) = breakpoint {
10676 self.edit_breakpoint_at_anchor(
10677 anchor,
10678 breakpoint,
10679 BreakpointEditAction::Toggle,
10680 cx,
10681 );
10682 } else {
10683 self.edit_breakpoint_at_anchor(
10684 anchor,
10685 Breakpoint::new_standard(),
10686 BreakpointEditAction::Toggle,
10687 cx,
10688 );
10689 }
10690 }
10691 }
10692
10693 pub fn edit_breakpoint_at_anchor(
10694 &mut self,
10695 breakpoint_position: Anchor,
10696 breakpoint: Breakpoint,
10697 edit_action: BreakpointEditAction,
10698 cx: &mut Context<Self>,
10699 ) {
10700 let Some(breakpoint_store) = &self.breakpoint_store else {
10701 return;
10702 };
10703
10704 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
10705 if breakpoint_position == Anchor::min() {
10706 self.buffer()
10707 .read(cx)
10708 .excerpt_buffer_ids()
10709 .into_iter()
10710 .next()
10711 } else {
10712 None
10713 }
10714 }) else {
10715 return;
10716 };
10717
10718 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
10719 return;
10720 };
10721
10722 breakpoint_store.update(cx, |breakpoint_store, cx| {
10723 breakpoint_store.toggle_breakpoint(
10724 buffer,
10725 BreakpointWithPosition {
10726 position: breakpoint_position.text_anchor,
10727 bp: breakpoint,
10728 },
10729 edit_action,
10730 cx,
10731 );
10732 });
10733
10734 cx.notify();
10735 }
10736
10737 #[cfg(any(test, feature = "test-support"))]
10738 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10739 self.breakpoint_store.clone()
10740 }
10741
10742 pub fn prepare_restore_change(
10743 &self,
10744 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10745 hunk: &MultiBufferDiffHunk,
10746 cx: &mut App,
10747 ) -> Option<()> {
10748 if hunk.is_created_file() {
10749 return None;
10750 }
10751 let buffer = self.buffer.read(cx);
10752 let diff = buffer.diff_for(hunk.buffer_id)?;
10753 let buffer = buffer.buffer(hunk.buffer_id)?;
10754 let buffer = buffer.read(cx);
10755 let original_text = diff
10756 .read(cx)
10757 .base_text()
10758 .as_rope()
10759 .slice(hunk.diff_base_byte_range.clone());
10760 let buffer_snapshot = buffer.snapshot();
10761 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
10762 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
10763 probe
10764 .0
10765 .start
10766 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
10767 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
10768 }) {
10769 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
10770 Some(())
10771 } else {
10772 None
10773 }
10774 }
10775
10776 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
10777 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
10778 }
10779
10780 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
10781 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
10782 }
10783
10784 fn manipulate_lines<M>(
10785 &mut self,
10786 window: &mut Window,
10787 cx: &mut Context<Self>,
10788 mut manipulate: M,
10789 ) where
10790 M: FnMut(&str) -> LineManipulationResult,
10791 {
10792 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10793
10794 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10795 let buffer = self.buffer.read(cx).snapshot(cx);
10796
10797 let mut edits = Vec::new();
10798
10799 let selections = self.selections.all::<Point>(cx);
10800 let mut selections = selections.iter().peekable();
10801 let mut contiguous_row_selections = Vec::new();
10802 let mut new_selections = Vec::new();
10803 let mut added_lines = 0;
10804 let mut removed_lines = 0;
10805
10806 while let Some(selection) = selections.next() {
10807 let (start_row, end_row) = consume_contiguous_rows(
10808 &mut contiguous_row_selections,
10809 selection,
10810 &display_map,
10811 &mut selections,
10812 );
10813
10814 let start_point = Point::new(start_row.0, 0);
10815 let end_point = Point::new(
10816 end_row.previous_row().0,
10817 buffer.line_len(end_row.previous_row()),
10818 );
10819 let text = buffer
10820 .text_for_range(start_point..end_point)
10821 .collect::<String>();
10822
10823 let LineManipulationResult {
10824 new_text,
10825 line_count_before,
10826 line_count_after,
10827 } = manipulate(&text);
10828
10829 edits.push((start_point..end_point, new_text));
10830
10831 // Selections must change based on added and removed line count
10832 let start_row =
10833 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
10834 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
10835 new_selections.push(Selection {
10836 id: selection.id,
10837 start: start_row,
10838 end: end_row,
10839 goal: SelectionGoal::None,
10840 reversed: selection.reversed,
10841 });
10842
10843 if line_count_after > line_count_before {
10844 added_lines += line_count_after - line_count_before;
10845 } else if line_count_before > line_count_after {
10846 removed_lines += line_count_before - line_count_after;
10847 }
10848 }
10849
10850 self.transact(window, cx, |this, window, cx| {
10851 let buffer = this.buffer.update(cx, |buffer, cx| {
10852 buffer.edit(edits, None, cx);
10853 buffer.snapshot(cx)
10854 });
10855
10856 // Recalculate offsets on newly edited buffer
10857 let new_selections = new_selections
10858 .iter()
10859 .map(|s| {
10860 let start_point = Point::new(s.start.0, 0);
10861 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
10862 Selection {
10863 id: s.id,
10864 start: buffer.point_to_offset(start_point),
10865 end: buffer.point_to_offset(end_point),
10866 goal: s.goal,
10867 reversed: s.reversed,
10868 }
10869 })
10870 .collect();
10871
10872 this.change_selections(Default::default(), window, cx, |s| {
10873 s.select(new_selections);
10874 });
10875
10876 this.request_autoscroll(Autoscroll::fit(), cx);
10877 });
10878 }
10879
10880 fn manipulate_immutable_lines<Fn>(
10881 &mut self,
10882 window: &mut Window,
10883 cx: &mut Context<Self>,
10884 mut callback: Fn,
10885 ) where
10886 Fn: FnMut(&mut Vec<&str>),
10887 {
10888 self.manipulate_lines(window, cx, |text| {
10889 let mut lines: Vec<&str> = text.split('\n').collect();
10890 let line_count_before = lines.len();
10891
10892 callback(&mut lines);
10893
10894 LineManipulationResult {
10895 new_text: lines.join("\n"),
10896 line_count_before,
10897 line_count_after: lines.len(),
10898 }
10899 });
10900 }
10901
10902 fn manipulate_mutable_lines<Fn>(
10903 &mut self,
10904 window: &mut Window,
10905 cx: &mut Context<Self>,
10906 mut callback: Fn,
10907 ) where
10908 Fn: FnMut(&mut Vec<Cow<'_, str>>),
10909 {
10910 self.manipulate_lines(window, cx, |text| {
10911 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
10912 let line_count_before = lines.len();
10913
10914 callback(&mut lines);
10915
10916 LineManipulationResult {
10917 new_text: lines.join("\n"),
10918 line_count_before,
10919 line_count_after: lines.len(),
10920 }
10921 });
10922 }
10923
10924 pub fn convert_indentation_to_spaces(
10925 &mut self,
10926 _: &ConvertIndentationToSpaces,
10927 window: &mut Window,
10928 cx: &mut Context<Self>,
10929 ) {
10930 let settings = self.buffer.read(cx).language_settings(cx);
10931 let tab_size = settings.tab_size.get() as usize;
10932
10933 self.manipulate_mutable_lines(window, cx, |lines| {
10934 // Allocates a reasonably sized scratch buffer once for the whole loop
10935 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
10936 // Avoids recomputing spaces that could be inserted many times
10937 let space_cache: Vec<Vec<char>> = (1..=tab_size)
10938 .map(|n| IndentSize::spaces(n as u32).chars().collect())
10939 .collect();
10940
10941 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
10942 let mut chars = line.as_ref().chars();
10943 let mut col = 0;
10944 let mut changed = false;
10945
10946 while let Some(ch) = chars.next() {
10947 match ch {
10948 ' ' => {
10949 reindented_line.push(' ');
10950 col += 1;
10951 }
10952 '\t' => {
10953 // \t are converted to spaces depending on the current column
10954 let spaces_len = tab_size - (col % tab_size);
10955 reindented_line.extend(&space_cache[spaces_len - 1]);
10956 col += spaces_len;
10957 changed = true;
10958 }
10959 _ => {
10960 // If we dont append before break, the character is consumed
10961 reindented_line.push(ch);
10962 break;
10963 }
10964 }
10965 }
10966
10967 if !changed {
10968 reindented_line.clear();
10969 continue;
10970 }
10971 // Append the rest of the line and replace old reference with new one
10972 reindented_line.extend(chars);
10973 *line = Cow::Owned(reindented_line.clone());
10974 reindented_line.clear();
10975 }
10976 });
10977 }
10978
10979 pub fn convert_indentation_to_tabs(
10980 &mut self,
10981 _: &ConvertIndentationToTabs,
10982 window: &mut Window,
10983 cx: &mut Context<Self>,
10984 ) {
10985 let settings = self.buffer.read(cx).language_settings(cx);
10986 let tab_size = settings.tab_size.get() as usize;
10987
10988 self.manipulate_mutable_lines(window, cx, |lines| {
10989 // Allocates a reasonably sized buffer once for the whole loop
10990 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
10991 // Avoids recomputing spaces that could be inserted many times
10992 let space_cache: Vec<Vec<char>> = (1..=tab_size)
10993 .map(|n| IndentSize::spaces(n as u32).chars().collect())
10994 .collect();
10995
10996 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
10997 let mut chars = line.chars();
10998 let mut spaces_count = 0;
10999 let mut first_non_indent_char = None;
11000 let mut changed = false;
11001
11002 while let Some(ch) = chars.next() {
11003 match ch {
11004 ' ' => {
11005 // Keep track of spaces. Append \t when we reach tab_size
11006 spaces_count += 1;
11007 changed = true;
11008 if spaces_count == tab_size {
11009 reindented_line.push('\t');
11010 spaces_count = 0;
11011 }
11012 }
11013 '\t' => {
11014 reindented_line.push('\t');
11015 spaces_count = 0;
11016 }
11017 _ => {
11018 // Dont append it yet, we might have remaining spaces
11019 first_non_indent_char = Some(ch);
11020 break;
11021 }
11022 }
11023 }
11024
11025 if !changed {
11026 reindented_line.clear();
11027 continue;
11028 }
11029 // Remaining spaces that didn't make a full tab stop
11030 if spaces_count > 0 {
11031 reindented_line.extend(&space_cache[spaces_count - 1]);
11032 }
11033 // If we consume an extra character that was not indentation, add it back
11034 if let Some(extra_char) = first_non_indent_char {
11035 reindented_line.push(extra_char);
11036 }
11037 // Append the rest of the line and replace old reference with new one
11038 reindented_line.extend(chars);
11039 *line = Cow::Owned(reindented_line.clone());
11040 reindented_line.clear();
11041 }
11042 });
11043 }
11044
11045 pub fn convert_to_upper_case(
11046 &mut self,
11047 _: &ConvertToUpperCase,
11048 window: &mut Window,
11049 cx: &mut Context<Self>,
11050 ) {
11051 self.manipulate_text(window, cx, |text| text.to_uppercase())
11052 }
11053
11054 pub fn convert_to_lower_case(
11055 &mut self,
11056 _: &ConvertToLowerCase,
11057 window: &mut Window,
11058 cx: &mut Context<Self>,
11059 ) {
11060 self.manipulate_text(window, cx, |text| text.to_lowercase())
11061 }
11062
11063 pub fn convert_to_title_case(
11064 &mut self,
11065 _: &ConvertToTitleCase,
11066 window: &mut Window,
11067 cx: &mut Context<Self>,
11068 ) {
11069 self.manipulate_text(window, cx, |text| {
11070 text.split('\n')
11071 .map(|line| line.to_case(Case::Title))
11072 .join("\n")
11073 })
11074 }
11075
11076 pub fn convert_to_snake_case(
11077 &mut self,
11078 _: &ConvertToSnakeCase,
11079 window: &mut Window,
11080 cx: &mut Context<Self>,
11081 ) {
11082 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11083 }
11084
11085 pub fn convert_to_kebab_case(
11086 &mut self,
11087 _: &ConvertToKebabCase,
11088 window: &mut Window,
11089 cx: &mut Context<Self>,
11090 ) {
11091 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11092 }
11093
11094 pub fn convert_to_upper_camel_case(
11095 &mut self,
11096 _: &ConvertToUpperCamelCase,
11097 window: &mut Window,
11098 cx: &mut Context<Self>,
11099 ) {
11100 self.manipulate_text(window, cx, |text| {
11101 text.split('\n')
11102 .map(|line| line.to_case(Case::UpperCamel))
11103 .join("\n")
11104 })
11105 }
11106
11107 pub fn convert_to_lower_camel_case(
11108 &mut self,
11109 _: &ConvertToLowerCamelCase,
11110 window: &mut Window,
11111 cx: &mut Context<Self>,
11112 ) {
11113 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11114 }
11115
11116 pub fn convert_to_opposite_case(
11117 &mut self,
11118 _: &ConvertToOppositeCase,
11119 window: &mut Window,
11120 cx: &mut Context<Self>,
11121 ) {
11122 self.manipulate_text(window, cx, |text| {
11123 text.chars()
11124 .fold(String::with_capacity(text.len()), |mut t, c| {
11125 if c.is_uppercase() {
11126 t.extend(c.to_lowercase());
11127 } else {
11128 t.extend(c.to_uppercase());
11129 }
11130 t
11131 })
11132 })
11133 }
11134
11135 pub fn convert_to_sentence_case(
11136 &mut self,
11137 _: &ConvertToSentenceCase,
11138 window: &mut Window,
11139 cx: &mut Context<Self>,
11140 ) {
11141 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11142 }
11143
11144 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11145 self.manipulate_text(window, cx, |text| {
11146 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11147 if has_upper_case_characters {
11148 text.to_lowercase()
11149 } else {
11150 text.to_uppercase()
11151 }
11152 })
11153 }
11154
11155 pub fn convert_to_rot13(
11156 &mut self,
11157 _: &ConvertToRot13,
11158 window: &mut Window,
11159 cx: &mut Context<Self>,
11160 ) {
11161 self.manipulate_text(window, cx, |text| {
11162 text.chars()
11163 .map(|c| match c {
11164 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11165 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11166 _ => c,
11167 })
11168 .collect()
11169 })
11170 }
11171
11172 pub fn convert_to_rot47(
11173 &mut self,
11174 _: &ConvertToRot47,
11175 window: &mut Window,
11176 cx: &mut Context<Self>,
11177 ) {
11178 self.manipulate_text(window, cx, |text| {
11179 text.chars()
11180 .map(|c| {
11181 let code_point = c as u32;
11182 if code_point >= 33 && code_point <= 126 {
11183 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11184 }
11185 c
11186 })
11187 .collect()
11188 })
11189 }
11190
11191 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11192 where
11193 Fn: FnMut(&str) -> String,
11194 {
11195 let buffer = self.buffer.read(cx).snapshot(cx);
11196
11197 let mut new_selections = Vec::new();
11198 let mut edits = Vec::new();
11199 let mut selection_adjustment = 0i32;
11200
11201 for selection in self.selections.all::<usize>(cx) {
11202 let selection_is_empty = selection.is_empty();
11203
11204 let (start, end) = if selection_is_empty {
11205 let (word_range, _) = buffer.surrounding_word(selection.start, false);
11206 (word_range.start, word_range.end)
11207 } else {
11208 (selection.start, selection.end)
11209 };
11210
11211 let text = buffer.text_for_range(start..end).collect::<String>();
11212 let old_length = text.len() as i32;
11213 let text = callback(&text);
11214
11215 new_selections.push(Selection {
11216 start: (start as i32 - selection_adjustment) as usize,
11217 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11218 goal: SelectionGoal::None,
11219 ..selection
11220 });
11221
11222 selection_adjustment += old_length - text.len() as i32;
11223
11224 edits.push((start..end, text));
11225 }
11226
11227 self.transact(window, cx, |this, window, cx| {
11228 this.buffer.update(cx, |buffer, cx| {
11229 buffer.edit(edits, None, cx);
11230 });
11231
11232 this.change_selections(Default::default(), window, cx, |s| {
11233 s.select(new_selections);
11234 });
11235
11236 this.request_autoscroll(Autoscroll::fit(), cx);
11237 });
11238 }
11239
11240 pub fn move_selection_on_drop(
11241 &mut self,
11242 selection: &Selection<Anchor>,
11243 target: DisplayPoint,
11244 is_cut: bool,
11245 window: &mut Window,
11246 cx: &mut Context<Self>,
11247 ) {
11248 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11249 let buffer = &display_map.buffer_snapshot;
11250 let mut edits = Vec::new();
11251 let insert_point = display_map
11252 .clip_point(target, Bias::Left)
11253 .to_point(&display_map);
11254 let text = buffer
11255 .text_for_range(selection.start..selection.end)
11256 .collect::<String>();
11257 if is_cut {
11258 edits.push(((selection.start..selection.end), String::new()));
11259 }
11260 let insert_anchor = buffer.anchor_before(insert_point);
11261 edits.push(((insert_anchor..insert_anchor), text));
11262 let last_edit_start = insert_anchor.bias_left(buffer);
11263 let last_edit_end = insert_anchor.bias_right(buffer);
11264 self.transact(window, cx, |this, window, cx| {
11265 this.buffer.update(cx, |buffer, cx| {
11266 buffer.edit(edits, None, cx);
11267 });
11268 this.change_selections(Default::default(), window, cx, |s| {
11269 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11270 });
11271 });
11272 }
11273
11274 pub fn clear_selection_drag_state(&mut self) {
11275 self.selection_drag_state = SelectionDragState::None;
11276 }
11277
11278 pub fn duplicate(
11279 &mut self,
11280 upwards: bool,
11281 whole_lines: bool,
11282 window: &mut Window,
11283 cx: &mut Context<Self>,
11284 ) {
11285 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11286
11287 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11288 let buffer = &display_map.buffer_snapshot;
11289 let selections = self.selections.all::<Point>(cx);
11290
11291 let mut edits = Vec::new();
11292 let mut selections_iter = selections.iter().peekable();
11293 while let Some(selection) = selections_iter.next() {
11294 let mut rows = selection.spanned_rows(false, &display_map);
11295 // duplicate line-wise
11296 if whole_lines || selection.start == selection.end {
11297 // Avoid duplicating the same lines twice.
11298 while let Some(next_selection) = selections_iter.peek() {
11299 let next_rows = next_selection.spanned_rows(false, &display_map);
11300 if next_rows.start < rows.end {
11301 rows.end = next_rows.end;
11302 selections_iter.next().unwrap();
11303 } else {
11304 break;
11305 }
11306 }
11307
11308 // Copy the text from the selected row region and splice it either at the start
11309 // or end of the region.
11310 let start = Point::new(rows.start.0, 0);
11311 let end = Point::new(
11312 rows.end.previous_row().0,
11313 buffer.line_len(rows.end.previous_row()),
11314 );
11315 let text = buffer
11316 .text_for_range(start..end)
11317 .chain(Some("\n"))
11318 .collect::<String>();
11319 let insert_location = if upwards {
11320 Point::new(rows.end.0, 0)
11321 } else {
11322 start
11323 };
11324 edits.push((insert_location..insert_location, text));
11325 } else {
11326 // duplicate character-wise
11327 let start = selection.start;
11328 let end = selection.end;
11329 let text = buffer.text_for_range(start..end).collect::<String>();
11330 edits.push((selection.end..selection.end, text));
11331 }
11332 }
11333
11334 self.transact(window, cx, |this, _, cx| {
11335 this.buffer.update(cx, |buffer, cx| {
11336 buffer.edit(edits, None, cx);
11337 });
11338
11339 this.request_autoscroll(Autoscroll::fit(), cx);
11340 });
11341 }
11342
11343 pub fn duplicate_line_up(
11344 &mut self,
11345 _: &DuplicateLineUp,
11346 window: &mut Window,
11347 cx: &mut Context<Self>,
11348 ) {
11349 self.duplicate(true, true, window, cx);
11350 }
11351
11352 pub fn duplicate_line_down(
11353 &mut self,
11354 _: &DuplicateLineDown,
11355 window: &mut Window,
11356 cx: &mut Context<Self>,
11357 ) {
11358 self.duplicate(false, true, window, cx);
11359 }
11360
11361 pub fn duplicate_selection(
11362 &mut self,
11363 _: &DuplicateSelection,
11364 window: &mut Window,
11365 cx: &mut Context<Self>,
11366 ) {
11367 self.duplicate(false, false, window, cx);
11368 }
11369
11370 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11371 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11372 if self.mode.is_single_line() {
11373 cx.propagate();
11374 return;
11375 }
11376
11377 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11378 let buffer = self.buffer.read(cx).snapshot(cx);
11379
11380 let mut edits = Vec::new();
11381 let mut unfold_ranges = Vec::new();
11382 let mut refold_creases = Vec::new();
11383
11384 let selections = self.selections.all::<Point>(cx);
11385 let mut selections = selections.iter().peekable();
11386 let mut contiguous_row_selections = Vec::new();
11387 let mut new_selections = Vec::new();
11388
11389 while let Some(selection) = selections.next() {
11390 // Find all the selections that span a contiguous row range
11391 let (start_row, end_row) = consume_contiguous_rows(
11392 &mut contiguous_row_selections,
11393 selection,
11394 &display_map,
11395 &mut selections,
11396 );
11397
11398 // Move the text spanned by the row range to be before the line preceding the row range
11399 if start_row.0 > 0 {
11400 let range_to_move = Point::new(
11401 start_row.previous_row().0,
11402 buffer.line_len(start_row.previous_row()),
11403 )
11404 ..Point::new(
11405 end_row.previous_row().0,
11406 buffer.line_len(end_row.previous_row()),
11407 );
11408 let insertion_point = display_map
11409 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11410 .0;
11411
11412 // Don't move lines across excerpts
11413 if buffer
11414 .excerpt_containing(insertion_point..range_to_move.end)
11415 .is_some()
11416 {
11417 let text = buffer
11418 .text_for_range(range_to_move.clone())
11419 .flat_map(|s| s.chars())
11420 .skip(1)
11421 .chain(['\n'])
11422 .collect::<String>();
11423
11424 edits.push((
11425 buffer.anchor_after(range_to_move.start)
11426 ..buffer.anchor_before(range_to_move.end),
11427 String::new(),
11428 ));
11429 let insertion_anchor = buffer.anchor_after(insertion_point);
11430 edits.push((insertion_anchor..insertion_anchor, text));
11431
11432 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11433
11434 // Move selections up
11435 new_selections.extend(contiguous_row_selections.drain(..).map(
11436 |mut selection| {
11437 selection.start.row -= row_delta;
11438 selection.end.row -= row_delta;
11439 selection
11440 },
11441 ));
11442
11443 // Move folds up
11444 unfold_ranges.push(range_to_move.clone());
11445 for fold in display_map.folds_in_range(
11446 buffer.anchor_before(range_to_move.start)
11447 ..buffer.anchor_after(range_to_move.end),
11448 ) {
11449 let mut start = fold.range.start.to_point(&buffer);
11450 let mut end = fold.range.end.to_point(&buffer);
11451 start.row -= row_delta;
11452 end.row -= row_delta;
11453 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11454 }
11455 }
11456 }
11457
11458 // If we didn't move line(s), preserve the existing selections
11459 new_selections.append(&mut contiguous_row_selections);
11460 }
11461
11462 self.transact(window, cx, |this, window, cx| {
11463 this.unfold_ranges(&unfold_ranges, true, true, cx);
11464 this.buffer.update(cx, |buffer, cx| {
11465 for (range, text) in edits {
11466 buffer.edit([(range, text)], None, cx);
11467 }
11468 });
11469 this.fold_creases(refold_creases, true, window, cx);
11470 this.change_selections(Default::default(), window, cx, |s| {
11471 s.select(new_selections);
11472 })
11473 });
11474 }
11475
11476 pub fn move_line_down(
11477 &mut self,
11478 _: &MoveLineDown,
11479 window: &mut Window,
11480 cx: &mut Context<Self>,
11481 ) {
11482 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11483 if self.mode.is_single_line() {
11484 cx.propagate();
11485 return;
11486 }
11487
11488 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11489 let buffer = self.buffer.read(cx).snapshot(cx);
11490
11491 let mut edits = Vec::new();
11492 let mut unfold_ranges = Vec::new();
11493 let mut refold_creases = Vec::new();
11494
11495 let selections = self.selections.all::<Point>(cx);
11496 let mut selections = selections.iter().peekable();
11497 let mut contiguous_row_selections = Vec::new();
11498 let mut new_selections = Vec::new();
11499
11500 while let Some(selection) = selections.next() {
11501 // Find all the selections that span a contiguous row range
11502 let (start_row, end_row) = consume_contiguous_rows(
11503 &mut contiguous_row_selections,
11504 selection,
11505 &display_map,
11506 &mut selections,
11507 );
11508
11509 // Move the text spanned by the row range to be after the last line of the row range
11510 if end_row.0 <= buffer.max_point().row {
11511 let range_to_move =
11512 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11513 let insertion_point = display_map
11514 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11515 .0;
11516
11517 // Don't move lines across excerpt boundaries
11518 if buffer
11519 .excerpt_containing(range_to_move.start..insertion_point)
11520 .is_some()
11521 {
11522 let mut text = String::from("\n");
11523 text.extend(buffer.text_for_range(range_to_move.clone()));
11524 text.pop(); // Drop trailing newline
11525 edits.push((
11526 buffer.anchor_after(range_to_move.start)
11527 ..buffer.anchor_before(range_to_move.end),
11528 String::new(),
11529 ));
11530 let insertion_anchor = buffer.anchor_after(insertion_point);
11531 edits.push((insertion_anchor..insertion_anchor, text));
11532
11533 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11534
11535 // Move selections down
11536 new_selections.extend(contiguous_row_selections.drain(..).map(
11537 |mut selection| {
11538 selection.start.row += row_delta;
11539 selection.end.row += row_delta;
11540 selection
11541 },
11542 ));
11543
11544 // Move folds down
11545 unfold_ranges.push(range_to_move.clone());
11546 for fold in display_map.folds_in_range(
11547 buffer.anchor_before(range_to_move.start)
11548 ..buffer.anchor_after(range_to_move.end),
11549 ) {
11550 let mut start = fold.range.start.to_point(&buffer);
11551 let mut end = fold.range.end.to_point(&buffer);
11552 start.row += row_delta;
11553 end.row += row_delta;
11554 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11555 }
11556 }
11557 }
11558
11559 // If we didn't move line(s), preserve the existing selections
11560 new_selections.append(&mut contiguous_row_selections);
11561 }
11562
11563 self.transact(window, cx, |this, window, cx| {
11564 this.unfold_ranges(&unfold_ranges, true, true, cx);
11565 this.buffer.update(cx, |buffer, cx| {
11566 for (range, text) in edits {
11567 buffer.edit([(range, text)], None, cx);
11568 }
11569 });
11570 this.fold_creases(refold_creases, true, window, cx);
11571 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
11572 });
11573 }
11574
11575 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11576 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11577 let text_layout_details = &self.text_layout_details(window);
11578 self.transact(window, cx, |this, window, cx| {
11579 let edits = this.change_selections(Default::default(), window, cx, |s| {
11580 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11581 s.move_with(|display_map, selection| {
11582 if !selection.is_empty() {
11583 return;
11584 }
11585
11586 let mut head = selection.head();
11587 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11588 if head.column() == display_map.line_len(head.row()) {
11589 transpose_offset = display_map
11590 .buffer_snapshot
11591 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11592 }
11593
11594 if transpose_offset == 0 {
11595 return;
11596 }
11597
11598 *head.column_mut() += 1;
11599 head = display_map.clip_point(head, Bias::Right);
11600 let goal = SelectionGoal::HorizontalPosition(
11601 display_map
11602 .x_for_display_point(head, text_layout_details)
11603 .into(),
11604 );
11605 selection.collapse_to(head, goal);
11606
11607 let transpose_start = display_map
11608 .buffer_snapshot
11609 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11610 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
11611 let transpose_end = display_map
11612 .buffer_snapshot
11613 .clip_offset(transpose_offset + 1, Bias::Right);
11614 if let Some(ch) =
11615 display_map.buffer_snapshot.chars_at(transpose_start).next()
11616 {
11617 edits.push((transpose_start..transpose_offset, String::new()));
11618 edits.push((transpose_end..transpose_end, ch.to_string()));
11619 }
11620 }
11621 });
11622 edits
11623 });
11624 this.buffer
11625 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11626 let selections = this.selections.all::<usize>(cx);
11627 this.change_selections(Default::default(), window, cx, |s| {
11628 s.select(selections);
11629 });
11630 });
11631 }
11632
11633 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
11634 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11635 if self.mode.is_single_line() {
11636 cx.propagate();
11637 return;
11638 }
11639
11640 self.rewrap_impl(RewrapOptions::default(), cx)
11641 }
11642
11643 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
11644 let buffer = self.buffer.read(cx).snapshot(cx);
11645 let selections = self.selections.all::<Point>(cx);
11646
11647 // Split selections to respect paragraph, indent, and comment prefix boundaries.
11648 let wrap_ranges = selections.into_iter().flat_map(|selection| {
11649 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
11650 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11651 .peekable();
11652
11653 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
11654 row
11655 } else {
11656 return Vec::new();
11657 };
11658
11659 let language_settings = buffer.language_settings_at(selection.head(), cx);
11660 let language_scope = buffer.language_scope_at(selection.head());
11661
11662 let indent_and_prefix_for_row =
11663 |row: u32| -> (IndentSize, Option<String>, Option<String>) {
11664 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
11665 let (comment_prefix, rewrap_prefix) =
11666 if let Some(language_scope) = &language_scope {
11667 let indent_end = Point::new(row, indent.len);
11668 let comment_prefix = language_scope
11669 .line_comment_prefixes()
11670 .iter()
11671 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11672 .map(|prefix| prefix.to_string());
11673 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11674 let line_text_after_indent = buffer
11675 .text_for_range(indent_end..line_end)
11676 .collect::<String>();
11677 let rewrap_prefix = language_scope
11678 .rewrap_prefixes()
11679 .iter()
11680 .find_map(|prefix_regex| {
11681 prefix_regex.find(&line_text_after_indent).map(|mat| {
11682 if mat.start() == 0 {
11683 Some(mat.as_str().to_string())
11684 } else {
11685 None
11686 }
11687 })
11688 })
11689 .flatten();
11690 (comment_prefix, rewrap_prefix)
11691 } else {
11692 (None, None)
11693 };
11694 (indent, comment_prefix, rewrap_prefix)
11695 };
11696
11697 let mut ranges = Vec::new();
11698 let from_empty_selection = selection.is_empty();
11699
11700 let mut current_range_start = first_row;
11701 let mut prev_row = first_row;
11702 let (
11703 mut current_range_indent,
11704 mut current_range_comment_prefix,
11705 mut current_range_rewrap_prefix,
11706 ) = indent_and_prefix_for_row(first_row);
11707
11708 for row in non_blank_rows_iter.skip(1) {
11709 let has_paragraph_break = row > prev_row + 1;
11710
11711 let (row_indent, row_comment_prefix, row_rewrap_prefix) =
11712 indent_and_prefix_for_row(row);
11713
11714 let has_indent_change = row_indent != current_range_indent;
11715 let has_comment_change = row_comment_prefix != current_range_comment_prefix;
11716
11717 let has_boundary_change = has_comment_change
11718 || row_rewrap_prefix.is_some()
11719 || (has_indent_change && current_range_comment_prefix.is_some());
11720
11721 if has_paragraph_break || has_boundary_change {
11722 ranges.push((
11723 language_settings.clone(),
11724 Point::new(current_range_start, 0)
11725 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
11726 current_range_indent,
11727 current_range_comment_prefix.clone(),
11728 current_range_rewrap_prefix.clone(),
11729 from_empty_selection,
11730 ));
11731 current_range_start = row;
11732 current_range_indent = row_indent;
11733 current_range_comment_prefix = row_comment_prefix;
11734 current_range_rewrap_prefix = row_rewrap_prefix;
11735 }
11736 prev_row = row;
11737 }
11738
11739 ranges.push((
11740 language_settings.clone(),
11741 Point::new(current_range_start, 0)
11742 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
11743 current_range_indent,
11744 current_range_comment_prefix,
11745 current_range_rewrap_prefix,
11746 from_empty_selection,
11747 ));
11748
11749 ranges
11750 });
11751
11752 let mut edits = Vec::new();
11753 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
11754
11755 for (
11756 language_settings,
11757 wrap_range,
11758 indent_size,
11759 comment_prefix,
11760 rewrap_prefix,
11761 from_empty_selection,
11762 ) in wrap_ranges
11763 {
11764 let mut start_row = wrap_range.start.row;
11765 let mut end_row = wrap_range.end.row;
11766
11767 // Skip selections that overlap with a range that has already been rewrapped.
11768 let selection_range = start_row..end_row;
11769 if rewrapped_row_ranges
11770 .iter()
11771 .any(|range| range.overlaps(&selection_range))
11772 {
11773 continue;
11774 }
11775
11776 let tab_size = language_settings.tab_size;
11777
11778 let indent_prefix = indent_size.chars().collect::<String>();
11779 let mut line_prefix = indent_prefix.clone();
11780 let mut inside_comment = false;
11781 if let Some(prefix) = &comment_prefix {
11782 line_prefix.push_str(prefix);
11783 inside_comment = true;
11784 }
11785 if let Some(prefix) = &rewrap_prefix {
11786 line_prefix.push_str(prefix);
11787 }
11788
11789 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
11790 RewrapBehavior::InComments => inside_comment,
11791 RewrapBehavior::InSelections => !wrap_range.is_empty(),
11792 RewrapBehavior::Anywhere => true,
11793 };
11794
11795 let should_rewrap = options.override_language_settings
11796 || allow_rewrap_based_on_language
11797 || self.hard_wrap.is_some();
11798 if !should_rewrap {
11799 continue;
11800 }
11801
11802 if from_empty_selection {
11803 'expand_upwards: while start_row > 0 {
11804 let prev_row = start_row - 1;
11805 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
11806 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
11807 && !buffer.is_line_blank(MultiBufferRow(prev_row))
11808 {
11809 start_row = prev_row;
11810 } else {
11811 break 'expand_upwards;
11812 }
11813 }
11814
11815 'expand_downwards: while end_row < buffer.max_point().row {
11816 let next_row = end_row + 1;
11817 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
11818 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
11819 && !buffer.is_line_blank(MultiBufferRow(next_row))
11820 {
11821 end_row = next_row;
11822 } else {
11823 break 'expand_downwards;
11824 }
11825 }
11826 }
11827
11828 let start = Point::new(start_row, 0);
11829 let start_offset = start.to_offset(&buffer);
11830 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
11831 let selection_text = buffer.text_for_range(start..end).collect::<String>();
11832 let Some(lines_without_prefixes) = selection_text
11833 .lines()
11834 .enumerate()
11835 .map(|(ix, line)| {
11836 let line_trimmed = line.trim_start();
11837 if rewrap_prefix.is_some() && ix > 0 {
11838 Ok(line_trimmed)
11839 } else {
11840 line_trimmed
11841 .strip_prefix(&line_prefix.trim_start())
11842 .with_context(|| {
11843 format!("line did not start with prefix {line_prefix:?}: {line:?}")
11844 })
11845 }
11846 })
11847 .collect::<Result<Vec<_>, _>>()
11848 .log_err()
11849 else {
11850 continue;
11851 };
11852
11853 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
11854 buffer
11855 .language_settings_at(Point::new(start_row, 0), cx)
11856 .preferred_line_length as usize
11857 });
11858
11859 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
11860 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
11861 } else {
11862 line_prefix.clone()
11863 };
11864
11865 let wrapped_text = wrap_with_prefix(
11866 line_prefix,
11867 subsequent_lines_prefix,
11868 lines_without_prefixes.join("\n"),
11869 wrap_column,
11870 tab_size,
11871 options.preserve_existing_whitespace,
11872 );
11873
11874 // TODO: should always use char-based diff while still supporting cursor behavior that
11875 // matches vim.
11876 let mut diff_options = DiffOptions::default();
11877 if options.override_language_settings {
11878 diff_options.max_word_diff_len = 0;
11879 diff_options.max_word_diff_line_count = 0;
11880 } else {
11881 diff_options.max_word_diff_len = usize::MAX;
11882 diff_options.max_word_diff_line_count = usize::MAX;
11883 }
11884
11885 for (old_range, new_text) in
11886 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
11887 {
11888 let edit_start = buffer.anchor_after(start_offset + old_range.start);
11889 let edit_end = buffer.anchor_after(start_offset + old_range.end);
11890 edits.push((edit_start..edit_end, new_text));
11891 }
11892
11893 rewrapped_row_ranges.push(start_row..=end_row);
11894 }
11895
11896 self.buffer
11897 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11898 }
11899
11900 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
11901 let mut text = String::new();
11902 let buffer = self.buffer.read(cx).snapshot(cx);
11903 let mut selections = self.selections.all::<Point>(cx);
11904 let mut clipboard_selections = Vec::with_capacity(selections.len());
11905 {
11906 let max_point = buffer.max_point();
11907 let mut is_first = true;
11908 for selection in &mut selections {
11909 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11910 if is_entire_line {
11911 selection.start = Point::new(selection.start.row, 0);
11912 if !selection.is_empty() && selection.end.column == 0 {
11913 selection.end = cmp::min(max_point, selection.end);
11914 } else {
11915 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
11916 }
11917 selection.goal = SelectionGoal::None;
11918 }
11919 if is_first {
11920 is_first = false;
11921 } else {
11922 text += "\n";
11923 }
11924 let mut len = 0;
11925 for chunk in buffer.text_for_range(selection.start..selection.end) {
11926 text.push_str(chunk);
11927 len += chunk.len();
11928 }
11929 clipboard_selections.push(ClipboardSelection {
11930 len,
11931 is_entire_line,
11932 first_line_indent: buffer
11933 .indent_size_for_line(MultiBufferRow(selection.start.row))
11934 .len,
11935 });
11936 }
11937 }
11938
11939 self.transact(window, cx, |this, window, cx| {
11940 this.change_selections(Default::default(), window, cx, |s| {
11941 s.select(selections);
11942 });
11943 this.insert("", window, cx);
11944 });
11945 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
11946 }
11947
11948 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
11949 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11950 let item = self.cut_common(window, cx);
11951 cx.write_to_clipboard(item);
11952 }
11953
11954 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
11955 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11956 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11957 s.move_with(|snapshot, sel| {
11958 if sel.is_empty() {
11959 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
11960 }
11961 });
11962 });
11963 let item = self.cut_common(window, cx);
11964 cx.set_global(KillRing(item))
11965 }
11966
11967 pub fn kill_ring_yank(
11968 &mut self,
11969 _: &KillRingYank,
11970 window: &mut Window,
11971 cx: &mut Context<Self>,
11972 ) {
11973 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11974 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
11975 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
11976 (kill_ring.text().to_string(), kill_ring.metadata_json())
11977 } else {
11978 return;
11979 }
11980 } else {
11981 return;
11982 };
11983 self.do_paste(&text, metadata, false, window, cx);
11984 }
11985
11986 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
11987 self.do_copy(true, cx);
11988 }
11989
11990 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
11991 self.do_copy(false, cx);
11992 }
11993
11994 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
11995 let selections = self.selections.all::<Point>(cx);
11996 let buffer = self.buffer.read(cx).read(cx);
11997 let mut text = String::new();
11998
11999 let mut clipboard_selections = Vec::with_capacity(selections.len());
12000 {
12001 let max_point = buffer.max_point();
12002 let mut is_first = true;
12003 for selection in &selections {
12004 let mut start = selection.start;
12005 let mut end = selection.end;
12006 let is_entire_line = selection.is_empty() || self.selections.line_mode;
12007 if is_entire_line {
12008 start = Point::new(start.row, 0);
12009 end = cmp::min(max_point, Point::new(end.row + 1, 0));
12010 }
12011
12012 let mut trimmed_selections = Vec::new();
12013 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12014 let row = MultiBufferRow(start.row);
12015 let first_indent = buffer.indent_size_for_line(row);
12016 if first_indent.len == 0 || start.column > first_indent.len {
12017 trimmed_selections.push(start..end);
12018 } else {
12019 trimmed_selections.push(
12020 Point::new(row.0, first_indent.len)
12021 ..Point::new(row.0, buffer.line_len(row)),
12022 );
12023 for row in start.row + 1..=end.row {
12024 let mut line_len = buffer.line_len(MultiBufferRow(row));
12025 if row == end.row {
12026 line_len = end.column;
12027 }
12028 if line_len == 0 {
12029 trimmed_selections
12030 .push(Point::new(row, 0)..Point::new(row, line_len));
12031 continue;
12032 }
12033 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12034 if row_indent_size.len >= first_indent.len {
12035 trimmed_selections.push(
12036 Point::new(row, first_indent.len)..Point::new(row, line_len),
12037 );
12038 } else {
12039 trimmed_selections.clear();
12040 trimmed_selections.push(start..end);
12041 break;
12042 }
12043 }
12044 }
12045 } else {
12046 trimmed_selections.push(start..end);
12047 }
12048
12049 for trimmed_range in trimmed_selections {
12050 if is_first {
12051 is_first = false;
12052 } else {
12053 text += "\n";
12054 }
12055 let mut len = 0;
12056 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12057 text.push_str(chunk);
12058 len += chunk.len();
12059 }
12060 clipboard_selections.push(ClipboardSelection {
12061 len,
12062 is_entire_line,
12063 first_line_indent: buffer
12064 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12065 .len,
12066 });
12067 }
12068 }
12069 }
12070
12071 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12072 text,
12073 clipboard_selections,
12074 ));
12075 }
12076
12077 pub fn do_paste(
12078 &mut self,
12079 text: &String,
12080 clipboard_selections: Option<Vec<ClipboardSelection>>,
12081 handle_entire_lines: bool,
12082 window: &mut Window,
12083 cx: &mut Context<Self>,
12084 ) {
12085 if self.read_only(cx) {
12086 return;
12087 }
12088
12089 let clipboard_text = Cow::Borrowed(text);
12090
12091 self.transact(window, cx, |this, window, cx| {
12092 if let Some(mut clipboard_selections) = clipboard_selections {
12093 let old_selections = this.selections.all::<usize>(cx);
12094 let all_selections_were_entire_line =
12095 clipboard_selections.iter().all(|s| s.is_entire_line);
12096 let first_selection_indent_column =
12097 clipboard_selections.first().map(|s| s.first_line_indent);
12098 if clipboard_selections.len() != old_selections.len() {
12099 clipboard_selections.drain(..);
12100 }
12101 let cursor_offset = this.selections.last::<usize>(cx).head();
12102 let mut auto_indent_on_paste = true;
12103
12104 this.buffer.update(cx, |buffer, cx| {
12105 let snapshot = buffer.read(cx);
12106 auto_indent_on_paste = snapshot
12107 .language_settings_at(cursor_offset, cx)
12108 .auto_indent_on_paste;
12109
12110 let mut start_offset = 0;
12111 let mut edits = Vec::new();
12112 let mut original_indent_columns = Vec::new();
12113 for (ix, selection) in old_selections.iter().enumerate() {
12114 let to_insert;
12115 let entire_line;
12116 let original_indent_column;
12117 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12118 let end_offset = start_offset + clipboard_selection.len;
12119 to_insert = &clipboard_text[start_offset..end_offset];
12120 entire_line = clipboard_selection.is_entire_line;
12121 start_offset = end_offset + 1;
12122 original_indent_column = Some(clipboard_selection.first_line_indent);
12123 } else {
12124 to_insert = clipboard_text.as_str();
12125 entire_line = all_selections_were_entire_line;
12126 original_indent_column = first_selection_indent_column
12127 }
12128
12129 // If the corresponding selection was empty when this slice of the
12130 // clipboard text was written, then the entire line containing the
12131 // selection was copied. If this selection is also currently empty,
12132 // then paste the line before the current line of the buffer.
12133 let range = if selection.is_empty() && handle_entire_lines && entire_line {
12134 let column = selection.start.to_point(&snapshot).column as usize;
12135 let line_start = selection.start - column;
12136 line_start..line_start
12137 } else {
12138 selection.range()
12139 };
12140
12141 edits.push((range, to_insert));
12142 original_indent_columns.push(original_indent_column);
12143 }
12144 drop(snapshot);
12145
12146 buffer.edit(
12147 edits,
12148 if auto_indent_on_paste {
12149 Some(AutoindentMode::Block {
12150 original_indent_columns,
12151 })
12152 } else {
12153 None
12154 },
12155 cx,
12156 );
12157 });
12158
12159 let selections = this.selections.all::<usize>(cx);
12160 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12161 } else {
12162 this.insert(&clipboard_text, window, cx);
12163 }
12164 });
12165 }
12166
12167 pub fn diff_clipboard_with_selection(
12168 &mut self,
12169 _: &DiffClipboardWithSelection,
12170 window: &mut Window,
12171 cx: &mut Context<Self>,
12172 ) {
12173 let selections = self.selections.all::<usize>(cx);
12174
12175 if selections.is_empty() {
12176 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12177 return;
12178 };
12179
12180 let clipboard_text = match cx.read_from_clipboard() {
12181 Some(item) => match item.entries().first() {
12182 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
12183 _ => None,
12184 },
12185 None => None,
12186 };
12187
12188 let Some(clipboard_text) = clipboard_text else {
12189 log::warn!("Clipboard doesn't contain text.");
12190 return;
12191 };
12192
12193 window.dispatch_action(
12194 Box::new(DiffClipboardWithSelectionData {
12195 clipboard_text,
12196 editor: cx.entity(),
12197 }),
12198 cx,
12199 );
12200 }
12201
12202 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12203 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12204 if let Some(item) = cx.read_from_clipboard() {
12205 let entries = item.entries();
12206
12207 match entries.first() {
12208 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12209 // of all the pasted entries.
12210 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12211 .do_paste(
12212 clipboard_string.text(),
12213 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12214 true,
12215 window,
12216 cx,
12217 ),
12218 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12219 }
12220 }
12221 }
12222
12223 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12224 if self.read_only(cx) {
12225 return;
12226 }
12227
12228 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12229
12230 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12231 if let Some((selections, _)) =
12232 self.selection_history.transaction(transaction_id).cloned()
12233 {
12234 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12235 s.select_anchors(selections.to_vec());
12236 });
12237 } else {
12238 log::error!(
12239 "No entry in selection_history found for undo. \
12240 This may correspond to a bug where undo does not update the selection. \
12241 If this is occurring, please add details to \
12242 https://github.com/zed-industries/zed/issues/22692"
12243 );
12244 }
12245 self.request_autoscroll(Autoscroll::fit(), cx);
12246 self.unmark_text(window, cx);
12247 self.refresh_inline_completion(true, false, window, cx);
12248 cx.emit(EditorEvent::Edited { transaction_id });
12249 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12250 }
12251 }
12252
12253 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12254 if self.read_only(cx) {
12255 return;
12256 }
12257
12258 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12259
12260 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12261 if let Some((_, Some(selections))) =
12262 self.selection_history.transaction(transaction_id).cloned()
12263 {
12264 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12265 s.select_anchors(selections.to_vec());
12266 });
12267 } else {
12268 log::error!(
12269 "No entry in selection_history found for redo. \
12270 This may correspond to a bug where undo does not update the selection. \
12271 If this is occurring, please add details to \
12272 https://github.com/zed-industries/zed/issues/22692"
12273 );
12274 }
12275 self.request_autoscroll(Autoscroll::fit(), cx);
12276 self.unmark_text(window, cx);
12277 self.refresh_inline_completion(true, false, window, cx);
12278 cx.emit(EditorEvent::Edited { transaction_id });
12279 }
12280 }
12281
12282 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12283 self.buffer
12284 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12285 }
12286
12287 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12288 self.buffer
12289 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12290 }
12291
12292 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
12293 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12294 self.change_selections(Default::default(), window, cx, |s| {
12295 s.move_with(|map, selection| {
12296 let cursor = if selection.is_empty() {
12297 movement::left(map, selection.start)
12298 } else {
12299 selection.start
12300 };
12301 selection.collapse_to(cursor, SelectionGoal::None);
12302 });
12303 })
12304 }
12305
12306 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12307 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12308 self.change_selections(Default::default(), window, cx, |s| {
12309 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12310 })
12311 }
12312
12313 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
12314 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12315 self.change_selections(Default::default(), window, cx, |s| {
12316 s.move_with(|map, selection| {
12317 let cursor = if selection.is_empty() {
12318 movement::right(map, selection.end)
12319 } else {
12320 selection.end
12321 };
12322 selection.collapse_to(cursor, SelectionGoal::None)
12323 });
12324 })
12325 }
12326
12327 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12328 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12329 self.change_selections(Default::default(), window, cx, |s| {
12330 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
12331 })
12332 }
12333
12334 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
12335 if self.take_rename(true, window, cx).is_some() {
12336 return;
12337 }
12338
12339 if self.mode.is_single_line() {
12340 cx.propagate();
12341 return;
12342 }
12343
12344 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12345
12346 let text_layout_details = &self.text_layout_details(window);
12347 let selection_count = self.selections.count();
12348 let first_selection = self.selections.first_anchor();
12349
12350 self.change_selections(Default::default(), window, cx, |s| {
12351 s.move_with(|map, selection| {
12352 if !selection.is_empty() {
12353 selection.goal = SelectionGoal::None;
12354 }
12355 let (cursor, goal) = movement::up(
12356 map,
12357 selection.start,
12358 selection.goal,
12359 false,
12360 text_layout_details,
12361 );
12362 selection.collapse_to(cursor, goal);
12363 });
12364 });
12365
12366 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12367 {
12368 cx.propagate();
12369 }
12370 }
12371
12372 pub fn move_up_by_lines(
12373 &mut self,
12374 action: &MoveUpByLines,
12375 window: &mut Window,
12376 cx: &mut Context<Self>,
12377 ) {
12378 if self.take_rename(true, window, cx).is_some() {
12379 return;
12380 }
12381
12382 if self.mode.is_single_line() {
12383 cx.propagate();
12384 return;
12385 }
12386
12387 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12388
12389 let text_layout_details = &self.text_layout_details(window);
12390
12391 self.change_selections(Default::default(), window, cx, |s| {
12392 s.move_with(|map, selection| {
12393 if !selection.is_empty() {
12394 selection.goal = SelectionGoal::None;
12395 }
12396 let (cursor, goal) = movement::up_by_rows(
12397 map,
12398 selection.start,
12399 action.lines,
12400 selection.goal,
12401 false,
12402 text_layout_details,
12403 );
12404 selection.collapse_to(cursor, goal);
12405 });
12406 })
12407 }
12408
12409 pub fn move_down_by_lines(
12410 &mut self,
12411 action: &MoveDownByLines,
12412 window: &mut Window,
12413 cx: &mut Context<Self>,
12414 ) {
12415 if self.take_rename(true, window, cx).is_some() {
12416 return;
12417 }
12418
12419 if self.mode.is_single_line() {
12420 cx.propagate();
12421 return;
12422 }
12423
12424 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12425
12426 let text_layout_details = &self.text_layout_details(window);
12427
12428 self.change_selections(Default::default(), window, cx, |s| {
12429 s.move_with(|map, selection| {
12430 if !selection.is_empty() {
12431 selection.goal = SelectionGoal::None;
12432 }
12433 let (cursor, goal) = movement::down_by_rows(
12434 map,
12435 selection.start,
12436 action.lines,
12437 selection.goal,
12438 false,
12439 text_layout_details,
12440 );
12441 selection.collapse_to(cursor, goal);
12442 });
12443 })
12444 }
12445
12446 pub fn select_down_by_lines(
12447 &mut self,
12448 action: &SelectDownByLines,
12449 window: &mut Window,
12450 cx: &mut Context<Self>,
12451 ) {
12452 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12453 let text_layout_details = &self.text_layout_details(window);
12454 self.change_selections(Default::default(), window, cx, |s| {
12455 s.move_heads_with(|map, head, goal| {
12456 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
12457 })
12458 })
12459 }
12460
12461 pub fn select_up_by_lines(
12462 &mut self,
12463 action: &SelectUpByLines,
12464 window: &mut Window,
12465 cx: &mut Context<Self>,
12466 ) {
12467 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12468 let text_layout_details = &self.text_layout_details(window);
12469 self.change_selections(Default::default(), window, cx, |s| {
12470 s.move_heads_with(|map, head, goal| {
12471 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
12472 })
12473 })
12474 }
12475
12476 pub fn select_page_up(
12477 &mut self,
12478 _: &SelectPageUp,
12479 window: &mut Window,
12480 cx: &mut Context<Self>,
12481 ) {
12482 let Some(row_count) = self.visible_row_count() else {
12483 return;
12484 };
12485
12486 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12487
12488 let text_layout_details = &self.text_layout_details(window);
12489
12490 self.change_selections(Default::default(), window, cx, |s| {
12491 s.move_heads_with(|map, head, goal| {
12492 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
12493 })
12494 })
12495 }
12496
12497 pub fn move_page_up(
12498 &mut self,
12499 action: &MovePageUp,
12500 window: &mut Window,
12501 cx: &mut Context<Self>,
12502 ) {
12503 if self.take_rename(true, window, cx).is_some() {
12504 return;
12505 }
12506
12507 if self
12508 .context_menu
12509 .borrow_mut()
12510 .as_mut()
12511 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
12512 .unwrap_or(false)
12513 {
12514 return;
12515 }
12516
12517 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12518 cx.propagate();
12519 return;
12520 }
12521
12522 let Some(row_count) = self.visible_row_count() else {
12523 return;
12524 };
12525
12526 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12527
12528 let effects = if action.center_cursor {
12529 SelectionEffects::scroll(Autoscroll::center())
12530 } else {
12531 SelectionEffects::default()
12532 };
12533
12534 let text_layout_details = &self.text_layout_details(window);
12535
12536 self.change_selections(effects, window, cx, |s| {
12537 s.move_with(|map, selection| {
12538 if !selection.is_empty() {
12539 selection.goal = SelectionGoal::None;
12540 }
12541 let (cursor, goal) = movement::up_by_rows(
12542 map,
12543 selection.end,
12544 row_count,
12545 selection.goal,
12546 false,
12547 text_layout_details,
12548 );
12549 selection.collapse_to(cursor, goal);
12550 });
12551 });
12552 }
12553
12554 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
12555 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12556 let text_layout_details = &self.text_layout_details(window);
12557 self.change_selections(Default::default(), window, cx, |s| {
12558 s.move_heads_with(|map, head, goal| {
12559 movement::up(map, head, goal, false, text_layout_details)
12560 })
12561 })
12562 }
12563
12564 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
12565 self.take_rename(true, window, cx);
12566
12567 if self.mode.is_single_line() {
12568 cx.propagate();
12569 return;
12570 }
12571
12572 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12573
12574 let text_layout_details = &self.text_layout_details(window);
12575 let selection_count = self.selections.count();
12576 let first_selection = self.selections.first_anchor();
12577
12578 self.change_selections(Default::default(), window, cx, |s| {
12579 s.move_with(|map, selection| {
12580 if !selection.is_empty() {
12581 selection.goal = SelectionGoal::None;
12582 }
12583 let (cursor, goal) = movement::down(
12584 map,
12585 selection.end,
12586 selection.goal,
12587 false,
12588 text_layout_details,
12589 );
12590 selection.collapse_to(cursor, goal);
12591 });
12592 });
12593
12594 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12595 {
12596 cx.propagate();
12597 }
12598 }
12599
12600 pub fn select_page_down(
12601 &mut self,
12602 _: &SelectPageDown,
12603 window: &mut Window,
12604 cx: &mut Context<Self>,
12605 ) {
12606 let Some(row_count) = self.visible_row_count() else {
12607 return;
12608 };
12609
12610 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12611
12612 let text_layout_details = &self.text_layout_details(window);
12613
12614 self.change_selections(Default::default(), window, cx, |s| {
12615 s.move_heads_with(|map, head, goal| {
12616 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
12617 })
12618 })
12619 }
12620
12621 pub fn move_page_down(
12622 &mut self,
12623 action: &MovePageDown,
12624 window: &mut Window,
12625 cx: &mut Context<Self>,
12626 ) {
12627 if self.take_rename(true, window, cx).is_some() {
12628 return;
12629 }
12630
12631 if self
12632 .context_menu
12633 .borrow_mut()
12634 .as_mut()
12635 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
12636 .unwrap_or(false)
12637 {
12638 return;
12639 }
12640
12641 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12642 cx.propagate();
12643 return;
12644 }
12645
12646 let Some(row_count) = self.visible_row_count() else {
12647 return;
12648 };
12649
12650 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12651
12652 let effects = if action.center_cursor {
12653 SelectionEffects::scroll(Autoscroll::center())
12654 } else {
12655 SelectionEffects::default()
12656 };
12657
12658 let text_layout_details = &self.text_layout_details(window);
12659 self.change_selections(effects, window, cx, |s| {
12660 s.move_with(|map, selection| {
12661 if !selection.is_empty() {
12662 selection.goal = SelectionGoal::None;
12663 }
12664 let (cursor, goal) = movement::down_by_rows(
12665 map,
12666 selection.end,
12667 row_count,
12668 selection.goal,
12669 false,
12670 text_layout_details,
12671 );
12672 selection.collapse_to(cursor, goal);
12673 });
12674 });
12675 }
12676
12677 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
12678 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12679 let text_layout_details = &self.text_layout_details(window);
12680 self.change_selections(Default::default(), window, cx, |s| {
12681 s.move_heads_with(|map, head, goal| {
12682 movement::down(map, head, goal, false, text_layout_details)
12683 })
12684 });
12685 }
12686
12687 pub fn context_menu_first(
12688 &mut self,
12689 _: &ContextMenuFirst,
12690 window: &mut Window,
12691 cx: &mut Context<Self>,
12692 ) {
12693 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12694 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
12695 }
12696 }
12697
12698 pub fn context_menu_prev(
12699 &mut self,
12700 _: &ContextMenuPrevious,
12701 window: &mut Window,
12702 cx: &mut Context<Self>,
12703 ) {
12704 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12705 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
12706 }
12707 }
12708
12709 pub fn context_menu_next(
12710 &mut self,
12711 _: &ContextMenuNext,
12712 window: &mut Window,
12713 cx: &mut Context<Self>,
12714 ) {
12715 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12716 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
12717 }
12718 }
12719
12720 pub fn context_menu_last(
12721 &mut self,
12722 _: &ContextMenuLast,
12723 window: &mut Window,
12724 cx: &mut Context<Self>,
12725 ) {
12726 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12727 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
12728 }
12729 }
12730
12731 pub fn signature_help_prev(
12732 &mut self,
12733 _: &SignatureHelpPrevious,
12734 _: &mut Window,
12735 cx: &mut Context<Self>,
12736 ) {
12737 if let Some(popover) = self.signature_help_state.popover_mut() {
12738 if popover.current_signature == 0 {
12739 popover.current_signature = popover.signatures.len() - 1;
12740 } else {
12741 popover.current_signature -= 1;
12742 }
12743 cx.notify();
12744 }
12745 }
12746
12747 pub fn signature_help_next(
12748 &mut self,
12749 _: &SignatureHelpNext,
12750 _: &mut Window,
12751 cx: &mut Context<Self>,
12752 ) {
12753 if let Some(popover) = self.signature_help_state.popover_mut() {
12754 if popover.current_signature + 1 == popover.signatures.len() {
12755 popover.current_signature = 0;
12756 } else {
12757 popover.current_signature += 1;
12758 }
12759 cx.notify();
12760 }
12761 }
12762
12763 pub fn move_to_previous_word_start(
12764 &mut self,
12765 _: &MoveToPreviousWordStart,
12766 window: &mut Window,
12767 cx: &mut Context<Self>,
12768 ) {
12769 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12770 self.change_selections(Default::default(), window, cx, |s| {
12771 s.move_cursors_with(|map, head, _| {
12772 (
12773 movement::previous_word_start(map, head),
12774 SelectionGoal::None,
12775 )
12776 });
12777 })
12778 }
12779
12780 pub fn move_to_previous_subword_start(
12781 &mut self,
12782 _: &MoveToPreviousSubwordStart,
12783 window: &mut Window,
12784 cx: &mut Context<Self>,
12785 ) {
12786 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12787 self.change_selections(Default::default(), window, cx, |s| {
12788 s.move_cursors_with(|map, head, _| {
12789 (
12790 movement::previous_subword_start(map, head),
12791 SelectionGoal::None,
12792 )
12793 });
12794 })
12795 }
12796
12797 pub fn select_to_previous_word_start(
12798 &mut self,
12799 _: &SelectToPreviousWordStart,
12800 window: &mut Window,
12801 cx: &mut Context<Self>,
12802 ) {
12803 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12804 self.change_selections(Default::default(), window, cx, |s| {
12805 s.move_heads_with(|map, head, _| {
12806 (
12807 movement::previous_word_start(map, head),
12808 SelectionGoal::None,
12809 )
12810 });
12811 })
12812 }
12813
12814 pub fn select_to_previous_subword_start(
12815 &mut self,
12816 _: &SelectToPreviousSubwordStart,
12817 window: &mut Window,
12818 cx: &mut Context<Self>,
12819 ) {
12820 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12821 self.change_selections(Default::default(), window, cx, |s| {
12822 s.move_heads_with(|map, head, _| {
12823 (
12824 movement::previous_subword_start(map, head),
12825 SelectionGoal::None,
12826 )
12827 });
12828 })
12829 }
12830
12831 pub fn delete_to_previous_word_start(
12832 &mut self,
12833 action: &DeleteToPreviousWordStart,
12834 window: &mut Window,
12835 cx: &mut Context<Self>,
12836 ) {
12837 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12838 self.transact(window, cx, |this, window, cx| {
12839 this.select_autoclose_pair(window, cx);
12840 this.change_selections(Default::default(), window, cx, |s| {
12841 s.move_with(|map, selection| {
12842 if selection.is_empty() {
12843 let cursor = if action.ignore_newlines {
12844 movement::previous_word_start(map, selection.head())
12845 } else {
12846 movement::previous_word_start_or_newline(map, selection.head())
12847 };
12848 selection.set_head(cursor, SelectionGoal::None);
12849 }
12850 });
12851 });
12852 this.insert("", window, cx);
12853 });
12854 }
12855
12856 pub fn delete_to_previous_subword_start(
12857 &mut self,
12858 _: &DeleteToPreviousSubwordStart,
12859 window: &mut Window,
12860 cx: &mut Context<Self>,
12861 ) {
12862 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12863 self.transact(window, cx, |this, window, cx| {
12864 this.select_autoclose_pair(window, cx);
12865 this.change_selections(Default::default(), window, cx, |s| {
12866 s.move_with(|map, selection| {
12867 if selection.is_empty() {
12868 let cursor = movement::previous_subword_start(map, selection.head());
12869 selection.set_head(cursor, SelectionGoal::None);
12870 }
12871 });
12872 });
12873 this.insert("", window, cx);
12874 });
12875 }
12876
12877 pub fn move_to_next_word_end(
12878 &mut self,
12879 _: &MoveToNextWordEnd,
12880 window: &mut Window,
12881 cx: &mut Context<Self>,
12882 ) {
12883 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12884 self.change_selections(Default::default(), window, cx, |s| {
12885 s.move_cursors_with(|map, head, _| {
12886 (movement::next_word_end(map, head), SelectionGoal::None)
12887 });
12888 })
12889 }
12890
12891 pub fn move_to_next_subword_end(
12892 &mut self,
12893 _: &MoveToNextSubwordEnd,
12894 window: &mut Window,
12895 cx: &mut Context<Self>,
12896 ) {
12897 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12898 self.change_selections(Default::default(), window, cx, |s| {
12899 s.move_cursors_with(|map, head, _| {
12900 (movement::next_subword_end(map, head), SelectionGoal::None)
12901 });
12902 })
12903 }
12904
12905 pub fn select_to_next_word_end(
12906 &mut self,
12907 _: &SelectToNextWordEnd,
12908 window: &mut Window,
12909 cx: &mut Context<Self>,
12910 ) {
12911 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12912 self.change_selections(Default::default(), window, cx, |s| {
12913 s.move_heads_with(|map, head, _| {
12914 (movement::next_word_end(map, head), SelectionGoal::None)
12915 });
12916 })
12917 }
12918
12919 pub fn select_to_next_subword_end(
12920 &mut self,
12921 _: &SelectToNextSubwordEnd,
12922 window: &mut Window,
12923 cx: &mut Context<Self>,
12924 ) {
12925 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12926 self.change_selections(Default::default(), window, cx, |s| {
12927 s.move_heads_with(|map, head, _| {
12928 (movement::next_subword_end(map, head), SelectionGoal::None)
12929 });
12930 })
12931 }
12932
12933 pub fn delete_to_next_word_end(
12934 &mut self,
12935 action: &DeleteToNextWordEnd,
12936 window: &mut Window,
12937 cx: &mut Context<Self>,
12938 ) {
12939 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12940 self.transact(window, cx, |this, window, cx| {
12941 this.change_selections(Default::default(), window, cx, |s| {
12942 s.move_with(|map, selection| {
12943 if selection.is_empty() {
12944 let cursor = if action.ignore_newlines {
12945 movement::next_word_end(map, selection.head())
12946 } else {
12947 movement::next_word_end_or_newline(map, selection.head())
12948 };
12949 selection.set_head(cursor, SelectionGoal::None);
12950 }
12951 });
12952 });
12953 this.insert("", window, cx);
12954 });
12955 }
12956
12957 pub fn delete_to_next_subword_end(
12958 &mut self,
12959 _: &DeleteToNextSubwordEnd,
12960 window: &mut Window,
12961 cx: &mut Context<Self>,
12962 ) {
12963 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12964 self.transact(window, cx, |this, window, cx| {
12965 this.change_selections(Default::default(), window, cx, |s| {
12966 s.move_with(|map, selection| {
12967 if selection.is_empty() {
12968 let cursor = movement::next_subword_end(map, selection.head());
12969 selection.set_head(cursor, SelectionGoal::None);
12970 }
12971 });
12972 });
12973 this.insert("", window, cx);
12974 });
12975 }
12976
12977 pub fn move_to_beginning_of_line(
12978 &mut self,
12979 action: &MoveToBeginningOfLine,
12980 window: &mut Window,
12981 cx: &mut Context<Self>,
12982 ) {
12983 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12984 self.change_selections(Default::default(), window, cx, |s| {
12985 s.move_cursors_with(|map, head, _| {
12986 (
12987 movement::indented_line_beginning(
12988 map,
12989 head,
12990 action.stop_at_soft_wraps,
12991 action.stop_at_indent,
12992 ),
12993 SelectionGoal::None,
12994 )
12995 });
12996 })
12997 }
12998
12999 pub fn select_to_beginning_of_line(
13000 &mut self,
13001 action: &SelectToBeginningOfLine,
13002 window: &mut Window,
13003 cx: &mut Context<Self>,
13004 ) {
13005 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13006 self.change_selections(Default::default(), window, cx, |s| {
13007 s.move_heads_with(|map, head, _| {
13008 (
13009 movement::indented_line_beginning(
13010 map,
13011 head,
13012 action.stop_at_soft_wraps,
13013 action.stop_at_indent,
13014 ),
13015 SelectionGoal::None,
13016 )
13017 });
13018 });
13019 }
13020
13021 pub fn delete_to_beginning_of_line(
13022 &mut self,
13023 action: &DeleteToBeginningOfLine,
13024 window: &mut Window,
13025 cx: &mut Context<Self>,
13026 ) {
13027 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13028 self.transact(window, cx, |this, window, cx| {
13029 this.change_selections(Default::default(), window, cx, |s| {
13030 s.move_with(|_, selection| {
13031 selection.reversed = true;
13032 });
13033 });
13034
13035 this.select_to_beginning_of_line(
13036 &SelectToBeginningOfLine {
13037 stop_at_soft_wraps: false,
13038 stop_at_indent: action.stop_at_indent,
13039 },
13040 window,
13041 cx,
13042 );
13043 this.backspace(&Backspace, window, cx);
13044 });
13045 }
13046
13047 pub fn move_to_end_of_line(
13048 &mut self,
13049 action: &MoveToEndOfLine,
13050 window: &mut Window,
13051 cx: &mut Context<Self>,
13052 ) {
13053 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13054 self.change_selections(Default::default(), window, cx, |s| {
13055 s.move_cursors_with(|map, head, _| {
13056 (
13057 movement::line_end(map, head, action.stop_at_soft_wraps),
13058 SelectionGoal::None,
13059 )
13060 });
13061 })
13062 }
13063
13064 pub fn select_to_end_of_line(
13065 &mut self,
13066 action: &SelectToEndOfLine,
13067 window: &mut Window,
13068 cx: &mut Context<Self>,
13069 ) {
13070 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13071 self.change_selections(Default::default(), window, cx, |s| {
13072 s.move_heads_with(|map, head, _| {
13073 (
13074 movement::line_end(map, head, action.stop_at_soft_wraps),
13075 SelectionGoal::None,
13076 )
13077 });
13078 })
13079 }
13080
13081 pub fn delete_to_end_of_line(
13082 &mut self,
13083 _: &DeleteToEndOfLine,
13084 window: &mut Window,
13085 cx: &mut Context<Self>,
13086 ) {
13087 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13088 self.transact(window, cx, |this, window, cx| {
13089 this.select_to_end_of_line(
13090 &SelectToEndOfLine {
13091 stop_at_soft_wraps: false,
13092 },
13093 window,
13094 cx,
13095 );
13096 this.delete(&Delete, window, cx);
13097 });
13098 }
13099
13100 pub fn cut_to_end_of_line(
13101 &mut self,
13102 _: &CutToEndOfLine,
13103 window: &mut Window,
13104 cx: &mut Context<Self>,
13105 ) {
13106 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13107 self.transact(window, cx, |this, window, cx| {
13108 this.select_to_end_of_line(
13109 &SelectToEndOfLine {
13110 stop_at_soft_wraps: false,
13111 },
13112 window,
13113 cx,
13114 );
13115 this.cut(&Cut, window, cx);
13116 });
13117 }
13118
13119 pub fn move_to_start_of_paragraph(
13120 &mut self,
13121 _: &MoveToStartOfParagraph,
13122 window: &mut Window,
13123 cx: &mut Context<Self>,
13124 ) {
13125 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13126 cx.propagate();
13127 return;
13128 }
13129 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13130 self.change_selections(Default::default(), window, cx, |s| {
13131 s.move_with(|map, selection| {
13132 selection.collapse_to(
13133 movement::start_of_paragraph(map, selection.head(), 1),
13134 SelectionGoal::None,
13135 )
13136 });
13137 })
13138 }
13139
13140 pub fn move_to_end_of_paragraph(
13141 &mut self,
13142 _: &MoveToEndOfParagraph,
13143 window: &mut Window,
13144 cx: &mut Context<Self>,
13145 ) {
13146 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13147 cx.propagate();
13148 return;
13149 }
13150 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13151 self.change_selections(Default::default(), window, cx, |s| {
13152 s.move_with(|map, selection| {
13153 selection.collapse_to(
13154 movement::end_of_paragraph(map, selection.head(), 1),
13155 SelectionGoal::None,
13156 )
13157 });
13158 })
13159 }
13160
13161 pub fn select_to_start_of_paragraph(
13162 &mut self,
13163 _: &SelectToStartOfParagraph,
13164 window: &mut Window,
13165 cx: &mut Context<Self>,
13166 ) {
13167 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13168 cx.propagate();
13169 return;
13170 }
13171 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13172 self.change_selections(Default::default(), window, cx, |s| {
13173 s.move_heads_with(|map, head, _| {
13174 (
13175 movement::start_of_paragraph(map, head, 1),
13176 SelectionGoal::None,
13177 )
13178 });
13179 })
13180 }
13181
13182 pub fn select_to_end_of_paragraph(
13183 &mut self,
13184 _: &SelectToEndOfParagraph,
13185 window: &mut Window,
13186 cx: &mut Context<Self>,
13187 ) {
13188 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13189 cx.propagate();
13190 return;
13191 }
13192 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13193 self.change_selections(Default::default(), window, cx, |s| {
13194 s.move_heads_with(|map, head, _| {
13195 (
13196 movement::end_of_paragraph(map, head, 1),
13197 SelectionGoal::None,
13198 )
13199 });
13200 })
13201 }
13202
13203 pub fn move_to_start_of_excerpt(
13204 &mut self,
13205 _: &MoveToStartOfExcerpt,
13206 window: &mut Window,
13207 cx: &mut Context<Self>,
13208 ) {
13209 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13210 cx.propagate();
13211 return;
13212 }
13213 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13214 self.change_selections(Default::default(), window, cx, |s| {
13215 s.move_with(|map, selection| {
13216 selection.collapse_to(
13217 movement::start_of_excerpt(
13218 map,
13219 selection.head(),
13220 workspace::searchable::Direction::Prev,
13221 ),
13222 SelectionGoal::None,
13223 )
13224 });
13225 })
13226 }
13227
13228 pub fn move_to_start_of_next_excerpt(
13229 &mut self,
13230 _: &MoveToStartOfNextExcerpt,
13231 window: &mut Window,
13232 cx: &mut Context<Self>,
13233 ) {
13234 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13235 cx.propagate();
13236 return;
13237 }
13238
13239 self.change_selections(Default::default(), window, cx, |s| {
13240 s.move_with(|map, selection| {
13241 selection.collapse_to(
13242 movement::start_of_excerpt(
13243 map,
13244 selection.head(),
13245 workspace::searchable::Direction::Next,
13246 ),
13247 SelectionGoal::None,
13248 )
13249 });
13250 })
13251 }
13252
13253 pub fn move_to_end_of_excerpt(
13254 &mut self,
13255 _: &MoveToEndOfExcerpt,
13256 window: &mut Window,
13257 cx: &mut Context<Self>,
13258 ) {
13259 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13260 cx.propagate();
13261 return;
13262 }
13263 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13264 self.change_selections(Default::default(), window, cx, |s| {
13265 s.move_with(|map, selection| {
13266 selection.collapse_to(
13267 movement::end_of_excerpt(
13268 map,
13269 selection.head(),
13270 workspace::searchable::Direction::Next,
13271 ),
13272 SelectionGoal::None,
13273 )
13274 });
13275 })
13276 }
13277
13278 pub fn move_to_end_of_previous_excerpt(
13279 &mut self,
13280 _: &MoveToEndOfPreviousExcerpt,
13281 window: &mut Window,
13282 cx: &mut Context<Self>,
13283 ) {
13284 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13285 cx.propagate();
13286 return;
13287 }
13288 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13289 self.change_selections(Default::default(), window, cx, |s| {
13290 s.move_with(|map, selection| {
13291 selection.collapse_to(
13292 movement::end_of_excerpt(
13293 map,
13294 selection.head(),
13295 workspace::searchable::Direction::Prev,
13296 ),
13297 SelectionGoal::None,
13298 )
13299 });
13300 })
13301 }
13302
13303 pub fn select_to_start_of_excerpt(
13304 &mut self,
13305 _: &SelectToStartOfExcerpt,
13306 window: &mut Window,
13307 cx: &mut Context<Self>,
13308 ) {
13309 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13310 cx.propagate();
13311 return;
13312 }
13313 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13314 self.change_selections(Default::default(), window, cx, |s| {
13315 s.move_heads_with(|map, head, _| {
13316 (
13317 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13318 SelectionGoal::None,
13319 )
13320 });
13321 })
13322 }
13323
13324 pub fn select_to_start_of_next_excerpt(
13325 &mut self,
13326 _: &SelectToStartOfNextExcerpt,
13327 window: &mut Window,
13328 cx: &mut Context<Self>,
13329 ) {
13330 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13331 cx.propagate();
13332 return;
13333 }
13334 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13335 self.change_selections(Default::default(), window, cx, |s| {
13336 s.move_heads_with(|map, head, _| {
13337 (
13338 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
13339 SelectionGoal::None,
13340 )
13341 });
13342 })
13343 }
13344
13345 pub fn select_to_end_of_excerpt(
13346 &mut self,
13347 _: &SelectToEndOfExcerpt,
13348 window: &mut Window,
13349 cx: &mut Context<Self>,
13350 ) {
13351 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13352 cx.propagate();
13353 return;
13354 }
13355 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13356 self.change_selections(Default::default(), window, cx, |s| {
13357 s.move_heads_with(|map, head, _| {
13358 (
13359 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
13360 SelectionGoal::None,
13361 )
13362 });
13363 })
13364 }
13365
13366 pub fn select_to_end_of_previous_excerpt(
13367 &mut self,
13368 _: &SelectToEndOfPreviousExcerpt,
13369 window: &mut Window,
13370 cx: &mut Context<Self>,
13371 ) {
13372 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13373 cx.propagate();
13374 return;
13375 }
13376 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13377 self.change_selections(Default::default(), window, cx, |s| {
13378 s.move_heads_with(|map, head, _| {
13379 (
13380 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13381 SelectionGoal::None,
13382 )
13383 });
13384 })
13385 }
13386
13387 pub fn move_to_beginning(
13388 &mut self,
13389 _: &MoveToBeginning,
13390 window: &mut Window,
13391 cx: &mut Context<Self>,
13392 ) {
13393 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13394 cx.propagate();
13395 return;
13396 }
13397 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13398 self.change_selections(Default::default(), window, cx, |s| {
13399 s.select_ranges(vec![0..0]);
13400 });
13401 }
13402
13403 pub fn select_to_beginning(
13404 &mut self,
13405 _: &SelectToBeginning,
13406 window: &mut Window,
13407 cx: &mut Context<Self>,
13408 ) {
13409 let mut selection = self.selections.last::<Point>(cx);
13410 selection.set_head(Point::zero(), SelectionGoal::None);
13411 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13412 self.change_selections(Default::default(), window, cx, |s| {
13413 s.select(vec![selection]);
13414 });
13415 }
13416
13417 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
13418 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13419 cx.propagate();
13420 return;
13421 }
13422 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13423 let cursor = self.buffer.read(cx).read(cx).len();
13424 self.change_selections(Default::default(), window, cx, |s| {
13425 s.select_ranges(vec![cursor..cursor])
13426 });
13427 }
13428
13429 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
13430 self.nav_history = nav_history;
13431 }
13432
13433 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
13434 self.nav_history.as_ref()
13435 }
13436
13437 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
13438 self.push_to_nav_history(
13439 self.selections.newest_anchor().head(),
13440 None,
13441 false,
13442 true,
13443 cx,
13444 );
13445 }
13446
13447 fn push_to_nav_history(
13448 &mut self,
13449 cursor_anchor: Anchor,
13450 new_position: Option<Point>,
13451 is_deactivate: bool,
13452 always: bool,
13453 cx: &mut Context<Self>,
13454 ) {
13455 if let Some(nav_history) = self.nav_history.as_mut() {
13456 let buffer = self.buffer.read(cx).read(cx);
13457 let cursor_position = cursor_anchor.to_point(&buffer);
13458 let scroll_state = self.scroll_manager.anchor();
13459 let scroll_top_row = scroll_state.top_row(&buffer);
13460 drop(buffer);
13461
13462 if let Some(new_position) = new_position {
13463 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
13464 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
13465 return;
13466 }
13467 }
13468
13469 nav_history.push(
13470 Some(NavigationData {
13471 cursor_anchor,
13472 cursor_position,
13473 scroll_anchor: scroll_state,
13474 scroll_top_row,
13475 }),
13476 cx,
13477 );
13478 cx.emit(EditorEvent::PushedToNavHistory {
13479 anchor: cursor_anchor,
13480 is_deactivate,
13481 })
13482 }
13483 }
13484
13485 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
13486 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13487 let buffer = self.buffer.read(cx).snapshot(cx);
13488 let mut selection = self.selections.first::<usize>(cx);
13489 selection.set_head(buffer.len(), SelectionGoal::None);
13490 self.change_selections(Default::default(), window, cx, |s| {
13491 s.select(vec![selection]);
13492 });
13493 }
13494
13495 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
13496 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13497 let end = self.buffer.read(cx).read(cx).len();
13498 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13499 s.select_ranges(vec![0..end]);
13500 });
13501 }
13502
13503 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
13504 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13505 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13506 let mut selections = self.selections.all::<Point>(cx);
13507 let max_point = display_map.buffer_snapshot.max_point();
13508 for selection in &mut selections {
13509 let rows = selection.spanned_rows(true, &display_map);
13510 selection.start = Point::new(rows.start.0, 0);
13511 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
13512 selection.reversed = false;
13513 }
13514 self.change_selections(Default::default(), window, cx, |s| {
13515 s.select(selections);
13516 });
13517 }
13518
13519 pub fn split_selection_into_lines(
13520 &mut self,
13521 _: &SplitSelectionIntoLines,
13522 window: &mut Window,
13523 cx: &mut Context<Self>,
13524 ) {
13525 let selections = self
13526 .selections
13527 .all::<Point>(cx)
13528 .into_iter()
13529 .map(|selection| selection.start..selection.end)
13530 .collect::<Vec<_>>();
13531 self.unfold_ranges(&selections, true, true, cx);
13532
13533 let mut new_selection_ranges = Vec::new();
13534 {
13535 let buffer = self.buffer.read(cx).read(cx);
13536 for selection in selections {
13537 for row in selection.start.row..selection.end.row {
13538 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
13539 new_selection_ranges.push(cursor..cursor);
13540 }
13541
13542 let is_multiline_selection = selection.start.row != selection.end.row;
13543 // Don't insert last one if it's a multi-line selection ending at the start of a line,
13544 // so this action feels more ergonomic when paired with other selection operations
13545 let should_skip_last = is_multiline_selection && selection.end.column == 0;
13546 if !should_skip_last {
13547 new_selection_ranges.push(selection.end..selection.end);
13548 }
13549 }
13550 }
13551 self.change_selections(Default::default(), window, cx, |s| {
13552 s.select_ranges(new_selection_ranges);
13553 });
13554 }
13555
13556 pub fn add_selection_above(
13557 &mut self,
13558 _: &AddSelectionAbove,
13559 window: &mut Window,
13560 cx: &mut Context<Self>,
13561 ) {
13562 self.add_selection(true, window, cx);
13563 }
13564
13565 pub fn add_selection_below(
13566 &mut self,
13567 _: &AddSelectionBelow,
13568 window: &mut Window,
13569 cx: &mut Context<Self>,
13570 ) {
13571 self.add_selection(false, window, cx);
13572 }
13573
13574 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
13575 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13576
13577 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13578 let all_selections = self.selections.all::<Point>(cx);
13579 let text_layout_details = self.text_layout_details(window);
13580
13581 let (mut columnar_selections, new_selections_to_columnarize) = {
13582 if let Some(state) = self.add_selections_state.as_ref() {
13583 let columnar_selection_ids: HashSet<_> = state
13584 .groups
13585 .iter()
13586 .flat_map(|group| group.stack.iter())
13587 .copied()
13588 .collect();
13589
13590 all_selections
13591 .into_iter()
13592 .partition(|s| columnar_selection_ids.contains(&s.id))
13593 } else {
13594 (Vec::new(), all_selections)
13595 }
13596 };
13597
13598 let mut state = self
13599 .add_selections_state
13600 .take()
13601 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
13602
13603 for selection in new_selections_to_columnarize {
13604 let range = selection.display_range(&display_map).sorted();
13605 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
13606 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
13607 let positions = start_x.min(end_x)..start_x.max(end_x);
13608 let mut stack = Vec::new();
13609 for row in range.start.row().0..=range.end.row().0 {
13610 if let Some(selection) = self.selections.build_columnar_selection(
13611 &display_map,
13612 DisplayRow(row),
13613 &positions,
13614 selection.reversed,
13615 &text_layout_details,
13616 ) {
13617 stack.push(selection.id);
13618 columnar_selections.push(selection);
13619 }
13620 }
13621 if !stack.is_empty() {
13622 if above {
13623 stack.reverse();
13624 }
13625 state.groups.push(AddSelectionsGroup { above, stack });
13626 }
13627 }
13628
13629 let mut final_selections = Vec::new();
13630 let end_row = if above {
13631 DisplayRow(0)
13632 } else {
13633 display_map.max_point().row()
13634 };
13635
13636 let mut last_added_item_per_group = HashMap::default();
13637 for group in state.groups.iter_mut() {
13638 if let Some(last_id) = group.stack.last() {
13639 last_added_item_per_group.insert(*last_id, group);
13640 }
13641 }
13642
13643 for selection in columnar_selections {
13644 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
13645 if above == group.above {
13646 let range = selection.display_range(&display_map).sorted();
13647 debug_assert_eq!(range.start.row(), range.end.row());
13648 let mut row = range.start.row();
13649 let positions =
13650 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
13651 px(start)..px(end)
13652 } else {
13653 let start_x =
13654 display_map.x_for_display_point(range.start, &text_layout_details);
13655 let end_x =
13656 display_map.x_for_display_point(range.end, &text_layout_details);
13657 start_x.min(end_x)..start_x.max(end_x)
13658 };
13659
13660 let mut maybe_new_selection = None;
13661 while row != end_row {
13662 if above {
13663 row.0 -= 1;
13664 } else {
13665 row.0 += 1;
13666 }
13667 if let Some(new_selection) = self.selections.build_columnar_selection(
13668 &display_map,
13669 row,
13670 &positions,
13671 selection.reversed,
13672 &text_layout_details,
13673 ) {
13674 maybe_new_selection = Some(new_selection);
13675 break;
13676 }
13677 }
13678
13679 if let Some(new_selection) = maybe_new_selection {
13680 group.stack.push(new_selection.id);
13681 if above {
13682 final_selections.push(new_selection);
13683 final_selections.push(selection);
13684 } else {
13685 final_selections.push(selection);
13686 final_selections.push(new_selection);
13687 }
13688 } else {
13689 final_selections.push(selection);
13690 }
13691 } else {
13692 group.stack.pop();
13693 }
13694 } else {
13695 final_selections.push(selection);
13696 }
13697 }
13698
13699 self.change_selections(Default::default(), window, cx, |s| {
13700 s.select(final_selections);
13701 });
13702
13703 let final_selection_ids: HashSet<_> = self
13704 .selections
13705 .all::<Point>(cx)
13706 .iter()
13707 .map(|s| s.id)
13708 .collect();
13709 state.groups.retain_mut(|group| {
13710 // selections might get merged above so we remove invalid items from stacks
13711 group.stack.retain(|id| final_selection_ids.contains(id));
13712
13713 // single selection in stack can be treated as initial state
13714 group.stack.len() > 1
13715 });
13716
13717 if !state.groups.is_empty() {
13718 self.add_selections_state = Some(state);
13719 }
13720 }
13721
13722 fn select_match_ranges(
13723 &mut self,
13724 range: Range<usize>,
13725 reversed: bool,
13726 replace_newest: bool,
13727 auto_scroll: Option<Autoscroll>,
13728 window: &mut Window,
13729 cx: &mut Context<Editor>,
13730 ) {
13731 self.unfold_ranges(
13732 std::slice::from_ref(&range),
13733 false,
13734 auto_scroll.is_some(),
13735 cx,
13736 );
13737 let effects = if let Some(scroll) = auto_scroll {
13738 SelectionEffects::scroll(scroll)
13739 } else {
13740 SelectionEffects::no_scroll()
13741 };
13742 self.change_selections(effects, window, cx, |s| {
13743 if replace_newest {
13744 s.delete(s.newest_anchor().id);
13745 }
13746 if reversed {
13747 s.insert_range(range.end..range.start);
13748 } else {
13749 s.insert_range(range);
13750 }
13751 });
13752 }
13753
13754 pub fn select_next_match_internal(
13755 &mut self,
13756 display_map: &DisplaySnapshot,
13757 replace_newest: bool,
13758 autoscroll: Option<Autoscroll>,
13759 window: &mut Window,
13760 cx: &mut Context<Self>,
13761 ) -> Result<()> {
13762 let buffer = &display_map.buffer_snapshot;
13763 let mut selections = self.selections.all::<usize>(cx);
13764 if let Some(mut select_next_state) = self.select_next_state.take() {
13765 let query = &select_next_state.query;
13766 if !select_next_state.done {
13767 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13768 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13769 let mut next_selected_range = None;
13770
13771 let bytes_after_last_selection =
13772 buffer.bytes_in_range(last_selection.end..buffer.len());
13773 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
13774 let query_matches = query
13775 .stream_find_iter(bytes_after_last_selection)
13776 .map(|result| (last_selection.end, result))
13777 .chain(
13778 query
13779 .stream_find_iter(bytes_before_first_selection)
13780 .map(|result| (0, result)),
13781 );
13782
13783 for (start_offset, query_match) in query_matches {
13784 let query_match = query_match.unwrap(); // can only fail due to I/O
13785 let offset_range =
13786 start_offset + query_match.start()..start_offset + query_match.end();
13787
13788 if !select_next_state.wordwise
13789 || (!buffer.is_inside_word(offset_range.start, false)
13790 && !buffer.is_inside_word(offset_range.end, false))
13791 {
13792 // TODO: This is n^2, because we might check all the selections
13793 if !selections
13794 .iter()
13795 .any(|selection| selection.range().overlaps(&offset_range))
13796 {
13797 next_selected_range = Some(offset_range);
13798 break;
13799 }
13800 }
13801 }
13802
13803 if let Some(next_selected_range) = next_selected_range {
13804 self.select_match_ranges(
13805 next_selected_range,
13806 last_selection.reversed,
13807 replace_newest,
13808 autoscroll,
13809 window,
13810 cx,
13811 );
13812 } else {
13813 select_next_state.done = true;
13814 }
13815 }
13816
13817 self.select_next_state = Some(select_next_state);
13818 } else {
13819 let mut only_carets = true;
13820 let mut same_text_selected = true;
13821 let mut selected_text = None;
13822
13823 let mut selections_iter = selections.iter().peekable();
13824 while let Some(selection) = selections_iter.next() {
13825 if selection.start != selection.end {
13826 only_carets = false;
13827 }
13828
13829 if same_text_selected {
13830 if selected_text.is_none() {
13831 selected_text =
13832 Some(buffer.text_for_range(selection.range()).collect::<String>());
13833 }
13834
13835 if let Some(next_selection) = selections_iter.peek() {
13836 if next_selection.range().len() == selection.range().len() {
13837 let next_selected_text = buffer
13838 .text_for_range(next_selection.range())
13839 .collect::<String>();
13840 if Some(next_selected_text) != selected_text {
13841 same_text_selected = false;
13842 selected_text = None;
13843 }
13844 } else {
13845 same_text_selected = false;
13846 selected_text = None;
13847 }
13848 }
13849 }
13850 }
13851
13852 if only_carets {
13853 for selection in &mut selections {
13854 let (word_range, _) = buffer.surrounding_word(selection.start, false);
13855 selection.start = word_range.start;
13856 selection.end = word_range.end;
13857 selection.goal = SelectionGoal::None;
13858 selection.reversed = false;
13859 self.select_match_ranges(
13860 selection.start..selection.end,
13861 selection.reversed,
13862 replace_newest,
13863 autoscroll,
13864 window,
13865 cx,
13866 );
13867 }
13868
13869 if selections.len() == 1 {
13870 let selection = selections
13871 .last()
13872 .expect("ensured that there's only one selection");
13873 let query = buffer
13874 .text_for_range(selection.start..selection.end)
13875 .collect::<String>();
13876 let is_empty = query.is_empty();
13877 let select_state = SelectNextState {
13878 query: AhoCorasick::new(&[query])?,
13879 wordwise: true,
13880 done: is_empty,
13881 };
13882 self.select_next_state = Some(select_state);
13883 } else {
13884 self.select_next_state = None;
13885 }
13886 } else if let Some(selected_text) = selected_text {
13887 self.select_next_state = Some(SelectNextState {
13888 query: AhoCorasick::new(&[selected_text])?,
13889 wordwise: false,
13890 done: false,
13891 });
13892 self.select_next_match_internal(
13893 display_map,
13894 replace_newest,
13895 autoscroll,
13896 window,
13897 cx,
13898 )?;
13899 }
13900 }
13901 Ok(())
13902 }
13903
13904 pub fn select_all_matches(
13905 &mut self,
13906 _action: &SelectAllMatches,
13907 window: &mut Window,
13908 cx: &mut Context<Self>,
13909 ) -> Result<()> {
13910 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13911
13912 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13913
13914 self.select_next_match_internal(&display_map, false, None, window, cx)?;
13915 let Some(select_next_state) = self.select_next_state.as_mut() else {
13916 return Ok(());
13917 };
13918 if select_next_state.done {
13919 return Ok(());
13920 }
13921
13922 let mut new_selections = Vec::new();
13923
13924 let reversed = self.selections.oldest::<usize>(cx).reversed;
13925 let buffer = &display_map.buffer_snapshot;
13926 let query_matches = select_next_state
13927 .query
13928 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
13929
13930 for query_match in query_matches.into_iter() {
13931 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
13932 let offset_range = if reversed {
13933 query_match.end()..query_match.start()
13934 } else {
13935 query_match.start()..query_match.end()
13936 };
13937
13938 if !select_next_state.wordwise
13939 || (!buffer.is_inside_word(offset_range.start, false)
13940 && !buffer.is_inside_word(offset_range.end, false))
13941 {
13942 new_selections.push(offset_range.start..offset_range.end);
13943 }
13944 }
13945
13946 select_next_state.done = true;
13947
13948 if new_selections.is_empty() {
13949 log::error!("bug: new_selections is empty in select_all_matches");
13950 return Ok(());
13951 }
13952
13953 self.unfold_ranges(&new_selections.clone(), false, false, cx);
13954 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
13955 selections.select_ranges(new_selections)
13956 });
13957
13958 Ok(())
13959 }
13960
13961 pub fn select_next(
13962 &mut self,
13963 action: &SelectNext,
13964 window: &mut Window,
13965 cx: &mut Context<Self>,
13966 ) -> Result<()> {
13967 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13968 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13969 self.select_next_match_internal(
13970 &display_map,
13971 action.replace_newest,
13972 Some(Autoscroll::newest()),
13973 window,
13974 cx,
13975 )?;
13976 Ok(())
13977 }
13978
13979 pub fn select_previous(
13980 &mut self,
13981 action: &SelectPrevious,
13982 window: &mut Window,
13983 cx: &mut Context<Self>,
13984 ) -> Result<()> {
13985 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13986 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13987 let buffer = &display_map.buffer_snapshot;
13988 let mut selections = self.selections.all::<usize>(cx);
13989 if let Some(mut select_prev_state) = self.select_prev_state.take() {
13990 let query = &select_prev_state.query;
13991 if !select_prev_state.done {
13992 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13993 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13994 let mut next_selected_range = None;
13995 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
13996 let bytes_before_last_selection =
13997 buffer.reversed_bytes_in_range(0..last_selection.start);
13998 let bytes_after_first_selection =
13999 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14000 let query_matches = query
14001 .stream_find_iter(bytes_before_last_selection)
14002 .map(|result| (last_selection.start, result))
14003 .chain(
14004 query
14005 .stream_find_iter(bytes_after_first_selection)
14006 .map(|result| (buffer.len(), result)),
14007 );
14008 for (end_offset, query_match) in query_matches {
14009 let query_match = query_match.unwrap(); // can only fail due to I/O
14010 let offset_range =
14011 end_offset - query_match.end()..end_offset - query_match.start();
14012
14013 if !select_prev_state.wordwise
14014 || (!buffer.is_inside_word(offset_range.start, false)
14015 && !buffer.is_inside_word(offset_range.end, false))
14016 {
14017 next_selected_range = Some(offset_range);
14018 break;
14019 }
14020 }
14021
14022 if let Some(next_selected_range) = next_selected_range {
14023 self.select_match_ranges(
14024 next_selected_range,
14025 last_selection.reversed,
14026 action.replace_newest,
14027 Some(Autoscroll::newest()),
14028 window,
14029 cx,
14030 );
14031 } else {
14032 select_prev_state.done = true;
14033 }
14034 }
14035
14036 self.select_prev_state = Some(select_prev_state);
14037 } else {
14038 let mut only_carets = true;
14039 let mut same_text_selected = true;
14040 let mut selected_text = None;
14041
14042 let mut selections_iter = selections.iter().peekable();
14043 while let Some(selection) = selections_iter.next() {
14044 if selection.start != selection.end {
14045 only_carets = false;
14046 }
14047
14048 if same_text_selected {
14049 if selected_text.is_none() {
14050 selected_text =
14051 Some(buffer.text_for_range(selection.range()).collect::<String>());
14052 }
14053
14054 if let Some(next_selection) = selections_iter.peek() {
14055 if next_selection.range().len() == selection.range().len() {
14056 let next_selected_text = buffer
14057 .text_for_range(next_selection.range())
14058 .collect::<String>();
14059 if Some(next_selected_text) != selected_text {
14060 same_text_selected = false;
14061 selected_text = None;
14062 }
14063 } else {
14064 same_text_selected = false;
14065 selected_text = None;
14066 }
14067 }
14068 }
14069 }
14070
14071 if only_carets {
14072 for selection in &mut selections {
14073 let (word_range, _) = buffer.surrounding_word(selection.start, false);
14074 selection.start = word_range.start;
14075 selection.end = word_range.end;
14076 selection.goal = SelectionGoal::None;
14077 selection.reversed = false;
14078 self.select_match_ranges(
14079 selection.start..selection.end,
14080 selection.reversed,
14081 action.replace_newest,
14082 Some(Autoscroll::newest()),
14083 window,
14084 cx,
14085 );
14086 }
14087 if selections.len() == 1 {
14088 let selection = selections
14089 .last()
14090 .expect("ensured that there's only one selection");
14091 let query = buffer
14092 .text_for_range(selection.start..selection.end)
14093 .collect::<String>();
14094 let is_empty = query.is_empty();
14095 let select_state = SelectNextState {
14096 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
14097 wordwise: true,
14098 done: is_empty,
14099 };
14100 self.select_prev_state = Some(select_state);
14101 } else {
14102 self.select_prev_state = None;
14103 }
14104 } else if let Some(selected_text) = selected_text {
14105 self.select_prev_state = Some(SelectNextState {
14106 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
14107 wordwise: false,
14108 done: false,
14109 });
14110 self.select_previous(action, window, cx)?;
14111 }
14112 }
14113 Ok(())
14114 }
14115
14116 pub fn find_next_match(
14117 &mut self,
14118 _: &FindNextMatch,
14119 window: &mut Window,
14120 cx: &mut Context<Self>,
14121 ) -> Result<()> {
14122 let selections = self.selections.disjoint_anchors();
14123 match selections.first() {
14124 Some(first) if selections.len() >= 2 => {
14125 self.change_selections(Default::default(), window, cx, |s| {
14126 s.select_ranges([first.range()]);
14127 });
14128 }
14129 _ => self.select_next(
14130 &SelectNext {
14131 replace_newest: true,
14132 },
14133 window,
14134 cx,
14135 )?,
14136 }
14137 Ok(())
14138 }
14139
14140 pub fn find_previous_match(
14141 &mut self,
14142 _: &FindPreviousMatch,
14143 window: &mut Window,
14144 cx: &mut Context<Self>,
14145 ) -> Result<()> {
14146 let selections = self.selections.disjoint_anchors();
14147 match selections.last() {
14148 Some(last) if selections.len() >= 2 => {
14149 self.change_selections(Default::default(), window, cx, |s| {
14150 s.select_ranges([last.range()]);
14151 });
14152 }
14153 _ => self.select_previous(
14154 &SelectPrevious {
14155 replace_newest: true,
14156 },
14157 window,
14158 cx,
14159 )?,
14160 }
14161 Ok(())
14162 }
14163
14164 pub fn toggle_comments(
14165 &mut self,
14166 action: &ToggleComments,
14167 window: &mut Window,
14168 cx: &mut Context<Self>,
14169 ) {
14170 if self.read_only(cx) {
14171 return;
14172 }
14173 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14174 let text_layout_details = &self.text_layout_details(window);
14175 self.transact(window, cx, |this, window, cx| {
14176 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
14177 let mut edits = Vec::new();
14178 let mut selection_edit_ranges = Vec::new();
14179 let mut last_toggled_row = None;
14180 let snapshot = this.buffer.read(cx).read(cx);
14181 let empty_str: Arc<str> = Arc::default();
14182 let mut suffixes_inserted = Vec::new();
14183 let ignore_indent = action.ignore_indent;
14184
14185 fn comment_prefix_range(
14186 snapshot: &MultiBufferSnapshot,
14187 row: MultiBufferRow,
14188 comment_prefix: &str,
14189 comment_prefix_whitespace: &str,
14190 ignore_indent: bool,
14191 ) -> Range<Point> {
14192 let indent_size = if ignore_indent {
14193 0
14194 } else {
14195 snapshot.indent_size_for_line(row).len
14196 };
14197
14198 let start = Point::new(row.0, indent_size);
14199
14200 let mut line_bytes = snapshot
14201 .bytes_in_range(start..snapshot.max_point())
14202 .flatten()
14203 .copied();
14204
14205 // If this line currently begins with the line comment prefix, then record
14206 // the range containing the prefix.
14207 if line_bytes
14208 .by_ref()
14209 .take(comment_prefix.len())
14210 .eq(comment_prefix.bytes())
14211 {
14212 // Include any whitespace that matches the comment prefix.
14213 let matching_whitespace_len = line_bytes
14214 .zip(comment_prefix_whitespace.bytes())
14215 .take_while(|(a, b)| a == b)
14216 .count() as u32;
14217 let end = Point::new(
14218 start.row,
14219 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14220 );
14221 start..end
14222 } else {
14223 start..start
14224 }
14225 }
14226
14227 fn comment_suffix_range(
14228 snapshot: &MultiBufferSnapshot,
14229 row: MultiBufferRow,
14230 comment_suffix: &str,
14231 comment_suffix_has_leading_space: bool,
14232 ) -> Range<Point> {
14233 let end = Point::new(row.0, snapshot.line_len(row));
14234 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14235
14236 let mut line_end_bytes = snapshot
14237 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14238 .flatten()
14239 .copied();
14240
14241 let leading_space_len = if suffix_start_column > 0
14242 && line_end_bytes.next() == Some(b' ')
14243 && comment_suffix_has_leading_space
14244 {
14245 1
14246 } else {
14247 0
14248 };
14249
14250 // If this line currently begins with the line comment prefix, then record
14251 // the range containing the prefix.
14252 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
14253 let start = Point::new(end.row, suffix_start_column - leading_space_len);
14254 start..end
14255 } else {
14256 end..end
14257 }
14258 }
14259
14260 // TODO: Handle selections that cross excerpts
14261 for selection in &mut selections {
14262 let start_column = snapshot
14263 .indent_size_for_line(MultiBufferRow(selection.start.row))
14264 .len;
14265 let language = if let Some(language) =
14266 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
14267 {
14268 language
14269 } else {
14270 continue;
14271 };
14272
14273 selection_edit_ranges.clear();
14274
14275 // If multiple selections contain a given row, avoid processing that
14276 // row more than once.
14277 let mut start_row = MultiBufferRow(selection.start.row);
14278 if last_toggled_row == Some(start_row) {
14279 start_row = start_row.next_row();
14280 }
14281 let end_row =
14282 if selection.end.row > selection.start.row && selection.end.column == 0 {
14283 MultiBufferRow(selection.end.row - 1)
14284 } else {
14285 MultiBufferRow(selection.end.row)
14286 };
14287 last_toggled_row = Some(end_row);
14288
14289 if start_row > end_row {
14290 continue;
14291 }
14292
14293 // If the language has line comments, toggle those.
14294 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
14295
14296 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
14297 if ignore_indent {
14298 full_comment_prefixes = full_comment_prefixes
14299 .into_iter()
14300 .map(|s| Arc::from(s.trim_end()))
14301 .collect();
14302 }
14303
14304 if !full_comment_prefixes.is_empty() {
14305 let first_prefix = full_comment_prefixes
14306 .first()
14307 .expect("prefixes is non-empty");
14308 let prefix_trimmed_lengths = full_comment_prefixes
14309 .iter()
14310 .map(|p| p.trim_end_matches(' ').len())
14311 .collect::<SmallVec<[usize; 4]>>();
14312
14313 let mut all_selection_lines_are_comments = true;
14314
14315 for row in start_row.0..=end_row.0 {
14316 let row = MultiBufferRow(row);
14317 if start_row < end_row && snapshot.is_line_blank(row) {
14318 continue;
14319 }
14320
14321 let prefix_range = full_comment_prefixes
14322 .iter()
14323 .zip(prefix_trimmed_lengths.iter().copied())
14324 .map(|(prefix, trimmed_prefix_len)| {
14325 comment_prefix_range(
14326 snapshot.deref(),
14327 row,
14328 &prefix[..trimmed_prefix_len],
14329 &prefix[trimmed_prefix_len..],
14330 ignore_indent,
14331 )
14332 })
14333 .max_by_key(|range| range.end.column - range.start.column)
14334 .expect("prefixes is non-empty");
14335
14336 if prefix_range.is_empty() {
14337 all_selection_lines_are_comments = false;
14338 }
14339
14340 selection_edit_ranges.push(prefix_range);
14341 }
14342
14343 if all_selection_lines_are_comments {
14344 edits.extend(
14345 selection_edit_ranges
14346 .iter()
14347 .cloned()
14348 .map(|range| (range, empty_str.clone())),
14349 );
14350 } else {
14351 let min_column = selection_edit_ranges
14352 .iter()
14353 .map(|range| range.start.column)
14354 .min()
14355 .unwrap_or(0);
14356 edits.extend(selection_edit_ranges.iter().map(|range| {
14357 let position = Point::new(range.start.row, min_column);
14358 (position..position, first_prefix.clone())
14359 }));
14360 }
14361 } else if let Some(BlockCommentConfig {
14362 start: full_comment_prefix,
14363 end: comment_suffix,
14364 ..
14365 }) = language.block_comment()
14366 {
14367 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
14368 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
14369 let prefix_range = comment_prefix_range(
14370 snapshot.deref(),
14371 start_row,
14372 comment_prefix,
14373 comment_prefix_whitespace,
14374 ignore_indent,
14375 );
14376 let suffix_range = comment_suffix_range(
14377 snapshot.deref(),
14378 end_row,
14379 comment_suffix.trim_start_matches(' '),
14380 comment_suffix.starts_with(' '),
14381 );
14382
14383 if prefix_range.is_empty() || suffix_range.is_empty() {
14384 edits.push((
14385 prefix_range.start..prefix_range.start,
14386 full_comment_prefix.clone(),
14387 ));
14388 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
14389 suffixes_inserted.push((end_row, comment_suffix.len()));
14390 } else {
14391 edits.push((prefix_range, empty_str.clone()));
14392 edits.push((suffix_range, empty_str.clone()));
14393 }
14394 } else {
14395 continue;
14396 }
14397 }
14398
14399 drop(snapshot);
14400 this.buffer.update(cx, |buffer, cx| {
14401 buffer.edit(edits, None, cx);
14402 });
14403
14404 // Adjust selections so that they end before any comment suffixes that
14405 // were inserted.
14406 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
14407 let mut selections = this.selections.all::<Point>(cx);
14408 let snapshot = this.buffer.read(cx).read(cx);
14409 for selection in &mut selections {
14410 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
14411 match row.cmp(&MultiBufferRow(selection.end.row)) {
14412 Ordering::Less => {
14413 suffixes_inserted.next();
14414 continue;
14415 }
14416 Ordering::Greater => break,
14417 Ordering::Equal => {
14418 if selection.end.column == snapshot.line_len(row) {
14419 if selection.is_empty() {
14420 selection.start.column -= suffix_len as u32;
14421 }
14422 selection.end.column -= suffix_len as u32;
14423 }
14424 break;
14425 }
14426 }
14427 }
14428 }
14429
14430 drop(snapshot);
14431 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
14432
14433 let selections = this.selections.all::<Point>(cx);
14434 let selections_on_single_row = selections.windows(2).all(|selections| {
14435 selections[0].start.row == selections[1].start.row
14436 && selections[0].end.row == selections[1].end.row
14437 && selections[0].start.row == selections[0].end.row
14438 });
14439 let selections_selecting = selections
14440 .iter()
14441 .any(|selection| selection.start != selection.end);
14442 let advance_downwards = action.advance_downwards
14443 && selections_on_single_row
14444 && !selections_selecting
14445 && !matches!(this.mode, EditorMode::SingleLine { .. });
14446
14447 if advance_downwards {
14448 let snapshot = this.buffer.read(cx).snapshot(cx);
14449
14450 this.change_selections(Default::default(), window, cx, |s| {
14451 s.move_cursors_with(|display_snapshot, display_point, _| {
14452 let mut point = display_point.to_point(display_snapshot);
14453 point.row += 1;
14454 point = snapshot.clip_point(point, Bias::Left);
14455 let display_point = point.to_display_point(display_snapshot);
14456 let goal = SelectionGoal::HorizontalPosition(
14457 display_snapshot
14458 .x_for_display_point(display_point, text_layout_details)
14459 .into(),
14460 );
14461 (display_point, goal)
14462 })
14463 });
14464 }
14465 });
14466 }
14467
14468 pub fn select_enclosing_symbol(
14469 &mut self,
14470 _: &SelectEnclosingSymbol,
14471 window: &mut Window,
14472 cx: &mut Context<Self>,
14473 ) {
14474 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14475
14476 let buffer = self.buffer.read(cx).snapshot(cx);
14477 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
14478
14479 fn update_selection(
14480 selection: &Selection<usize>,
14481 buffer_snap: &MultiBufferSnapshot,
14482 ) -> Option<Selection<usize>> {
14483 let cursor = selection.head();
14484 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
14485 for symbol in symbols.iter().rev() {
14486 let start = symbol.range.start.to_offset(buffer_snap);
14487 let end = symbol.range.end.to_offset(buffer_snap);
14488 let new_range = start..end;
14489 if start < selection.start || end > selection.end {
14490 return Some(Selection {
14491 id: selection.id,
14492 start: new_range.start,
14493 end: new_range.end,
14494 goal: SelectionGoal::None,
14495 reversed: selection.reversed,
14496 });
14497 }
14498 }
14499 None
14500 }
14501
14502 let mut selected_larger_symbol = false;
14503 let new_selections = old_selections
14504 .iter()
14505 .map(|selection| match update_selection(selection, &buffer) {
14506 Some(new_selection) => {
14507 if new_selection.range() != selection.range() {
14508 selected_larger_symbol = true;
14509 }
14510 new_selection
14511 }
14512 None => selection.clone(),
14513 })
14514 .collect::<Vec<_>>();
14515
14516 if selected_larger_symbol {
14517 self.change_selections(Default::default(), window, cx, |s| {
14518 s.select(new_selections);
14519 });
14520 }
14521 }
14522
14523 pub fn select_larger_syntax_node(
14524 &mut self,
14525 _: &SelectLargerSyntaxNode,
14526 window: &mut Window,
14527 cx: &mut Context<Self>,
14528 ) {
14529 let Some(visible_row_count) = self.visible_row_count() else {
14530 return;
14531 };
14532 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
14533 if old_selections.is_empty() {
14534 return;
14535 }
14536
14537 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14538
14539 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14540 let buffer = self.buffer.read(cx).snapshot(cx);
14541
14542 let mut selected_larger_node = false;
14543 let mut new_selections = old_selections
14544 .iter()
14545 .map(|selection| {
14546 let old_range = selection.start..selection.end;
14547
14548 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
14549 // manually select word at selection
14550 if ["string_content", "inline"].contains(&node.kind()) {
14551 let (word_range, _) = buffer.surrounding_word(old_range.start, false);
14552 // ignore if word is already selected
14553 if !word_range.is_empty() && old_range != word_range {
14554 let (last_word_range, _) =
14555 buffer.surrounding_word(old_range.end, false);
14556 // only select word if start and end point belongs to same word
14557 if word_range == last_word_range {
14558 selected_larger_node = true;
14559 return Selection {
14560 id: selection.id,
14561 start: word_range.start,
14562 end: word_range.end,
14563 goal: SelectionGoal::None,
14564 reversed: selection.reversed,
14565 };
14566 }
14567 }
14568 }
14569 }
14570
14571 let mut new_range = old_range.clone();
14572 while let Some((_node, containing_range)) =
14573 buffer.syntax_ancestor(new_range.clone())
14574 {
14575 new_range = match containing_range {
14576 MultiOrSingleBufferOffsetRange::Single(_) => break,
14577 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14578 };
14579 if !display_map.intersects_fold(new_range.start)
14580 && !display_map.intersects_fold(new_range.end)
14581 {
14582 break;
14583 }
14584 }
14585
14586 selected_larger_node |= new_range != old_range;
14587 Selection {
14588 id: selection.id,
14589 start: new_range.start,
14590 end: new_range.end,
14591 goal: SelectionGoal::None,
14592 reversed: selection.reversed,
14593 }
14594 })
14595 .collect::<Vec<_>>();
14596
14597 if !selected_larger_node {
14598 return; // don't put this call in the history
14599 }
14600
14601 // scroll based on transformation done to the last selection created by the user
14602 let (last_old, last_new) = old_selections
14603 .last()
14604 .zip(new_selections.last().cloned())
14605 .expect("old_selections isn't empty");
14606
14607 // revert selection
14608 let is_selection_reversed = {
14609 let should_newest_selection_be_reversed = last_old.start != last_new.start;
14610 new_selections.last_mut().expect("checked above").reversed =
14611 should_newest_selection_be_reversed;
14612 should_newest_selection_be_reversed
14613 };
14614
14615 if selected_larger_node {
14616 self.select_syntax_node_history.disable_clearing = true;
14617 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14618 s.select(new_selections.clone());
14619 });
14620 self.select_syntax_node_history.disable_clearing = false;
14621 }
14622
14623 let start_row = last_new.start.to_display_point(&display_map).row().0;
14624 let end_row = last_new.end.to_display_point(&display_map).row().0;
14625 let selection_height = end_row - start_row + 1;
14626 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
14627
14628 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
14629 let scroll_behavior = if fits_on_the_screen {
14630 self.request_autoscroll(Autoscroll::fit(), cx);
14631 SelectSyntaxNodeScrollBehavior::FitSelection
14632 } else if is_selection_reversed {
14633 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14634 SelectSyntaxNodeScrollBehavior::CursorTop
14635 } else {
14636 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14637 SelectSyntaxNodeScrollBehavior::CursorBottom
14638 };
14639
14640 self.select_syntax_node_history.push((
14641 old_selections,
14642 scroll_behavior,
14643 is_selection_reversed,
14644 ));
14645 }
14646
14647 pub fn select_smaller_syntax_node(
14648 &mut self,
14649 _: &SelectSmallerSyntaxNode,
14650 window: &mut Window,
14651 cx: &mut Context<Self>,
14652 ) {
14653 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14654
14655 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
14656 self.select_syntax_node_history.pop()
14657 {
14658 if let Some(selection) = selections.last_mut() {
14659 selection.reversed = is_selection_reversed;
14660 }
14661
14662 self.select_syntax_node_history.disable_clearing = true;
14663 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14664 s.select(selections.to_vec());
14665 });
14666 self.select_syntax_node_history.disable_clearing = false;
14667
14668 match scroll_behavior {
14669 SelectSyntaxNodeScrollBehavior::CursorTop => {
14670 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14671 }
14672 SelectSyntaxNodeScrollBehavior::FitSelection => {
14673 self.request_autoscroll(Autoscroll::fit(), cx);
14674 }
14675 SelectSyntaxNodeScrollBehavior::CursorBottom => {
14676 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14677 }
14678 }
14679 }
14680 }
14681
14682 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
14683 if !EditorSettings::get_global(cx).gutter.runnables {
14684 self.clear_tasks();
14685 return Task::ready(());
14686 }
14687 let project = self.project.as_ref().map(Entity::downgrade);
14688 let task_sources = self.lsp_task_sources(cx);
14689 let multi_buffer = self.buffer.downgrade();
14690 cx.spawn_in(window, async move |editor, cx| {
14691 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
14692 let Some(project) = project.and_then(|p| p.upgrade()) else {
14693 return;
14694 };
14695 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
14696 this.display_map.update(cx, |map, cx| map.snapshot(cx))
14697 }) else {
14698 return;
14699 };
14700
14701 let hide_runnables = project
14702 .update(cx, |project, cx| {
14703 // Do not display any test indicators in non-dev server remote projects.
14704 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
14705 })
14706 .unwrap_or(true);
14707 if hide_runnables {
14708 return;
14709 }
14710 let new_rows =
14711 cx.background_spawn({
14712 let snapshot = display_snapshot.clone();
14713 async move {
14714 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
14715 }
14716 })
14717 .await;
14718 let Ok(lsp_tasks) =
14719 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
14720 else {
14721 return;
14722 };
14723 let lsp_tasks = lsp_tasks.await;
14724
14725 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
14726 lsp_tasks
14727 .into_iter()
14728 .flat_map(|(kind, tasks)| {
14729 tasks.into_iter().filter_map(move |(location, task)| {
14730 Some((kind.clone(), location?, task))
14731 })
14732 })
14733 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
14734 let buffer = location.target.buffer;
14735 let buffer_snapshot = buffer.read(cx).snapshot();
14736 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
14737 |(excerpt_id, snapshot, _)| {
14738 if snapshot.remote_id() == buffer_snapshot.remote_id() {
14739 display_snapshot
14740 .buffer_snapshot
14741 .anchor_in_excerpt(excerpt_id, location.target.range.start)
14742 } else {
14743 None
14744 }
14745 },
14746 );
14747 if let Some(offset) = offset {
14748 let task_buffer_range =
14749 location.target.range.to_point(&buffer_snapshot);
14750 let context_buffer_range =
14751 task_buffer_range.to_offset(&buffer_snapshot);
14752 let context_range = BufferOffset(context_buffer_range.start)
14753 ..BufferOffset(context_buffer_range.end);
14754
14755 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
14756 .or_insert_with(|| RunnableTasks {
14757 templates: Vec::new(),
14758 offset,
14759 column: task_buffer_range.start.column,
14760 extra_variables: HashMap::default(),
14761 context_range,
14762 })
14763 .templates
14764 .push((kind, task.original_task().clone()));
14765 }
14766
14767 acc
14768 })
14769 }) else {
14770 return;
14771 };
14772
14773 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
14774 buffer.language_settings(cx).tasks.prefer_lsp
14775 }) else {
14776 return;
14777 };
14778
14779 let rows = Self::runnable_rows(
14780 project,
14781 display_snapshot,
14782 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
14783 new_rows,
14784 cx.clone(),
14785 )
14786 .await;
14787 editor
14788 .update(cx, |editor, _| {
14789 editor.clear_tasks();
14790 for (key, mut value) in rows {
14791 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
14792 value.templates.extend(lsp_tasks.templates);
14793 }
14794
14795 editor.insert_tasks(key, value);
14796 }
14797 for (key, value) in lsp_tasks_by_rows {
14798 editor.insert_tasks(key, value);
14799 }
14800 })
14801 .ok();
14802 })
14803 }
14804 fn fetch_runnable_ranges(
14805 snapshot: &DisplaySnapshot,
14806 range: Range<Anchor>,
14807 ) -> Vec<language::RunnableRange> {
14808 snapshot.buffer_snapshot.runnable_ranges(range).collect()
14809 }
14810
14811 fn runnable_rows(
14812 project: Entity<Project>,
14813 snapshot: DisplaySnapshot,
14814 prefer_lsp: bool,
14815 runnable_ranges: Vec<RunnableRange>,
14816 cx: AsyncWindowContext,
14817 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
14818 cx.spawn(async move |cx| {
14819 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
14820 for mut runnable in runnable_ranges {
14821 let Some(tasks) = cx
14822 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
14823 .ok()
14824 else {
14825 continue;
14826 };
14827 let mut tasks = tasks.await;
14828
14829 if prefer_lsp {
14830 tasks.retain(|(task_kind, _)| {
14831 !matches!(task_kind, TaskSourceKind::Language { .. })
14832 });
14833 }
14834 if tasks.is_empty() {
14835 continue;
14836 }
14837
14838 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
14839 let Some(row) = snapshot
14840 .buffer_snapshot
14841 .buffer_line_for_row(MultiBufferRow(point.row))
14842 .map(|(_, range)| range.start.row)
14843 else {
14844 continue;
14845 };
14846
14847 let context_range =
14848 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
14849 runnable_rows.push((
14850 (runnable.buffer_id, row),
14851 RunnableTasks {
14852 templates: tasks,
14853 offset: snapshot
14854 .buffer_snapshot
14855 .anchor_before(runnable.run_range.start),
14856 context_range,
14857 column: point.column,
14858 extra_variables: runnable.extra_captures,
14859 },
14860 ));
14861 }
14862 runnable_rows
14863 })
14864 }
14865
14866 fn templates_with_tags(
14867 project: &Entity<Project>,
14868 runnable: &mut Runnable,
14869 cx: &mut App,
14870 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
14871 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
14872 let (worktree_id, file) = project
14873 .buffer_for_id(runnable.buffer, cx)
14874 .and_then(|buffer| buffer.read(cx).file())
14875 .map(|file| (file.worktree_id(cx), file.clone()))
14876 .unzip();
14877
14878 (
14879 project.task_store().read(cx).task_inventory().cloned(),
14880 worktree_id,
14881 file,
14882 )
14883 });
14884
14885 let tags = mem::take(&mut runnable.tags);
14886 let language = runnable.language.clone();
14887 cx.spawn(async move |cx| {
14888 let mut templates_with_tags = Vec::new();
14889 if let Some(inventory) = inventory {
14890 for RunnableTag(tag) in tags {
14891 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
14892 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
14893 }) else {
14894 return templates_with_tags;
14895 };
14896 templates_with_tags.extend(new_tasks.await.into_iter().filter(
14897 move |(_, template)| {
14898 template.tags.iter().any(|source_tag| source_tag == &tag)
14899 },
14900 ));
14901 }
14902 }
14903 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
14904
14905 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
14906 // Strongest source wins; if we have worktree tag binding, prefer that to
14907 // global and language bindings;
14908 // if we have a global binding, prefer that to language binding.
14909 let first_mismatch = templates_with_tags
14910 .iter()
14911 .position(|(tag_source, _)| tag_source != leading_tag_source);
14912 if let Some(index) = first_mismatch {
14913 templates_with_tags.truncate(index);
14914 }
14915 }
14916
14917 templates_with_tags
14918 })
14919 }
14920
14921 pub fn move_to_enclosing_bracket(
14922 &mut self,
14923 _: &MoveToEnclosingBracket,
14924 window: &mut Window,
14925 cx: &mut Context<Self>,
14926 ) {
14927 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14928 self.change_selections(Default::default(), window, cx, |s| {
14929 s.move_offsets_with(|snapshot, selection| {
14930 let Some(enclosing_bracket_ranges) =
14931 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
14932 else {
14933 return;
14934 };
14935
14936 let mut best_length = usize::MAX;
14937 let mut best_inside = false;
14938 let mut best_in_bracket_range = false;
14939 let mut best_destination = None;
14940 for (open, close) in enclosing_bracket_ranges {
14941 let close = close.to_inclusive();
14942 let length = close.end() - open.start;
14943 let inside = selection.start >= open.end && selection.end <= *close.start();
14944 let in_bracket_range = open.to_inclusive().contains(&selection.head())
14945 || close.contains(&selection.head());
14946
14947 // If best is next to a bracket and current isn't, skip
14948 if !in_bracket_range && best_in_bracket_range {
14949 continue;
14950 }
14951
14952 // Prefer smaller lengths unless best is inside and current isn't
14953 if length > best_length && (best_inside || !inside) {
14954 continue;
14955 }
14956
14957 best_length = length;
14958 best_inside = inside;
14959 best_in_bracket_range = in_bracket_range;
14960 best_destination = Some(
14961 if close.contains(&selection.start) && close.contains(&selection.end) {
14962 if inside { open.end } else { open.start }
14963 } else if inside {
14964 *close.start()
14965 } else {
14966 *close.end()
14967 },
14968 );
14969 }
14970
14971 if let Some(destination) = best_destination {
14972 selection.collapse_to(destination, SelectionGoal::None);
14973 }
14974 })
14975 });
14976 }
14977
14978 pub fn undo_selection(
14979 &mut self,
14980 _: &UndoSelection,
14981 window: &mut Window,
14982 cx: &mut Context<Self>,
14983 ) {
14984 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14985 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
14986 self.selection_history.mode = SelectionHistoryMode::Undoing;
14987 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
14988 this.end_selection(window, cx);
14989 this.change_selections(
14990 SelectionEffects::scroll(Autoscroll::newest()),
14991 window,
14992 cx,
14993 |s| s.select_anchors(entry.selections.to_vec()),
14994 );
14995 });
14996 self.selection_history.mode = SelectionHistoryMode::Normal;
14997
14998 self.select_next_state = entry.select_next_state;
14999 self.select_prev_state = entry.select_prev_state;
15000 self.add_selections_state = entry.add_selections_state;
15001 }
15002 }
15003
15004 pub fn redo_selection(
15005 &mut self,
15006 _: &RedoSelection,
15007 window: &mut Window,
15008 cx: &mut Context<Self>,
15009 ) {
15010 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15011 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
15012 self.selection_history.mode = SelectionHistoryMode::Redoing;
15013 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15014 this.end_selection(window, cx);
15015 this.change_selections(
15016 SelectionEffects::scroll(Autoscroll::newest()),
15017 window,
15018 cx,
15019 |s| s.select_anchors(entry.selections.to_vec()),
15020 );
15021 });
15022 self.selection_history.mode = SelectionHistoryMode::Normal;
15023
15024 self.select_next_state = entry.select_next_state;
15025 self.select_prev_state = entry.select_prev_state;
15026 self.add_selections_state = entry.add_selections_state;
15027 }
15028 }
15029
15030 pub fn expand_excerpts(
15031 &mut self,
15032 action: &ExpandExcerpts,
15033 _: &mut Window,
15034 cx: &mut Context<Self>,
15035 ) {
15036 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
15037 }
15038
15039 pub fn expand_excerpts_down(
15040 &mut self,
15041 action: &ExpandExcerptsDown,
15042 _: &mut Window,
15043 cx: &mut Context<Self>,
15044 ) {
15045 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
15046 }
15047
15048 pub fn expand_excerpts_up(
15049 &mut self,
15050 action: &ExpandExcerptsUp,
15051 _: &mut Window,
15052 cx: &mut Context<Self>,
15053 ) {
15054 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
15055 }
15056
15057 pub fn expand_excerpts_for_direction(
15058 &mut self,
15059 lines: u32,
15060 direction: ExpandExcerptDirection,
15061
15062 cx: &mut Context<Self>,
15063 ) {
15064 let selections = self.selections.disjoint_anchors();
15065
15066 let lines = if lines == 0 {
15067 EditorSettings::get_global(cx).expand_excerpt_lines
15068 } else {
15069 lines
15070 };
15071
15072 self.buffer.update(cx, |buffer, cx| {
15073 let snapshot = buffer.snapshot(cx);
15074 let mut excerpt_ids = selections
15075 .iter()
15076 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
15077 .collect::<Vec<_>>();
15078 excerpt_ids.sort();
15079 excerpt_ids.dedup();
15080 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
15081 })
15082 }
15083
15084 pub fn expand_excerpt(
15085 &mut self,
15086 excerpt: ExcerptId,
15087 direction: ExpandExcerptDirection,
15088 window: &mut Window,
15089 cx: &mut Context<Self>,
15090 ) {
15091 let current_scroll_position = self.scroll_position(cx);
15092 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
15093 let mut should_scroll_up = false;
15094
15095 if direction == ExpandExcerptDirection::Down {
15096 let multi_buffer = self.buffer.read(cx);
15097 let snapshot = multi_buffer.snapshot(cx);
15098 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
15099 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
15100 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
15101 let buffer_snapshot = buffer.read(cx).snapshot();
15102 let excerpt_end_row =
15103 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
15104 let last_row = buffer_snapshot.max_point().row;
15105 let lines_below = last_row.saturating_sub(excerpt_end_row);
15106 should_scroll_up = lines_below >= lines_to_expand;
15107 }
15108 }
15109 }
15110 }
15111
15112 self.buffer.update(cx, |buffer, cx| {
15113 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
15114 });
15115
15116 if should_scroll_up {
15117 let new_scroll_position =
15118 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
15119 self.set_scroll_position(new_scroll_position, window, cx);
15120 }
15121 }
15122
15123 pub fn go_to_singleton_buffer_point(
15124 &mut self,
15125 point: Point,
15126 window: &mut Window,
15127 cx: &mut Context<Self>,
15128 ) {
15129 self.go_to_singleton_buffer_range(point..point, window, cx);
15130 }
15131
15132 pub fn go_to_singleton_buffer_range(
15133 &mut self,
15134 range: Range<Point>,
15135 window: &mut Window,
15136 cx: &mut Context<Self>,
15137 ) {
15138 let multibuffer = self.buffer().read(cx);
15139 let Some(buffer) = multibuffer.as_singleton() else {
15140 return;
15141 };
15142 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
15143 return;
15144 };
15145 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
15146 return;
15147 };
15148 self.change_selections(
15149 SelectionEffects::default().nav_history(true),
15150 window,
15151 cx,
15152 |s| s.select_anchor_ranges([start..end]),
15153 );
15154 }
15155
15156 pub fn go_to_diagnostic(
15157 &mut self,
15158 action: &GoToDiagnostic,
15159 window: &mut Window,
15160 cx: &mut Context<Self>,
15161 ) {
15162 if !self.diagnostics_enabled() {
15163 return;
15164 }
15165 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15166 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
15167 }
15168
15169 pub fn go_to_prev_diagnostic(
15170 &mut self,
15171 action: &GoToPreviousDiagnostic,
15172 window: &mut Window,
15173 cx: &mut Context<Self>,
15174 ) {
15175 if !self.diagnostics_enabled() {
15176 return;
15177 }
15178 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15179 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
15180 }
15181
15182 pub fn go_to_diagnostic_impl(
15183 &mut self,
15184 direction: Direction,
15185 severity: GoToDiagnosticSeverityFilter,
15186 window: &mut Window,
15187 cx: &mut Context<Self>,
15188 ) {
15189 let buffer = self.buffer.read(cx).snapshot(cx);
15190 let selection = self.selections.newest::<usize>(cx);
15191
15192 let mut active_group_id = None;
15193 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
15194 if active_group.active_range.start.to_offset(&buffer) == selection.start {
15195 active_group_id = Some(active_group.group_id);
15196 }
15197 }
15198
15199 fn filtered(
15200 snapshot: EditorSnapshot,
15201 severity: GoToDiagnosticSeverityFilter,
15202 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
15203 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
15204 diagnostics
15205 .filter(move |entry| severity.matches(entry.diagnostic.severity))
15206 .filter(|entry| entry.range.start != entry.range.end)
15207 .filter(|entry| !entry.diagnostic.is_unnecessary)
15208 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
15209 }
15210
15211 let snapshot = self.snapshot(window, cx);
15212 let before = filtered(
15213 snapshot.clone(),
15214 severity,
15215 buffer
15216 .diagnostics_in_range(0..selection.start)
15217 .filter(|entry| entry.range.start <= selection.start),
15218 );
15219 let after = filtered(
15220 snapshot,
15221 severity,
15222 buffer
15223 .diagnostics_in_range(selection.start..buffer.len())
15224 .filter(|entry| entry.range.start >= selection.start),
15225 );
15226
15227 let mut found: Option<DiagnosticEntry<usize>> = None;
15228 if direction == Direction::Prev {
15229 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
15230 {
15231 for diagnostic in prev_diagnostics.into_iter().rev() {
15232 if diagnostic.range.start != selection.start
15233 || active_group_id
15234 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
15235 {
15236 found = Some(diagnostic);
15237 break 'outer;
15238 }
15239 }
15240 }
15241 } else {
15242 for diagnostic in after.chain(before) {
15243 if diagnostic.range.start != selection.start
15244 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
15245 {
15246 found = Some(diagnostic);
15247 break;
15248 }
15249 }
15250 }
15251 let Some(next_diagnostic) = found else {
15252 return;
15253 };
15254
15255 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
15256 return;
15257 };
15258 self.change_selections(Default::default(), window, cx, |s| {
15259 s.select_ranges(vec![
15260 next_diagnostic.range.start..next_diagnostic.range.start,
15261 ])
15262 });
15263 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
15264 self.refresh_inline_completion(false, true, window, cx);
15265 }
15266
15267 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
15268 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15269 let snapshot = self.snapshot(window, cx);
15270 let selection = self.selections.newest::<Point>(cx);
15271 self.go_to_hunk_before_or_after_position(
15272 &snapshot,
15273 selection.head(),
15274 Direction::Next,
15275 window,
15276 cx,
15277 );
15278 }
15279
15280 pub fn go_to_hunk_before_or_after_position(
15281 &mut self,
15282 snapshot: &EditorSnapshot,
15283 position: Point,
15284 direction: Direction,
15285 window: &mut Window,
15286 cx: &mut Context<Editor>,
15287 ) {
15288 let row = if direction == Direction::Next {
15289 self.hunk_after_position(snapshot, position)
15290 .map(|hunk| hunk.row_range.start)
15291 } else {
15292 self.hunk_before_position(snapshot, position)
15293 };
15294
15295 if let Some(row) = row {
15296 let destination = Point::new(row.0, 0);
15297 let autoscroll = Autoscroll::center();
15298
15299 self.unfold_ranges(&[destination..destination], false, false, cx);
15300 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
15301 s.select_ranges([destination..destination]);
15302 });
15303 }
15304 }
15305
15306 fn hunk_after_position(
15307 &mut self,
15308 snapshot: &EditorSnapshot,
15309 position: Point,
15310 ) -> Option<MultiBufferDiffHunk> {
15311 snapshot
15312 .buffer_snapshot
15313 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
15314 .find(|hunk| hunk.row_range.start.0 > position.row)
15315 .or_else(|| {
15316 snapshot
15317 .buffer_snapshot
15318 .diff_hunks_in_range(Point::zero()..position)
15319 .find(|hunk| hunk.row_range.end.0 < position.row)
15320 })
15321 }
15322
15323 fn go_to_prev_hunk(
15324 &mut self,
15325 _: &GoToPreviousHunk,
15326 window: &mut Window,
15327 cx: &mut Context<Self>,
15328 ) {
15329 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15330 let snapshot = self.snapshot(window, cx);
15331 let selection = self.selections.newest::<Point>(cx);
15332 self.go_to_hunk_before_or_after_position(
15333 &snapshot,
15334 selection.head(),
15335 Direction::Prev,
15336 window,
15337 cx,
15338 );
15339 }
15340
15341 fn hunk_before_position(
15342 &mut self,
15343 snapshot: &EditorSnapshot,
15344 position: Point,
15345 ) -> Option<MultiBufferRow> {
15346 snapshot
15347 .buffer_snapshot
15348 .diff_hunk_before(position)
15349 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
15350 }
15351
15352 fn go_to_next_change(
15353 &mut self,
15354 _: &GoToNextChange,
15355 window: &mut Window,
15356 cx: &mut Context<Self>,
15357 ) {
15358 if let Some(selections) = self
15359 .change_list
15360 .next_change(1, Direction::Next)
15361 .map(|s| s.to_vec())
15362 {
15363 self.change_selections(Default::default(), window, cx, |s| {
15364 let map = s.display_map();
15365 s.select_display_ranges(selections.iter().map(|a| {
15366 let point = a.to_display_point(&map);
15367 point..point
15368 }))
15369 })
15370 }
15371 }
15372
15373 fn go_to_previous_change(
15374 &mut self,
15375 _: &GoToPreviousChange,
15376 window: &mut Window,
15377 cx: &mut Context<Self>,
15378 ) {
15379 if let Some(selections) = self
15380 .change_list
15381 .next_change(1, Direction::Prev)
15382 .map(|s| s.to_vec())
15383 {
15384 self.change_selections(Default::default(), window, cx, |s| {
15385 let map = s.display_map();
15386 s.select_display_ranges(selections.iter().map(|a| {
15387 let point = a.to_display_point(&map);
15388 point..point
15389 }))
15390 })
15391 }
15392 }
15393
15394 fn go_to_line<T: 'static>(
15395 &mut self,
15396 position: Anchor,
15397 highlight_color: Option<Hsla>,
15398 window: &mut Window,
15399 cx: &mut Context<Self>,
15400 ) {
15401 let snapshot = self.snapshot(window, cx).display_snapshot;
15402 let position = position.to_point(&snapshot.buffer_snapshot);
15403 let start = snapshot
15404 .buffer_snapshot
15405 .clip_point(Point::new(position.row, 0), Bias::Left);
15406 let end = start + Point::new(1, 0);
15407 let start = snapshot.buffer_snapshot.anchor_before(start);
15408 let end = snapshot.buffer_snapshot.anchor_before(end);
15409
15410 self.highlight_rows::<T>(
15411 start..end,
15412 highlight_color
15413 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
15414 Default::default(),
15415 cx,
15416 );
15417
15418 if self.buffer.read(cx).is_singleton() {
15419 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
15420 }
15421 }
15422
15423 pub fn go_to_definition(
15424 &mut self,
15425 _: &GoToDefinition,
15426 window: &mut Window,
15427 cx: &mut Context<Self>,
15428 ) -> Task<Result<Navigated>> {
15429 let definition =
15430 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
15431 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
15432 cx.spawn_in(window, async move |editor, cx| {
15433 if definition.await? == Navigated::Yes {
15434 return Ok(Navigated::Yes);
15435 }
15436 match fallback_strategy {
15437 GoToDefinitionFallback::None => Ok(Navigated::No),
15438 GoToDefinitionFallback::FindAllReferences => {
15439 match editor.update_in(cx, |editor, window, cx| {
15440 editor.find_all_references(&FindAllReferences, window, cx)
15441 })? {
15442 Some(references) => references.await,
15443 None => Ok(Navigated::No),
15444 }
15445 }
15446 }
15447 })
15448 }
15449
15450 pub fn go_to_declaration(
15451 &mut self,
15452 _: &GoToDeclaration,
15453 window: &mut Window,
15454 cx: &mut Context<Self>,
15455 ) -> Task<Result<Navigated>> {
15456 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
15457 }
15458
15459 pub fn go_to_declaration_split(
15460 &mut self,
15461 _: &GoToDeclaration,
15462 window: &mut Window,
15463 cx: &mut Context<Self>,
15464 ) -> Task<Result<Navigated>> {
15465 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
15466 }
15467
15468 pub fn go_to_implementation(
15469 &mut self,
15470 _: &GoToImplementation,
15471 window: &mut Window,
15472 cx: &mut Context<Self>,
15473 ) -> Task<Result<Navigated>> {
15474 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
15475 }
15476
15477 pub fn go_to_implementation_split(
15478 &mut self,
15479 _: &GoToImplementationSplit,
15480 window: &mut Window,
15481 cx: &mut Context<Self>,
15482 ) -> Task<Result<Navigated>> {
15483 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
15484 }
15485
15486 pub fn go_to_type_definition(
15487 &mut self,
15488 _: &GoToTypeDefinition,
15489 window: &mut Window,
15490 cx: &mut Context<Self>,
15491 ) -> Task<Result<Navigated>> {
15492 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
15493 }
15494
15495 pub fn go_to_definition_split(
15496 &mut self,
15497 _: &GoToDefinitionSplit,
15498 window: &mut Window,
15499 cx: &mut Context<Self>,
15500 ) -> Task<Result<Navigated>> {
15501 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
15502 }
15503
15504 pub fn go_to_type_definition_split(
15505 &mut self,
15506 _: &GoToTypeDefinitionSplit,
15507 window: &mut Window,
15508 cx: &mut Context<Self>,
15509 ) -> Task<Result<Navigated>> {
15510 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
15511 }
15512
15513 fn go_to_definition_of_kind(
15514 &mut self,
15515 kind: GotoDefinitionKind,
15516 split: bool,
15517 window: &mut Window,
15518 cx: &mut Context<Self>,
15519 ) -> Task<Result<Navigated>> {
15520 let Some(provider) = self.semantics_provider.clone() else {
15521 return Task::ready(Ok(Navigated::No));
15522 };
15523 let head = self.selections.newest::<usize>(cx).head();
15524 let buffer = self.buffer.read(cx);
15525 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
15526 text_anchor
15527 } else {
15528 return Task::ready(Ok(Navigated::No));
15529 };
15530
15531 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
15532 return Task::ready(Ok(Navigated::No));
15533 };
15534
15535 cx.spawn_in(window, async move |editor, cx| {
15536 let definitions = definitions.await?;
15537 let navigated = editor
15538 .update_in(cx, |editor, window, cx| {
15539 editor.navigate_to_hover_links(
15540 Some(kind),
15541 definitions
15542 .into_iter()
15543 .filter(|location| {
15544 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
15545 })
15546 .map(HoverLink::Text)
15547 .collect::<Vec<_>>(),
15548 split,
15549 window,
15550 cx,
15551 )
15552 })?
15553 .await?;
15554 anyhow::Ok(navigated)
15555 })
15556 }
15557
15558 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
15559 let selection = self.selections.newest_anchor();
15560 let head = selection.head();
15561 let tail = selection.tail();
15562
15563 let Some((buffer, start_position)) =
15564 self.buffer.read(cx).text_anchor_for_position(head, cx)
15565 else {
15566 return;
15567 };
15568
15569 let end_position = if head != tail {
15570 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
15571 return;
15572 };
15573 Some(pos)
15574 } else {
15575 None
15576 };
15577
15578 let url_finder = cx.spawn_in(window, async move |editor, cx| {
15579 let url = if let Some(end_pos) = end_position {
15580 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
15581 } else {
15582 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
15583 };
15584
15585 if let Some(url) = url {
15586 editor.update(cx, |_, cx| {
15587 cx.open_url(&url);
15588 })
15589 } else {
15590 Ok(())
15591 }
15592 });
15593
15594 url_finder.detach();
15595 }
15596
15597 pub fn open_selected_filename(
15598 &mut self,
15599 _: &OpenSelectedFilename,
15600 window: &mut Window,
15601 cx: &mut Context<Self>,
15602 ) {
15603 let Some(workspace) = self.workspace() else {
15604 return;
15605 };
15606
15607 let position = self.selections.newest_anchor().head();
15608
15609 let Some((buffer, buffer_position)) =
15610 self.buffer.read(cx).text_anchor_for_position(position, cx)
15611 else {
15612 return;
15613 };
15614
15615 let project = self.project.clone();
15616
15617 cx.spawn_in(window, async move |_, cx| {
15618 let result = find_file(&buffer, project, buffer_position, cx).await;
15619
15620 if let Some((_, path)) = result {
15621 workspace
15622 .update_in(cx, |workspace, window, cx| {
15623 workspace.open_resolved_path(path, window, cx)
15624 })?
15625 .await?;
15626 }
15627 anyhow::Ok(())
15628 })
15629 .detach();
15630 }
15631
15632 pub(crate) fn navigate_to_hover_links(
15633 &mut self,
15634 kind: Option<GotoDefinitionKind>,
15635 mut definitions: Vec<HoverLink>,
15636 split: bool,
15637 window: &mut Window,
15638 cx: &mut Context<Editor>,
15639 ) -> Task<Result<Navigated>> {
15640 // If there is one definition, just open it directly
15641 if definitions.len() == 1 {
15642 let definition = definitions.pop().unwrap();
15643
15644 enum TargetTaskResult {
15645 Location(Option<Location>),
15646 AlreadyNavigated,
15647 }
15648
15649 let target_task = match definition {
15650 HoverLink::Text(link) => {
15651 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
15652 }
15653 HoverLink::InlayHint(lsp_location, server_id) => {
15654 let computation =
15655 self.compute_target_location(lsp_location, server_id, window, cx);
15656 cx.background_spawn(async move {
15657 let location = computation.await?;
15658 Ok(TargetTaskResult::Location(location))
15659 })
15660 }
15661 HoverLink::Url(url) => {
15662 cx.open_url(&url);
15663 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
15664 }
15665 HoverLink::File(path) => {
15666 if let Some(workspace) = self.workspace() {
15667 cx.spawn_in(window, async move |_, cx| {
15668 workspace
15669 .update_in(cx, |workspace, window, cx| {
15670 workspace.open_resolved_path(path, window, cx)
15671 })?
15672 .await
15673 .map(|_| TargetTaskResult::AlreadyNavigated)
15674 })
15675 } else {
15676 Task::ready(Ok(TargetTaskResult::Location(None)))
15677 }
15678 }
15679 };
15680 cx.spawn_in(window, async move |editor, cx| {
15681 let target = match target_task.await.context("target resolution task")? {
15682 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
15683 TargetTaskResult::Location(None) => return Ok(Navigated::No),
15684 TargetTaskResult::Location(Some(target)) => target,
15685 };
15686
15687 editor.update_in(cx, |editor, window, cx| {
15688 let Some(workspace) = editor.workspace() else {
15689 return Navigated::No;
15690 };
15691 let pane = workspace.read(cx).active_pane().clone();
15692
15693 let range = target.range.to_point(target.buffer.read(cx));
15694 let range = editor.range_for_match(&range);
15695 let range = collapse_multiline_range(range);
15696
15697 if !split
15698 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
15699 {
15700 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
15701 } else {
15702 window.defer(cx, move |window, cx| {
15703 let target_editor: Entity<Self> =
15704 workspace.update(cx, |workspace, cx| {
15705 let pane = if split {
15706 workspace.adjacent_pane(window, cx)
15707 } else {
15708 workspace.active_pane().clone()
15709 };
15710
15711 workspace.open_project_item(
15712 pane,
15713 target.buffer.clone(),
15714 true,
15715 true,
15716 window,
15717 cx,
15718 )
15719 });
15720 target_editor.update(cx, |target_editor, cx| {
15721 // When selecting a definition in a different buffer, disable the nav history
15722 // to avoid creating a history entry at the previous cursor location.
15723 pane.update(cx, |pane, _| pane.disable_history());
15724 target_editor.go_to_singleton_buffer_range(range, window, cx);
15725 pane.update(cx, |pane, _| pane.enable_history());
15726 });
15727 });
15728 }
15729 Navigated::Yes
15730 })
15731 })
15732 } else if !definitions.is_empty() {
15733 cx.spawn_in(window, async move |editor, cx| {
15734 let (title, location_tasks, workspace) = editor
15735 .update_in(cx, |editor, window, cx| {
15736 let tab_kind = match kind {
15737 Some(GotoDefinitionKind::Implementation) => "Implementations",
15738 _ => "Definitions",
15739 };
15740 let title = definitions
15741 .iter()
15742 .find_map(|definition| match definition {
15743 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
15744 let buffer = origin.buffer.read(cx);
15745 format!(
15746 "{} for {}",
15747 tab_kind,
15748 buffer
15749 .text_for_range(origin.range.clone())
15750 .collect::<String>()
15751 )
15752 }),
15753 HoverLink::InlayHint(_, _) => None,
15754 HoverLink::Url(_) => None,
15755 HoverLink::File(_) => None,
15756 })
15757 .unwrap_or(tab_kind.to_string());
15758 let location_tasks = definitions
15759 .into_iter()
15760 .map(|definition| match definition {
15761 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
15762 HoverLink::InlayHint(lsp_location, server_id) => editor
15763 .compute_target_location(lsp_location, server_id, window, cx),
15764 HoverLink::Url(_) => Task::ready(Ok(None)),
15765 HoverLink::File(_) => Task::ready(Ok(None)),
15766 })
15767 .collect::<Vec<_>>();
15768 (title, location_tasks, editor.workspace().clone())
15769 })
15770 .context("location tasks preparation")?;
15771
15772 let locations: Vec<Location> = future::join_all(location_tasks)
15773 .await
15774 .into_iter()
15775 .filter_map(|location| location.transpose())
15776 .collect::<Result<_>>()
15777 .context("location tasks")?;
15778
15779 if locations.is_empty() {
15780 return Ok(Navigated::No);
15781 }
15782
15783 let Some(workspace) = workspace else {
15784 return Ok(Navigated::No);
15785 };
15786
15787 let opened = workspace
15788 .update_in(cx, |workspace, window, cx| {
15789 Self::open_locations_in_multibuffer(
15790 workspace,
15791 locations,
15792 title,
15793 split,
15794 MultibufferSelectionMode::First,
15795 window,
15796 cx,
15797 )
15798 })
15799 .ok();
15800
15801 anyhow::Ok(Navigated::from_bool(opened.is_some()))
15802 })
15803 } else {
15804 Task::ready(Ok(Navigated::No))
15805 }
15806 }
15807
15808 fn compute_target_location(
15809 &self,
15810 lsp_location: lsp::Location,
15811 server_id: LanguageServerId,
15812 window: &mut Window,
15813 cx: &mut Context<Self>,
15814 ) -> Task<anyhow::Result<Option<Location>>> {
15815 let Some(project) = self.project.clone() else {
15816 return Task::ready(Ok(None));
15817 };
15818
15819 cx.spawn_in(window, async move |editor, cx| {
15820 let location_task = editor.update(cx, |_, cx| {
15821 project.update(cx, |project, cx| {
15822 let language_server_name = project
15823 .language_server_statuses(cx)
15824 .find(|(id, _)| server_id == *id)
15825 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
15826 language_server_name.map(|language_server_name| {
15827 project.open_local_buffer_via_lsp(
15828 lsp_location.uri.clone(),
15829 server_id,
15830 language_server_name,
15831 cx,
15832 )
15833 })
15834 })
15835 })?;
15836 let location = match location_task {
15837 Some(task) => Some({
15838 let target_buffer_handle = task.await.context("open local buffer")?;
15839 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
15840 let target_start = target_buffer
15841 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
15842 let target_end = target_buffer
15843 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
15844 target_buffer.anchor_after(target_start)
15845 ..target_buffer.anchor_before(target_end)
15846 })?;
15847 Location {
15848 buffer: target_buffer_handle,
15849 range,
15850 }
15851 }),
15852 None => None,
15853 };
15854 Ok(location)
15855 })
15856 }
15857
15858 pub fn find_all_references(
15859 &mut self,
15860 _: &FindAllReferences,
15861 window: &mut Window,
15862 cx: &mut Context<Self>,
15863 ) -> Option<Task<Result<Navigated>>> {
15864 let selection = self.selections.newest::<usize>(cx);
15865 let multi_buffer = self.buffer.read(cx);
15866 let head = selection.head();
15867
15868 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
15869 let head_anchor = multi_buffer_snapshot.anchor_at(
15870 head,
15871 if head < selection.tail() {
15872 Bias::Right
15873 } else {
15874 Bias::Left
15875 },
15876 );
15877
15878 match self
15879 .find_all_references_task_sources
15880 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15881 {
15882 Ok(_) => {
15883 log::info!(
15884 "Ignoring repeated FindAllReferences invocation with the position of already running task"
15885 );
15886 return None;
15887 }
15888 Err(i) => {
15889 self.find_all_references_task_sources.insert(i, head_anchor);
15890 }
15891 }
15892
15893 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
15894 let workspace = self.workspace()?;
15895 let project = workspace.read(cx).project().clone();
15896 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
15897 Some(cx.spawn_in(window, async move |editor, cx| {
15898 let _cleanup = cx.on_drop(&editor, move |editor, _| {
15899 if let Ok(i) = editor
15900 .find_all_references_task_sources
15901 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15902 {
15903 editor.find_all_references_task_sources.remove(i);
15904 }
15905 });
15906
15907 let locations = references.await?;
15908 if locations.is_empty() {
15909 return anyhow::Ok(Navigated::No);
15910 }
15911
15912 workspace.update_in(cx, |workspace, window, cx| {
15913 let title = locations
15914 .first()
15915 .as_ref()
15916 .map(|location| {
15917 let buffer = location.buffer.read(cx);
15918 format!(
15919 "References to `{}`",
15920 buffer
15921 .text_for_range(location.range.clone())
15922 .collect::<String>()
15923 )
15924 })
15925 .unwrap();
15926 Self::open_locations_in_multibuffer(
15927 workspace,
15928 locations,
15929 title,
15930 false,
15931 MultibufferSelectionMode::First,
15932 window,
15933 cx,
15934 );
15935 Navigated::Yes
15936 })
15937 }))
15938 }
15939
15940 /// Opens a multibuffer with the given project locations in it
15941 pub fn open_locations_in_multibuffer(
15942 workspace: &mut Workspace,
15943 mut locations: Vec<Location>,
15944 title: String,
15945 split: bool,
15946 multibuffer_selection_mode: MultibufferSelectionMode,
15947 window: &mut Window,
15948 cx: &mut Context<Workspace>,
15949 ) {
15950 if locations.is_empty() {
15951 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
15952 return;
15953 }
15954
15955 // If there are multiple definitions, open them in a multibuffer
15956 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
15957 let mut locations = locations.into_iter().peekable();
15958 let mut ranges: Vec<Range<Anchor>> = Vec::new();
15959 let capability = workspace.project().read(cx).capability();
15960
15961 let excerpt_buffer = cx.new(|cx| {
15962 let mut multibuffer = MultiBuffer::new(capability);
15963 while let Some(location) = locations.next() {
15964 let buffer = location.buffer.read(cx);
15965 let mut ranges_for_buffer = Vec::new();
15966 let range = location.range.to_point(buffer);
15967 ranges_for_buffer.push(range.clone());
15968
15969 while let Some(next_location) = locations.peek() {
15970 if next_location.buffer == location.buffer {
15971 ranges_for_buffer.push(next_location.range.to_point(buffer));
15972 locations.next();
15973 } else {
15974 break;
15975 }
15976 }
15977
15978 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
15979 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
15980 PathKey::for_buffer(&location.buffer, cx),
15981 location.buffer.clone(),
15982 ranges_for_buffer,
15983 DEFAULT_MULTIBUFFER_CONTEXT,
15984 cx,
15985 );
15986 ranges.extend(new_ranges)
15987 }
15988
15989 multibuffer.with_title(title)
15990 });
15991
15992 let editor = cx.new(|cx| {
15993 Editor::for_multibuffer(
15994 excerpt_buffer,
15995 Some(workspace.project().clone()),
15996 window,
15997 cx,
15998 )
15999 });
16000 editor.update(cx, |editor, cx| {
16001 match multibuffer_selection_mode {
16002 MultibufferSelectionMode::First => {
16003 if let Some(first_range) = ranges.first() {
16004 editor.change_selections(
16005 SelectionEffects::no_scroll(),
16006 window,
16007 cx,
16008 |selections| {
16009 selections.clear_disjoint();
16010 selections
16011 .select_anchor_ranges(std::iter::once(first_range.clone()));
16012 },
16013 );
16014 }
16015 editor.highlight_background::<Self>(
16016 &ranges,
16017 |theme| theme.colors().editor_highlighted_line_background,
16018 cx,
16019 );
16020 }
16021 MultibufferSelectionMode::All => {
16022 editor.change_selections(
16023 SelectionEffects::no_scroll(),
16024 window,
16025 cx,
16026 |selections| {
16027 selections.clear_disjoint();
16028 selections.select_anchor_ranges(ranges);
16029 },
16030 );
16031 }
16032 }
16033 editor.register_buffers_with_language_servers(cx);
16034 });
16035
16036 let item = Box::new(editor);
16037 let item_id = item.item_id();
16038
16039 if split {
16040 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
16041 } else {
16042 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
16043 let (preview_item_id, preview_item_idx) =
16044 workspace.active_pane().read_with(cx, |pane, _| {
16045 (pane.preview_item_id(), pane.preview_item_idx())
16046 });
16047
16048 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
16049
16050 if let Some(preview_item_id) = preview_item_id {
16051 workspace.active_pane().update(cx, |pane, cx| {
16052 pane.remove_item(preview_item_id, false, false, window, cx);
16053 });
16054 }
16055 } else {
16056 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
16057 }
16058 }
16059 workspace.active_pane().update(cx, |pane, cx| {
16060 pane.set_preview_item_id(Some(item_id), cx);
16061 });
16062 }
16063
16064 pub fn rename(
16065 &mut self,
16066 _: &Rename,
16067 window: &mut Window,
16068 cx: &mut Context<Self>,
16069 ) -> Option<Task<Result<()>>> {
16070 use language::ToOffset as _;
16071
16072 let provider = self.semantics_provider.clone()?;
16073 let selection = self.selections.newest_anchor().clone();
16074 let (cursor_buffer, cursor_buffer_position) = self
16075 .buffer
16076 .read(cx)
16077 .text_anchor_for_position(selection.head(), cx)?;
16078 let (tail_buffer, cursor_buffer_position_end) = self
16079 .buffer
16080 .read(cx)
16081 .text_anchor_for_position(selection.tail(), cx)?;
16082 if tail_buffer != cursor_buffer {
16083 return None;
16084 }
16085
16086 let snapshot = cursor_buffer.read(cx).snapshot();
16087 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
16088 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
16089 let prepare_rename = provider
16090 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
16091 .unwrap_or_else(|| Task::ready(Ok(None)));
16092 drop(snapshot);
16093
16094 Some(cx.spawn_in(window, async move |this, cx| {
16095 let rename_range = if let Some(range) = prepare_rename.await? {
16096 Some(range)
16097 } else {
16098 this.update(cx, |this, cx| {
16099 let buffer = this.buffer.read(cx).snapshot(cx);
16100 let mut buffer_highlights = this
16101 .document_highlights_for_position(selection.head(), &buffer)
16102 .filter(|highlight| {
16103 highlight.start.excerpt_id == selection.head().excerpt_id
16104 && highlight.end.excerpt_id == selection.head().excerpt_id
16105 });
16106 buffer_highlights
16107 .next()
16108 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
16109 })?
16110 };
16111 if let Some(rename_range) = rename_range {
16112 this.update_in(cx, |this, window, cx| {
16113 let snapshot = cursor_buffer.read(cx).snapshot();
16114 let rename_buffer_range = rename_range.to_offset(&snapshot);
16115 let cursor_offset_in_rename_range =
16116 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
16117 let cursor_offset_in_rename_range_end =
16118 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
16119
16120 this.take_rename(false, window, cx);
16121 let buffer = this.buffer.read(cx).read(cx);
16122 let cursor_offset = selection.head().to_offset(&buffer);
16123 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
16124 let rename_end = rename_start + rename_buffer_range.len();
16125 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
16126 let mut old_highlight_id = None;
16127 let old_name: Arc<str> = buffer
16128 .chunks(rename_start..rename_end, true)
16129 .map(|chunk| {
16130 if old_highlight_id.is_none() {
16131 old_highlight_id = chunk.syntax_highlight_id;
16132 }
16133 chunk.text
16134 })
16135 .collect::<String>()
16136 .into();
16137
16138 drop(buffer);
16139
16140 // Position the selection in the rename editor so that it matches the current selection.
16141 this.show_local_selections = false;
16142 let rename_editor = cx.new(|cx| {
16143 let mut editor = Editor::single_line(window, cx);
16144 editor.buffer.update(cx, |buffer, cx| {
16145 buffer.edit([(0..0, old_name.clone())], None, cx)
16146 });
16147 let rename_selection_range = match cursor_offset_in_rename_range
16148 .cmp(&cursor_offset_in_rename_range_end)
16149 {
16150 Ordering::Equal => {
16151 editor.select_all(&SelectAll, window, cx);
16152 return editor;
16153 }
16154 Ordering::Less => {
16155 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
16156 }
16157 Ordering::Greater => {
16158 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
16159 }
16160 };
16161 if rename_selection_range.end > old_name.len() {
16162 editor.select_all(&SelectAll, window, cx);
16163 } else {
16164 editor.change_selections(Default::default(), window, cx, |s| {
16165 s.select_ranges([rename_selection_range]);
16166 });
16167 }
16168 editor
16169 });
16170 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
16171 if e == &EditorEvent::Focused {
16172 cx.emit(EditorEvent::FocusedIn)
16173 }
16174 })
16175 .detach();
16176
16177 let write_highlights =
16178 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
16179 let read_highlights =
16180 this.clear_background_highlights::<DocumentHighlightRead>(cx);
16181 let ranges = write_highlights
16182 .iter()
16183 .flat_map(|(_, ranges)| ranges.iter())
16184 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
16185 .cloned()
16186 .collect();
16187
16188 this.highlight_text::<Rename>(
16189 ranges,
16190 HighlightStyle {
16191 fade_out: Some(0.6),
16192 ..Default::default()
16193 },
16194 cx,
16195 );
16196 let rename_focus_handle = rename_editor.focus_handle(cx);
16197 window.focus(&rename_focus_handle);
16198 let block_id = this.insert_blocks(
16199 [BlockProperties {
16200 style: BlockStyle::Flex,
16201 placement: BlockPlacement::Below(range.start),
16202 height: Some(1),
16203 render: Arc::new({
16204 let rename_editor = rename_editor.clone();
16205 move |cx: &mut BlockContext| {
16206 let mut text_style = cx.editor_style.text.clone();
16207 if let Some(highlight_style) = old_highlight_id
16208 .and_then(|h| h.style(&cx.editor_style.syntax))
16209 {
16210 text_style = text_style.highlight(highlight_style);
16211 }
16212 div()
16213 .block_mouse_except_scroll()
16214 .pl(cx.anchor_x)
16215 .child(EditorElement::new(
16216 &rename_editor,
16217 EditorStyle {
16218 background: cx.theme().system().transparent,
16219 local_player: cx.editor_style.local_player,
16220 text: text_style,
16221 scrollbar_width: cx.editor_style.scrollbar_width,
16222 syntax: cx.editor_style.syntax.clone(),
16223 status: cx.editor_style.status.clone(),
16224 inlay_hints_style: HighlightStyle {
16225 font_weight: Some(FontWeight::BOLD),
16226 ..make_inlay_hints_style(cx.app)
16227 },
16228 inline_completion_styles: make_suggestion_styles(
16229 cx.app,
16230 ),
16231 ..EditorStyle::default()
16232 },
16233 ))
16234 .into_any_element()
16235 }
16236 }),
16237 priority: 0,
16238 }],
16239 Some(Autoscroll::fit()),
16240 cx,
16241 )[0];
16242 this.pending_rename = Some(RenameState {
16243 range,
16244 old_name,
16245 editor: rename_editor,
16246 block_id,
16247 });
16248 })?;
16249 }
16250
16251 Ok(())
16252 }))
16253 }
16254
16255 pub fn confirm_rename(
16256 &mut self,
16257 _: &ConfirmRename,
16258 window: &mut Window,
16259 cx: &mut Context<Self>,
16260 ) -> Option<Task<Result<()>>> {
16261 let rename = self.take_rename(false, window, cx)?;
16262 let workspace = self.workspace()?.downgrade();
16263 let (buffer, start) = self
16264 .buffer
16265 .read(cx)
16266 .text_anchor_for_position(rename.range.start, cx)?;
16267 let (end_buffer, _) = self
16268 .buffer
16269 .read(cx)
16270 .text_anchor_for_position(rename.range.end, cx)?;
16271 if buffer != end_buffer {
16272 return None;
16273 }
16274
16275 let old_name = rename.old_name;
16276 let new_name = rename.editor.read(cx).text(cx);
16277
16278 let rename = self.semantics_provider.as_ref()?.perform_rename(
16279 &buffer,
16280 start,
16281 new_name.clone(),
16282 cx,
16283 )?;
16284
16285 Some(cx.spawn_in(window, async move |editor, cx| {
16286 let project_transaction = rename.await?;
16287 Self::open_project_transaction(
16288 &editor,
16289 workspace,
16290 project_transaction,
16291 format!("Rename: {} → {}", old_name, new_name),
16292 cx,
16293 )
16294 .await?;
16295
16296 editor.update(cx, |editor, cx| {
16297 editor.refresh_document_highlights(cx);
16298 })?;
16299 Ok(())
16300 }))
16301 }
16302
16303 fn take_rename(
16304 &mut self,
16305 moving_cursor: bool,
16306 window: &mut Window,
16307 cx: &mut Context<Self>,
16308 ) -> Option<RenameState> {
16309 let rename = self.pending_rename.take()?;
16310 if rename.editor.focus_handle(cx).is_focused(window) {
16311 window.focus(&self.focus_handle);
16312 }
16313
16314 self.remove_blocks(
16315 [rename.block_id].into_iter().collect(),
16316 Some(Autoscroll::fit()),
16317 cx,
16318 );
16319 self.clear_highlights::<Rename>(cx);
16320 self.show_local_selections = true;
16321
16322 if moving_cursor {
16323 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
16324 editor.selections.newest::<usize>(cx).head()
16325 });
16326
16327 // Update the selection to match the position of the selection inside
16328 // the rename editor.
16329 let snapshot = self.buffer.read(cx).read(cx);
16330 let rename_range = rename.range.to_offset(&snapshot);
16331 let cursor_in_editor = snapshot
16332 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
16333 .min(rename_range.end);
16334 drop(snapshot);
16335
16336 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16337 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
16338 });
16339 } else {
16340 self.refresh_document_highlights(cx);
16341 }
16342
16343 Some(rename)
16344 }
16345
16346 pub fn pending_rename(&self) -> Option<&RenameState> {
16347 self.pending_rename.as_ref()
16348 }
16349
16350 fn format(
16351 &mut self,
16352 _: &Format,
16353 window: &mut Window,
16354 cx: &mut Context<Self>,
16355 ) -> Option<Task<Result<()>>> {
16356 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16357
16358 let project = match &self.project {
16359 Some(project) => project.clone(),
16360 None => return None,
16361 };
16362
16363 Some(self.perform_format(
16364 project,
16365 FormatTrigger::Manual,
16366 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
16367 window,
16368 cx,
16369 ))
16370 }
16371
16372 fn format_selections(
16373 &mut self,
16374 _: &FormatSelections,
16375 window: &mut Window,
16376 cx: &mut Context<Self>,
16377 ) -> Option<Task<Result<()>>> {
16378 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16379
16380 let project = match &self.project {
16381 Some(project) => project.clone(),
16382 None => return None,
16383 };
16384
16385 let ranges = self
16386 .selections
16387 .all_adjusted(cx)
16388 .into_iter()
16389 .map(|selection| selection.range())
16390 .collect_vec();
16391
16392 Some(self.perform_format(
16393 project,
16394 FormatTrigger::Manual,
16395 FormatTarget::Ranges(ranges),
16396 window,
16397 cx,
16398 ))
16399 }
16400
16401 fn perform_format(
16402 &mut self,
16403 project: Entity<Project>,
16404 trigger: FormatTrigger,
16405 target: FormatTarget,
16406 window: &mut Window,
16407 cx: &mut Context<Self>,
16408 ) -> Task<Result<()>> {
16409 let buffer = self.buffer.clone();
16410 let (buffers, target) = match target {
16411 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
16412 FormatTarget::Ranges(selection_ranges) => {
16413 let multi_buffer = buffer.read(cx);
16414 let snapshot = multi_buffer.read(cx);
16415 let mut buffers = HashSet::default();
16416 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
16417 BTreeMap::new();
16418 for selection_range in selection_ranges {
16419 for (buffer, buffer_range, _) in
16420 snapshot.range_to_buffer_ranges(selection_range)
16421 {
16422 let buffer_id = buffer.remote_id();
16423 let start = buffer.anchor_before(buffer_range.start);
16424 let end = buffer.anchor_after(buffer_range.end);
16425 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
16426 buffer_id_to_ranges
16427 .entry(buffer_id)
16428 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
16429 .or_insert_with(|| vec![start..end]);
16430 }
16431 }
16432 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
16433 }
16434 };
16435
16436 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
16437 let selections_prev = transaction_id_prev
16438 .and_then(|transaction_id_prev| {
16439 // default to selections as they were after the last edit, if we have them,
16440 // instead of how they are now.
16441 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
16442 // will take you back to where you made the last edit, instead of staying where you scrolled
16443 self.selection_history
16444 .transaction(transaction_id_prev)
16445 .map(|t| t.0.clone())
16446 })
16447 .unwrap_or_else(|| {
16448 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
16449 self.selections.disjoint_anchors()
16450 });
16451
16452 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
16453 let format = project.update(cx, |project, cx| {
16454 project.format(buffers, target, true, trigger, cx)
16455 });
16456
16457 cx.spawn_in(window, async move |editor, cx| {
16458 let transaction = futures::select_biased! {
16459 transaction = format.log_err().fuse() => transaction,
16460 () = timeout => {
16461 log::warn!("timed out waiting for formatting");
16462 None
16463 }
16464 };
16465
16466 buffer
16467 .update(cx, |buffer, cx| {
16468 if let Some(transaction) = transaction {
16469 if !buffer.is_singleton() {
16470 buffer.push_transaction(&transaction.0, cx);
16471 }
16472 }
16473 cx.notify();
16474 })
16475 .ok();
16476
16477 if let Some(transaction_id_now) =
16478 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
16479 {
16480 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
16481 if has_new_transaction {
16482 _ = editor.update(cx, |editor, _| {
16483 editor
16484 .selection_history
16485 .insert_transaction(transaction_id_now, selections_prev);
16486 });
16487 }
16488 }
16489
16490 Ok(())
16491 })
16492 }
16493
16494 fn organize_imports(
16495 &mut self,
16496 _: &OrganizeImports,
16497 window: &mut Window,
16498 cx: &mut Context<Self>,
16499 ) -> Option<Task<Result<()>>> {
16500 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16501 let project = match &self.project {
16502 Some(project) => project.clone(),
16503 None => return None,
16504 };
16505 Some(self.perform_code_action_kind(
16506 project,
16507 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
16508 window,
16509 cx,
16510 ))
16511 }
16512
16513 fn perform_code_action_kind(
16514 &mut self,
16515 project: Entity<Project>,
16516 kind: CodeActionKind,
16517 window: &mut Window,
16518 cx: &mut Context<Self>,
16519 ) -> Task<Result<()>> {
16520 let buffer = self.buffer.clone();
16521 let buffers = buffer.read(cx).all_buffers();
16522 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
16523 let apply_action = project.update(cx, |project, cx| {
16524 project.apply_code_action_kind(buffers, kind, true, cx)
16525 });
16526 cx.spawn_in(window, async move |_, cx| {
16527 let transaction = futures::select_biased! {
16528 () = timeout => {
16529 log::warn!("timed out waiting for executing code action");
16530 None
16531 }
16532 transaction = apply_action.log_err().fuse() => transaction,
16533 };
16534 buffer
16535 .update(cx, |buffer, cx| {
16536 // check if we need this
16537 if let Some(transaction) = transaction {
16538 if !buffer.is_singleton() {
16539 buffer.push_transaction(&transaction.0, cx);
16540 }
16541 }
16542 cx.notify();
16543 })
16544 .ok();
16545 Ok(())
16546 })
16547 }
16548
16549 pub fn restart_language_server(
16550 &mut self,
16551 _: &RestartLanguageServer,
16552 _: &mut Window,
16553 cx: &mut Context<Self>,
16554 ) {
16555 if let Some(project) = self.project.clone() {
16556 self.buffer.update(cx, |multi_buffer, cx| {
16557 project.update(cx, |project, cx| {
16558 project.restart_language_servers_for_buffers(
16559 multi_buffer.all_buffers().into_iter().collect(),
16560 HashSet::default(),
16561 cx,
16562 );
16563 });
16564 })
16565 }
16566 }
16567
16568 pub fn stop_language_server(
16569 &mut self,
16570 _: &StopLanguageServer,
16571 _: &mut Window,
16572 cx: &mut Context<Self>,
16573 ) {
16574 if let Some(project) = self.project.clone() {
16575 self.buffer.update(cx, |multi_buffer, cx| {
16576 project.update(cx, |project, cx| {
16577 project.stop_language_servers_for_buffers(
16578 multi_buffer.all_buffers().into_iter().collect(),
16579 HashSet::default(),
16580 cx,
16581 );
16582 cx.emit(project::Event::RefreshInlayHints);
16583 });
16584 });
16585 }
16586 }
16587
16588 fn cancel_language_server_work(
16589 workspace: &mut Workspace,
16590 _: &actions::CancelLanguageServerWork,
16591 _: &mut Window,
16592 cx: &mut Context<Workspace>,
16593 ) {
16594 let project = workspace.project();
16595 let buffers = workspace
16596 .active_item(cx)
16597 .and_then(|item| item.act_as::<Editor>(cx))
16598 .map_or(HashSet::default(), |editor| {
16599 editor.read(cx).buffer.read(cx).all_buffers()
16600 });
16601 project.update(cx, |project, cx| {
16602 project.cancel_language_server_work_for_buffers(buffers, cx);
16603 });
16604 }
16605
16606 fn show_character_palette(
16607 &mut self,
16608 _: &ShowCharacterPalette,
16609 window: &mut Window,
16610 _: &mut Context<Self>,
16611 ) {
16612 window.show_character_palette();
16613 }
16614
16615 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
16616 if !self.diagnostics_enabled() {
16617 return;
16618 }
16619
16620 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
16621 let buffer = self.buffer.read(cx).snapshot(cx);
16622 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
16623 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
16624 let is_valid = buffer
16625 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
16626 .any(|entry| {
16627 entry.diagnostic.is_primary
16628 && !entry.range.is_empty()
16629 && entry.range.start == primary_range_start
16630 && entry.diagnostic.message == active_diagnostics.active_message
16631 });
16632
16633 if !is_valid {
16634 self.dismiss_diagnostics(cx);
16635 }
16636 }
16637 }
16638
16639 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
16640 match &self.active_diagnostics {
16641 ActiveDiagnostic::Group(group) => Some(group),
16642 _ => None,
16643 }
16644 }
16645
16646 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
16647 if !self.diagnostics_enabled() {
16648 return;
16649 }
16650 self.dismiss_diagnostics(cx);
16651 self.active_diagnostics = ActiveDiagnostic::All;
16652 }
16653
16654 fn activate_diagnostics(
16655 &mut self,
16656 buffer_id: BufferId,
16657 diagnostic: DiagnosticEntry<usize>,
16658 window: &mut Window,
16659 cx: &mut Context<Self>,
16660 ) {
16661 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16662 return;
16663 }
16664 self.dismiss_diagnostics(cx);
16665 let snapshot = self.snapshot(window, cx);
16666 let buffer = self.buffer.read(cx).snapshot(cx);
16667 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
16668 return;
16669 };
16670
16671 let diagnostic_group = buffer
16672 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
16673 .collect::<Vec<_>>();
16674
16675 let blocks =
16676 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
16677
16678 let blocks = self.display_map.update(cx, |display_map, cx| {
16679 display_map.insert_blocks(blocks, cx).into_iter().collect()
16680 });
16681 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
16682 active_range: buffer.anchor_before(diagnostic.range.start)
16683 ..buffer.anchor_after(diagnostic.range.end),
16684 active_message: diagnostic.diagnostic.message.clone(),
16685 group_id: diagnostic.diagnostic.group_id,
16686 blocks,
16687 });
16688 cx.notify();
16689 }
16690
16691 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
16692 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16693 return;
16694 };
16695
16696 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
16697 if let ActiveDiagnostic::Group(group) = prev {
16698 self.display_map.update(cx, |display_map, cx| {
16699 display_map.remove_blocks(group.blocks, cx);
16700 });
16701 cx.notify();
16702 }
16703 }
16704
16705 /// Disable inline diagnostics rendering for this editor.
16706 pub fn disable_inline_diagnostics(&mut self) {
16707 self.inline_diagnostics_enabled = false;
16708 self.inline_diagnostics_update = Task::ready(());
16709 self.inline_diagnostics.clear();
16710 }
16711
16712 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
16713 self.diagnostics_enabled = false;
16714 self.dismiss_diagnostics(cx);
16715 self.inline_diagnostics_update = Task::ready(());
16716 self.inline_diagnostics.clear();
16717 }
16718
16719 pub fn diagnostics_enabled(&self) -> bool {
16720 self.diagnostics_enabled && self.mode.is_full()
16721 }
16722
16723 pub fn inline_diagnostics_enabled(&self) -> bool {
16724 self.inline_diagnostics_enabled && self.diagnostics_enabled()
16725 }
16726
16727 pub fn show_inline_diagnostics(&self) -> bool {
16728 self.show_inline_diagnostics
16729 }
16730
16731 pub fn toggle_inline_diagnostics(
16732 &mut self,
16733 _: &ToggleInlineDiagnostics,
16734 window: &mut Window,
16735 cx: &mut Context<Editor>,
16736 ) {
16737 self.show_inline_diagnostics = !self.show_inline_diagnostics;
16738 self.refresh_inline_diagnostics(false, window, cx);
16739 }
16740
16741 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
16742 self.diagnostics_max_severity = severity;
16743 self.display_map.update(cx, |display_map, _| {
16744 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
16745 });
16746 }
16747
16748 pub fn toggle_diagnostics(
16749 &mut self,
16750 _: &ToggleDiagnostics,
16751 window: &mut Window,
16752 cx: &mut Context<Editor>,
16753 ) {
16754 if !self.diagnostics_enabled() {
16755 return;
16756 }
16757
16758 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16759 EditorSettings::get_global(cx)
16760 .diagnostics_max_severity
16761 .filter(|severity| severity != &DiagnosticSeverity::Off)
16762 .unwrap_or(DiagnosticSeverity::Hint)
16763 } else {
16764 DiagnosticSeverity::Off
16765 };
16766 self.set_max_diagnostics_severity(new_severity, cx);
16767 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16768 self.active_diagnostics = ActiveDiagnostic::None;
16769 self.inline_diagnostics_update = Task::ready(());
16770 self.inline_diagnostics.clear();
16771 } else {
16772 self.refresh_inline_diagnostics(false, window, cx);
16773 }
16774
16775 cx.notify();
16776 }
16777
16778 pub fn toggle_minimap(
16779 &mut self,
16780 _: &ToggleMinimap,
16781 window: &mut Window,
16782 cx: &mut Context<Editor>,
16783 ) {
16784 if self.supports_minimap(cx) {
16785 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
16786 }
16787 }
16788
16789 fn refresh_inline_diagnostics(
16790 &mut self,
16791 debounce: bool,
16792 window: &mut Window,
16793 cx: &mut Context<Self>,
16794 ) {
16795 let max_severity = ProjectSettings::get_global(cx)
16796 .diagnostics
16797 .inline
16798 .max_severity
16799 .unwrap_or(self.diagnostics_max_severity);
16800
16801 if !self.inline_diagnostics_enabled()
16802 || !self.show_inline_diagnostics
16803 || max_severity == DiagnosticSeverity::Off
16804 {
16805 self.inline_diagnostics_update = Task::ready(());
16806 self.inline_diagnostics.clear();
16807 return;
16808 }
16809
16810 let debounce_ms = ProjectSettings::get_global(cx)
16811 .diagnostics
16812 .inline
16813 .update_debounce_ms;
16814 let debounce = if debounce && debounce_ms > 0 {
16815 Some(Duration::from_millis(debounce_ms))
16816 } else {
16817 None
16818 };
16819 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
16820 if let Some(debounce) = debounce {
16821 cx.background_executor().timer(debounce).await;
16822 }
16823 let Some(snapshot) = editor.upgrade().and_then(|editor| {
16824 editor
16825 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
16826 .ok()
16827 }) else {
16828 return;
16829 };
16830
16831 let new_inline_diagnostics = cx
16832 .background_spawn(async move {
16833 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
16834 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
16835 let message = diagnostic_entry
16836 .diagnostic
16837 .message
16838 .split_once('\n')
16839 .map(|(line, _)| line)
16840 .map(SharedString::new)
16841 .unwrap_or_else(|| {
16842 SharedString::from(diagnostic_entry.diagnostic.message)
16843 });
16844 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
16845 let (Ok(i) | Err(i)) = inline_diagnostics
16846 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
16847 inline_diagnostics.insert(
16848 i,
16849 (
16850 start_anchor,
16851 InlineDiagnostic {
16852 message,
16853 group_id: diagnostic_entry.diagnostic.group_id,
16854 start: diagnostic_entry.range.start.to_point(&snapshot),
16855 is_primary: diagnostic_entry.diagnostic.is_primary,
16856 severity: diagnostic_entry.diagnostic.severity,
16857 },
16858 ),
16859 );
16860 }
16861 inline_diagnostics
16862 })
16863 .await;
16864
16865 editor
16866 .update(cx, |editor, cx| {
16867 editor.inline_diagnostics = new_inline_diagnostics;
16868 cx.notify();
16869 })
16870 .ok();
16871 });
16872 }
16873
16874 fn pull_diagnostics(
16875 &mut self,
16876 buffer_id: Option<BufferId>,
16877 window: &Window,
16878 cx: &mut Context<Self>,
16879 ) -> Option<()> {
16880 if !self.mode().is_full() {
16881 return None;
16882 }
16883 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
16884 .diagnostics
16885 .lsp_pull_diagnostics;
16886 if !pull_diagnostics_settings.enabled {
16887 return None;
16888 }
16889 let project = self.project.as_ref()?.downgrade();
16890 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
16891 let mut buffers = self.buffer.read(cx).all_buffers();
16892 if let Some(buffer_id) = buffer_id {
16893 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
16894 }
16895
16896 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
16897 cx.background_executor().timer(debounce).await;
16898
16899 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
16900 buffers
16901 .into_iter()
16902 .filter_map(|buffer| {
16903 project
16904 .update(cx, |project, cx| {
16905 project.lsp_store().update(cx, |lsp_store, cx| {
16906 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
16907 })
16908 })
16909 .ok()
16910 })
16911 .collect::<FuturesUnordered<_>>()
16912 }) else {
16913 return;
16914 };
16915
16916 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
16917 match pull_task {
16918 Ok(()) => {
16919 if editor
16920 .update_in(cx, |editor, window, cx| {
16921 editor.update_diagnostics_state(window, cx);
16922 })
16923 .is_err()
16924 {
16925 return;
16926 }
16927 }
16928 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
16929 }
16930 }
16931 });
16932
16933 Some(())
16934 }
16935
16936 pub fn set_selections_from_remote(
16937 &mut self,
16938 selections: Vec<Selection<Anchor>>,
16939 pending_selection: Option<Selection<Anchor>>,
16940 window: &mut Window,
16941 cx: &mut Context<Self>,
16942 ) {
16943 let old_cursor_position = self.selections.newest_anchor().head();
16944 self.selections.change_with(cx, |s| {
16945 s.select_anchors(selections);
16946 if let Some(pending_selection) = pending_selection {
16947 s.set_pending(pending_selection, SelectMode::Character);
16948 } else {
16949 s.clear_pending();
16950 }
16951 });
16952 self.selections_did_change(
16953 false,
16954 &old_cursor_position,
16955 SelectionEffects::default(),
16956 window,
16957 cx,
16958 );
16959 }
16960
16961 pub fn transact(
16962 &mut self,
16963 window: &mut Window,
16964 cx: &mut Context<Self>,
16965 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
16966 ) -> Option<TransactionId> {
16967 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16968 this.start_transaction_at(Instant::now(), window, cx);
16969 update(this, window, cx);
16970 this.end_transaction_at(Instant::now(), cx)
16971 })
16972 }
16973
16974 pub fn start_transaction_at(
16975 &mut self,
16976 now: Instant,
16977 window: &mut Window,
16978 cx: &mut Context<Self>,
16979 ) -> Option<TransactionId> {
16980 self.end_selection(window, cx);
16981 if let Some(tx_id) = self
16982 .buffer
16983 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
16984 {
16985 self.selection_history
16986 .insert_transaction(tx_id, self.selections.disjoint_anchors());
16987 cx.emit(EditorEvent::TransactionBegun {
16988 transaction_id: tx_id,
16989 });
16990 Some(tx_id)
16991 } else {
16992 None
16993 }
16994 }
16995
16996 pub fn end_transaction_at(
16997 &mut self,
16998 now: Instant,
16999 cx: &mut Context<Self>,
17000 ) -> Option<TransactionId> {
17001 if let Some(transaction_id) = self
17002 .buffer
17003 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
17004 {
17005 if let Some((_, end_selections)) =
17006 self.selection_history.transaction_mut(transaction_id)
17007 {
17008 *end_selections = Some(self.selections.disjoint_anchors());
17009 } else {
17010 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
17011 }
17012
17013 cx.emit(EditorEvent::Edited { transaction_id });
17014 Some(transaction_id)
17015 } else {
17016 None
17017 }
17018 }
17019
17020 pub fn modify_transaction_selection_history(
17021 &mut self,
17022 transaction_id: TransactionId,
17023 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
17024 ) -> bool {
17025 self.selection_history
17026 .transaction_mut(transaction_id)
17027 .map(modify)
17028 .is_some()
17029 }
17030
17031 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
17032 if self.selection_mark_mode {
17033 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17034 s.move_with(|_, sel| {
17035 sel.collapse_to(sel.head(), SelectionGoal::None);
17036 });
17037 })
17038 }
17039 self.selection_mark_mode = true;
17040 cx.notify();
17041 }
17042
17043 pub fn swap_selection_ends(
17044 &mut self,
17045 _: &actions::SwapSelectionEnds,
17046 window: &mut Window,
17047 cx: &mut Context<Self>,
17048 ) {
17049 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17050 s.move_with(|_, sel| {
17051 if sel.start != sel.end {
17052 sel.reversed = !sel.reversed
17053 }
17054 });
17055 });
17056 self.request_autoscroll(Autoscroll::newest(), cx);
17057 cx.notify();
17058 }
17059
17060 pub fn toggle_focus(
17061 workspace: &mut Workspace,
17062 _: &actions::ToggleFocus,
17063 window: &mut Window,
17064 cx: &mut Context<Workspace>,
17065 ) {
17066 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
17067 return;
17068 };
17069 workspace.activate_item(&item, true, true, window, cx);
17070 }
17071
17072 pub fn toggle_fold(
17073 &mut self,
17074 _: &actions::ToggleFold,
17075 window: &mut Window,
17076 cx: &mut Context<Self>,
17077 ) {
17078 if self.is_singleton(cx) {
17079 let selection = self.selections.newest::<Point>(cx);
17080
17081 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17082 let range = if selection.is_empty() {
17083 let point = selection.head().to_display_point(&display_map);
17084 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17085 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17086 .to_point(&display_map);
17087 start..end
17088 } else {
17089 selection.range()
17090 };
17091 if display_map.folds_in_range(range).next().is_some() {
17092 self.unfold_lines(&Default::default(), window, cx)
17093 } else {
17094 self.fold(&Default::default(), window, cx)
17095 }
17096 } else {
17097 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17098 let buffer_ids: HashSet<_> = self
17099 .selections
17100 .disjoint_anchor_ranges()
17101 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17102 .collect();
17103
17104 let should_unfold = buffer_ids
17105 .iter()
17106 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17107
17108 for buffer_id in buffer_ids {
17109 if should_unfold {
17110 self.unfold_buffer(buffer_id, cx);
17111 } else {
17112 self.fold_buffer(buffer_id, cx);
17113 }
17114 }
17115 }
17116 }
17117
17118 pub fn toggle_fold_recursive(
17119 &mut self,
17120 _: &actions::ToggleFoldRecursive,
17121 window: &mut Window,
17122 cx: &mut Context<Self>,
17123 ) {
17124 let selection = self.selections.newest::<Point>(cx);
17125
17126 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17127 let range = if selection.is_empty() {
17128 let point = selection.head().to_display_point(&display_map);
17129 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17130 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17131 .to_point(&display_map);
17132 start..end
17133 } else {
17134 selection.range()
17135 };
17136 if display_map.folds_in_range(range).next().is_some() {
17137 self.unfold_recursive(&Default::default(), window, cx)
17138 } else {
17139 self.fold_recursive(&Default::default(), window, cx)
17140 }
17141 }
17142
17143 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
17144 if self.is_singleton(cx) {
17145 let mut to_fold = Vec::new();
17146 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17147 let selections = self.selections.all_adjusted(cx);
17148
17149 for selection in selections {
17150 let range = selection.range().sorted();
17151 let buffer_start_row = range.start.row;
17152
17153 if range.start.row != range.end.row {
17154 let mut found = false;
17155 let mut row = range.start.row;
17156 while row <= range.end.row {
17157 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
17158 {
17159 found = true;
17160 row = crease.range().end.row + 1;
17161 to_fold.push(crease);
17162 } else {
17163 row += 1
17164 }
17165 }
17166 if found {
17167 continue;
17168 }
17169 }
17170
17171 for row in (0..=range.start.row).rev() {
17172 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17173 if crease.range().end.row >= buffer_start_row {
17174 to_fold.push(crease);
17175 if row <= range.start.row {
17176 break;
17177 }
17178 }
17179 }
17180 }
17181 }
17182
17183 self.fold_creases(to_fold, true, window, cx);
17184 } else {
17185 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17186 let buffer_ids = self
17187 .selections
17188 .disjoint_anchor_ranges()
17189 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17190 .collect::<HashSet<_>>();
17191 for buffer_id in buffer_ids {
17192 self.fold_buffer(buffer_id, cx);
17193 }
17194 }
17195 }
17196
17197 pub fn toggle_fold_all(
17198 &mut self,
17199 _: &actions::ToggleFoldAll,
17200 window: &mut Window,
17201 cx: &mut Context<Self>,
17202 ) {
17203 if self.buffer.read(cx).is_singleton() {
17204 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17205 let has_folds = display_map
17206 .folds_in_range(0..display_map.buffer_snapshot.len())
17207 .next()
17208 .is_some();
17209
17210 if has_folds {
17211 self.unfold_all(&actions::UnfoldAll, window, cx);
17212 } else {
17213 self.fold_all(&actions::FoldAll, window, cx);
17214 }
17215 } else {
17216 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
17217 let should_unfold = buffer_ids
17218 .iter()
17219 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17220
17221 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17222 editor
17223 .update_in(cx, |editor, _, cx| {
17224 for buffer_id in buffer_ids {
17225 if should_unfold {
17226 editor.unfold_buffer(buffer_id, cx);
17227 } else {
17228 editor.fold_buffer(buffer_id, cx);
17229 }
17230 }
17231 })
17232 .ok();
17233 });
17234 }
17235 }
17236
17237 fn fold_at_level(
17238 &mut self,
17239 fold_at: &FoldAtLevel,
17240 window: &mut Window,
17241 cx: &mut Context<Self>,
17242 ) {
17243 if !self.buffer.read(cx).is_singleton() {
17244 return;
17245 }
17246
17247 let fold_at_level = fold_at.0;
17248 let snapshot = self.buffer.read(cx).snapshot(cx);
17249 let mut to_fold = Vec::new();
17250 let mut stack = vec![(0, snapshot.max_row().0, 1)];
17251
17252 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
17253 while start_row < end_row {
17254 match self
17255 .snapshot(window, cx)
17256 .crease_for_buffer_row(MultiBufferRow(start_row))
17257 {
17258 Some(crease) => {
17259 let nested_start_row = crease.range().start.row + 1;
17260 let nested_end_row = crease.range().end.row;
17261
17262 if current_level < fold_at_level {
17263 stack.push((nested_start_row, nested_end_row, current_level + 1));
17264 } else if current_level == fold_at_level {
17265 to_fold.push(crease);
17266 }
17267
17268 start_row = nested_end_row + 1;
17269 }
17270 None => start_row += 1,
17271 }
17272 }
17273 }
17274
17275 self.fold_creases(to_fold, true, window, cx);
17276 }
17277
17278 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
17279 if self.buffer.read(cx).is_singleton() {
17280 let mut fold_ranges = Vec::new();
17281 let snapshot = self.buffer.read(cx).snapshot(cx);
17282
17283 for row in 0..snapshot.max_row().0 {
17284 if let Some(foldable_range) = self
17285 .snapshot(window, cx)
17286 .crease_for_buffer_row(MultiBufferRow(row))
17287 {
17288 fold_ranges.push(foldable_range);
17289 }
17290 }
17291
17292 self.fold_creases(fold_ranges, true, window, cx);
17293 } else {
17294 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17295 editor
17296 .update_in(cx, |editor, _, cx| {
17297 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17298 editor.fold_buffer(buffer_id, cx);
17299 }
17300 })
17301 .ok();
17302 });
17303 }
17304 }
17305
17306 pub fn fold_function_bodies(
17307 &mut self,
17308 _: &actions::FoldFunctionBodies,
17309 window: &mut Window,
17310 cx: &mut Context<Self>,
17311 ) {
17312 let snapshot = self.buffer.read(cx).snapshot(cx);
17313
17314 let ranges = snapshot
17315 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
17316 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
17317 .collect::<Vec<_>>();
17318
17319 let creases = ranges
17320 .into_iter()
17321 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
17322 .collect();
17323
17324 self.fold_creases(creases, true, window, cx);
17325 }
17326
17327 pub fn fold_recursive(
17328 &mut self,
17329 _: &actions::FoldRecursive,
17330 window: &mut Window,
17331 cx: &mut Context<Self>,
17332 ) {
17333 let mut to_fold = Vec::new();
17334 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17335 let selections = self.selections.all_adjusted(cx);
17336
17337 for selection in selections {
17338 let range = selection.range().sorted();
17339 let buffer_start_row = range.start.row;
17340
17341 if range.start.row != range.end.row {
17342 let mut found = false;
17343 for row in range.start.row..=range.end.row {
17344 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17345 found = true;
17346 to_fold.push(crease);
17347 }
17348 }
17349 if found {
17350 continue;
17351 }
17352 }
17353
17354 for row in (0..=range.start.row).rev() {
17355 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17356 if crease.range().end.row >= buffer_start_row {
17357 to_fold.push(crease);
17358 } else {
17359 break;
17360 }
17361 }
17362 }
17363 }
17364
17365 self.fold_creases(to_fold, true, window, cx);
17366 }
17367
17368 pub fn fold_at(
17369 &mut self,
17370 buffer_row: MultiBufferRow,
17371 window: &mut Window,
17372 cx: &mut Context<Self>,
17373 ) {
17374 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17375
17376 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
17377 let autoscroll = self
17378 .selections
17379 .all::<Point>(cx)
17380 .iter()
17381 .any(|selection| crease.range().overlaps(&selection.range()));
17382
17383 self.fold_creases(vec![crease], autoscroll, window, cx);
17384 }
17385 }
17386
17387 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
17388 if self.is_singleton(cx) {
17389 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17390 let buffer = &display_map.buffer_snapshot;
17391 let selections = self.selections.all::<Point>(cx);
17392 let ranges = selections
17393 .iter()
17394 .map(|s| {
17395 let range = s.display_range(&display_map).sorted();
17396 let mut start = range.start.to_point(&display_map);
17397 let mut end = range.end.to_point(&display_map);
17398 start.column = 0;
17399 end.column = buffer.line_len(MultiBufferRow(end.row));
17400 start..end
17401 })
17402 .collect::<Vec<_>>();
17403
17404 self.unfold_ranges(&ranges, true, true, cx);
17405 } else {
17406 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17407 let buffer_ids = self
17408 .selections
17409 .disjoint_anchor_ranges()
17410 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17411 .collect::<HashSet<_>>();
17412 for buffer_id in buffer_ids {
17413 self.unfold_buffer(buffer_id, cx);
17414 }
17415 }
17416 }
17417
17418 pub fn unfold_recursive(
17419 &mut self,
17420 _: &UnfoldRecursive,
17421 _window: &mut Window,
17422 cx: &mut Context<Self>,
17423 ) {
17424 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17425 let selections = self.selections.all::<Point>(cx);
17426 let ranges = selections
17427 .iter()
17428 .map(|s| {
17429 let mut range = s.display_range(&display_map).sorted();
17430 *range.start.column_mut() = 0;
17431 *range.end.column_mut() = display_map.line_len(range.end.row());
17432 let start = range.start.to_point(&display_map);
17433 let end = range.end.to_point(&display_map);
17434 start..end
17435 })
17436 .collect::<Vec<_>>();
17437
17438 self.unfold_ranges(&ranges, true, true, cx);
17439 }
17440
17441 pub fn unfold_at(
17442 &mut self,
17443 buffer_row: MultiBufferRow,
17444 _window: &mut Window,
17445 cx: &mut Context<Self>,
17446 ) {
17447 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17448
17449 let intersection_range = Point::new(buffer_row.0, 0)
17450 ..Point::new(
17451 buffer_row.0,
17452 display_map.buffer_snapshot.line_len(buffer_row),
17453 );
17454
17455 let autoscroll = self
17456 .selections
17457 .all::<Point>(cx)
17458 .iter()
17459 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
17460
17461 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
17462 }
17463
17464 pub fn unfold_all(
17465 &mut self,
17466 _: &actions::UnfoldAll,
17467 _window: &mut Window,
17468 cx: &mut Context<Self>,
17469 ) {
17470 if self.buffer.read(cx).is_singleton() {
17471 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17472 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
17473 } else {
17474 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
17475 editor
17476 .update(cx, |editor, cx| {
17477 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17478 editor.unfold_buffer(buffer_id, cx);
17479 }
17480 })
17481 .ok();
17482 });
17483 }
17484 }
17485
17486 pub fn fold_selected_ranges(
17487 &mut self,
17488 _: &FoldSelectedRanges,
17489 window: &mut Window,
17490 cx: &mut Context<Self>,
17491 ) {
17492 let selections = self.selections.all_adjusted(cx);
17493 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17494 let ranges = selections
17495 .into_iter()
17496 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
17497 .collect::<Vec<_>>();
17498 self.fold_creases(ranges, true, window, cx);
17499 }
17500
17501 pub fn fold_ranges<T: ToOffset + Clone>(
17502 &mut self,
17503 ranges: Vec<Range<T>>,
17504 auto_scroll: bool,
17505 window: &mut Window,
17506 cx: &mut Context<Self>,
17507 ) {
17508 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17509 let ranges = ranges
17510 .into_iter()
17511 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
17512 .collect::<Vec<_>>();
17513 self.fold_creases(ranges, auto_scroll, window, cx);
17514 }
17515
17516 pub fn fold_creases<T: ToOffset + Clone>(
17517 &mut self,
17518 creases: Vec<Crease<T>>,
17519 auto_scroll: bool,
17520 _window: &mut Window,
17521 cx: &mut Context<Self>,
17522 ) {
17523 if creases.is_empty() {
17524 return;
17525 }
17526
17527 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
17528
17529 if auto_scroll {
17530 self.request_autoscroll(Autoscroll::fit(), cx);
17531 }
17532
17533 cx.notify();
17534
17535 self.scrollbar_marker_state.dirty = true;
17536 self.folds_did_change(cx);
17537 }
17538
17539 /// Removes any folds whose ranges intersect any of the given ranges.
17540 pub fn unfold_ranges<T: ToOffset + Clone>(
17541 &mut self,
17542 ranges: &[Range<T>],
17543 inclusive: bool,
17544 auto_scroll: bool,
17545 cx: &mut Context<Self>,
17546 ) {
17547 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17548 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
17549 });
17550 self.folds_did_change(cx);
17551 }
17552
17553 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17554 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
17555 return;
17556 }
17557 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17558 self.display_map.update(cx, |display_map, cx| {
17559 display_map.fold_buffers([buffer_id], cx)
17560 });
17561 cx.emit(EditorEvent::BufferFoldToggled {
17562 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
17563 folded: true,
17564 });
17565 cx.notify();
17566 }
17567
17568 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17569 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
17570 return;
17571 }
17572 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17573 self.display_map.update(cx, |display_map, cx| {
17574 display_map.unfold_buffers([buffer_id], cx);
17575 });
17576 cx.emit(EditorEvent::BufferFoldToggled {
17577 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
17578 folded: false,
17579 });
17580 cx.notify();
17581 }
17582
17583 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
17584 self.display_map.read(cx).is_buffer_folded(buffer)
17585 }
17586
17587 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
17588 self.display_map.read(cx).folded_buffers()
17589 }
17590
17591 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17592 self.display_map.update(cx, |display_map, cx| {
17593 display_map.disable_header_for_buffer(buffer_id, cx);
17594 });
17595 cx.notify();
17596 }
17597
17598 /// Removes any folds with the given ranges.
17599 pub fn remove_folds_with_type<T: ToOffset + Clone>(
17600 &mut self,
17601 ranges: &[Range<T>],
17602 type_id: TypeId,
17603 auto_scroll: bool,
17604 cx: &mut Context<Self>,
17605 ) {
17606 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17607 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
17608 });
17609 self.folds_did_change(cx);
17610 }
17611
17612 fn remove_folds_with<T: ToOffset + Clone>(
17613 &mut self,
17614 ranges: &[Range<T>],
17615 auto_scroll: bool,
17616 cx: &mut Context<Self>,
17617 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
17618 ) {
17619 if ranges.is_empty() {
17620 return;
17621 }
17622
17623 let mut buffers_affected = HashSet::default();
17624 let multi_buffer = self.buffer().read(cx);
17625 for range in ranges {
17626 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
17627 buffers_affected.insert(buffer.read(cx).remote_id());
17628 };
17629 }
17630
17631 self.display_map.update(cx, update);
17632
17633 if auto_scroll {
17634 self.request_autoscroll(Autoscroll::fit(), cx);
17635 }
17636
17637 cx.notify();
17638 self.scrollbar_marker_state.dirty = true;
17639 self.active_indent_guides_state.dirty = true;
17640 }
17641
17642 pub fn update_renderer_widths(
17643 &mut self,
17644 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
17645 cx: &mut Context<Self>,
17646 ) -> bool {
17647 self.display_map
17648 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
17649 }
17650
17651 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
17652 self.display_map.read(cx).fold_placeholder.clone()
17653 }
17654
17655 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
17656 self.buffer.update(cx, |buffer, cx| {
17657 buffer.set_all_diff_hunks_expanded(cx);
17658 });
17659 }
17660
17661 pub fn expand_all_diff_hunks(
17662 &mut self,
17663 _: &ExpandAllDiffHunks,
17664 _window: &mut Window,
17665 cx: &mut Context<Self>,
17666 ) {
17667 self.buffer.update(cx, |buffer, cx| {
17668 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
17669 });
17670 }
17671
17672 pub fn toggle_selected_diff_hunks(
17673 &mut self,
17674 _: &ToggleSelectedDiffHunks,
17675 _window: &mut Window,
17676 cx: &mut Context<Self>,
17677 ) {
17678 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17679 self.toggle_diff_hunks_in_ranges(ranges, cx);
17680 }
17681
17682 pub fn diff_hunks_in_ranges<'a>(
17683 &'a self,
17684 ranges: &'a [Range<Anchor>],
17685 buffer: &'a MultiBufferSnapshot,
17686 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
17687 ranges.iter().flat_map(move |range| {
17688 let end_excerpt_id = range.end.excerpt_id;
17689 let range = range.to_point(buffer);
17690 let mut peek_end = range.end;
17691 if range.end.row < buffer.max_row().0 {
17692 peek_end = Point::new(range.end.row + 1, 0);
17693 }
17694 buffer
17695 .diff_hunks_in_range(range.start..peek_end)
17696 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
17697 })
17698 }
17699
17700 pub fn has_stageable_diff_hunks_in_ranges(
17701 &self,
17702 ranges: &[Range<Anchor>],
17703 snapshot: &MultiBufferSnapshot,
17704 ) -> bool {
17705 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
17706 hunks.any(|hunk| hunk.status().has_secondary_hunk())
17707 }
17708
17709 pub fn toggle_staged_selected_diff_hunks(
17710 &mut self,
17711 _: &::git::ToggleStaged,
17712 _: &mut Window,
17713 cx: &mut Context<Self>,
17714 ) {
17715 let snapshot = self.buffer.read(cx).snapshot(cx);
17716 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17717 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
17718 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17719 }
17720
17721 pub fn set_render_diff_hunk_controls(
17722 &mut self,
17723 render_diff_hunk_controls: RenderDiffHunkControlsFn,
17724 cx: &mut Context<Self>,
17725 ) {
17726 self.render_diff_hunk_controls = render_diff_hunk_controls;
17727 cx.notify();
17728 }
17729
17730 pub fn stage_and_next(
17731 &mut self,
17732 _: &::git::StageAndNext,
17733 window: &mut Window,
17734 cx: &mut Context<Self>,
17735 ) {
17736 self.do_stage_or_unstage_and_next(true, window, cx);
17737 }
17738
17739 pub fn unstage_and_next(
17740 &mut self,
17741 _: &::git::UnstageAndNext,
17742 window: &mut Window,
17743 cx: &mut Context<Self>,
17744 ) {
17745 self.do_stage_or_unstage_and_next(false, window, cx);
17746 }
17747
17748 pub fn stage_or_unstage_diff_hunks(
17749 &mut self,
17750 stage: bool,
17751 ranges: Vec<Range<Anchor>>,
17752 cx: &mut Context<Self>,
17753 ) {
17754 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
17755 cx.spawn(async move |this, cx| {
17756 task.await?;
17757 this.update(cx, |this, cx| {
17758 let snapshot = this.buffer.read(cx).snapshot(cx);
17759 let chunk_by = this
17760 .diff_hunks_in_ranges(&ranges, &snapshot)
17761 .chunk_by(|hunk| hunk.buffer_id);
17762 for (buffer_id, hunks) in &chunk_by {
17763 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
17764 }
17765 })
17766 })
17767 .detach_and_log_err(cx);
17768 }
17769
17770 fn save_buffers_for_ranges_if_needed(
17771 &mut self,
17772 ranges: &[Range<Anchor>],
17773 cx: &mut Context<Editor>,
17774 ) -> Task<Result<()>> {
17775 let multibuffer = self.buffer.read(cx);
17776 let snapshot = multibuffer.read(cx);
17777 let buffer_ids: HashSet<_> = ranges
17778 .iter()
17779 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
17780 .collect();
17781 drop(snapshot);
17782
17783 let mut buffers = HashSet::default();
17784 for buffer_id in buffer_ids {
17785 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
17786 let buffer = buffer_entity.read(cx);
17787 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
17788 {
17789 buffers.insert(buffer_entity);
17790 }
17791 }
17792 }
17793
17794 if let Some(project) = &self.project {
17795 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
17796 } else {
17797 Task::ready(Ok(()))
17798 }
17799 }
17800
17801 fn do_stage_or_unstage_and_next(
17802 &mut self,
17803 stage: bool,
17804 window: &mut Window,
17805 cx: &mut Context<Self>,
17806 ) {
17807 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
17808
17809 if ranges.iter().any(|range| range.start != range.end) {
17810 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17811 return;
17812 }
17813
17814 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17815 let snapshot = self.snapshot(window, cx);
17816 let position = self.selections.newest::<Point>(cx).head();
17817 let mut row = snapshot
17818 .buffer_snapshot
17819 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
17820 .find(|hunk| hunk.row_range.start.0 > position.row)
17821 .map(|hunk| hunk.row_range.start);
17822
17823 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
17824 // Outside of the project diff editor, wrap around to the beginning.
17825 if !all_diff_hunks_expanded {
17826 row = row.or_else(|| {
17827 snapshot
17828 .buffer_snapshot
17829 .diff_hunks_in_range(Point::zero()..position)
17830 .find(|hunk| hunk.row_range.end.0 < position.row)
17831 .map(|hunk| hunk.row_range.start)
17832 });
17833 }
17834
17835 if let Some(row) = row {
17836 let destination = Point::new(row.0, 0);
17837 let autoscroll = Autoscroll::center();
17838
17839 self.unfold_ranges(&[destination..destination], false, false, cx);
17840 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17841 s.select_ranges([destination..destination]);
17842 });
17843 }
17844 }
17845
17846 fn do_stage_or_unstage(
17847 &self,
17848 stage: bool,
17849 buffer_id: BufferId,
17850 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
17851 cx: &mut App,
17852 ) -> Option<()> {
17853 let project = self.project.as_ref()?;
17854 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
17855 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
17856 let buffer_snapshot = buffer.read(cx).snapshot();
17857 let file_exists = buffer_snapshot
17858 .file()
17859 .is_some_and(|file| file.disk_state().exists());
17860 diff.update(cx, |diff, cx| {
17861 diff.stage_or_unstage_hunks(
17862 stage,
17863 &hunks
17864 .map(|hunk| buffer_diff::DiffHunk {
17865 buffer_range: hunk.buffer_range,
17866 diff_base_byte_range: hunk.diff_base_byte_range,
17867 secondary_status: hunk.secondary_status,
17868 range: Point::zero()..Point::zero(), // unused
17869 })
17870 .collect::<Vec<_>>(),
17871 &buffer_snapshot,
17872 file_exists,
17873 cx,
17874 )
17875 });
17876 None
17877 }
17878
17879 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
17880 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17881 self.buffer
17882 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
17883 }
17884
17885 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
17886 self.buffer.update(cx, |buffer, cx| {
17887 let ranges = vec![Anchor::min()..Anchor::max()];
17888 if !buffer.all_diff_hunks_expanded()
17889 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
17890 {
17891 buffer.collapse_diff_hunks(ranges, cx);
17892 true
17893 } else {
17894 false
17895 }
17896 })
17897 }
17898
17899 fn toggle_diff_hunks_in_ranges(
17900 &mut self,
17901 ranges: Vec<Range<Anchor>>,
17902 cx: &mut Context<Editor>,
17903 ) {
17904 self.buffer.update(cx, |buffer, cx| {
17905 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
17906 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
17907 })
17908 }
17909
17910 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
17911 self.buffer.update(cx, |buffer, cx| {
17912 let snapshot = buffer.snapshot(cx);
17913 let excerpt_id = range.end.excerpt_id;
17914 let point_range = range.to_point(&snapshot);
17915 let expand = !buffer.single_hunk_is_expanded(range, cx);
17916 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
17917 })
17918 }
17919
17920 pub(crate) fn apply_all_diff_hunks(
17921 &mut self,
17922 _: &ApplyAllDiffHunks,
17923 window: &mut Window,
17924 cx: &mut Context<Self>,
17925 ) {
17926 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17927
17928 let buffers = self.buffer.read(cx).all_buffers();
17929 for branch_buffer in buffers {
17930 branch_buffer.update(cx, |branch_buffer, cx| {
17931 branch_buffer.merge_into_base(Vec::new(), cx);
17932 });
17933 }
17934
17935 if let Some(project) = self.project.clone() {
17936 self.save(
17937 SaveOptions {
17938 format: true,
17939 autosave: false,
17940 },
17941 project,
17942 window,
17943 cx,
17944 )
17945 .detach_and_log_err(cx);
17946 }
17947 }
17948
17949 pub(crate) fn apply_selected_diff_hunks(
17950 &mut self,
17951 _: &ApplyDiffHunk,
17952 window: &mut Window,
17953 cx: &mut Context<Self>,
17954 ) {
17955 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17956 let snapshot = self.snapshot(window, cx);
17957 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
17958 let mut ranges_by_buffer = HashMap::default();
17959 self.transact(window, cx, |editor, _window, cx| {
17960 for hunk in hunks {
17961 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
17962 ranges_by_buffer
17963 .entry(buffer.clone())
17964 .or_insert_with(Vec::new)
17965 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
17966 }
17967 }
17968
17969 for (buffer, ranges) in ranges_by_buffer {
17970 buffer.update(cx, |buffer, cx| {
17971 buffer.merge_into_base(ranges, cx);
17972 });
17973 }
17974 });
17975
17976 if let Some(project) = self.project.clone() {
17977 self.save(
17978 SaveOptions {
17979 format: true,
17980 autosave: false,
17981 },
17982 project,
17983 window,
17984 cx,
17985 )
17986 .detach_and_log_err(cx);
17987 }
17988 }
17989
17990 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
17991 if hovered != self.gutter_hovered {
17992 self.gutter_hovered = hovered;
17993 cx.notify();
17994 }
17995 }
17996
17997 pub fn insert_blocks(
17998 &mut self,
17999 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
18000 autoscroll: Option<Autoscroll>,
18001 cx: &mut Context<Self>,
18002 ) -> Vec<CustomBlockId> {
18003 let blocks = self
18004 .display_map
18005 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
18006 if let Some(autoscroll) = autoscroll {
18007 self.request_autoscroll(autoscroll, cx);
18008 }
18009 cx.notify();
18010 blocks
18011 }
18012
18013 pub fn resize_blocks(
18014 &mut self,
18015 heights: HashMap<CustomBlockId, u32>,
18016 autoscroll: Option<Autoscroll>,
18017 cx: &mut Context<Self>,
18018 ) {
18019 self.display_map
18020 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
18021 if let Some(autoscroll) = autoscroll {
18022 self.request_autoscroll(autoscroll, cx);
18023 }
18024 cx.notify();
18025 }
18026
18027 pub fn replace_blocks(
18028 &mut self,
18029 renderers: HashMap<CustomBlockId, RenderBlock>,
18030 autoscroll: Option<Autoscroll>,
18031 cx: &mut Context<Self>,
18032 ) {
18033 self.display_map
18034 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
18035 if let Some(autoscroll) = autoscroll {
18036 self.request_autoscroll(autoscroll, cx);
18037 }
18038 cx.notify();
18039 }
18040
18041 pub fn remove_blocks(
18042 &mut self,
18043 block_ids: HashSet<CustomBlockId>,
18044 autoscroll: Option<Autoscroll>,
18045 cx: &mut Context<Self>,
18046 ) {
18047 self.display_map.update(cx, |display_map, cx| {
18048 display_map.remove_blocks(block_ids, cx)
18049 });
18050 if let Some(autoscroll) = autoscroll {
18051 self.request_autoscroll(autoscroll, cx);
18052 }
18053 cx.notify();
18054 }
18055
18056 pub fn row_for_block(
18057 &self,
18058 block_id: CustomBlockId,
18059 cx: &mut Context<Self>,
18060 ) -> Option<DisplayRow> {
18061 self.display_map
18062 .update(cx, |map, cx| map.row_for_block(block_id, cx))
18063 }
18064
18065 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
18066 self.focused_block = Some(focused_block);
18067 }
18068
18069 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
18070 self.focused_block.take()
18071 }
18072
18073 pub fn insert_creases(
18074 &mut self,
18075 creases: impl IntoIterator<Item = Crease<Anchor>>,
18076 cx: &mut Context<Self>,
18077 ) -> Vec<CreaseId> {
18078 self.display_map
18079 .update(cx, |map, cx| map.insert_creases(creases, cx))
18080 }
18081
18082 pub fn remove_creases(
18083 &mut self,
18084 ids: impl IntoIterator<Item = CreaseId>,
18085 cx: &mut Context<Self>,
18086 ) -> Vec<(CreaseId, Range<Anchor>)> {
18087 self.display_map
18088 .update(cx, |map, cx| map.remove_creases(ids, cx))
18089 }
18090
18091 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
18092 self.display_map
18093 .update(cx, |map, cx| map.snapshot(cx))
18094 .longest_row()
18095 }
18096
18097 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
18098 self.display_map
18099 .update(cx, |map, cx| map.snapshot(cx))
18100 .max_point()
18101 }
18102
18103 pub fn text(&self, cx: &App) -> String {
18104 self.buffer.read(cx).read(cx).text()
18105 }
18106
18107 pub fn is_empty(&self, cx: &App) -> bool {
18108 self.buffer.read(cx).read(cx).is_empty()
18109 }
18110
18111 pub fn text_option(&self, cx: &App) -> Option<String> {
18112 let text = self.text(cx);
18113 let text = text.trim();
18114
18115 if text.is_empty() {
18116 return None;
18117 }
18118
18119 Some(text.to_string())
18120 }
18121
18122 pub fn set_text(
18123 &mut self,
18124 text: impl Into<Arc<str>>,
18125 window: &mut Window,
18126 cx: &mut Context<Self>,
18127 ) {
18128 self.transact(window, cx, |this, _, cx| {
18129 this.buffer
18130 .read(cx)
18131 .as_singleton()
18132 .expect("you can only call set_text on editors for singleton buffers")
18133 .update(cx, |buffer, cx| buffer.set_text(text, cx));
18134 });
18135 }
18136
18137 pub fn display_text(&self, cx: &mut App) -> String {
18138 self.display_map
18139 .update(cx, |map, cx| map.snapshot(cx))
18140 .text()
18141 }
18142
18143 fn create_minimap(
18144 &self,
18145 minimap_settings: MinimapSettings,
18146 window: &mut Window,
18147 cx: &mut Context<Self>,
18148 ) -> Option<Entity<Self>> {
18149 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
18150 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
18151 }
18152
18153 fn initialize_new_minimap(
18154 &self,
18155 minimap_settings: MinimapSettings,
18156 window: &mut Window,
18157 cx: &mut Context<Self>,
18158 ) -> Entity<Self> {
18159 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
18160
18161 let mut minimap = Editor::new_internal(
18162 EditorMode::Minimap {
18163 parent: cx.weak_entity(),
18164 },
18165 self.buffer.clone(),
18166 None,
18167 Some(self.display_map.clone()),
18168 window,
18169 cx,
18170 );
18171 minimap.scroll_manager.clone_state(&self.scroll_manager);
18172 minimap.set_text_style_refinement(TextStyleRefinement {
18173 font_size: Some(MINIMAP_FONT_SIZE),
18174 font_weight: Some(MINIMAP_FONT_WEIGHT),
18175 ..Default::default()
18176 });
18177 minimap.update_minimap_configuration(minimap_settings, cx);
18178 cx.new(|_| minimap)
18179 }
18180
18181 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
18182 let current_line_highlight = minimap_settings
18183 .current_line_highlight
18184 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
18185 self.set_current_line_highlight(Some(current_line_highlight));
18186 }
18187
18188 pub fn minimap(&self) -> Option<&Entity<Self>> {
18189 self.minimap
18190 .as_ref()
18191 .filter(|_| self.minimap_visibility.visible())
18192 }
18193
18194 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
18195 let mut wrap_guides = smallvec![];
18196
18197 if self.show_wrap_guides == Some(false) {
18198 return wrap_guides;
18199 }
18200
18201 let settings = self.buffer.read(cx).language_settings(cx);
18202 if settings.show_wrap_guides {
18203 match self.soft_wrap_mode(cx) {
18204 SoftWrap::Column(soft_wrap) => {
18205 wrap_guides.push((soft_wrap as usize, true));
18206 }
18207 SoftWrap::Bounded(soft_wrap) => {
18208 wrap_guides.push((soft_wrap as usize, true));
18209 }
18210 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
18211 }
18212 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
18213 }
18214
18215 wrap_guides
18216 }
18217
18218 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
18219 let settings = self.buffer.read(cx).language_settings(cx);
18220 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
18221 match mode {
18222 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
18223 SoftWrap::None
18224 }
18225 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
18226 language_settings::SoftWrap::PreferredLineLength => {
18227 SoftWrap::Column(settings.preferred_line_length)
18228 }
18229 language_settings::SoftWrap::Bounded => {
18230 SoftWrap::Bounded(settings.preferred_line_length)
18231 }
18232 }
18233 }
18234
18235 pub fn set_soft_wrap_mode(
18236 &mut self,
18237 mode: language_settings::SoftWrap,
18238
18239 cx: &mut Context<Self>,
18240 ) {
18241 self.soft_wrap_mode_override = Some(mode);
18242 cx.notify();
18243 }
18244
18245 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
18246 self.hard_wrap = hard_wrap;
18247 cx.notify();
18248 }
18249
18250 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
18251 self.text_style_refinement = Some(style);
18252 }
18253
18254 /// called by the Element so we know what style we were most recently rendered with.
18255 pub(crate) fn set_style(
18256 &mut self,
18257 style: EditorStyle,
18258 window: &mut Window,
18259 cx: &mut Context<Self>,
18260 ) {
18261 // We intentionally do not inform the display map about the minimap style
18262 // so that wrapping is not recalculated and stays consistent for the editor
18263 // and its linked minimap.
18264 if !self.mode.is_minimap() {
18265 let rem_size = window.rem_size();
18266 self.display_map.update(cx, |map, cx| {
18267 map.set_font(
18268 style.text.font(),
18269 style.text.font_size.to_pixels(rem_size),
18270 cx,
18271 )
18272 });
18273 }
18274 self.style = Some(style);
18275 }
18276
18277 pub fn style(&self) -> Option<&EditorStyle> {
18278 self.style.as_ref()
18279 }
18280
18281 // Called by the element. This method is not designed to be called outside of the editor
18282 // element's layout code because it does not notify when rewrapping is computed synchronously.
18283 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
18284 self.display_map
18285 .update(cx, |map, cx| map.set_wrap_width(width, cx))
18286 }
18287
18288 pub fn set_soft_wrap(&mut self) {
18289 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
18290 }
18291
18292 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
18293 if self.soft_wrap_mode_override.is_some() {
18294 self.soft_wrap_mode_override.take();
18295 } else {
18296 let soft_wrap = match self.soft_wrap_mode(cx) {
18297 SoftWrap::GitDiff => return,
18298 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
18299 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
18300 language_settings::SoftWrap::None
18301 }
18302 };
18303 self.soft_wrap_mode_override = Some(soft_wrap);
18304 }
18305 cx.notify();
18306 }
18307
18308 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
18309 let Some(workspace) = self.workspace() else {
18310 return;
18311 };
18312 let fs = workspace.read(cx).app_state().fs.clone();
18313 let current_show = TabBarSettings::get_global(cx).show;
18314 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
18315 setting.show = Some(!current_show);
18316 });
18317 }
18318
18319 pub fn toggle_indent_guides(
18320 &mut self,
18321 _: &ToggleIndentGuides,
18322 _: &mut Window,
18323 cx: &mut Context<Self>,
18324 ) {
18325 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
18326 self.buffer
18327 .read(cx)
18328 .language_settings(cx)
18329 .indent_guides
18330 .enabled
18331 });
18332 self.show_indent_guides = Some(!currently_enabled);
18333 cx.notify();
18334 }
18335
18336 fn should_show_indent_guides(&self) -> Option<bool> {
18337 self.show_indent_guides
18338 }
18339
18340 pub fn toggle_line_numbers(
18341 &mut self,
18342 _: &ToggleLineNumbers,
18343 _: &mut Window,
18344 cx: &mut Context<Self>,
18345 ) {
18346 let mut editor_settings = EditorSettings::get_global(cx).clone();
18347 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
18348 EditorSettings::override_global(editor_settings, cx);
18349 }
18350
18351 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
18352 if let Some(show_line_numbers) = self.show_line_numbers {
18353 return show_line_numbers;
18354 }
18355 EditorSettings::get_global(cx).gutter.line_numbers
18356 }
18357
18358 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
18359 self.use_relative_line_numbers
18360 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
18361 }
18362
18363 pub fn toggle_relative_line_numbers(
18364 &mut self,
18365 _: &ToggleRelativeLineNumbers,
18366 _: &mut Window,
18367 cx: &mut Context<Self>,
18368 ) {
18369 let is_relative = self.should_use_relative_line_numbers(cx);
18370 self.set_relative_line_number(Some(!is_relative), cx)
18371 }
18372
18373 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
18374 self.use_relative_line_numbers = is_relative;
18375 cx.notify();
18376 }
18377
18378 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
18379 self.show_gutter = show_gutter;
18380 cx.notify();
18381 }
18382
18383 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
18384 self.show_scrollbars = ScrollbarAxes {
18385 horizontal: show,
18386 vertical: show,
18387 };
18388 cx.notify();
18389 }
18390
18391 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18392 self.show_scrollbars.vertical = show;
18393 cx.notify();
18394 }
18395
18396 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18397 self.show_scrollbars.horizontal = show;
18398 cx.notify();
18399 }
18400
18401 pub fn set_minimap_visibility(
18402 &mut self,
18403 minimap_visibility: MinimapVisibility,
18404 window: &mut Window,
18405 cx: &mut Context<Self>,
18406 ) {
18407 if self.minimap_visibility != minimap_visibility {
18408 if minimap_visibility.visible() && self.minimap.is_none() {
18409 let minimap_settings = EditorSettings::get_global(cx).minimap;
18410 self.minimap =
18411 self.create_minimap(minimap_settings.with_show_override(), window, cx);
18412 }
18413 self.minimap_visibility = minimap_visibility;
18414 cx.notify();
18415 }
18416 }
18417
18418 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18419 self.set_show_scrollbars(false, cx);
18420 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
18421 }
18422
18423 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18424 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
18425 }
18426
18427 /// Normally the text in full mode and auto height editors is padded on the
18428 /// left side by roughly half a character width for improved hit testing.
18429 ///
18430 /// Use this method to disable this for cases where this is not wanted (e.g.
18431 /// if you want to align the editor text with some other text above or below)
18432 /// or if you want to add this padding to single-line editors.
18433 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
18434 self.offset_content = offset_content;
18435 cx.notify();
18436 }
18437
18438 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
18439 self.show_line_numbers = Some(show_line_numbers);
18440 cx.notify();
18441 }
18442
18443 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
18444 self.disable_expand_excerpt_buttons = true;
18445 cx.notify();
18446 }
18447
18448 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
18449 self.show_git_diff_gutter = Some(show_git_diff_gutter);
18450 cx.notify();
18451 }
18452
18453 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
18454 self.show_code_actions = Some(show_code_actions);
18455 cx.notify();
18456 }
18457
18458 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
18459 self.show_runnables = Some(show_runnables);
18460 cx.notify();
18461 }
18462
18463 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
18464 self.show_breakpoints = Some(show_breakpoints);
18465 cx.notify();
18466 }
18467
18468 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
18469 if self.display_map.read(cx).masked != masked {
18470 self.display_map.update(cx, |map, _| map.masked = masked);
18471 }
18472 cx.notify()
18473 }
18474
18475 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
18476 self.show_wrap_guides = Some(show_wrap_guides);
18477 cx.notify();
18478 }
18479
18480 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
18481 self.show_indent_guides = Some(show_indent_guides);
18482 cx.notify();
18483 }
18484
18485 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
18486 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
18487 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
18488 if let Some(dir) = file.abs_path(cx).parent() {
18489 return Some(dir.to_owned());
18490 }
18491 }
18492
18493 if let Some(project_path) = buffer.read(cx).project_path(cx) {
18494 return Some(project_path.path.to_path_buf());
18495 }
18496 }
18497
18498 None
18499 }
18500
18501 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
18502 self.active_excerpt(cx)?
18503 .1
18504 .read(cx)
18505 .file()
18506 .and_then(|f| f.as_local())
18507 }
18508
18509 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18510 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18511 let buffer = buffer.read(cx);
18512 if let Some(project_path) = buffer.project_path(cx) {
18513 let project = self.project.as_ref()?.read(cx);
18514 project.absolute_path(&project_path, cx)
18515 } else {
18516 buffer
18517 .file()
18518 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
18519 }
18520 })
18521 }
18522
18523 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18524 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18525 let project_path = buffer.read(cx).project_path(cx)?;
18526 let project = self.project.as_ref()?.read(cx);
18527 let entry = project.entry_for_path(&project_path, cx)?;
18528 let path = entry.path.to_path_buf();
18529 Some(path)
18530 })
18531 }
18532
18533 pub fn reveal_in_finder(
18534 &mut self,
18535 _: &RevealInFileManager,
18536 _window: &mut Window,
18537 cx: &mut Context<Self>,
18538 ) {
18539 if let Some(target) = self.target_file(cx) {
18540 cx.reveal_path(&target.abs_path(cx));
18541 }
18542 }
18543
18544 pub fn copy_path(
18545 &mut self,
18546 _: &zed_actions::workspace::CopyPath,
18547 _window: &mut Window,
18548 cx: &mut Context<Self>,
18549 ) {
18550 if let Some(path) = self.target_file_abs_path(cx) {
18551 if let Some(path) = path.to_str() {
18552 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18553 }
18554 }
18555 }
18556
18557 pub fn copy_relative_path(
18558 &mut self,
18559 _: &zed_actions::workspace::CopyRelativePath,
18560 _window: &mut Window,
18561 cx: &mut Context<Self>,
18562 ) {
18563 if let Some(path) = self.target_file_path(cx) {
18564 if let Some(path) = path.to_str() {
18565 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18566 }
18567 }
18568 }
18569
18570 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
18571 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
18572 buffer.read(cx).project_path(cx)
18573 } else {
18574 None
18575 }
18576 }
18577
18578 // Returns true if the editor handled a go-to-line request
18579 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
18580 maybe!({
18581 let breakpoint_store = self.breakpoint_store.as_ref()?;
18582
18583 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
18584 else {
18585 self.clear_row_highlights::<ActiveDebugLine>();
18586 return None;
18587 };
18588
18589 let position = active_stack_frame.position;
18590 let buffer_id = position.buffer_id?;
18591 let snapshot = self
18592 .project
18593 .as_ref()?
18594 .read(cx)
18595 .buffer_for_id(buffer_id, cx)?
18596 .read(cx)
18597 .snapshot();
18598
18599 let mut handled = false;
18600 for (id, ExcerptRange { context, .. }) in
18601 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
18602 {
18603 if context.start.cmp(&position, &snapshot).is_ge()
18604 || context.end.cmp(&position, &snapshot).is_lt()
18605 {
18606 continue;
18607 }
18608 let snapshot = self.buffer.read(cx).snapshot(cx);
18609 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
18610
18611 handled = true;
18612 self.clear_row_highlights::<ActiveDebugLine>();
18613
18614 self.go_to_line::<ActiveDebugLine>(
18615 multibuffer_anchor,
18616 Some(cx.theme().colors().editor_debugger_active_line_background),
18617 window,
18618 cx,
18619 );
18620
18621 cx.notify();
18622 }
18623
18624 handled.then_some(())
18625 })
18626 .is_some()
18627 }
18628
18629 pub fn copy_file_name_without_extension(
18630 &mut self,
18631 _: &CopyFileNameWithoutExtension,
18632 _: &mut Window,
18633 cx: &mut Context<Self>,
18634 ) {
18635 if let Some(file) = self.target_file(cx) {
18636 if let Some(file_stem) = file.path().file_stem() {
18637 if let Some(name) = file_stem.to_str() {
18638 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18639 }
18640 }
18641 }
18642 }
18643
18644 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
18645 if let Some(file) = self.target_file(cx) {
18646 if let Some(file_name) = file.path().file_name() {
18647 if let Some(name) = file_name.to_str() {
18648 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18649 }
18650 }
18651 }
18652 }
18653
18654 pub fn toggle_git_blame(
18655 &mut self,
18656 _: &::git::Blame,
18657 window: &mut Window,
18658 cx: &mut Context<Self>,
18659 ) {
18660 self.show_git_blame_gutter = !self.show_git_blame_gutter;
18661
18662 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
18663 self.start_git_blame(true, window, cx);
18664 }
18665
18666 cx.notify();
18667 }
18668
18669 pub fn toggle_git_blame_inline(
18670 &mut self,
18671 _: &ToggleGitBlameInline,
18672 window: &mut Window,
18673 cx: &mut Context<Self>,
18674 ) {
18675 self.toggle_git_blame_inline_internal(true, window, cx);
18676 cx.notify();
18677 }
18678
18679 pub fn open_git_blame_commit(
18680 &mut self,
18681 _: &OpenGitBlameCommit,
18682 window: &mut Window,
18683 cx: &mut Context<Self>,
18684 ) {
18685 self.open_git_blame_commit_internal(window, cx);
18686 }
18687
18688 fn open_git_blame_commit_internal(
18689 &mut self,
18690 window: &mut Window,
18691 cx: &mut Context<Self>,
18692 ) -> Option<()> {
18693 let blame = self.blame.as_ref()?;
18694 let snapshot = self.snapshot(window, cx);
18695 let cursor = self.selections.newest::<Point>(cx).head();
18696 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
18697 let blame_entry = blame
18698 .update(cx, |blame, cx| {
18699 blame
18700 .blame_for_rows(
18701 &[RowInfo {
18702 buffer_id: Some(buffer.remote_id()),
18703 buffer_row: Some(point.row),
18704 ..Default::default()
18705 }],
18706 cx,
18707 )
18708 .next()
18709 })
18710 .flatten()?;
18711 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
18712 let repo = blame.read(cx).repository(cx)?;
18713 let workspace = self.workspace()?.downgrade();
18714 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
18715 None
18716 }
18717
18718 pub fn git_blame_inline_enabled(&self) -> bool {
18719 self.git_blame_inline_enabled
18720 }
18721
18722 pub fn toggle_selection_menu(
18723 &mut self,
18724 _: &ToggleSelectionMenu,
18725 _: &mut Window,
18726 cx: &mut Context<Self>,
18727 ) {
18728 self.show_selection_menu = self
18729 .show_selection_menu
18730 .map(|show_selections_menu| !show_selections_menu)
18731 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
18732
18733 cx.notify();
18734 }
18735
18736 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
18737 self.show_selection_menu
18738 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
18739 }
18740
18741 fn start_git_blame(
18742 &mut self,
18743 user_triggered: bool,
18744 window: &mut Window,
18745 cx: &mut Context<Self>,
18746 ) {
18747 if let Some(project) = self.project.as_ref() {
18748 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
18749 return;
18750 };
18751
18752 if buffer.read(cx).file().is_none() {
18753 return;
18754 }
18755
18756 let focused = self.focus_handle(cx).contains_focused(window, cx);
18757
18758 let project = project.clone();
18759 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
18760 self.blame_subscription =
18761 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
18762 self.blame = Some(blame);
18763 }
18764 }
18765
18766 fn toggle_git_blame_inline_internal(
18767 &mut self,
18768 user_triggered: bool,
18769 window: &mut Window,
18770 cx: &mut Context<Self>,
18771 ) {
18772 if self.git_blame_inline_enabled {
18773 self.git_blame_inline_enabled = false;
18774 self.show_git_blame_inline = false;
18775 self.show_git_blame_inline_delay_task.take();
18776 } else {
18777 self.git_blame_inline_enabled = true;
18778 self.start_git_blame_inline(user_triggered, window, cx);
18779 }
18780
18781 cx.notify();
18782 }
18783
18784 fn start_git_blame_inline(
18785 &mut self,
18786 user_triggered: bool,
18787 window: &mut Window,
18788 cx: &mut Context<Self>,
18789 ) {
18790 self.start_git_blame(user_triggered, window, cx);
18791
18792 if ProjectSettings::get_global(cx)
18793 .git
18794 .inline_blame_delay()
18795 .is_some()
18796 {
18797 self.start_inline_blame_timer(window, cx);
18798 } else {
18799 self.show_git_blame_inline = true
18800 }
18801 }
18802
18803 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
18804 self.blame.as_ref()
18805 }
18806
18807 pub fn show_git_blame_gutter(&self) -> bool {
18808 self.show_git_blame_gutter
18809 }
18810
18811 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
18812 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
18813 }
18814
18815 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
18816 self.show_git_blame_inline
18817 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
18818 && !self.newest_selection_head_on_empty_line(cx)
18819 && self.has_blame_entries(cx)
18820 }
18821
18822 fn has_blame_entries(&self, cx: &App) -> bool {
18823 self.blame()
18824 .map_or(false, |blame| blame.read(cx).has_generated_entries())
18825 }
18826
18827 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
18828 let cursor_anchor = self.selections.newest_anchor().head();
18829
18830 let snapshot = self.buffer.read(cx).snapshot(cx);
18831 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
18832
18833 snapshot.line_len(buffer_row) == 0
18834 }
18835
18836 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
18837 let buffer_and_selection = maybe!({
18838 let selection = self.selections.newest::<Point>(cx);
18839 let selection_range = selection.range();
18840
18841 let multi_buffer = self.buffer().read(cx);
18842 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18843 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
18844
18845 let (buffer, range, _) = if selection.reversed {
18846 buffer_ranges.first()
18847 } else {
18848 buffer_ranges.last()
18849 }?;
18850
18851 let selection = text::ToPoint::to_point(&range.start, &buffer).row
18852 ..text::ToPoint::to_point(&range.end, &buffer).row;
18853 Some((
18854 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
18855 selection,
18856 ))
18857 });
18858
18859 let Some((buffer, selection)) = buffer_and_selection else {
18860 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
18861 };
18862
18863 let Some(project) = self.project.as_ref() else {
18864 return Task::ready(Err(anyhow!("editor does not have project")));
18865 };
18866
18867 project.update(cx, |project, cx| {
18868 project.get_permalink_to_line(&buffer, selection, cx)
18869 })
18870 }
18871
18872 pub fn copy_permalink_to_line(
18873 &mut self,
18874 _: &CopyPermalinkToLine,
18875 window: &mut Window,
18876 cx: &mut Context<Self>,
18877 ) {
18878 let permalink_task = self.get_permalink_to_line(cx);
18879 let workspace = self.workspace();
18880
18881 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18882 Ok(permalink) => {
18883 cx.update(|_, cx| {
18884 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
18885 })
18886 .ok();
18887 }
18888 Err(err) => {
18889 let message = format!("Failed to copy permalink: {err}");
18890
18891 anyhow::Result::<()>::Err(err).log_err();
18892
18893 if let Some(workspace) = workspace {
18894 workspace
18895 .update_in(cx, |workspace, _, cx| {
18896 struct CopyPermalinkToLine;
18897
18898 workspace.show_toast(
18899 Toast::new(
18900 NotificationId::unique::<CopyPermalinkToLine>(),
18901 message,
18902 ),
18903 cx,
18904 )
18905 })
18906 .ok();
18907 }
18908 }
18909 })
18910 .detach();
18911 }
18912
18913 pub fn copy_file_location(
18914 &mut self,
18915 _: &CopyFileLocation,
18916 _: &mut Window,
18917 cx: &mut Context<Self>,
18918 ) {
18919 let selection = self.selections.newest::<Point>(cx).start.row + 1;
18920 if let Some(file) = self.target_file(cx) {
18921 if let Some(path) = file.path().to_str() {
18922 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
18923 }
18924 }
18925 }
18926
18927 pub fn open_permalink_to_line(
18928 &mut self,
18929 _: &OpenPermalinkToLine,
18930 window: &mut Window,
18931 cx: &mut Context<Self>,
18932 ) {
18933 let permalink_task = self.get_permalink_to_line(cx);
18934 let workspace = self.workspace();
18935
18936 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18937 Ok(permalink) => {
18938 cx.update(|_, cx| {
18939 cx.open_url(permalink.as_ref());
18940 })
18941 .ok();
18942 }
18943 Err(err) => {
18944 let message = format!("Failed to open permalink: {err}");
18945
18946 anyhow::Result::<()>::Err(err).log_err();
18947
18948 if let Some(workspace) = workspace {
18949 workspace
18950 .update(cx, |workspace, cx| {
18951 struct OpenPermalinkToLine;
18952
18953 workspace.show_toast(
18954 Toast::new(
18955 NotificationId::unique::<OpenPermalinkToLine>(),
18956 message,
18957 ),
18958 cx,
18959 )
18960 })
18961 .ok();
18962 }
18963 }
18964 })
18965 .detach();
18966 }
18967
18968 pub fn insert_uuid_v4(
18969 &mut self,
18970 _: &InsertUuidV4,
18971 window: &mut Window,
18972 cx: &mut Context<Self>,
18973 ) {
18974 self.insert_uuid(UuidVersion::V4, window, cx);
18975 }
18976
18977 pub fn insert_uuid_v7(
18978 &mut self,
18979 _: &InsertUuidV7,
18980 window: &mut Window,
18981 cx: &mut Context<Self>,
18982 ) {
18983 self.insert_uuid(UuidVersion::V7, window, cx);
18984 }
18985
18986 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
18987 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18988 self.transact(window, cx, |this, window, cx| {
18989 let edits = this
18990 .selections
18991 .all::<Point>(cx)
18992 .into_iter()
18993 .map(|selection| {
18994 let uuid = match version {
18995 UuidVersion::V4 => uuid::Uuid::new_v4(),
18996 UuidVersion::V7 => uuid::Uuid::now_v7(),
18997 };
18998
18999 (selection.range(), uuid.to_string())
19000 });
19001 this.edit(edits, cx);
19002 this.refresh_inline_completion(true, false, window, cx);
19003 });
19004 }
19005
19006 pub fn open_selections_in_multibuffer(
19007 &mut self,
19008 _: &OpenSelectionsInMultibuffer,
19009 window: &mut Window,
19010 cx: &mut Context<Self>,
19011 ) {
19012 let multibuffer = self.buffer.read(cx);
19013
19014 let Some(buffer) = multibuffer.as_singleton() else {
19015 return;
19016 };
19017
19018 let Some(workspace) = self.workspace() else {
19019 return;
19020 };
19021
19022 let title = multibuffer.title(cx).to_string();
19023
19024 let locations = self
19025 .selections
19026 .all_anchors(cx)
19027 .into_iter()
19028 .map(|selection| Location {
19029 buffer: buffer.clone(),
19030 range: selection.start.text_anchor..selection.end.text_anchor,
19031 })
19032 .collect::<Vec<_>>();
19033
19034 cx.spawn_in(window, async move |_, cx| {
19035 workspace.update_in(cx, |workspace, window, cx| {
19036 Self::open_locations_in_multibuffer(
19037 workspace,
19038 locations,
19039 format!("Selections for '{title}'"),
19040 false,
19041 MultibufferSelectionMode::All,
19042 window,
19043 cx,
19044 );
19045 })
19046 })
19047 .detach();
19048 }
19049
19050 /// Adds a row highlight for the given range. If a row has multiple highlights, the
19051 /// last highlight added will be used.
19052 ///
19053 /// If the range ends at the beginning of a line, then that line will not be highlighted.
19054 pub fn highlight_rows<T: 'static>(
19055 &mut self,
19056 range: Range<Anchor>,
19057 color: Hsla,
19058 options: RowHighlightOptions,
19059 cx: &mut Context<Self>,
19060 ) {
19061 let snapshot = self.buffer().read(cx).snapshot(cx);
19062 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19063 let ix = row_highlights.binary_search_by(|highlight| {
19064 Ordering::Equal
19065 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
19066 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
19067 });
19068
19069 if let Err(mut ix) = ix {
19070 let index = post_inc(&mut self.highlight_order);
19071
19072 // If this range intersects with the preceding highlight, then merge it with
19073 // the preceding highlight. Otherwise insert a new highlight.
19074 let mut merged = false;
19075 if ix > 0 {
19076 let prev_highlight = &mut row_highlights[ix - 1];
19077 if prev_highlight
19078 .range
19079 .end
19080 .cmp(&range.start, &snapshot)
19081 .is_ge()
19082 {
19083 ix -= 1;
19084 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
19085 prev_highlight.range.end = range.end;
19086 }
19087 merged = true;
19088 prev_highlight.index = index;
19089 prev_highlight.color = color;
19090 prev_highlight.options = options;
19091 }
19092 }
19093
19094 if !merged {
19095 row_highlights.insert(
19096 ix,
19097 RowHighlight {
19098 range: range.clone(),
19099 index,
19100 color,
19101 options,
19102 type_id: TypeId::of::<T>(),
19103 },
19104 );
19105 }
19106
19107 // If any of the following highlights intersect with this one, merge them.
19108 while let Some(next_highlight) = row_highlights.get(ix + 1) {
19109 let highlight = &row_highlights[ix];
19110 if next_highlight
19111 .range
19112 .start
19113 .cmp(&highlight.range.end, &snapshot)
19114 .is_le()
19115 {
19116 if next_highlight
19117 .range
19118 .end
19119 .cmp(&highlight.range.end, &snapshot)
19120 .is_gt()
19121 {
19122 row_highlights[ix].range.end = next_highlight.range.end;
19123 }
19124 row_highlights.remove(ix + 1);
19125 } else {
19126 break;
19127 }
19128 }
19129 }
19130 }
19131
19132 /// Remove any highlighted row ranges of the given type that intersect the
19133 /// given ranges.
19134 pub fn remove_highlighted_rows<T: 'static>(
19135 &mut self,
19136 ranges_to_remove: Vec<Range<Anchor>>,
19137 cx: &mut Context<Self>,
19138 ) {
19139 let snapshot = self.buffer().read(cx).snapshot(cx);
19140 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19141 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19142 row_highlights.retain(|highlight| {
19143 while let Some(range_to_remove) = ranges_to_remove.peek() {
19144 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
19145 Ordering::Less | Ordering::Equal => {
19146 ranges_to_remove.next();
19147 }
19148 Ordering::Greater => {
19149 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
19150 Ordering::Less | Ordering::Equal => {
19151 return false;
19152 }
19153 Ordering::Greater => break,
19154 }
19155 }
19156 }
19157 }
19158
19159 true
19160 })
19161 }
19162
19163 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
19164 pub fn clear_row_highlights<T: 'static>(&mut self) {
19165 self.highlighted_rows.remove(&TypeId::of::<T>());
19166 }
19167
19168 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
19169 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
19170 self.highlighted_rows
19171 .get(&TypeId::of::<T>())
19172 .map_or(&[] as &[_], |vec| vec.as_slice())
19173 .iter()
19174 .map(|highlight| (highlight.range.clone(), highlight.color))
19175 }
19176
19177 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
19178 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
19179 /// Allows to ignore certain kinds of highlights.
19180 pub fn highlighted_display_rows(
19181 &self,
19182 window: &mut Window,
19183 cx: &mut App,
19184 ) -> BTreeMap<DisplayRow, LineHighlight> {
19185 let snapshot = self.snapshot(window, cx);
19186 let mut used_highlight_orders = HashMap::default();
19187 self.highlighted_rows
19188 .iter()
19189 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
19190 .fold(
19191 BTreeMap::<DisplayRow, LineHighlight>::new(),
19192 |mut unique_rows, highlight| {
19193 let start = highlight.range.start.to_display_point(&snapshot);
19194 let end = highlight.range.end.to_display_point(&snapshot);
19195 let start_row = start.row().0;
19196 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
19197 && end.column() == 0
19198 {
19199 end.row().0.saturating_sub(1)
19200 } else {
19201 end.row().0
19202 };
19203 for row in start_row..=end_row {
19204 let used_index =
19205 used_highlight_orders.entry(row).or_insert(highlight.index);
19206 if highlight.index >= *used_index {
19207 *used_index = highlight.index;
19208 unique_rows.insert(
19209 DisplayRow(row),
19210 LineHighlight {
19211 include_gutter: highlight.options.include_gutter,
19212 border: None,
19213 background: highlight.color.into(),
19214 type_id: Some(highlight.type_id),
19215 },
19216 );
19217 }
19218 }
19219 unique_rows
19220 },
19221 )
19222 }
19223
19224 pub fn highlighted_display_row_for_autoscroll(
19225 &self,
19226 snapshot: &DisplaySnapshot,
19227 ) -> Option<DisplayRow> {
19228 self.highlighted_rows
19229 .values()
19230 .flat_map(|highlighted_rows| highlighted_rows.iter())
19231 .filter_map(|highlight| {
19232 if highlight.options.autoscroll {
19233 Some(highlight.range.start.to_display_point(snapshot).row())
19234 } else {
19235 None
19236 }
19237 })
19238 .min()
19239 }
19240
19241 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
19242 self.highlight_background::<SearchWithinRange>(
19243 ranges,
19244 |colors| colors.colors().editor_document_highlight_read_background,
19245 cx,
19246 )
19247 }
19248
19249 pub fn set_breadcrumb_header(&mut self, new_header: String) {
19250 self.breadcrumb_header = Some(new_header);
19251 }
19252
19253 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
19254 self.clear_background_highlights::<SearchWithinRange>(cx);
19255 }
19256
19257 pub fn highlight_background<T: 'static>(
19258 &mut self,
19259 ranges: &[Range<Anchor>],
19260 color_fetcher: fn(&Theme) -> Hsla,
19261 cx: &mut Context<Self>,
19262 ) {
19263 self.background_highlights.insert(
19264 HighlightKey::Type(TypeId::of::<T>()),
19265 (color_fetcher, Arc::from(ranges)),
19266 );
19267 self.scrollbar_marker_state.dirty = true;
19268 cx.notify();
19269 }
19270
19271 pub fn highlight_background_key<T: 'static>(
19272 &mut self,
19273 key: usize,
19274 ranges: &[Range<Anchor>],
19275 color_fetcher: fn(&Theme) -> Hsla,
19276 cx: &mut Context<Self>,
19277 ) {
19278 self.background_highlights.insert(
19279 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19280 (color_fetcher, Arc::from(ranges)),
19281 );
19282 self.scrollbar_marker_state.dirty = true;
19283 cx.notify();
19284 }
19285
19286 pub fn clear_background_highlights<T: 'static>(
19287 &mut self,
19288 cx: &mut Context<Self>,
19289 ) -> Option<BackgroundHighlight> {
19290 let text_highlights = self
19291 .background_highlights
19292 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
19293 if !text_highlights.1.is_empty() {
19294 self.scrollbar_marker_state.dirty = true;
19295 cx.notify();
19296 }
19297 Some(text_highlights)
19298 }
19299
19300 pub fn highlight_gutter<T: 'static>(
19301 &mut self,
19302 ranges: impl Into<Vec<Range<Anchor>>>,
19303 color_fetcher: fn(&App) -> Hsla,
19304 cx: &mut Context<Self>,
19305 ) {
19306 self.gutter_highlights
19307 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
19308 cx.notify();
19309 }
19310
19311 pub fn clear_gutter_highlights<T: 'static>(
19312 &mut self,
19313 cx: &mut Context<Self>,
19314 ) -> Option<GutterHighlight> {
19315 cx.notify();
19316 self.gutter_highlights.remove(&TypeId::of::<T>())
19317 }
19318
19319 pub fn insert_gutter_highlight<T: 'static>(
19320 &mut self,
19321 range: Range<Anchor>,
19322 color_fetcher: fn(&App) -> Hsla,
19323 cx: &mut Context<Self>,
19324 ) {
19325 let snapshot = self.buffer().read(cx).snapshot(cx);
19326 let mut highlights = self
19327 .gutter_highlights
19328 .remove(&TypeId::of::<T>())
19329 .map(|(_, highlights)| highlights)
19330 .unwrap_or_default();
19331 let ix = highlights.binary_search_by(|highlight| {
19332 Ordering::Equal
19333 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
19334 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
19335 });
19336 if let Err(ix) = ix {
19337 highlights.insert(ix, range);
19338 }
19339 self.gutter_highlights
19340 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
19341 }
19342
19343 pub fn remove_gutter_highlights<T: 'static>(
19344 &mut self,
19345 ranges_to_remove: Vec<Range<Anchor>>,
19346 cx: &mut Context<Self>,
19347 ) {
19348 let snapshot = self.buffer().read(cx).snapshot(cx);
19349 let Some((color_fetcher, mut gutter_highlights)) =
19350 self.gutter_highlights.remove(&TypeId::of::<T>())
19351 else {
19352 return;
19353 };
19354 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19355 gutter_highlights.retain(|highlight| {
19356 while let Some(range_to_remove) = ranges_to_remove.peek() {
19357 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
19358 Ordering::Less | Ordering::Equal => {
19359 ranges_to_remove.next();
19360 }
19361 Ordering::Greater => {
19362 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
19363 Ordering::Less | Ordering::Equal => {
19364 return false;
19365 }
19366 Ordering::Greater => break,
19367 }
19368 }
19369 }
19370 }
19371
19372 true
19373 });
19374 self.gutter_highlights
19375 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
19376 }
19377
19378 #[cfg(feature = "test-support")]
19379 pub fn all_text_highlights(
19380 &self,
19381 window: &mut Window,
19382 cx: &mut Context<Self>,
19383 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
19384 let snapshot = self.snapshot(window, cx);
19385 self.display_map.update(cx, |display_map, _| {
19386 display_map
19387 .all_text_highlights()
19388 .map(|highlight| {
19389 let (style, ranges) = highlight.as_ref();
19390 (
19391 *style,
19392 ranges
19393 .iter()
19394 .map(|range| range.clone().to_display_points(&snapshot))
19395 .collect(),
19396 )
19397 })
19398 .collect()
19399 })
19400 }
19401
19402 #[cfg(feature = "test-support")]
19403 pub fn all_text_background_highlights(
19404 &self,
19405 window: &mut Window,
19406 cx: &mut Context<Self>,
19407 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19408 let snapshot = self.snapshot(window, cx);
19409 let buffer = &snapshot.buffer_snapshot;
19410 let start = buffer.anchor_before(0);
19411 let end = buffer.anchor_after(buffer.len());
19412 self.background_highlights_in_range(start..end, &snapshot, cx.theme())
19413 }
19414
19415 #[cfg(feature = "test-support")]
19416 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
19417 let snapshot = self.buffer().read(cx).snapshot(cx);
19418
19419 let highlights = self
19420 .background_highlights
19421 .get(&HighlightKey::Type(TypeId::of::<
19422 items::BufferSearchHighlights,
19423 >()));
19424
19425 if let Some((_color, ranges)) = highlights {
19426 ranges
19427 .iter()
19428 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
19429 .collect_vec()
19430 } else {
19431 vec![]
19432 }
19433 }
19434
19435 fn document_highlights_for_position<'a>(
19436 &'a self,
19437 position: Anchor,
19438 buffer: &'a MultiBufferSnapshot,
19439 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
19440 let read_highlights = self
19441 .background_highlights
19442 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
19443 .map(|h| &h.1);
19444 let write_highlights = self
19445 .background_highlights
19446 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
19447 .map(|h| &h.1);
19448 let left_position = position.bias_left(buffer);
19449 let right_position = position.bias_right(buffer);
19450 read_highlights
19451 .into_iter()
19452 .chain(write_highlights)
19453 .flat_map(move |ranges| {
19454 let start_ix = match ranges.binary_search_by(|probe| {
19455 let cmp = probe.end.cmp(&left_position, buffer);
19456 if cmp.is_ge() {
19457 Ordering::Greater
19458 } else {
19459 Ordering::Less
19460 }
19461 }) {
19462 Ok(i) | Err(i) => i,
19463 };
19464
19465 ranges[start_ix..]
19466 .iter()
19467 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
19468 })
19469 }
19470
19471 pub fn has_background_highlights<T: 'static>(&self) -> bool {
19472 self.background_highlights
19473 .get(&HighlightKey::Type(TypeId::of::<T>()))
19474 .map_or(false, |(_, highlights)| !highlights.is_empty())
19475 }
19476
19477 pub fn background_highlights_in_range(
19478 &self,
19479 search_range: Range<Anchor>,
19480 display_snapshot: &DisplaySnapshot,
19481 theme: &Theme,
19482 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19483 let mut results = Vec::new();
19484 for (color_fetcher, ranges) in self.background_highlights.values() {
19485 let color = color_fetcher(theme);
19486 let start_ix = match ranges.binary_search_by(|probe| {
19487 let cmp = probe
19488 .end
19489 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19490 if cmp.is_gt() {
19491 Ordering::Greater
19492 } else {
19493 Ordering::Less
19494 }
19495 }) {
19496 Ok(i) | Err(i) => i,
19497 };
19498 for range in &ranges[start_ix..] {
19499 if range
19500 .start
19501 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19502 .is_ge()
19503 {
19504 break;
19505 }
19506
19507 let start = range.start.to_display_point(display_snapshot);
19508 let end = range.end.to_display_point(display_snapshot);
19509 results.push((start..end, color))
19510 }
19511 }
19512 results
19513 }
19514
19515 pub fn background_highlight_row_ranges<T: 'static>(
19516 &self,
19517 search_range: Range<Anchor>,
19518 display_snapshot: &DisplaySnapshot,
19519 count: usize,
19520 ) -> Vec<RangeInclusive<DisplayPoint>> {
19521 let mut results = Vec::new();
19522 let Some((_, ranges)) = self
19523 .background_highlights
19524 .get(&HighlightKey::Type(TypeId::of::<T>()))
19525 else {
19526 return vec![];
19527 };
19528
19529 let start_ix = match ranges.binary_search_by(|probe| {
19530 let cmp = probe
19531 .end
19532 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19533 if cmp.is_gt() {
19534 Ordering::Greater
19535 } else {
19536 Ordering::Less
19537 }
19538 }) {
19539 Ok(i) | Err(i) => i,
19540 };
19541 let mut push_region = |start: Option<Point>, end: Option<Point>| {
19542 if let (Some(start_display), Some(end_display)) = (start, end) {
19543 results.push(
19544 start_display.to_display_point(display_snapshot)
19545 ..=end_display.to_display_point(display_snapshot),
19546 );
19547 }
19548 };
19549 let mut start_row: Option<Point> = None;
19550 let mut end_row: Option<Point> = None;
19551 if ranges.len() > count {
19552 return Vec::new();
19553 }
19554 for range in &ranges[start_ix..] {
19555 if range
19556 .start
19557 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19558 .is_ge()
19559 {
19560 break;
19561 }
19562 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
19563 if let Some(current_row) = &end_row {
19564 if end.row == current_row.row {
19565 continue;
19566 }
19567 }
19568 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
19569 if start_row.is_none() {
19570 assert_eq!(end_row, None);
19571 start_row = Some(start);
19572 end_row = Some(end);
19573 continue;
19574 }
19575 if let Some(current_end) = end_row.as_mut() {
19576 if start.row > current_end.row + 1 {
19577 push_region(start_row, end_row);
19578 start_row = Some(start);
19579 end_row = Some(end);
19580 } else {
19581 // Merge two hunks.
19582 *current_end = end;
19583 }
19584 } else {
19585 unreachable!();
19586 }
19587 }
19588 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
19589 push_region(start_row, end_row);
19590 results
19591 }
19592
19593 pub fn gutter_highlights_in_range(
19594 &self,
19595 search_range: Range<Anchor>,
19596 display_snapshot: &DisplaySnapshot,
19597 cx: &App,
19598 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19599 let mut results = Vec::new();
19600 for (color_fetcher, ranges) in self.gutter_highlights.values() {
19601 let color = color_fetcher(cx);
19602 let start_ix = match ranges.binary_search_by(|probe| {
19603 let cmp = probe
19604 .end
19605 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19606 if cmp.is_gt() {
19607 Ordering::Greater
19608 } else {
19609 Ordering::Less
19610 }
19611 }) {
19612 Ok(i) | Err(i) => i,
19613 };
19614 for range in &ranges[start_ix..] {
19615 if range
19616 .start
19617 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19618 .is_ge()
19619 {
19620 break;
19621 }
19622
19623 let start = range.start.to_display_point(display_snapshot);
19624 let end = range.end.to_display_point(display_snapshot);
19625 results.push((start..end, color))
19626 }
19627 }
19628 results
19629 }
19630
19631 /// Get the text ranges corresponding to the redaction query
19632 pub fn redacted_ranges(
19633 &self,
19634 search_range: Range<Anchor>,
19635 display_snapshot: &DisplaySnapshot,
19636 cx: &App,
19637 ) -> Vec<Range<DisplayPoint>> {
19638 display_snapshot
19639 .buffer_snapshot
19640 .redacted_ranges(search_range, |file| {
19641 if let Some(file) = file {
19642 file.is_private()
19643 && EditorSettings::get(
19644 Some(SettingsLocation {
19645 worktree_id: file.worktree_id(cx),
19646 path: file.path().as_ref(),
19647 }),
19648 cx,
19649 )
19650 .redact_private_values
19651 } else {
19652 false
19653 }
19654 })
19655 .map(|range| {
19656 range.start.to_display_point(display_snapshot)
19657 ..range.end.to_display_point(display_snapshot)
19658 })
19659 .collect()
19660 }
19661
19662 pub fn highlight_text_key<T: 'static>(
19663 &mut self,
19664 key: usize,
19665 ranges: Vec<Range<Anchor>>,
19666 style: HighlightStyle,
19667 cx: &mut Context<Self>,
19668 ) {
19669 self.display_map.update(cx, |map, _| {
19670 map.highlight_text(
19671 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19672 ranges,
19673 style,
19674 );
19675 });
19676 cx.notify();
19677 }
19678
19679 pub fn highlight_text<T: 'static>(
19680 &mut self,
19681 ranges: Vec<Range<Anchor>>,
19682 style: HighlightStyle,
19683 cx: &mut Context<Self>,
19684 ) {
19685 self.display_map.update(cx, |map, _| {
19686 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
19687 });
19688 cx.notify();
19689 }
19690
19691 pub(crate) fn highlight_inlays<T: 'static>(
19692 &mut self,
19693 highlights: Vec<InlayHighlight>,
19694 style: HighlightStyle,
19695 cx: &mut Context<Self>,
19696 ) {
19697 self.display_map.update(cx, |map, _| {
19698 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
19699 });
19700 cx.notify();
19701 }
19702
19703 pub fn text_highlights<'a, T: 'static>(
19704 &'a self,
19705 cx: &'a App,
19706 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
19707 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
19708 }
19709
19710 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
19711 let cleared = self
19712 .display_map
19713 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
19714 if cleared {
19715 cx.notify();
19716 }
19717 }
19718
19719 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
19720 (self.read_only(cx) || self.blink_manager.read(cx).visible())
19721 && self.focus_handle.is_focused(window)
19722 }
19723
19724 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
19725 self.show_cursor_when_unfocused = is_enabled;
19726 cx.notify();
19727 }
19728
19729 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
19730 cx.notify();
19731 }
19732
19733 fn on_debug_session_event(
19734 &mut self,
19735 _session: Entity<Session>,
19736 event: &SessionEvent,
19737 cx: &mut Context<Self>,
19738 ) {
19739 match event {
19740 SessionEvent::InvalidateInlineValue => {
19741 self.refresh_inline_values(cx);
19742 }
19743 _ => {}
19744 }
19745 }
19746
19747 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
19748 let Some(project) = self.project.clone() else {
19749 return;
19750 };
19751
19752 if !self.inline_value_cache.enabled {
19753 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
19754 self.splice_inlays(&inlays, Vec::new(), cx);
19755 return;
19756 }
19757
19758 let current_execution_position = self
19759 .highlighted_rows
19760 .get(&TypeId::of::<ActiveDebugLine>())
19761 .and_then(|lines| lines.last().map(|line| line.range.end));
19762
19763 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
19764 let inline_values = editor
19765 .update(cx, |editor, cx| {
19766 let Some(current_execution_position) = current_execution_position else {
19767 return Some(Task::ready(Ok(Vec::new())));
19768 };
19769
19770 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
19771 let snapshot = buffer.snapshot(cx);
19772
19773 let excerpt = snapshot.excerpt_containing(
19774 current_execution_position..current_execution_position,
19775 )?;
19776
19777 editor.buffer.read(cx).buffer(excerpt.buffer_id())
19778 })?;
19779
19780 let range =
19781 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
19782
19783 project.inline_values(buffer, range, cx)
19784 })
19785 .ok()
19786 .flatten()?
19787 .await
19788 .context("refreshing debugger inlays")
19789 .log_err()?;
19790
19791 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
19792
19793 for (buffer_id, inline_value) in inline_values
19794 .into_iter()
19795 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
19796 {
19797 buffer_inline_values
19798 .entry(buffer_id)
19799 .or_default()
19800 .push(inline_value);
19801 }
19802
19803 editor
19804 .update(cx, |editor, cx| {
19805 let snapshot = editor.buffer.read(cx).snapshot(cx);
19806 let mut new_inlays = Vec::default();
19807
19808 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
19809 let buffer_id = buffer_snapshot.remote_id();
19810 buffer_inline_values
19811 .get(&buffer_id)
19812 .into_iter()
19813 .flatten()
19814 .for_each(|hint| {
19815 let inlay = Inlay::debugger(
19816 post_inc(&mut editor.next_inlay_id),
19817 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
19818 hint.text(),
19819 );
19820 if !inlay.text.chars().contains(&'\n') {
19821 new_inlays.push(inlay);
19822 }
19823 });
19824 }
19825
19826 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
19827 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
19828
19829 editor.splice_inlays(&inlay_ids, new_inlays, cx);
19830 })
19831 .ok()?;
19832 Some(())
19833 });
19834 }
19835
19836 fn on_buffer_event(
19837 &mut self,
19838 multibuffer: &Entity<MultiBuffer>,
19839 event: &multi_buffer::Event,
19840 window: &mut Window,
19841 cx: &mut Context<Self>,
19842 ) {
19843 match event {
19844 multi_buffer::Event::Edited {
19845 singleton_buffer_edited,
19846 edited_buffer,
19847 } => {
19848 self.scrollbar_marker_state.dirty = true;
19849 self.active_indent_guides_state.dirty = true;
19850 self.refresh_active_diagnostics(cx);
19851 self.refresh_code_actions(window, cx);
19852 self.refresh_selected_text_highlights(true, window, cx);
19853 self.refresh_single_line_folds(window, cx);
19854 refresh_matching_bracket_highlights(self, window, cx);
19855 if self.has_active_inline_completion() {
19856 self.update_visible_inline_completion(window, cx);
19857 }
19858 if let Some(project) = self.project.as_ref() {
19859 if let Some(edited_buffer) = edited_buffer {
19860 project.update(cx, |project, cx| {
19861 self.registered_buffers
19862 .entry(edited_buffer.read(cx).remote_id())
19863 .or_insert_with(|| {
19864 project
19865 .register_buffer_with_language_servers(&edited_buffer, cx)
19866 });
19867 });
19868 }
19869 }
19870 cx.emit(EditorEvent::BufferEdited);
19871 cx.emit(SearchEvent::MatchesInvalidated);
19872
19873 if let Some(buffer) = edited_buffer {
19874 self.update_lsp_data(false, Some(buffer.read(cx).remote_id()), window, cx);
19875 }
19876
19877 if *singleton_buffer_edited {
19878 if let Some(buffer) = edited_buffer {
19879 if buffer.read(cx).file().is_none() {
19880 cx.emit(EditorEvent::TitleChanged);
19881 }
19882 }
19883 if let Some(project) = &self.project {
19884 #[allow(clippy::mutable_key_type)]
19885 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
19886 multibuffer
19887 .all_buffers()
19888 .into_iter()
19889 .filter_map(|buffer| {
19890 buffer.update(cx, |buffer, cx| {
19891 let language = buffer.language()?;
19892 let should_discard = project.update(cx, |project, cx| {
19893 project.is_local()
19894 && !project.has_language_servers_for(buffer, cx)
19895 });
19896 should_discard.not().then_some(language.clone())
19897 })
19898 })
19899 .collect::<HashSet<_>>()
19900 });
19901 if !languages_affected.is_empty() {
19902 self.refresh_inlay_hints(
19903 InlayHintRefreshReason::BufferEdited(languages_affected),
19904 cx,
19905 );
19906 }
19907 }
19908 }
19909
19910 let Some(project) = &self.project else { return };
19911 let (telemetry, is_via_ssh) = {
19912 let project = project.read(cx);
19913 let telemetry = project.client().telemetry().clone();
19914 let is_via_ssh = project.is_via_ssh();
19915 (telemetry, is_via_ssh)
19916 };
19917 refresh_linked_ranges(self, window, cx);
19918 telemetry.log_edit_event("editor", is_via_ssh);
19919 }
19920 multi_buffer::Event::ExcerptsAdded {
19921 buffer,
19922 predecessor,
19923 excerpts,
19924 } => {
19925 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19926 let buffer_id = buffer.read(cx).remote_id();
19927 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
19928 if let Some(project) = &self.project {
19929 update_uncommitted_diff_for_buffer(
19930 cx.entity(),
19931 project,
19932 [buffer.clone()],
19933 self.buffer.clone(),
19934 cx,
19935 )
19936 .detach();
19937 }
19938 }
19939 self.update_lsp_data(false, Some(buffer_id), window, cx);
19940 cx.emit(EditorEvent::ExcerptsAdded {
19941 buffer: buffer.clone(),
19942 predecessor: *predecessor,
19943 excerpts: excerpts.clone(),
19944 });
19945 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
19946 }
19947 multi_buffer::Event::ExcerptsRemoved {
19948 ids,
19949 removed_buffer_ids,
19950 } => {
19951 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
19952 let buffer = self.buffer.read(cx);
19953 self.registered_buffers
19954 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
19955 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19956 cx.emit(EditorEvent::ExcerptsRemoved {
19957 ids: ids.clone(),
19958 removed_buffer_ids: removed_buffer_ids.clone(),
19959 });
19960 }
19961 multi_buffer::Event::ExcerptsEdited {
19962 excerpt_ids,
19963 buffer_ids,
19964 } => {
19965 self.display_map.update(cx, |map, cx| {
19966 map.unfold_buffers(buffer_ids.iter().copied(), cx)
19967 });
19968 cx.emit(EditorEvent::ExcerptsEdited {
19969 ids: excerpt_ids.clone(),
19970 });
19971 }
19972 multi_buffer::Event::ExcerptsExpanded { ids } => {
19973 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
19974 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
19975 }
19976 multi_buffer::Event::Reparsed(buffer_id) => {
19977 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19978 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19979
19980 cx.emit(EditorEvent::Reparsed(*buffer_id));
19981 }
19982 multi_buffer::Event::DiffHunksToggled => {
19983 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19984 }
19985 multi_buffer::Event::LanguageChanged(buffer_id) => {
19986 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
19987 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19988 cx.emit(EditorEvent::Reparsed(*buffer_id));
19989 cx.notify();
19990 }
19991 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
19992 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
19993 multi_buffer::Event::FileHandleChanged
19994 | multi_buffer::Event::Reloaded
19995 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
19996 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
19997 multi_buffer::Event::DiagnosticsUpdated => {
19998 self.update_diagnostics_state(window, cx);
19999 }
20000 _ => {}
20001 };
20002 }
20003
20004 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
20005 if !self.diagnostics_enabled() {
20006 return;
20007 }
20008 self.refresh_active_diagnostics(cx);
20009 self.refresh_inline_diagnostics(true, window, cx);
20010 self.scrollbar_marker_state.dirty = true;
20011 cx.notify();
20012 }
20013
20014 pub fn start_temporary_diff_override(&mut self) {
20015 self.load_diff_task.take();
20016 self.temporary_diff_override = true;
20017 }
20018
20019 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
20020 self.temporary_diff_override = false;
20021 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
20022 self.buffer.update(cx, |buffer, cx| {
20023 buffer.set_all_diff_hunks_collapsed(cx);
20024 });
20025
20026 if let Some(project) = self.project.clone() {
20027 self.load_diff_task = Some(
20028 update_uncommitted_diff_for_buffer(
20029 cx.entity(),
20030 &project,
20031 self.buffer.read(cx).all_buffers(),
20032 self.buffer.clone(),
20033 cx,
20034 )
20035 .shared(),
20036 );
20037 }
20038 }
20039
20040 fn on_display_map_changed(
20041 &mut self,
20042 _: Entity<DisplayMap>,
20043 _: &mut Window,
20044 cx: &mut Context<Self>,
20045 ) {
20046 cx.notify();
20047 }
20048
20049 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20050 if self.diagnostics_enabled() {
20051 let new_severity = EditorSettings::get_global(cx)
20052 .diagnostics_max_severity
20053 .unwrap_or(DiagnosticSeverity::Hint);
20054 self.set_max_diagnostics_severity(new_severity, cx);
20055 }
20056 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20057 self.update_edit_prediction_settings(cx);
20058 self.refresh_inline_completion(true, false, window, cx);
20059 self.refresh_inline_values(cx);
20060 self.refresh_inlay_hints(
20061 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
20062 self.selections.newest_anchor().head(),
20063 &self.buffer.read(cx).snapshot(cx),
20064 cx,
20065 )),
20066 cx,
20067 );
20068
20069 let old_cursor_shape = self.cursor_shape;
20070
20071 {
20072 let editor_settings = EditorSettings::get_global(cx);
20073 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
20074 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
20075 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
20076 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
20077 }
20078
20079 if old_cursor_shape != self.cursor_shape {
20080 cx.emit(EditorEvent::CursorShapeChanged);
20081 }
20082
20083 let project_settings = ProjectSettings::get_global(cx);
20084 self.serialize_dirty_buffers =
20085 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
20086
20087 if self.mode.is_full() {
20088 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
20089 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
20090 if self.show_inline_diagnostics != show_inline_diagnostics {
20091 self.show_inline_diagnostics = show_inline_diagnostics;
20092 self.refresh_inline_diagnostics(false, window, cx);
20093 }
20094
20095 if self.git_blame_inline_enabled != inline_blame_enabled {
20096 self.toggle_git_blame_inline_internal(false, window, cx);
20097 }
20098
20099 let minimap_settings = EditorSettings::get_global(cx).minimap;
20100 if self.minimap_visibility != MinimapVisibility::Disabled {
20101 if self.minimap_visibility.settings_visibility()
20102 != minimap_settings.minimap_enabled()
20103 {
20104 self.set_minimap_visibility(
20105 MinimapVisibility::for_mode(self.mode(), cx),
20106 window,
20107 cx,
20108 );
20109 } else if let Some(minimap_entity) = self.minimap.as_ref() {
20110 minimap_entity.update(cx, |minimap_editor, cx| {
20111 minimap_editor.update_minimap_configuration(minimap_settings, cx)
20112 })
20113 }
20114 }
20115 }
20116
20117 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
20118 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
20119 }) {
20120 if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
20121 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
20122 }
20123 self.refresh_colors(false, None, window, cx);
20124 }
20125
20126 cx.notify();
20127 }
20128
20129 pub fn set_searchable(&mut self, searchable: bool) {
20130 self.searchable = searchable;
20131 }
20132
20133 pub fn searchable(&self) -> bool {
20134 self.searchable
20135 }
20136
20137 fn open_proposed_changes_editor(
20138 &mut self,
20139 _: &OpenProposedChangesEditor,
20140 window: &mut Window,
20141 cx: &mut Context<Self>,
20142 ) {
20143 let Some(workspace) = self.workspace() else {
20144 cx.propagate();
20145 return;
20146 };
20147
20148 let selections = self.selections.all::<usize>(cx);
20149 let multi_buffer = self.buffer.read(cx);
20150 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20151 let mut new_selections_by_buffer = HashMap::default();
20152 for selection in selections {
20153 for (buffer, range, _) in
20154 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
20155 {
20156 let mut range = range.to_point(buffer);
20157 range.start.column = 0;
20158 range.end.column = buffer.line_len(range.end.row);
20159 new_selections_by_buffer
20160 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
20161 .or_insert(Vec::new())
20162 .push(range)
20163 }
20164 }
20165
20166 let proposed_changes_buffers = new_selections_by_buffer
20167 .into_iter()
20168 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
20169 .collect::<Vec<_>>();
20170 let proposed_changes_editor = cx.new(|cx| {
20171 ProposedChangesEditor::new(
20172 "Proposed changes",
20173 proposed_changes_buffers,
20174 self.project.clone(),
20175 window,
20176 cx,
20177 )
20178 });
20179
20180 window.defer(cx, move |window, cx| {
20181 workspace.update(cx, |workspace, cx| {
20182 workspace.active_pane().update(cx, |pane, cx| {
20183 pane.add_item(
20184 Box::new(proposed_changes_editor),
20185 true,
20186 true,
20187 None,
20188 window,
20189 cx,
20190 );
20191 });
20192 });
20193 });
20194 }
20195
20196 pub fn open_excerpts_in_split(
20197 &mut self,
20198 _: &OpenExcerptsSplit,
20199 window: &mut Window,
20200 cx: &mut Context<Self>,
20201 ) {
20202 self.open_excerpts_common(None, true, window, cx)
20203 }
20204
20205 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
20206 self.open_excerpts_common(None, false, window, cx)
20207 }
20208
20209 fn open_excerpts_common(
20210 &mut self,
20211 jump_data: Option<JumpData>,
20212 split: bool,
20213 window: &mut Window,
20214 cx: &mut Context<Self>,
20215 ) {
20216 let Some(workspace) = self.workspace() else {
20217 cx.propagate();
20218 return;
20219 };
20220
20221 if self.buffer.read(cx).is_singleton() {
20222 cx.propagate();
20223 return;
20224 }
20225
20226 let mut new_selections_by_buffer = HashMap::default();
20227 match &jump_data {
20228 Some(JumpData::MultiBufferPoint {
20229 excerpt_id,
20230 position,
20231 anchor,
20232 line_offset_from_top,
20233 }) => {
20234 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20235 if let Some(buffer) = multi_buffer_snapshot
20236 .buffer_id_for_excerpt(*excerpt_id)
20237 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
20238 {
20239 let buffer_snapshot = buffer.read(cx).snapshot();
20240 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
20241 language::ToPoint::to_point(anchor, &buffer_snapshot)
20242 } else {
20243 buffer_snapshot.clip_point(*position, Bias::Left)
20244 };
20245 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
20246 new_selections_by_buffer.insert(
20247 buffer,
20248 (
20249 vec![jump_to_offset..jump_to_offset],
20250 Some(*line_offset_from_top),
20251 ),
20252 );
20253 }
20254 }
20255 Some(JumpData::MultiBufferRow {
20256 row,
20257 line_offset_from_top,
20258 }) => {
20259 let point = MultiBufferPoint::new(row.0, 0);
20260 if let Some((buffer, buffer_point, _)) =
20261 self.buffer.read(cx).point_to_buffer_point(point, cx)
20262 {
20263 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
20264 new_selections_by_buffer
20265 .entry(buffer)
20266 .or_insert((Vec::new(), Some(*line_offset_from_top)))
20267 .0
20268 .push(buffer_offset..buffer_offset)
20269 }
20270 }
20271 None => {
20272 let selections = self.selections.all::<usize>(cx);
20273 let multi_buffer = self.buffer.read(cx);
20274 for selection in selections {
20275 for (snapshot, range, _, anchor) in multi_buffer
20276 .snapshot(cx)
20277 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
20278 {
20279 if let Some(anchor) = anchor {
20280 // selection is in a deleted hunk
20281 let Some(buffer_id) = anchor.buffer_id else {
20282 continue;
20283 };
20284 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
20285 continue;
20286 };
20287 let offset = text::ToOffset::to_offset(
20288 &anchor.text_anchor,
20289 &buffer_handle.read(cx).snapshot(),
20290 );
20291 let range = offset..offset;
20292 new_selections_by_buffer
20293 .entry(buffer_handle)
20294 .or_insert((Vec::new(), None))
20295 .0
20296 .push(range)
20297 } else {
20298 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
20299 else {
20300 continue;
20301 };
20302 new_selections_by_buffer
20303 .entry(buffer_handle)
20304 .or_insert((Vec::new(), None))
20305 .0
20306 .push(range)
20307 }
20308 }
20309 }
20310 }
20311 }
20312
20313 new_selections_by_buffer
20314 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
20315
20316 if new_selections_by_buffer.is_empty() {
20317 return;
20318 }
20319
20320 // We defer the pane interaction because we ourselves are a workspace item
20321 // and activating a new item causes the pane to call a method on us reentrantly,
20322 // which panics if we're on the stack.
20323 window.defer(cx, move |window, cx| {
20324 workspace.update(cx, |workspace, cx| {
20325 let pane = if split {
20326 workspace.adjacent_pane(window, cx)
20327 } else {
20328 workspace.active_pane().clone()
20329 };
20330
20331 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
20332 let editor = buffer
20333 .read(cx)
20334 .file()
20335 .is_none()
20336 .then(|| {
20337 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
20338 // so `workspace.open_project_item` will never find them, always opening a new editor.
20339 // Instead, we try to activate the existing editor in the pane first.
20340 let (editor, pane_item_index) =
20341 pane.read(cx).items().enumerate().find_map(|(i, item)| {
20342 let editor = item.downcast::<Editor>()?;
20343 let singleton_buffer =
20344 editor.read(cx).buffer().read(cx).as_singleton()?;
20345 if singleton_buffer == buffer {
20346 Some((editor, i))
20347 } else {
20348 None
20349 }
20350 })?;
20351 pane.update(cx, |pane, cx| {
20352 pane.activate_item(pane_item_index, true, true, window, cx)
20353 });
20354 Some(editor)
20355 })
20356 .flatten()
20357 .unwrap_or_else(|| {
20358 workspace.open_project_item::<Self>(
20359 pane.clone(),
20360 buffer,
20361 true,
20362 true,
20363 window,
20364 cx,
20365 )
20366 });
20367
20368 editor.update(cx, |editor, cx| {
20369 let autoscroll = match scroll_offset {
20370 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
20371 None => Autoscroll::newest(),
20372 };
20373 let nav_history = editor.nav_history.take();
20374 editor.change_selections(
20375 SelectionEffects::scroll(autoscroll),
20376 window,
20377 cx,
20378 |s| {
20379 s.select_ranges(ranges);
20380 },
20381 );
20382 editor.nav_history = nav_history;
20383 });
20384 }
20385 })
20386 });
20387 }
20388
20389 // For now, don't allow opening excerpts in buffers that aren't backed by
20390 // regular project files.
20391 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
20392 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
20393 }
20394
20395 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
20396 let snapshot = self.buffer.read(cx).read(cx);
20397 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
20398 Some(
20399 ranges
20400 .iter()
20401 .map(move |range| {
20402 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
20403 })
20404 .collect(),
20405 )
20406 }
20407
20408 fn selection_replacement_ranges(
20409 &self,
20410 range: Range<OffsetUtf16>,
20411 cx: &mut App,
20412 ) -> Vec<Range<OffsetUtf16>> {
20413 let selections = self.selections.all::<OffsetUtf16>(cx);
20414 let newest_selection = selections
20415 .iter()
20416 .max_by_key(|selection| selection.id)
20417 .unwrap();
20418 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
20419 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
20420 let snapshot = self.buffer.read(cx).read(cx);
20421 selections
20422 .into_iter()
20423 .map(|mut selection| {
20424 selection.start.0 =
20425 (selection.start.0 as isize).saturating_add(start_delta) as usize;
20426 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
20427 snapshot.clip_offset_utf16(selection.start, Bias::Left)
20428 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
20429 })
20430 .collect()
20431 }
20432
20433 fn report_editor_event(
20434 &self,
20435 event_type: &'static str,
20436 file_extension: Option<String>,
20437 cx: &App,
20438 ) {
20439 if cfg!(any(test, feature = "test-support")) {
20440 return;
20441 }
20442
20443 let Some(project) = &self.project else { return };
20444
20445 // If None, we are in a file without an extension
20446 let file = self
20447 .buffer
20448 .read(cx)
20449 .as_singleton()
20450 .and_then(|b| b.read(cx).file());
20451 let file_extension = file_extension.or(file
20452 .as_ref()
20453 .and_then(|file| Path::new(file.file_name(cx)).extension())
20454 .and_then(|e| e.to_str())
20455 .map(|a| a.to_string()));
20456
20457 let vim_mode = vim_enabled(cx);
20458
20459 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
20460 let copilot_enabled = edit_predictions_provider
20461 == language::language_settings::EditPredictionProvider::Copilot;
20462 let copilot_enabled_for_language = self
20463 .buffer
20464 .read(cx)
20465 .language_settings(cx)
20466 .show_edit_predictions;
20467
20468 let project = project.read(cx);
20469 telemetry::event!(
20470 event_type,
20471 file_extension,
20472 vim_mode,
20473 copilot_enabled,
20474 copilot_enabled_for_language,
20475 edit_predictions_provider,
20476 is_via_ssh = project.is_via_ssh(),
20477 );
20478 }
20479
20480 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
20481 /// with each line being an array of {text, highlight} objects.
20482 fn copy_highlight_json(
20483 &mut self,
20484 _: &CopyHighlightJson,
20485 window: &mut Window,
20486 cx: &mut Context<Self>,
20487 ) {
20488 #[derive(Serialize)]
20489 struct Chunk<'a> {
20490 text: String,
20491 highlight: Option<&'a str>,
20492 }
20493
20494 let snapshot = self.buffer.read(cx).snapshot(cx);
20495 let range = self
20496 .selected_text_range(false, window, cx)
20497 .and_then(|selection| {
20498 if selection.range.is_empty() {
20499 None
20500 } else {
20501 Some(selection.range)
20502 }
20503 })
20504 .unwrap_or_else(|| 0..snapshot.len());
20505
20506 let chunks = snapshot.chunks(range, true);
20507 let mut lines = Vec::new();
20508 let mut line: VecDeque<Chunk> = VecDeque::new();
20509
20510 let Some(style) = self.style.as_ref() else {
20511 return;
20512 };
20513
20514 for chunk in chunks {
20515 let highlight = chunk
20516 .syntax_highlight_id
20517 .and_then(|id| id.name(&style.syntax));
20518 let mut chunk_lines = chunk.text.split('\n').peekable();
20519 while let Some(text) = chunk_lines.next() {
20520 let mut merged_with_last_token = false;
20521 if let Some(last_token) = line.back_mut() {
20522 if last_token.highlight == highlight {
20523 last_token.text.push_str(text);
20524 merged_with_last_token = true;
20525 }
20526 }
20527
20528 if !merged_with_last_token {
20529 line.push_back(Chunk {
20530 text: text.into(),
20531 highlight,
20532 });
20533 }
20534
20535 if chunk_lines.peek().is_some() {
20536 if line.len() > 1 && line.front().unwrap().text.is_empty() {
20537 line.pop_front();
20538 }
20539 if line.len() > 1 && line.back().unwrap().text.is_empty() {
20540 line.pop_back();
20541 }
20542
20543 lines.push(mem::take(&mut line));
20544 }
20545 }
20546 }
20547
20548 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
20549 return;
20550 };
20551 cx.write_to_clipboard(ClipboardItem::new_string(lines));
20552 }
20553
20554 pub fn open_context_menu(
20555 &mut self,
20556 _: &OpenContextMenu,
20557 window: &mut Window,
20558 cx: &mut Context<Self>,
20559 ) {
20560 self.request_autoscroll(Autoscroll::newest(), cx);
20561 let position = self.selections.newest_display(cx).start;
20562 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
20563 }
20564
20565 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
20566 &self.inlay_hint_cache
20567 }
20568
20569 pub fn replay_insert_event(
20570 &mut self,
20571 text: &str,
20572 relative_utf16_range: Option<Range<isize>>,
20573 window: &mut Window,
20574 cx: &mut Context<Self>,
20575 ) {
20576 if !self.input_enabled {
20577 cx.emit(EditorEvent::InputIgnored { text: text.into() });
20578 return;
20579 }
20580 if let Some(relative_utf16_range) = relative_utf16_range {
20581 let selections = self.selections.all::<OffsetUtf16>(cx);
20582 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20583 let new_ranges = selections.into_iter().map(|range| {
20584 let start = OffsetUtf16(
20585 range
20586 .head()
20587 .0
20588 .saturating_add_signed(relative_utf16_range.start),
20589 );
20590 let end = OffsetUtf16(
20591 range
20592 .head()
20593 .0
20594 .saturating_add_signed(relative_utf16_range.end),
20595 );
20596 start..end
20597 });
20598 s.select_ranges(new_ranges);
20599 });
20600 }
20601
20602 self.handle_input(text, window, cx);
20603 }
20604
20605 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
20606 let Some(provider) = self.semantics_provider.as_ref() else {
20607 return false;
20608 };
20609
20610 let mut supports = false;
20611 self.buffer().update(cx, |this, cx| {
20612 this.for_each_buffer(|buffer| {
20613 supports |= provider.supports_inlay_hints(buffer, cx);
20614 });
20615 });
20616
20617 supports
20618 }
20619
20620 pub fn is_focused(&self, window: &Window) -> bool {
20621 self.focus_handle.is_focused(window)
20622 }
20623
20624 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20625 cx.emit(EditorEvent::Focused);
20626
20627 if let Some(descendant) = self
20628 .last_focused_descendant
20629 .take()
20630 .and_then(|descendant| descendant.upgrade())
20631 {
20632 window.focus(&descendant);
20633 } else {
20634 if let Some(blame) = self.blame.as_ref() {
20635 blame.update(cx, GitBlame::focus)
20636 }
20637
20638 self.blink_manager.update(cx, BlinkManager::enable);
20639 self.show_cursor_names(window, cx);
20640 self.buffer.update(cx, |buffer, cx| {
20641 buffer.finalize_last_transaction(cx);
20642 if self.leader_id.is_none() {
20643 buffer.set_active_selections(
20644 &self.selections.disjoint_anchors(),
20645 self.selections.line_mode,
20646 self.cursor_shape,
20647 cx,
20648 );
20649 }
20650 });
20651 }
20652 }
20653
20654 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
20655 cx.emit(EditorEvent::FocusedIn)
20656 }
20657
20658 fn handle_focus_out(
20659 &mut self,
20660 event: FocusOutEvent,
20661 _window: &mut Window,
20662 cx: &mut Context<Self>,
20663 ) {
20664 if event.blurred != self.focus_handle {
20665 self.last_focused_descendant = Some(event.blurred);
20666 }
20667 self.selection_drag_state = SelectionDragState::None;
20668 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
20669 }
20670
20671 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20672 self.blink_manager.update(cx, BlinkManager::disable);
20673 self.buffer
20674 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
20675
20676 if let Some(blame) = self.blame.as_ref() {
20677 blame.update(cx, GitBlame::blur)
20678 }
20679 if !self.hover_state.focused(window, cx) {
20680 hide_hover(self, cx);
20681 }
20682 if !self
20683 .context_menu
20684 .borrow()
20685 .as_ref()
20686 .is_some_and(|context_menu| context_menu.focused(window, cx))
20687 {
20688 self.hide_context_menu(window, cx);
20689 }
20690 self.discard_inline_completion(false, cx);
20691 cx.emit(EditorEvent::Blurred);
20692 cx.notify();
20693 }
20694
20695 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20696 let mut pending: String = window
20697 .pending_input_keystrokes()
20698 .into_iter()
20699 .flatten()
20700 .filter_map(|keystroke| {
20701 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
20702 keystroke.key_char.clone()
20703 } else {
20704 None
20705 }
20706 })
20707 .collect();
20708
20709 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
20710 pending = "".to_string();
20711 }
20712
20713 let existing_pending = self
20714 .text_highlights::<PendingInput>(cx)
20715 .map(|(_, ranges)| ranges.iter().cloned().collect::<Vec<_>>());
20716 if existing_pending.is_none() && pending.is_empty() {
20717 return;
20718 }
20719 let transaction =
20720 self.transact(window, cx, |this, window, cx| {
20721 let selections = this.selections.all::<usize>(cx);
20722 let edits = selections
20723 .iter()
20724 .map(|selection| (selection.end..selection.end, pending.clone()));
20725 this.edit(edits, cx);
20726 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20727 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
20728 sel.start + ix * pending.len()..sel.end + ix * pending.len()
20729 }));
20730 });
20731 if let Some(existing_ranges) = existing_pending {
20732 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
20733 this.edit(edits, cx);
20734 }
20735 });
20736
20737 let snapshot = self.snapshot(window, cx);
20738 let ranges = self
20739 .selections
20740 .all::<usize>(cx)
20741 .into_iter()
20742 .map(|selection| {
20743 snapshot.buffer_snapshot.anchor_after(selection.end)
20744 ..snapshot
20745 .buffer_snapshot
20746 .anchor_before(selection.end + pending.len())
20747 })
20748 .collect();
20749
20750 if pending.is_empty() {
20751 self.clear_highlights::<PendingInput>(cx);
20752 } else {
20753 self.highlight_text::<PendingInput>(
20754 ranges,
20755 HighlightStyle {
20756 underline: Some(UnderlineStyle {
20757 thickness: px(1.),
20758 color: None,
20759 wavy: false,
20760 }),
20761 ..Default::default()
20762 },
20763 cx,
20764 );
20765 }
20766
20767 self.ime_transaction = self.ime_transaction.or(transaction);
20768 if let Some(transaction) = self.ime_transaction {
20769 self.buffer.update(cx, |buffer, cx| {
20770 buffer.group_until_transaction(transaction, cx);
20771 });
20772 }
20773
20774 if self.text_highlights::<PendingInput>(cx).is_none() {
20775 self.ime_transaction.take();
20776 }
20777 }
20778
20779 pub fn register_action_renderer(
20780 &mut self,
20781 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
20782 ) -> Subscription {
20783 let id = self.next_editor_action_id.post_inc();
20784 self.editor_actions
20785 .borrow_mut()
20786 .insert(id, Box::new(listener));
20787
20788 let editor_actions = self.editor_actions.clone();
20789 Subscription::new(move || {
20790 editor_actions.borrow_mut().remove(&id);
20791 })
20792 }
20793
20794 pub fn register_action<A: Action>(
20795 &mut self,
20796 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
20797 ) -> Subscription {
20798 let id = self.next_editor_action_id.post_inc();
20799 let listener = Arc::new(listener);
20800 self.editor_actions.borrow_mut().insert(
20801 id,
20802 Box::new(move |_, window, _| {
20803 let listener = listener.clone();
20804 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
20805 let action = action.downcast_ref().unwrap();
20806 if phase == DispatchPhase::Bubble {
20807 listener(action, window, cx)
20808 }
20809 })
20810 }),
20811 );
20812
20813 let editor_actions = self.editor_actions.clone();
20814 Subscription::new(move || {
20815 editor_actions.borrow_mut().remove(&id);
20816 })
20817 }
20818
20819 pub fn file_header_size(&self) -> u32 {
20820 FILE_HEADER_HEIGHT
20821 }
20822
20823 pub fn restore(
20824 &mut self,
20825 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
20826 window: &mut Window,
20827 cx: &mut Context<Self>,
20828 ) {
20829 let workspace = self.workspace();
20830 let project = self.project.as_ref();
20831 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
20832 let mut tasks = Vec::new();
20833 for (buffer_id, changes) in revert_changes {
20834 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
20835 buffer.update(cx, |buffer, cx| {
20836 buffer.edit(
20837 changes
20838 .into_iter()
20839 .map(|(range, text)| (range, text.to_string())),
20840 None,
20841 cx,
20842 );
20843 });
20844
20845 if let Some(project) =
20846 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
20847 {
20848 project.update(cx, |project, cx| {
20849 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
20850 })
20851 }
20852 }
20853 }
20854 tasks
20855 });
20856 cx.spawn_in(window, async move |_, cx| {
20857 for (buffer, task) in save_tasks {
20858 let result = task.await;
20859 if result.is_err() {
20860 let Some(path) = buffer
20861 .read_with(cx, |buffer, cx| buffer.project_path(cx))
20862 .ok()
20863 else {
20864 continue;
20865 };
20866 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
20867 let Some(task) = cx
20868 .update_window_entity(&workspace, |workspace, window, cx| {
20869 workspace
20870 .open_path_preview(path, None, false, false, false, window, cx)
20871 })
20872 .ok()
20873 else {
20874 continue;
20875 };
20876 task.await.log_err();
20877 }
20878 }
20879 }
20880 })
20881 .detach();
20882 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
20883 selections.refresh()
20884 });
20885 }
20886
20887 pub fn to_pixel_point(
20888 &self,
20889 source: multi_buffer::Anchor,
20890 editor_snapshot: &EditorSnapshot,
20891 window: &mut Window,
20892 ) -> Option<gpui::Point<Pixels>> {
20893 let source_point = source.to_display_point(editor_snapshot);
20894 self.display_to_pixel_point(source_point, editor_snapshot, window)
20895 }
20896
20897 pub fn display_to_pixel_point(
20898 &self,
20899 source: DisplayPoint,
20900 editor_snapshot: &EditorSnapshot,
20901 window: &mut Window,
20902 ) -> Option<gpui::Point<Pixels>> {
20903 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
20904 let text_layout_details = self.text_layout_details(window);
20905 let scroll_top = text_layout_details
20906 .scroll_anchor
20907 .scroll_position(editor_snapshot)
20908 .y;
20909
20910 if source.row().as_f32() < scroll_top.floor() {
20911 return None;
20912 }
20913 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
20914 let source_y = line_height * (source.row().as_f32() - scroll_top);
20915 Some(gpui::Point::new(source_x, source_y))
20916 }
20917
20918 pub fn has_visible_completions_menu(&self) -> bool {
20919 !self.edit_prediction_preview_is_active()
20920 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
20921 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
20922 })
20923 }
20924
20925 pub fn register_addon<T: Addon>(&mut self, instance: T) {
20926 if self.mode.is_minimap() {
20927 return;
20928 }
20929 self.addons
20930 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
20931 }
20932
20933 pub fn unregister_addon<T: Addon>(&mut self) {
20934 self.addons.remove(&std::any::TypeId::of::<T>());
20935 }
20936
20937 pub fn addon<T: Addon>(&self) -> Option<&T> {
20938 let type_id = std::any::TypeId::of::<T>();
20939 self.addons
20940 .get(&type_id)
20941 .and_then(|item| item.to_any().downcast_ref::<T>())
20942 }
20943
20944 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
20945 let type_id = std::any::TypeId::of::<T>();
20946 self.addons
20947 .get_mut(&type_id)
20948 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
20949 }
20950
20951 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
20952 let text_layout_details = self.text_layout_details(window);
20953 let style = &text_layout_details.editor_style;
20954 let font_id = window.text_system().resolve_font(&style.text.font());
20955 let font_size = style.text.font_size.to_pixels(window.rem_size());
20956 let line_height = style.text.line_height_in_pixels(window.rem_size());
20957 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
20958 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
20959
20960 CharacterDimensions {
20961 em_width,
20962 em_advance,
20963 line_height,
20964 }
20965 }
20966
20967 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
20968 self.load_diff_task.clone()
20969 }
20970
20971 fn read_metadata_from_db(
20972 &mut self,
20973 item_id: u64,
20974 workspace_id: WorkspaceId,
20975 window: &mut Window,
20976 cx: &mut Context<Editor>,
20977 ) {
20978 if self.is_singleton(cx)
20979 && !self.mode.is_minimap()
20980 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
20981 {
20982 let buffer_snapshot = OnceCell::new();
20983
20984 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
20985 if !folds.is_empty() {
20986 let snapshot =
20987 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
20988 self.fold_ranges(
20989 folds
20990 .into_iter()
20991 .map(|(start, end)| {
20992 snapshot.clip_offset(start, Bias::Left)
20993 ..snapshot.clip_offset(end, Bias::Right)
20994 })
20995 .collect(),
20996 false,
20997 window,
20998 cx,
20999 );
21000 }
21001 }
21002
21003 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
21004 if !selections.is_empty() {
21005 let snapshot =
21006 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21007 // skip adding the initial selection to selection history
21008 self.selection_history.mode = SelectionHistoryMode::Skipping;
21009 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21010 s.select_ranges(selections.into_iter().map(|(start, end)| {
21011 snapshot.clip_offset(start, Bias::Left)
21012 ..snapshot.clip_offset(end, Bias::Right)
21013 }));
21014 });
21015 self.selection_history.mode = SelectionHistoryMode::Normal;
21016 }
21017 };
21018 }
21019
21020 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
21021 }
21022
21023 fn update_lsp_data(
21024 &mut self,
21025 ignore_cache: bool,
21026 for_buffer: Option<BufferId>,
21027 window: &mut Window,
21028 cx: &mut Context<'_, Self>,
21029 ) {
21030 self.pull_diagnostics(for_buffer, window, cx);
21031 self.refresh_colors(ignore_cache, for_buffer, window, cx);
21032 }
21033}
21034
21035fn vim_enabled(cx: &App) -> bool {
21036 cx.global::<SettingsStore>()
21037 .raw_user_settings()
21038 .get("vim_mode")
21039 == Some(&serde_json::Value::Bool(true))
21040}
21041
21042fn process_completion_for_edit(
21043 completion: &Completion,
21044 intent: CompletionIntent,
21045 buffer: &Entity<Buffer>,
21046 cursor_position: &text::Anchor,
21047 cx: &mut Context<Editor>,
21048) -> CompletionEdit {
21049 let buffer = buffer.read(cx);
21050 let buffer_snapshot = buffer.snapshot();
21051 let (snippet, new_text) = if completion.is_snippet() {
21052 // Workaround for typescript language server issues so that methods don't expand within
21053 // strings and functions with type expressions. The previous point is used because the query
21054 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
21055 let mut snippet_source = completion.new_text.clone();
21056 let mut previous_point = text::ToPoint::to_point(cursor_position, buffer);
21057 previous_point.column = previous_point.column.saturating_sub(1);
21058 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point) {
21059 if scope.prefers_label_for_snippet_in_completion() {
21060 if let Some(label) = completion.label() {
21061 if matches!(
21062 completion.kind(),
21063 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
21064 ) {
21065 snippet_source = label;
21066 }
21067 }
21068 }
21069 }
21070 match Snippet::parse(&snippet_source).log_err() {
21071 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
21072 None => (None, completion.new_text.clone()),
21073 }
21074 } else {
21075 (None, completion.new_text.clone())
21076 };
21077
21078 let mut range_to_replace = {
21079 let replace_range = &completion.replace_range;
21080 if let CompletionSource::Lsp {
21081 insert_range: Some(insert_range),
21082 ..
21083 } = &completion.source
21084 {
21085 debug_assert_eq!(
21086 insert_range.start, replace_range.start,
21087 "insert_range and replace_range should start at the same position"
21088 );
21089 debug_assert!(
21090 insert_range
21091 .start
21092 .cmp(&cursor_position, &buffer_snapshot)
21093 .is_le(),
21094 "insert_range should start before or at cursor position"
21095 );
21096 debug_assert!(
21097 replace_range
21098 .start
21099 .cmp(&cursor_position, &buffer_snapshot)
21100 .is_le(),
21101 "replace_range should start before or at cursor position"
21102 );
21103 debug_assert!(
21104 insert_range
21105 .end
21106 .cmp(&cursor_position, &buffer_snapshot)
21107 .is_le(),
21108 "insert_range should end before or at cursor position"
21109 );
21110
21111 let should_replace = match intent {
21112 CompletionIntent::CompleteWithInsert => false,
21113 CompletionIntent::CompleteWithReplace => true,
21114 CompletionIntent::Complete | CompletionIntent::Compose => {
21115 let insert_mode =
21116 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
21117 .completions
21118 .lsp_insert_mode;
21119 match insert_mode {
21120 LspInsertMode::Insert => false,
21121 LspInsertMode::Replace => true,
21122 LspInsertMode::ReplaceSubsequence => {
21123 let mut text_to_replace = buffer.chars_for_range(
21124 buffer.anchor_before(replace_range.start)
21125 ..buffer.anchor_after(replace_range.end),
21126 );
21127 let mut current_needle = text_to_replace.next();
21128 for haystack_ch in completion.label.text.chars() {
21129 if let Some(needle_ch) = current_needle {
21130 if haystack_ch.eq_ignore_ascii_case(&needle_ch) {
21131 current_needle = text_to_replace.next();
21132 }
21133 }
21134 }
21135 current_needle.is_none()
21136 }
21137 LspInsertMode::ReplaceSuffix => {
21138 if replace_range
21139 .end
21140 .cmp(&cursor_position, &buffer_snapshot)
21141 .is_gt()
21142 {
21143 let range_after_cursor = *cursor_position..replace_range.end;
21144 let text_after_cursor = buffer
21145 .text_for_range(
21146 buffer.anchor_before(range_after_cursor.start)
21147 ..buffer.anchor_after(range_after_cursor.end),
21148 )
21149 .collect::<String>()
21150 .to_ascii_lowercase();
21151 completion
21152 .label
21153 .text
21154 .to_ascii_lowercase()
21155 .ends_with(&text_after_cursor)
21156 } else {
21157 true
21158 }
21159 }
21160 }
21161 }
21162 };
21163
21164 if should_replace {
21165 replace_range.clone()
21166 } else {
21167 insert_range.clone()
21168 }
21169 } else {
21170 replace_range.clone()
21171 }
21172 };
21173
21174 if range_to_replace
21175 .end
21176 .cmp(&cursor_position, &buffer_snapshot)
21177 .is_lt()
21178 {
21179 range_to_replace.end = *cursor_position;
21180 }
21181
21182 CompletionEdit {
21183 new_text,
21184 replace_range: range_to_replace.to_offset(&buffer),
21185 snippet,
21186 }
21187}
21188
21189struct CompletionEdit {
21190 new_text: String,
21191 replace_range: Range<usize>,
21192 snippet: Option<Snippet>,
21193}
21194
21195fn insert_extra_newline_brackets(
21196 buffer: &MultiBufferSnapshot,
21197 range: Range<usize>,
21198 language: &language::LanguageScope,
21199) -> bool {
21200 let leading_whitespace_len = buffer
21201 .reversed_chars_at(range.start)
21202 .take_while(|c| c.is_whitespace() && *c != '\n')
21203 .map(|c| c.len_utf8())
21204 .sum::<usize>();
21205 let trailing_whitespace_len = buffer
21206 .chars_at(range.end)
21207 .take_while(|c| c.is_whitespace() && *c != '\n')
21208 .map(|c| c.len_utf8())
21209 .sum::<usize>();
21210 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
21211
21212 language.brackets().any(|(pair, enabled)| {
21213 let pair_start = pair.start.trim_end();
21214 let pair_end = pair.end.trim_start();
21215
21216 enabled
21217 && pair.newline
21218 && buffer.contains_str_at(range.end, pair_end)
21219 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
21220 })
21221}
21222
21223fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
21224 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
21225 [(buffer, range, _)] => (*buffer, range.clone()),
21226 _ => return false,
21227 };
21228 let pair = {
21229 let mut result: Option<BracketMatch> = None;
21230
21231 for pair in buffer
21232 .all_bracket_ranges(range.clone())
21233 .filter(move |pair| {
21234 pair.open_range.start <= range.start && pair.close_range.end >= range.end
21235 })
21236 {
21237 let len = pair.close_range.end - pair.open_range.start;
21238
21239 if let Some(existing) = &result {
21240 let existing_len = existing.close_range.end - existing.open_range.start;
21241 if len > existing_len {
21242 continue;
21243 }
21244 }
21245
21246 result = Some(pair);
21247 }
21248
21249 result
21250 };
21251 let Some(pair) = pair else {
21252 return false;
21253 };
21254 pair.newline_only
21255 && buffer
21256 .chars_for_range(pair.open_range.end..range.start)
21257 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
21258 .all(|c| c.is_whitespace() && c != '\n')
21259}
21260
21261fn update_uncommitted_diff_for_buffer(
21262 editor: Entity<Editor>,
21263 project: &Entity<Project>,
21264 buffers: impl IntoIterator<Item = Entity<Buffer>>,
21265 buffer: Entity<MultiBuffer>,
21266 cx: &mut App,
21267) -> Task<()> {
21268 let mut tasks = Vec::new();
21269 project.update(cx, |project, cx| {
21270 for buffer in buffers {
21271 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
21272 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
21273 }
21274 }
21275 });
21276 cx.spawn(async move |cx| {
21277 let diffs = future::join_all(tasks).await;
21278 if editor
21279 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
21280 .unwrap_or(false)
21281 {
21282 return;
21283 }
21284
21285 buffer
21286 .update(cx, |buffer, cx| {
21287 for diff in diffs.into_iter().flatten() {
21288 buffer.add_diff(diff, cx);
21289 }
21290 })
21291 .ok();
21292 })
21293}
21294
21295fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
21296 let tab_size = tab_size.get() as usize;
21297 let mut width = offset;
21298
21299 for ch in text.chars() {
21300 width += if ch == '\t' {
21301 tab_size - (width % tab_size)
21302 } else {
21303 1
21304 };
21305 }
21306
21307 width - offset
21308}
21309
21310#[cfg(test)]
21311mod tests {
21312 use super::*;
21313
21314 #[test]
21315 fn test_string_size_with_expanded_tabs() {
21316 let nz = |val| NonZeroU32::new(val).unwrap();
21317 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
21318 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
21319 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
21320 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
21321 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
21322 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
21323 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
21324 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
21325 }
21326}
21327
21328/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
21329struct WordBreakingTokenizer<'a> {
21330 input: &'a str,
21331}
21332
21333impl<'a> WordBreakingTokenizer<'a> {
21334 fn new(input: &'a str) -> Self {
21335 Self { input }
21336 }
21337}
21338
21339fn is_char_ideographic(ch: char) -> bool {
21340 use unicode_script::Script::*;
21341 use unicode_script::UnicodeScript;
21342 matches!(ch.script(), Han | Tangut | Yi)
21343}
21344
21345fn is_grapheme_ideographic(text: &str) -> bool {
21346 text.chars().any(is_char_ideographic)
21347}
21348
21349fn is_grapheme_whitespace(text: &str) -> bool {
21350 text.chars().any(|x| x.is_whitespace())
21351}
21352
21353fn should_stay_with_preceding_ideograph(text: &str) -> bool {
21354 text.chars().next().map_or(false, |ch| {
21355 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
21356 })
21357}
21358
21359#[derive(PartialEq, Eq, Debug, Clone, Copy)]
21360enum WordBreakToken<'a> {
21361 Word { token: &'a str, grapheme_len: usize },
21362 InlineWhitespace { token: &'a str, grapheme_len: usize },
21363 Newline,
21364}
21365
21366impl<'a> Iterator for WordBreakingTokenizer<'a> {
21367 /// Yields a span, the count of graphemes in the token, and whether it was
21368 /// whitespace. Note that it also breaks at word boundaries.
21369 type Item = WordBreakToken<'a>;
21370
21371 fn next(&mut self) -> Option<Self::Item> {
21372 use unicode_segmentation::UnicodeSegmentation;
21373 if self.input.is_empty() {
21374 return None;
21375 }
21376
21377 let mut iter = self.input.graphemes(true).peekable();
21378 let mut offset = 0;
21379 let mut grapheme_len = 0;
21380 if let Some(first_grapheme) = iter.next() {
21381 let is_newline = first_grapheme == "\n";
21382 let is_whitespace = is_grapheme_whitespace(first_grapheme);
21383 offset += first_grapheme.len();
21384 grapheme_len += 1;
21385 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
21386 if let Some(grapheme) = iter.peek().copied() {
21387 if should_stay_with_preceding_ideograph(grapheme) {
21388 offset += grapheme.len();
21389 grapheme_len += 1;
21390 }
21391 }
21392 } else {
21393 let mut words = self.input[offset..].split_word_bound_indices().peekable();
21394 let mut next_word_bound = words.peek().copied();
21395 if next_word_bound.map_or(false, |(i, _)| i == 0) {
21396 next_word_bound = words.next();
21397 }
21398 while let Some(grapheme) = iter.peek().copied() {
21399 if next_word_bound.map_or(false, |(i, _)| i == offset) {
21400 break;
21401 };
21402 if is_grapheme_whitespace(grapheme) != is_whitespace
21403 || (grapheme == "\n") != is_newline
21404 {
21405 break;
21406 };
21407 offset += grapheme.len();
21408 grapheme_len += 1;
21409 iter.next();
21410 }
21411 }
21412 let token = &self.input[..offset];
21413 self.input = &self.input[offset..];
21414 if token == "\n" {
21415 Some(WordBreakToken::Newline)
21416 } else if is_whitespace {
21417 Some(WordBreakToken::InlineWhitespace {
21418 token,
21419 grapheme_len,
21420 })
21421 } else {
21422 Some(WordBreakToken::Word {
21423 token,
21424 grapheme_len,
21425 })
21426 }
21427 } else {
21428 None
21429 }
21430 }
21431}
21432
21433#[test]
21434fn test_word_breaking_tokenizer() {
21435 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
21436 ("", &[]),
21437 (" ", &[whitespace(" ", 2)]),
21438 ("Ʒ", &[word("Ʒ", 1)]),
21439 ("Ǽ", &[word("Ǽ", 1)]),
21440 ("⋑", &[word("⋑", 1)]),
21441 ("⋑⋑", &[word("⋑⋑", 2)]),
21442 (
21443 "原理,进而",
21444 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
21445 ),
21446 (
21447 "hello world",
21448 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
21449 ),
21450 (
21451 "hello, world",
21452 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
21453 ),
21454 (
21455 " hello world",
21456 &[
21457 whitespace(" ", 2),
21458 word("hello", 5),
21459 whitespace(" ", 1),
21460 word("world", 5),
21461 ],
21462 ),
21463 (
21464 "这是什么 \n 钢笔",
21465 &[
21466 word("这", 1),
21467 word("是", 1),
21468 word("什", 1),
21469 word("么", 1),
21470 whitespace(" ", 1),
21471 newline(),
21472 whitespace(" ", 1),
21473 word("钢", 1),
21474 word("笔", 1),
21475 ],
21476 ),
21477 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
21478 ];
21479
21480 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21481 WordBreakToken::Word {
21482 token,
21483 grapheme_len,
21484 }
21485 }
21486
21487 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21488 WordBreakToken::InlineWhitespace {
21489 token,
21490 grapheme_len,
21491 }
21492 }
21493
21494 fn newline() -> WordBreakToken<'static> {
21495 WordBreakToken::Newline
21496 }
21497
21498 for (input, result) in tests {
21499 assert_eq!(
21500 WordBreakingTokenizer::new(input)
21501 .collect::<Vec<_>>()
21502 .as_slice(),
21503 *result,
21504 );
21505 }
21506}
21507
21508fn wrap_with_prefix(
21509 first_line_prefix: String,
21510 subsequent_lines_prefix: String,
21511 unwrapped_text: String,
21512 wrap_column: usize,
21513 tab_size: NonZeroU32,
21514 preserve_existing_whitespace: bool,
21515) -> String {
21516 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
21517 let subsequent_lines_prefix_len =
21518 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
21519 let mut wrapped_text = String::new();
21520 let mut current_line = first_line_prefix.clone();
21521 let mut is_first_line = true;
21522
21523 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
21524 let mut current_line_len = first_line_prefix_len;
21525 let mut in_whitespace = false;
21526 for token in tokenizer {
21527 let have_preceding_whitespace = in_whitespace;
21528 match token {
21529 WordBreakToken::Word {
21530 token,
21531 grapheme_len,
21532 } => {
21533 in_whitespace = false;
21534 let current_prefix_len = if is_first_line {
21535 first_line_prefix_len
21536 } else {
21537 subsequent_lines_prefix_len
21538 };
21539 if current_line_len + grapheme_len > wrap_column
21540 && current_line_len != current_prefix_len
21541 {
21542 wrapped_text.push_str(current_line.trim_end());
21543 wrapped_text.push('\n');
21544 is_first_line = false;
21545 current_line = subsequent_lines_prefix.clone();
21546 current_line_len = subsequent_lines_prefix_len;
21547 }
21548 current_line.push_str(token);
21549 current_line_len += grapheme_len;
21550 }
21551 WordBreakToken::InlineWhitespace {
21552 mut token,
21553 mut grapheme_len,
21554 } => {
21555 in_whitespace = true;
21556 if have_preceding_whitespace && !preserve_existing_whitespace {
21557 continue;
21558 }
21559 if !preserve_existing_whitespace {
21560 token = " ";
21561 grapheme_len = 1;
21562 }
21563 let current_prefix_len = if is_first_line {
21564 first_line_prefix_len
21565 } else {
21566 subsequent_lines_prefix_len
21567 };
21568 if current_line_len + grapheme_len > wrap_column {
21569 wrapped_text.push_str(current_line.trim_end());
21570 wrapped_text.push('\n');
21571 is_first_line = false;
21572 current_line = subsequent_lines_prefix.clone();
21573 current_line_len = subsequent_lines_prefix_len;
21574 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
21575 current_line.push_str(token);
21576 current_line_len += grapheme_len;
21577 }
21578 }
21579 WordBreakToken::Newline => {
21580 in_whitespace = true;
21581 let current_prefix_len = if is_first_line {
21582 first_line_prefix_len
21583 } else {
21584 subsequent_lines_prefix_len
21585 };
21586 if preserve_existing_whitespace {
21587 wrapped_text.push_str(current_line.trim_end());
21588 wrapped_text.push('\n');
21589 is_first_line = false;
21590 current_line = subsequent_lines_prefix.clone();
21591 current_line_len = subsequent_lines_prefix_len;
21592 } else if have_preceding_whitespace {
21593 continue;
21594 } else if current_line_len + 1 > wrap_column
21595 && current_line_len != current_prefix_len
21596 {
21597 wrapped_text.push_str(current_line.trim_end());
21598 wrapped_text.push('\n');
21599 is_first_line = false;
21600 current_line = subsequent_lines_prefix.clone();
21601 current_line_len = subsequent_lines_prefix_len;
21602 } else if current_line_len != current_prefix_len {
21603 current_line.push(' ');
21604 current_line_len += 1;
21605 }
21606 }
21607 }
21608 }
21609
21610 if !current_line.is_empty() {
21611 wrapped_text.push_str(¤t_line);
21612 }
21613 wrapped_text
21614}
21615
21616#[test]
21617fn test_wrap_with_prefix() {
21618 assert_eq!(
21619 wrap_with_prefix(
21620 "# ".to_string(),
21621 "# ".to_string(),
21622 "abcdefg".to_string(),
21623 4,
21624 NonZeroU32::new(4).unwrap(),
21625 false,
21626 ),
21627 "# abcdefg"
21628 );
21629 assert_eq!(
21630 wrap_with_prefix(
21631 "".to_string(),
21632 "".to_string(),
21633 "\thello world".to_string(),
21634 8,
21635 NonZeroU32::new(4).unwrap(),
21636 false,
21637 ),
21638 "hello\nworld"
21639 );
21640 assert_eq!(
21641 wrap_with_prefix(
21642 "// ".to_string(),
21643 "// ".to_string(),
21644 "xx \nyy zz aa bb cc".to_string(),
21645 12,
21646 NonZeroU32::new(4).unwrap(),
21647 false,
21648 ),
21649 "// xx yy zz\n// aa bb cc"
21650 );
21651 assert_eq!(
21652 wrap_with_prefix(
21653 String::new(),
21654 String::new(),
21655 "这是什么 \n 钢笔".to_string(),
21656 3,
21657 NonZeroU32::new(4).unwrap(),
21658 false,
21659 ),
21660 "这是什\n么 钢\n笔"
21661 );
21662}
21663
21664pub trait CollaborationHub {
21665 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
21666 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
21667 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
21668}
21669
21670impl CollaborationHub for Entity<Project> {
21671 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
21672 self.read(cx).collaborators()
21673 }
21674
21675 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
21676 self.read(cx).user_store().read(cx).participant_indices()
21677 }
21678
21679 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
21680 let this = self.read(cx);
21681 let user_ids = this.collaborators().values().map(|c| c.user_id);
21682 this.user_store().read(cx).participant_names(user_ids, cx)
21683 }
21684}
21685
21686pub trait SemanticsProvider {
21687 fn hover(
21688 &self,
21689 buffer: &Entity<Buffer>,
21690 position: text::Anchor,
21691 cx: &mut App,
21692 ) -> Option<Task<Vec<project::Hover>>>;
21693
21694 fn inline_values(
21695 &self,
21696 buffer_handle: Entity<Buffer>,
21697 range: Range<text::Anchor>,
21698 cx: &mut App,
21699 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21700
21701 fn inlay_hints(
21702 &self,
21703 buffer_handle: Entity<Buffer>,
21704 range: Range<text::Anchor>,
21705 cx: &mut App,
21706 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21707
21708 fn resolve_inlay_hint(
21709 &self,
21710 hint: InlayHint,
21711 buffer_handle: Entity<Buffer>,
21712 server_id: LanguageServerId,
21713 cx: &mut App,
21714 ) -> Option<Task<anyhow::Result<InlayHint>>>;
21715
21716 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
21717
21718 fn document_highlights(
21719 &self,
21720 buffer: &Entity<Buffer>,
21721 position: text::Anchor,
21722 cx: &mut App,
21723 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
21724
21725 fn definitions(
21726 &self,
21727 buffer: &Entity<Buffer>,
21728 position: text::Anchor,
21729 kind: GotoDefinitionKind,
21730 cx: &mut App,
21731 ) -> Option<Task<Result<Vec<LocationLink>>>>;
21732
21733 fn range_for_rename(
21734 &self,
21735 buffer: &Entity<Buffer>,
21736 position: text::Anchor,
21737 cx: &mut App,
21738 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
21739
21740 fn perform_rename(
21741 &self,
21742 buffer: &Entity<Buffer>,
21743 position: text::Anchor,
21744 new_name: String,
21745 cx: &mut App,
21746 ) -> Option<Task<Result<ProjectTransaction>>>;
21747}
21748
21749pub trait CompletionProvider {
21750 fn completions(
21751 &self,
21752 excerpt_id: ExcerptId,
21753 buffer: &Entity<Buffer>,
21754 buffer_position: text::Anchor,
21755 trigger: CompletionContext,
21756 window: &mut Window,
21757 cx: &mut Context<Editor>,
21758 ) -> Task<Result<Vec<CompletionResponse>>>;
21759
21760 fn resolve_completions(
21761 &self,
21762 _buffer: Entity<Buffer>,
21763 _completion_indices: Vec<usize>,
21764 _completions: Rc<RefCell<Box<[Completion]>>>,
21765 _cx: &mut Context<Editor>,
21766 ) -> Task<Result<bool>> {
21767 Task::ready(Ok(false))
21768 }
21769
21770 fn apply_additional_edits_for_completion(
21771 &self,
21772 _buffer: Entity<Buffer>,
21773 _completions: Rc<RefCell<Box<[Completion]>>>,
21774 _completion_index: usize,
21775 _push_to_history: bool,
21776 _cx: &mut Context<Editor>,
21777 ) -> Task<Result<Option<language::Transaction>>> {
21778 Task::ready(Ok(None))
21779 }
21780
21781 fn is_completion_trigger(
21782 &self,
21783 buffer: &Entity<Buffer>,
21784 position: language::Anchor,
21785 text: &str,
21786 trigger_in_words: bool,
21787 menu_is_open: bool,
21788 cx: &mut Context<Editor>,
21789 ) -> bool;
21790
21791 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
21792
21793 fn sort_completions(&self) -> bool {
21794 true
21795 }
21796
21797 fn filter_completions(&self) -> bool {
21798 true
21799 }
21800}
21801
21802pub trait CodeActionProvider {
21803 fn id(&self) -> Arc<str>;
21804
21805 fn code_actions(
21806 &self,
21807 buffer: &Entity<Buffer>,
21808 range: Range<text::Anchor>,
21809 window: &mut Window,
21810 cx: &mut App,
21811 ) -> Task<Result<Vec<CodeAction>>>;
21812
21813 fn apply_code_action(
21814 &self,
21815 buffer_handle: Entity<Buffer>,
21816 action: CodeAction,
21817 excerpt_id: ExcerptId,
21818 push_to_history: bool,
21819 window: &mut Window,
21820 cx: &mut App,
21821 ) -> Task<Result<ProjectTransaction>>;
21822}
21823
21824impl CodeActionProvider for Entity<Project> {
21825 fn id(&self) -> Arc<str> {
21826 "project".into()
21827 }
21828
21829 fn code_actions(
21830 &self,
21831 buffer: &Entity<Buffer>,
21832 range: Range<text::Anchor>,
21833 _window: &mut Window,
21834 cx: &mut App,
21835 ) -> Task<Result<Vec<CodeAction>>> {
21836 self.update(cx, |project, cx| {
21837 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
21838 let code_actions = project.code_actions(buffer, range, None, cx);
21839 cx.background_spawn(async move {
21840 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
21841 Ok(code_lens_actions
21842 .context("code lens fetch")?
21843 .into_iter()
21844 .chain(code_actions.context("code action fetch")?)
21845 .collect())
21846 })
21847 })
21848 }
21849
21850 fn apply_code_action(
21851 &self,
21852 buffer_handle: Entity<Buffer>,
21853 action: CodeAction,
21854 _excerpt_id: ExcerptId,
21855 push_to_history: bool,
21856 _window: &mut Window,
21857 cx: &mut App,
21858 ) -> Task<Result<ProjectTransaction>> {
21859 self.update(cx, |project, cx| {
21860 project.apply_code_action(buffer_handle, action, push_to_history, cx)
21861 })
21862 }
21863}
21864
21865fn snippet_completions(
21866 project: &Project,
21867 buffer: &Entity<Buffer>,
21868 buffer_position: text::Anchor,
21869 cx: &mut App,
21870) -> Task<Result<CompletionResponse>> {
21871 let languages = buffer.read(cx).languages_at(buffer_position);
21872 let snippet_store = project.snippets().read(cx);
21873
21874 let scopes: Vec<_> = languages
21875 .iter()
21876 .filter_map(|language| {
21877 let language_name = language.lsp_id();
21878 let snippets = snippet_store.snippets_for(Some(language_name), cx);
21879
21880 if snippets.is_empty() {
21881 None
21882 } else {
21883 Some((language.default_scope(), snippets))
21884 }
21885 })
21886 .collect();
21887
21888 if scopes.is_empty() {
21889 return Task::ready(Ok(CompletionResponse {
21890 completions: vec![],
21891 is_incomplete: false,
21892 }));
21893 }
21894
21895 let snapshot = buffer.read(cx).text_snapshot();
21896 let chars: String = snapshot
21897 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
21898 .collect();
21899 let executor = cx.background_executor().clone();
21900
21901 cx.background_spawn(async move {
21902 let mut is_incomplete = false;
21903 let mut completions: Vec<Completion> = Vec::new();
21904 for (scope, snippets) in scopes.into_iter() {
21905 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
21906 let mut last_word = chars
21907 .chars()
21908 .take_while(|c| classifier.is_word(*c))
21909 .collect::<String>();
21910 last_word = last_word.chars().rev().collect();
21911
21912 if last_word.is_empty() {
21913 return Ok(CompletionResponse {
21914 completions: vec![],
21915 is_incomplete: true,
21916 });
21917 }
21918
21919 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
21920 let to_lsp = |point: &text::Anchor| {
21921 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
21922 point_to_lsp(end)
21923 };
21924 let lsp_end = to_lsp(&buffer_position);
21925
21926 let candidates = snippets
21927 .iter()
21928 .enumerate()
21929 .flat_map(|(ix, snippet)| {
21930 snippet
21931 .prefix
21932 .iter()
21933 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
21934 })
21935 .collect::<Vec<StringMatchCandidate>>();
21936
21937 const MAX_RESULTS: usize = 100;
21938 let mut matches = fuzzy::match_strings(
21939 &candidates,
21940 &last_word,
21941 last_word.chars().any(|c| c.is_uppercase()),
21942 true,
21943 MAX_RESULTS,
21944 &Default::default(),
21945 executor.clone(),
21946 )
21947 .await;
21948
21949 if matches.len() >= MAX_RESULTS {
21950 is_incomplete = true;
21951 }
21952
21953 // Remove all candidates where the query's start does not match the start of any word in the candidate
21954 if let Some(query_start) = last_word.chars().next() {
21955 matches.retain(|string_match| {
21956 split_words(&string_match.string).any(|word| {
21957 // Check that the first codepoint of the word as lowercase matches the first
21958 // codepoint of the query as lowercase
21959 word.chars()
21960 .flat_map(|codepoint| codepoint.to_lowercase())
21961 .zip(query_start.to_lowercase())
21962 .all(|(word_cp, query_cp)| word_cp == query_cp)
21963 })
21964 });
21965 }
21966
21967 let matched_strings = matches
21968 .into_iter()
21969 .map(|m| m.string)
21970 .collect::<HashSet<_>>();
21971
21972 completions.extend(snippets.iter().filter_map(|snippet| {
21973 let matching_prefix = snippet
21974 .prefix
21975 .iter()
21976 .find(|prefix| matched_strings.contains(*prefix))?;
21977 let start = as_offset - last_word.len();
21978 let start = snapshot.anchor_before(start);
21979 let range = start..buffer_position;
21980 let lsp_start = to_lsp(&start);
21981 let lsp_range = lsp::Range {
21982 start: lsp_start,
21983 end: lsp_end,
21984 };
21985 Some(Completion {
21986 replace_range: range,
21987 new_text: snippet.body.clone(),
21988 source: CompletionSource::Lsp {
21989 insert_range: None,
21990 server_id: LanguageServerId(usize::MAX),
21991 resolved: true,
21992 lsp_completion: Box::new(lsp::CompletionItem {
21993 label: snippet.prefix.first().unwrap().clone(),
21994 kind: Some(CompletionItemKind::SNIPPET),
21995 label_details: snippet.description.as_ref().map(|description| {
21996 lsp::CompletionItemLabelDetails {
21997 detail: Some(description.clone()),
21998 description: None,
21999 }
22000 }),
22001 insert_text_format: Some(InsertTextFormat::SNIPPET),
22002 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22003 lsp::InsertReplaceEdit {
22004 new_text: snippet.body.clone(),
22005 insert: lsp_range,
22006 replace: lsp_range,
22007 },
22008 )),
22009 filter_text: Some(snippet.body.clone()),
22010 sort_text: Some(char::MAX.to_string()),
22011 ..lsp::CompletionItem::default()
22012 }),
22013 lsp_defaults: None,
22014 },
22015 label: CodeLabel {
22016 text: matching_prefix.clone(),
22017 runs: Vec::new(),
22018 filter_range: 0..matching_prefix.len(),
22019 },
22020 icon_path: None,
22021 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
22022 single_line: snippet.name.clone().into(),
22023 plain_text: snippet
22024 .description
22025 .clone()
22026 .map(|description| description.into()),
22027 }),
22028 insert_text_mode: None,
22029 confirm: None,
22030 })
22031 }))
22032 }
22033
22034 Ok(CompletionResponse {
22035 completions,
22036 is_incomplete,
22037 })
22038 })
22039}
22040
22041impl CompletionProvider for Entity<Project> {
22042 fn completions(
22043 &self,
22044 _excerpt_id: ExcerptId,
22045 buffer: &Entity<Buffer>,
22046 buffer_position: text::Anchor,
22047 options: CompletionContext,
22048 _window: &mut Window,
22049 cx: &mut Context<Editor>,
22050 ) -> Task<Result<Vec<CompletionResponse>>> {
22051 self.update(cx, |project, cx| {
22052 let snippets = snippet_completions(project, buffer, buffer_position, cx);
22053 let project_completions = project.completions(buffer, buffer_position, options, cx);
22054 cx.background_spawn(async move {
22055 let mut responses = project_completions.await?;
22056 let snippets = snippets.await?;
22057 if !snippets.completions.is_empty() {
22058 responses.push(snippets);
22059 }
22060 Ok(responses)
22061 })
22062 })
22063 }
22064
22065 fn resolve_completions(
22066 &self,
22067 buffer: Entity<Buffer>,
22068 completion_indices: Vec<usize>,
22069 completions: Rc<RefCell<Box<[Completion]>>>,
22070 cx: &mut Context<Editor>,
22071 ) -> Task<Result<bool>> {
22072 self.update(cx, |project, cx| {
22073 project.lsp_store().update(cx, |lsp_store, cx| {
22074 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
22075 })
22076 })
22077 }
22078
22079 fn apply_additional_edits_for_completion(
22080 &self,
22081 buffer: Entity<Buffer>,
22082 completions: Rc<RefCell<Box<[Completion]>>>,
22083 completion_index: usize,
22084 push_to_history: bool,
22085 cx: &mut Context<Editor>,
22086 ) -> Task<Result<Option<language::Transaction>>> {
22087 self.update(cx, |project, cx| {
22088 project.lsp_store().update(cx, |lsp_store, cx| {
22089 lsp_store.apply_additional_edits_for_completion(
22090 buffer,
22091 completions,
22092 completion_index,
22093 push_to_history,
22094 cx,
22095 )
22096 })
22097 })
22098 }
22099
22100 fn is_completion_trigger(
22101 &self,
22102 buffer: &Entity<Buffer>,
22103 position: language::Anchor,
22104 text: &str,
22105 trigger_in_words: bool,
22106 menu_is_open: bool,
22107 cx: &mut Context<Editor>,
22108 ) -> bool {
22109 let mut chars = text.chars();
22110 let char = if let Some(char) = chars.next() {
22111 char
22112 } else {
22113 return false;
22114 };
22115 if chars.next().is_some() {
22116 return false;
22117 }
22118
22119 let buffer = buffer.read(cx);
22120 let snapshot = buffer.snapshot();
22121 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
22122 return false;
22123 }
22124 let classifier = snapshot.char_classifier_at(position).for_completion(true);
22125 if trigger_in_words && classifier.is_word(char) {
22126 return true;
22127 }
22128
22129 buffer.completion_triggers().contains(text)
22130 }
22131}
22132
22133impl SemanticsProvider for Entity<Project> {
22134 fn hover(
22135 &self,
22136 buffer: &Entity<Buffer>,
22137 position: text::Anchor,
22138 cx: &mut App,
22139 ) -> Option<Task<Vec<project::Hover>>> {
22140 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
22141 }
22142
22143 fn document_highlights(
22144 &self,
22145 buffer: &Entity<Buffer>,
22146 position: text::Anchor,
22147 cx: &mut App,
22148 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
22149 Some(self.update(cx, |project, cx| {
22150 project.document_highlights(buffer, position, cx)
22151 }))
22152 }
22153
22154 fn definitions(
22155 &self,
22156 buffer: &Entity<Buffer>,
22157 position: text::Anchor,
22158 kind: GotoDefinitionKind,
22159 cx: &mut App,
22160 ) -> Option<Task<Result<Vec<LocationLink>>>> {
22161 Some(self.update(cx, |project, cx| match kind {
22162 GotoDefinitionKind::Symbol => project.definitions(&buffer, position, cx),
22163 GotoDefinitionKind::Declaration => project.declarations(&buffer, position, cx),
22164 GotoDefinitionKind::Type => project.type_definitions(&buffer, position, cx),
22165 GotoDefinitionKind::Implementation => project.implementations(&buffer, position, cx),
22166 }))
22167 }
22168
22169 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
22170 // TODO: make this work for remote projects
22171 self.update(cx, |project, cx| {
22172 if project
22173 .active_debug_session(cx)
22174 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
22175 {
22176 return true;
22177 }
22178
22179 buffer.update(cx, |buffer, cx| {
22180 project.any_language_server_supports_inlay_hints(buffer, cx)
22181 })
22182 })
22183 }
22184
22185 fn inline_values(
22186 &self,
22187 buffer_handle: Entity<Buffer>,
22188 range: Range<text::Anchor>,
22189 cx: &mut App,
22190 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22191 self.update(cx, |project, cx| {
22192 let (session, active_stack_frame) = project.active_debug_session(cx)?;
22193
22194 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
22195 })
22196 }
22197
22198 fn inlay_hints(
22199 &self,
22200 buffer_handle: Entity<Buffer>,
22201 range: Range<text::Anchor>,
22202 cx: &mut App,
22203 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22204 Some(self.update(cx, |project, cx| {
22205 project.inlay_hints(buffer_handle, range, cx)
22206 }))
22207 }
22208
22209 fn resolve_inlay_hint(
22210 &self,
22211 hint: InlayHint,
22212 buffer_handle: Entity<Buffer>,
22213 server_id: LanguageServerId,
22214 cx: &mut App,
22215 ) -> Option<Task<anyhow::Result<InlayHint>>> {
22216 Some(self.update(cx, |project, cx| {
22217 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
22218 }))
22219 }
22220
22221 fn range_for_rename(
22222 &self,
22223 buffer: &Entity<Buffer>,
22224 position: text::Anchor,
22225 cx: &mut App,
22226 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
22227 Some(self.update(cx, |project, cx| {
22228 let buffer = buffer.clone();
22229 let task = project.prepare_rename(buffer.clone(), position, cx);
22230 cx.spawn(async move |_, cx| {
22231 Ok(match task.await? {
22232 PrepareRenameResponse::Success(range) => Some(range),
22233 PrepareRenameResponse::InvalidPosition => None,
22234 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
22235 // Fallback on using TreeSitter info to determine identifier range
22236 buffer.read_with(cx, |buffer, _| {
22237 let snapshot = buffer.snapshot();
22238 let (range, kind) = snapshot.surrounding_word(position, false);
22239 if kind != Some(CharKind::Word) {
22240 return None;
22241 }
22242 Some(
22243 snapshot.anchor_before(range.start)
22244 ..snapshot.anchor_after(range.end),
22245 )
22246 })?
22247 }
22248 })
22249 })
22250 }))
22251 }
22252
22253 fn perform_rename(
22254 &self,
22255 buffer: &Entity<Buffer>,
22256 position: text::Anchor,
22257 new_name: String,
22258 cx: &mut App,
22259 ) -> Option<Task<Result<ProjectTransaction>>> {
22260 Some(self.update(cx, |project, cx| {
22261 project.perform_rename(buffer.clone(), position, new_name, cx)
22262 }))
22263 }
22264}
22265
22266fn inlay_hint_settings(
22267 location: Anchor,
22268 snapshot: &MultiBufferSnapshot,
22269 cx: &mut Context<Editor>,
22270) -> InlayHintSettings {
22271 let file = snapshot.file_at(location);
22272 let language = snapshot.language_at(location).map(|l| l.name());
22273 language_settings(language, file, cx).inlay_hints
22274}
22275
22276fn consume_contiguous_rows(
22277 contiguous_row_selections: &mut Vec<Selection<Point>>,
22278 selection: &Selection<Point>,
22279 display_map: &DisplaySnapshot,
22280 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
22281) -> (MultiBufferRow, MultiBufferRow) {
22282 contiguous_row_selections.push(selection.clone());
22283 let start_row = starting_row(selection, display_map);
22284 let mut end_row = ending_row(selection, display_map);
22285
22286 while let Some(next_selection) = selections.peek() {
22287 if next_selection.start.row <= end_row.0 {
22288 end_row = ending_row(next_selection, display_map);
22289 contiguous_row_selections.push(selections.next().unwrap().clone());
22290 } else {
22291 break;
22292 }
22293 }
22294 (start_row, end_row)
22295}
22296
22297fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22298 if selection.start.column > 0 {
22299 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
22300 } else {
22301 MultiBufferRow(selection.start.row)
22302 }
22303}
22304
22305fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22306 if next_selection.end.column > 0 || next_selection.is_empty() {
22307 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
22308 } else {
22309 MultiBufferRow(next_selection.end.row)
22310 }
22311}
22312
22313impl EditorSnapshot {
22314 pub fn remote_selections_in_range<'a>(
22315 &'a self,
22316 range: &'a Range<Anchor>,
22317 collaboration_hub: &dyn CollaborationHub,
22318 cx: &'a App,
22319 ) -> impl 'a + Iterator<Item = RemoteSelection> {
22320 let participant_names = collaboration_hub.user_names(cx);
22321 let participant_indices = collaboration_hub.user_participant_indices(cx);
22322 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
22323 let collaborators_by_replica_id = collaborators_by_peer_id
22324 .values()
22325 .map(|collaborator| (collaborator.replica_id, collaborator))
22326 .collect::<HashMap<_, _>>();
22327 self.buffer_snapshot
22328 .selections_in_range(range, false)
22329 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
22330 if replica_id == AGENT_REPLICA_ID {
22331 Some(RemoteSelection {
22332 replica_id,
22333 selection,
22334 cursor_shape,
22335 line_mode,
22336 collaborator_id: CollaboratorId::Agent,
22337 user_name: Some("Agent".into()),
22338 color: cx.theme().players().agent(),
22339 })
22340 } else {
22341 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
22342 let participant_index = participant_indices.get(&collaborator.user_id).copied();
22343 let user_name = participant_names.get(&collaborator.user_id).cloned();
22344 Some(RemoteSelection {
22345 replica_id,
22346 selection,
22347 cursor_shape,
22348 line_mode,
22349 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
22350 user_name,
22351 color: if let Some(index) = participant_index {
22352 cx.theme().players().color_for_participant(index.0)
22353 } else {
22354 cx.theme().players().absent()
22355 },
22356 })
22357 }
22358 })
22359 }
22360
22361 pub fn hunks_for_ranges(
22362 &self,
22363 ranges: impl IntoIterator<Item = Range<Point>>,
22364 ) -> Vec<MultiBufferDiffHunk> {
22365 let mut hunks = Vec::new();
22366 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
22367 HashMap::default();
22368 for query_range in ranges {
22369 let query_rows =
22370 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
22371 for hunk in self.buffer_snapshot.diff_hunks_in_range(
22372 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
22373 ) {
22374 // Include deleted hunks that are adjacent to the query range, because
22375 // otherwise they would be missed.
22376 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
22377 if hunk.status().is_deleted() {
22378 intersects_range |= hunk.row_range.start == query_rows.end;
22379 intersects_range |= hunk.row_range.end == query_rows.start;
22380 }
22381 if intersects_range {
22382 if !processed_buffer_rows
22383 .entry(hunk.buffer_id)
22384 .or_default()
22385 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
22386 {
22387 continue;
22388 }
22389 hunks.push(hunk);
22390 }
22391 }
22392 }
22393
22394 hunks
22395 }
22396
22397 fn display_diff_hunks_for_rows<'a>(
22398 &'a self,
22399 display_rows: Range<DisplayRow>,
22400 folded_buffers: &'a HashSet<BufferId>,
22401 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
22402 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
22403 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
22404
22405 self.buffer_snapshot
22406 .diff_hunks_in_range(buffer_start..buffer_end)
22407 .filter_map(|hunk| {
22408 if folded_buffers.contains(&hunk.buffer_id) {
22409 return None;
22410 }
22411
22412 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
22413 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
22414
22415 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
22416 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
22417
22418 let display_hunk = if hunk_display_start.column() != 0 {
22419 DisplayDiffHunk::Folded {
22420 display_row: hunk_display_start.row(),
22421 }
22422 } else {
22423 let mut end_row = hunk_display_end.row();
22424 if hunk_display_end.column() > 0 {
22425 end_row.0 += 1;
22426 }
22427 let is_created_file = hunk.is_created_file();
22428 DisplayDiffHunk::Unfolded {
22429 status: hunk.status(),
22430 diff_base_byte_range: hunk.diff_base_byte_range,
22431 display_row_range: hunk_display_start.row()..end_row,
22432 multi_buffer_range: Anchor::range_in_buffer(
22433 hunk.excerpt_id,
22434 hunk.buffer_id,
22435 hunk.buffer_range,
22436 ),
22437 is_created_file,
22438 }
22439 };
22440
22441 Some(display_hunk)
22442 })
22443 }
22444
22445 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
22446 self.display_snapshot.buffer_snapshot.language_at(position)
22447 }
22448
22449 pub fn is_focused(&self) -> bool {
22450 self.is_focused
22451 }
22452
22453 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
22454 self.placeholder_text.as_ref()
22455 }
22456
22457 pub fn scroll_position(&self) -> gpui::Point<f32> {
22458 self.scroll_anchor.scroll_position(&self.display_snapshot)
22459 }
22460
22461 fn gutter_dimensions(
22462 &self,
22463 font_id: FontId,
22464 font_size: Pixels,
22465 max_line_number_width: Pixels,
22466 cx: &App,
22467 ) -> Option<GutterDimensions> {
22468 if !self.show_gutter {
22469 return None;
22470 }
22471
22472 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
22473 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
22474
22475 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
22476 matches!(
22477 ProjectSettings::get_global(cx).git.git_gutter,
22478 Some(GitGutterSetting::TrackedFiles)
22479 )
22480 });
22481 let gutter_settings = EditorSettings::get_global(cx).gutter;
22482 let show_line_numbers = self
22483 .show_line_numbers
22484 .unwrap_or(gutter_settings.line_numbers);
22485 let line_gutter_width = if show_line_numbers {
22486 // Avoid flicker-like gutter resizes when the line number gains another digit by
22487 // only resizing the gutter on files with > 10**min_line_number_digits lines.
22488 let min_width_for_number_on_gutter =
22489 ch_advance * gutter_settings.min_line_number_digits as f32;
22490 max_line_number_width.max(min_width_for_number_on_gutter)
22491 } else {
22492 0.0.into()
22493 };
22494
22495 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
22496 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
22497
22498 let git_blame_entries_width =
22499 self.git_blame_gutter_max_author_length
22500 .map(|max_author_length| {
22501 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
22502 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
22503
22504 /// The number of characters to dedicate to gaps and margins.
22505 const SPACING_WIDTH: usize = 4;
22506
22507 let max_char_count = max_author_length.min(renderer.max_author_length())
22508 + ::git::SHORT_SHA_LENGTH
22509 + MAX_RELATIVE_TIMESTAMP.len()
22510 + SPACING_WIDTH;
22511
22512 ch_advance * max_char_count
22513 });
22514
22515 let is_singleton = self.buffer_snapshot.is_singleton();
22516
22517 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
22518 left_padding += if !is_singleton {
22519 ch_width * 4.0
22520 } else if show_runnables || show_breakpoints {
22521 ch_width * 3.0
22522 } else if show_git_gutter && show_line_numbers {
22523 ch_width * 2.0
22524 } else if show_git_gutter || show_line_numbers {
22525 ch_width
22526 } else {
22527 px(0.)
22528 };
22529
22530 let shows_folds = is_singleton && gutter_settings.folds;
22531
22532 let right_padding = if shows_folds && show_line_numbers {
22533 ch_width * 4.0
22534 } else if shows_folds || (!is_singleton && show_line_numbers) {
22535 ch_width * 3.0
22536 } else if show_line_numbers {
22537 ch_width
22538 } else {
22539 px(0.)
22540 };
22541
22542 Some(GutterDimensions {
22543 left_padding,
22544 right_padding,
22545 width: line_gutter_width + left_padding + right_padding,
22546 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
22547 git_blame_entries_width,
22548 })
22549 }
22550
22551 pub fn render_crease_toggle(
22552 &self,
22553 buffer_row: MultiBufferRow,
22554 row_contains_cursor: bool,
22555 editor: Entity<Editor>,
22556 window: &mut Window,
22557 cx: &mut App,
22558 ) -> Option<AnyElement> {
22559 let folded = self.is_line_folded(buffer_row);
22560 let mut is_foldable = false;
22561
22562 if let Some(crease) = self
22563 .crease_snapshot
22564 .query_row(buffer_row, &self.buffer_snapshot)
22565 {
22566 is_foldable = true;
22567 match crease {
22568 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
22569 if let Some(render_toggle) = render_toggle {
22570 let toggle_callback =
22571 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
22572 if folded {
22573 editor.update(cx, |editor, cx| {
22574 editor.fold_at(buffer_row, window, cx)
22575 });
22576 } else {
22577 editor.update(cx, |editor, cx| {
22578 editor.unfold_at(buffer_row, window, cx)
22579 });
22580 }
22581 });
22582 return Some((render_toggle)(
22583 buffer_row,
22584 folded,
22585 toggle_callback,
22586 window,
22587 cx,
22588 ));
22589 }
22590 }
22591 }
22592 }
22593
22594 is_foldable |= self.starts_indent(buffer_row);
22595
22596 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
22597 Some(
22598 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
22599 .toggle_state(folded)
22600 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
22601 if folded {
22602 this.unfold_at(buffer_row, window, cx);
22603 } else {
22604 this.fold_at(buffer_row, window, cx);
22605 }
22606 }))
22607 .into_any_element(),
22608 )
22609 } else {
22610 None
22611 }
22612 }
22613
22614 pub fn render_crease_trailer(
22615 &self,
22616 buffer_row: MultiBufferRow,
22617 window: &mut Window,
22618 cx: &mut App,
22619 ) -> Option<AnyElement> {
22620 let folded = self.is_line_folded(buffer_row);
22621 if let Crease::Inline { render_trailer, .. } = self
22622 .crease_snapshot
22623 .query_row(buffer_row, &self.buffer_snapshot)?
22624 {
22625 let render_trailer = render_trailer.as_ref()?;
22626 Some(render_trailer(buffer_row, folded, window, cx))
22627 } else {
22628 None
22629 }
22630 }
22631}
22632
22633impl Deref for EditorSnapshot {
22634 type Target = DisplaySnapshot;
22635
22636 fn deref(&self) -> &Self::Target {
22637 &self.display_snapshot
22638 }
22639}
22640
22641#[derive(Clone, Debug, PartialEq, Eq)]
22642pub enum EditorEvent {
22643 InputIgnored {
22644 text: Arc<str>,
22645 },
22646 InputHandled {
22647 utf16_range_to_replace: Option<Range<isize>>,
22648 text: Arc<str>,
22649 },
22650 ExcerptsAdded {
22651 buffer: Entity<Buffer>,
22652 predecessor: ExcerptId,
22653 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
22654 },
22655 ExcerptsRemoved {
22656 ids: Vec<ExcerptId>,
22657 removed_buffer_ids: Vec<BufferId>,
22658 },
22659 BufferFoldToggled {
22660 ids: Vec<ExcerptId>,
22661 folded: bool,
22662 },
22663 ExcerptsEdited {
22664 ids: Vec<ExcerptId>,
22665 },
22666 ExcerptsExpanded {
22667 ids: Vec<ExcerptId>,
22668 },
22669 BufferEdited,
22670 Edited {
22671 transaction_id: clock::Lamport,
22672 },
22673 Reparsed(BufferId),
22674 Focused,
22675 FocusedIn,
22676 Blurred,
22677 DirtyChanged,
22678 Saved,
22679 TitleChanged,
22680 DiffBaseChanged,
22681 SelectionsChanged {
22682 local: bool,
22683 },
22684 ScrollPositionChanged {
22685 local: bool,
22686 autoscroll: bool,
22687 },
22688 Closed,
22689 TransactionUndone {
22690 transaction_id: clock::Lamport,
22691 },
22692 TransactionBegun {
22693 transaction_id: clock::Lamport,
22694 },
22695 Reloaded,
22696 CursorShapeChanged,
22697 PushedToNavHistory {
22698 anchor: Anchor,
22699 is_deactivate: bool,
22700 },
22701}
22702
22703impl EventEmitter<EditorEvent> for Editor {}
22704
22705impl Focusable for Editor {
22706 fn focus_handle(&self, _cx: &App) -> FocusHandle {
22707 self.focus_handle.clone()
22708 }
22709}
22710
22711impl Render for Editor {
22712 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22713 let settings = ThemeSettings::get_global(cx);
22714
22715 let mut text_style = match self.mode {
22716 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
22717 color: cx.theme().colors().editor_foreground,
22718 font_family: settings.ui_font.family.clone(),
22719 font_features: settings.ui_font.features.clone(),
22720 font_fallbacks: settings.ui_font.fallbacks.clone(),
22721 font_size: rems(0.875).into(),
22722 font_weight: settings.ui_font.weight,
22723 line_height: relative(settings.buffer_line_height.value()),
22724 ..Default::default()
22725 },
22726 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
22727 color: cx.theme().colors().editor_foreground,
22728 font_family: settings.buffer_font.family.clone(),
22729 font_features: settings.buffer_font.features.clone(),
22730 font_fallbacks: settings.buffer_font.fallbacks.clone(),
22731 font_size: settings.buffer_font_size(cx).into(),
22732 font_weight: settings.buffer_font.weight,
22733 line_height: relative(settings.buffer_line_height.value()),
22734 ..Default::default()
22735 },
22736 };
22737 if let Some(text_style_refinement) = &self.text_style_refinement {
22738 text_style.refine(text_style_refinement)
22739 }
22740
22741 let background = match self.mode {
22742 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
22743 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
22744 EditorMode::Full { .. } => cx.theme().colors().editor_background,
22745 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
22746 };
22747
22748 EditorElement::new(
22749 &cx.entity(),
22750 EditorStyle {
22751 background,
22752 border: cx.theme().colors().border,
22753 local_player: cx.theme().players().local(),
22754 text: text_style,
22755 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
22756 syntax: cx.theme().syntax().clone(),
22757 status: cx.theme().status().clone(),
22758 inlay_hints_style: make_inlay_hints_style(cx),
22759 inline_completion_styles: make_suggestion_styles(cx),
22760 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
22761 show_underlines: self.diagnostics_enabled(),
22762 },
22763 )
22764 }
22765}
22766
22767impl EntityInputHandler for Editor {
22768 fn text_for_range(
22769 &mut self,
22770 range_utf16: Range<usize>,
22771 adjusted_range: &mut Option<Range<usize>>,
22772 _: &mut Window,
22773 cx: &mut Context<Self>,
22774 ) -> Option<String> {
22775 let snapshot = self.buffer.read(cx).read(cx);
22776 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
22777 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
22778 if (start.0..end.0) != range_utf16 {
22779 adjusted_range.replace(start.0..end.0);
22780 }
22781 Some(snapshot.text_for_range(start..end).collect())
22782 }
22783
22784 fn selected_text_range(
22785 &mut self,
22786 ignore_disabled_input: bool,
22787 _: &mut Window,
22788 cx: &mut Context<Self>,
22789 ) -> Option<UTF16Selection> {
22790 // Prevent the IME menu from appearing when holding down an alphabetic key
22791 // while input is disabled.
22792 if !ignore_disabled_input && !self.input_enabled {
22793 return None;
22794 }
22795
22796 let selection = self.selections.newest::<OffsetUtf16>(cx);
22797 let range = selection.range();
22798
22799 Some(UTF16Selection {
22800 range: range.start.0..range.end.0,
22801 reversed: selection.reversed,
22802 })
22803 }
22804
22805 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
22806 let snapshot = self.buffer.read(cx).read(cx);
22807 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
22808 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
22809 }
22810
22811 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
22812 self.clear_highlights::<InputComposition>(cx);
22813 self.ime_transaction.take();
22814 }
22815
22816 fn replace_text_in_range(
22817 &mut self,
22818 range_utf16: Option<Range<usize>>,
22819 text: &str,
22820 window: &mut Window,
22821 cx: &mut Context<Self>,
22822 ) {
22823 if !self.input_enabled {
22824 cx.emit(EditorEvent::InputIgnored { text: text.into() });
22825 return;
22826 }
22827
22828 self.transact(window, cx, |this, window, cx| {
22829 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
22830 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
22831 Some(this.selection_replacement_ranges(range_utf16, cx))
22832 } else {
22833 this.marked_text_ranges(cx)
22834 };
22835
22836 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
22837 let newest_selection_id = this.selections.newest_anchor().id;
22838 this.selections
22839 .all::<OffsetUtf16>(cx)
22840 .iter()
22841 .zip(ranges_to_replace.iter())
22842 .find_map(|(selection, range)| {
22843 if selection.id == newest_selection_id {
22844 Some(
22845 (range.start.0 as isize - selection.head().0 as isize)
22846 ..(range.end.0 as isize - selection.head().0 as isize),
22847 )
22848 } else {
22849 None
22850 }
22851 })
22852 });
22853
22854 cx.emit(EditorEvent::InputHandled {
22855 utf16_range_to_replace: range_to_replace,
22856 text: text.into(),
22857 });
22858
22859 if let Some(new_selected_ranges) = new_selected_ranges {
22860 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22861 selections.select_ranges(new_selected_ranges)
22862 });
22863 this.backspace(&Default::default(), window, cx);
22864 }
22865
22866 this.handle_input(text, window, cx);
22867 });
22868
22869 if let Some(transaction) = self.ime_transaction {
22870 self.buffer.update(cx, |buffer, cx| {
22871 buffer.group_until_transaction(transaction, cx);
22872 });
22873 }
22874
22875 self.unmark_text(window, cx);
22876 }
22877
22878 fn replace_and_mark_text_in_range(
22879 &mut self,
22880 range_utf16: Option<Range<usize>>,
22881 text: &str,
22882 new_selected_range_utf16: Option<Range<usize>>,
22883 window: &mut Window,
22884 cx: &mut Context<Self>,
22885 ) {
22886 if !self.input_enabled {
22887 return;
22888 }
22889
22890 let transaction = self.transact(window, cx, |this, window, cx| {
22891 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
22892 let snapshot = this.buffer.read(cx).read(cx);
22893 if let Some(relative_range_utf16) = range_utf16.as_ref() {
22894 for marked_range in &mut marked_ranges {
22895 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
22896 marked_range.start.0 += relative_range_utf16.start;
22897 marked_range.start =
22898 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
22899 marked_range.end =
22900 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
22901 }
22902 }
22903 Some(marked_ranges)
22904 } else if let Some(range_utf16) = range_utf16 {
22905 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
22906 Some(this.selection_replacement_ranges(range_utf16, cx))
22907 } else {
22908 None
22909 };
22910
22911 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
22912 let newest_selection_id = this.selections.newest_anchor().id;
22913 this.selections
22914 .all::<OffsetUtf16>(cx)
22915 .iter()
22916 .zip(ranges_to_replace.iter())
22917 .find_map(|(selection, range)| {
22918 if selection.id == newest_selection_id {
22919 Some(
22920 (range.start.0 as isize - selection.head().0 as isize)
22921 ..(range.end.0 as isize - selection.head().0 as isize),
22922 )
22923 } else {
22924 None
22925 }
22926 })
22927 });
22928
22929 cx.emit(EditorEvent::InputHandled {
22930 utf16_range_to_replace: range_to_replace,
22931 text: text.into(),
22932 });
22933
22934 if let Some(ranges) = ranges_to_replace {
22935 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22936 s.select_ranges(ranges)
22937 });
22938 }
22939
22940 let marked_ranges = {
22941 let snapshot = this.buffer.read(cx).read(cx);
22942 this.selections
22943 .disjoint_anchors()
22944 .iter()
22945 .map(|selection| {
22946 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
22947 })
22948 .collect::<Vec<_>>()
22949 };
22950
22951 if text.is_empty() {
22952 this.unmark_text(window, cx);
22953 } else {
22954 this.highlight_text::<InputComposition>(
22955 marked_ranges.clone(),
22956 HighlightStyle {
22957 underline: Some(UnderlineStyle {
22958 thickness: px(1.),
22959 color: None,
22960 wavy: false,
22961 }),
22962 ..Default::default()
22963 },
22964 cx,
22965 );
22966 }
22967
22968 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
22969 let use_autoclose = this.use_autoclose;
22970 let use_auto_surround = this.use_auto_surround;
22971 this.set_use_autoclose(false);
22972 this.set_use_auto_surround(false);
22973 this.handle_input(text, window, cx);
22974 this.set_use_autoclose(use_autoclose);
22975 this.set_use_auto_surround(use_auto_surround);
22976
22977 if let Some(new_selected_range) = new_selected_range_utf16 {
22978 let snapshot = this.buffer.read(cx).read(cx);
22979 let new_selected_ranges = marked_ranges
22980 .into_iter()
22981 .map(|marked_range| {
22982 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
22983 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
22984 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
22985 snapshot.clip_offset_utf16(new_start, Bias::Left)
22986 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
22987 })
22988 .collect::<Vec<_>>();
22989
22990 drop(snapshot);
22991 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22992 selections.select_ranges(new_selected_ranges)
22993 });
22994 }
22995 });
22996
22997 self.ime_transaction = self.ime_transaction.or(transaction);
22998 if let Some(transaction) = self.ime_transaction {
22999 self.buffer.update(cx, |buffer, cx| {
23000 buffer.group_until_transaction(transaction, cx);
23001 });
23002 }
23003
23004 if self.text_highlights::<InputComposition>(cx).is_none() {
23005 self.ime_transaction.take();
23006 }
23007 }
23008
23009 fn bounds_for_range(
23010 &mut self,
23011 range_utf16: Range<usize>,
23012 element_bounds: gpui::Bounds<Pixels>,
23013 window: &mut Window,
23014 cx: &mut Context<Self>,
23015 ) -> Option<gpui::Bounds<Pixels>> {
23016 let text_layout_details = self.text_layout_details(window);
23017 let CharacterDimensions {
23018 em_width,
23019 em_advance,
23020 line_height,
23021 } = self.character_dimensions(window);
23022
23023 let snapshot = self.snapshot(window, cx);
23024 let scroll_position = snapshot.scroll_position();
23025 let scroll_left = scroll_position.x * em_advance;
23026
23027 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
23028 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
23029 + self.gutter_dimensions.full_width();
23030 let y = line_height * (start.row().as_f32() - scroll_position.y);
23031
23032 Some(Bounds {
23033 origin: element_bounds.origin + point(x, y),
23034 size: size(em_width, line_height),
23035 })
23036 }
23037
23038 fn character_index_for_point(
23039 &mut self,
23040 point: gpui::Point<Pixels>,
23041 _window: &mut Window,
23042 _cx: &mut Context<Self>,
23043 ) -> Option<usize> {
23044 let position_map = self.last_position_map.as_ref()?;
23045 if !position_map.text_hitbox.contains(&point) {
23046 return None;
23047 }
23048 let display_point = position_map.point_for_position(point).previous_valid;
23049 let anchor = position_map
23050 .snapshot
23051 .display_point_to_anchor(display_point, Bias::Left);
23052 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
23053 Some(utf16_offset.0)
23054 }
23055}
23056
23057trait SelectionExt {
23058 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
23059 fn spanned_rows(
23060 &self,
23061 include_end_if_at_line_start: bool,
23062 map: &DisplaySnapshot,
23063 ) -> Range<MultiBufferRow>;
23064}
23065
23066impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
23067 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
23068 let start = self
23069 .start
23070 .to_point(&map.buffer_snapshot)
23071 .to_display_point(map);
23072 let end = self
23073 .end
23074 .to_point(&map.buffer_snapshot)
23075 .to_display_point(map);
23076 if self.reversed {
23077 end..start
23078 } else {
23079 start..end
23080 }
23081 }
23082
23083 fn spanned_rows(
23084 &self,
23085 include_end_if_at_line_start: bool,
23086 map: &DisplaySnapshot,
23087 ) -> Range<MultiBufferRow> {
23088 let start = self.start.to_point(&map.buffer_snapshot);
23089 let mut end = self.end.to_point(&map.buffer_snapshot);
23090 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
23091 end.row -= 1;
23092 }
23093
23094 let buffer_start = map.prev_line_boundary(start).0;
23095 let buffer_end = map.next_line_boundary(end).0;
23096 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
23097 }
23098}
23099
23100impl<T: InvalidationRegion> InvalidationStack<T> {
23101 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
23102 where
23103 S: Clone + ToOffset,
23104 {
23105 while let Some(region) = self.last() {
23106 let all_selections_inside_invalidation_ranges =
23107 if selections.len() == region.ranges().len() {
23108 selections
23109 .iter()
23110 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
23111 .all(|(selection, invalidation_range)| {
23112 let head = selection.head().to_offset(buffer);
23113 invalidation_range.start <= head && invalidation_range.end >= head
23114 })
23115 } else {
23116 false
23117 };
23118
23119 if all_selections_inside_invalidation_ranges {
23120 break;
23121 } else {
23122 self.pop();
23123 }
23124 }
23125 }
23126}
23127
23128impl<T> Default for InvalidationStack<T> {
23129 fn default() -> Self {
23130 Self(Default::default())
23131 }
23132}
23133
23134impl<T> Deref for InvalidationStack<T> {
23135 type Target = Vec<T>;
23136
23137 fn deref(&self) -> &Self::Target {
23138 &self.0
23139 }
23140}
23141
23142impl<T> DerefMut for InvalidationStack<T> {
23143 fn deref_mut(&mut self) -> &mut Self::Target {
23144 &mut self.0
23145 }
23146}
23147
23148impl InvalidationRegion for SnippetState {
23149 fn ranges(&self) -> &[Range<Anchor>] {
23150 &self.ranges[self.active_index]
23151 }
23152}
23153
23154fn inline_completion_edit_text(
23155 current_snapshot: &BufferSnapshot,
23156 edits: &[(Range<Anchor>, String)],
23157 edit_preview: &EditPreview,
23158 include_deletions: bool,
23159 cx: &App,
23160) -> HighlightedText {
23161 let edits = edits
23162 .iter()
23163 .map(|(anchor, text)| {
23164 (
23165 anchor.start.text_anchor..anchor.end.text_anchor,
23166 text.clone(),
23167 )
23168 })
23169 .collect::<Vec<_>>();
23170
23171 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
23172}
23173
23174pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
23175 match severity {
23176 lsp::DiagnosticSeverity::ERROR => colors.error,
23177 lsp::DiagnosticSeverity::WARNING => colors.warning,
23178 lsp::DiagnosticSeverity::INFORMATION => colors.info,
23179 lsp::DiagnosticSeverity::HINT => colors.info,
23180 _ => colors.ignored,
23181 }
23182}
23183
23184pub fn styled_runs_for_code_label<'a>(
23185 label: &'a CodeLabel,
23186 syntax_theme: &'a theme::SyntaxTheme,
23187) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
23188 let fade_out = HighlightStyle {
23189 fade_out: Some(0.35),
23190 ..Default::default()
23191 };
23192
23193 let mut prev_end = label.filter_range.end;
23194 label
23195 .runs
23196 .iter()
23197 .enumerate()
23198 .flat_map(move |(ix, (range, highlight_id))| {
23199 let style = if let Some(style) = highlight_id.style(syntax_theme) {
23200 style
23201 } else {
23202 return Default::default();
23203 };
23204 let mut muted_style = style;
23205 muted_style.highlight(fade_out);
23206
23207 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
23208 if range.start >= label.filter_range.end {
23209 if range.start > prev_end {
23210 runs.push((prev_end..range.start, fade_out));
23211 }
23212 runs.push((range.clone(), muted_style));
23213 } else if range.end <= label.filter_range.end {
23214 runs.push((range.clone(), style));
23215 } else {
23216 runs.push((range.start..label.filter_range.end, style));
23217 runs.push((label.filter_range.end..range.end, muted_style));
23218 }
23219 prev_end = cmp::max(prev_end, range.end);
23220
23221 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
23222 runs.push((prev_end..label.text.len(), fade_out));
23223 }
23224
23225 runs
23226 })
23227}
23228
23229pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
23230 let mut prev_index = 0;
23231 let mut prev_codepoint: Option<char> = None;
23232 text.char_indices()
23233 .chain([(text.len(), '\0')])
23234 .filter_map(move |(index, codepoint)| {
23235 let prev_codepoint = prev_codepoint.replace(codepoint)?;
23236 let is_boundary = index == text.len()
23237 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
23238 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
23239 if is_boundary {
23240 let chunk = &text[prev_index..index];
23241 prev_index = index;
23242 Some(chunk)
23243 } else {
23244 None
23245 }
23246 })
23247}
23248
23249pub trait RangeToAnchorExt: Sized {
23250 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
23251
23252 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
23253 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
23254 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
23255 }
23256}
23257
23258impl<T: ToOffset> RangeToAnchorExt for Range<T> {
23259 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
23260 let start_offset = self.start.to_offset(snapshot);
23261 let end_offset = self.end.to_offset(snapshot);
23262 if start_offset == end_offset {
23263 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
23264 } else {
23265 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
23266 }
23267 }
23268}
23269
23270pub trait RowExt {
23271 fn as_f32(&self) -> f32;
23272
23273 fn next_row(&self) -> Self;
23274
23275 fn previous_row(&self) -> Self;
23276
23277 fn minus(&self, other: Self) -> u32;
23278}
23279
23280impl RowExt for DisplayRow {
23281 fn as_f32(&self) -> f32 {
23282 self.0 as f32
23283 }
23284
23285 fn next_row(&self) -> Self {
23286 Self(self.0 + 1)
23287 }
23288
23289 fn previous_row(&self) -> Self {
23290 Self(self.0.saturating_sub(1))
23291 }
23292
23293 fn minus(&self, other: Self) -> u32 {
23294 self.0 - other.0
23295 }
23296}
23297
23298impl RowExt for MultiBufferRow {
23299 fn as_f32(&self) -> f32 {
23300 self.0 as f32
23301 }
23302
23303 fn next_row(&self) -> Self {
23304 Self(self.0 + 1)
23305 }
23306
23307 fn previous_row(&self) -> Self {
23308 Self(self.0.saturating_sub(1))
23309 }
23310
23311 fn minus(&self, other: Self) -> u32 {
23312 self.0 - other.0
23313 }
23314}
23315
23316trait RowRangeExt {
23317 type Row;
23318
23319 fn len(&self) -> usize;
23320
23321 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
23322}
23323
23324impl RowRangeExt for Range<MultiBufferRow> {
23325 type Row = MultiBufferRow;
23326
23327 fn len(&self) -> usize {
23328 (self.end.0 - self.start.0) as usize
23329 }
23330
23331 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
23332 (self.start.0..self.end.0).map(MultiBufferRow)
23333 }
23334}
23335
23336impl RowRangeExt for Range<DisplayRow> {
23337 type Row = DisplayRow;
23338
23339 fn len(&self) -> usize {
23340 (self.end.0 - self.start.0) as usize
23341 }
23342
23343 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
23344 (self.start.0..self.end.0).map(DisplayRow)
23345 }
23346}
23347
23348/// If select range has more than one line, we
23349/// just point the cursor to range.start.
23350fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
23351 if range.start.row == range.end.row {
23352 range
23353 } else {
23354 range.start..range.start
23355 }
23356}
23357pub struct KillRing(ClipboardItem);
23358impl Global for KillRing {}
23359
23360const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
23361
23362enum BreakpointPromptEditAction {
23363 Log,
23364 Condition,
23365 HitCondition,
23366}
23367
23368struct BreakpointPromptEditor {
23369 pub(crate) prompt: Entity<Editor>,
23370 editor: WeakEntity<Editor>,
23371 breakpoint_anchor: Anchor,
23372 breakpoint: Breakpoint,
23373 edit_action: BreakpointPromptEditAction,
23374 block_ids: HashSet<CustomBlockId>,
23375 editor_margins: Arc<Mutex<EditorMargins>>,
23376 _subscriptions: Vec<Subscription>,
23377}
23378
23379impl BreakpointPromptEditor {
23380 const MAX_LINES: u8 = 4;
23381
23382 fn new(
23383 editor: WeakEntity<Editor>,
23384 breakpoint_anchor: Anchor,
23385 breakpoint: Breakpoint,
23386 edit_action: BreakpointPromptEditAction,
23387 window: &mut Window,
23388 cx: &mut Context<Self>,
23389 ) -> Self {
23390 let base_text = match edit_action {
23391 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
23392 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
23393 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
23394 }
23395 .map(|msg| msg.to_string())
23396 .unwrap_or_default();
23397
23398 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
23399 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
23400
23401 let prompt = cx.new(|cx| {
23402 let mut prompt = Editor::new(
23403 EditorMode::AutoHeight {
23404 min_lines: 1,
23405 max_lines: Some(Self::MAX_LINES as usize),
23406 },
23407 buffer,
23408 None,
23409 window,
23410 cx,
23411 );
23412 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
23413 prompt.set_show_cursor_when_unfocused(false, cx);
23414 prompt.set_placeholder_text(
23415 match edit_action {
23416 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
23417 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
23418 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
23419 },
23420 cx,
23421 );
23422
23423 prompt
23424 });
23425
23426 Self {
23427 prompt,
23428 editor,
23429 breakpoint_anchor,
23430 breakpoint,
23431 edit_action,
23432 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
23433 block_ids: Default::default(),
23434 _subscriptions: vec![],
23435 }
23436 }
23437
23438 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
23439 self.block_ids.extend(block_ids)
23440 }
23441
23442 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
23443 if let Some(editor) = self.editor.upgrade() {
23444 let message = self
23445 .prompt
23446 .read(cx)
23447 .buffer
23448 .read(cx)
23449 .as_singleton()
23450 .expect("A multi buffer in breakpoint prompt isn't possible")
23451 .read(cx)
23452 .as_rope()
23453 .to_string();
23454
23455 editor.update(cx, |editor, cx| {
23456 editor.edit_breakpoint_at_anchor(
23457 self.breakpoint_anchor,
23458 self.breakpoint.clone(),
23459 match self.edit_action {
23460 BreakpointPromptEditAction::Log => {
23461 BreakpointEditAction::EditLogMessage(message.into())
23462 }
23463 BreakpointPromptEditAction::Condition => {
23464 BreakpointEditAction::EditCondition(message.into())
23465 }
23466 BreakpointPromptEditAction::HitCondition => {
23467 BreakpointEditAction::EditHitCondition(message.into())
23468 }
23469 },
23470 cx,
23471 );
23472
23473 editor.remove_blocks(self.block_ids.clone(), None, cx);
23474 cx.focus_self(window);
23475 });
23476 }
23477 }
23478
23479 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
23480 self.editor
23481 .update(cx, |editor, cx| {
23482 editor.remove_blocks(self.block_ids.clone(), None, cx);
23483 window.focus(&editor.focus_handle);
23484 })
23485 .log_err();
23486 }
23487
23488 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
23489 let settings = ThemeSettings::get_global(cx);
23490 let text_style = TextStyle {
23491 color: if self.prompt.read(cx).read_only(cx) {
23492 cx.theme().colors().text_disabled
23493 } else {
23494 cx.theme().colors().text
23495 },
23496 font_family: settings.buffer_font.family.clone(),
23497 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23498 font_size: settings.buffer_font_size(cx).into(),
23499 font_weight: settings.buffer_font.weight,
23500 line_height: relative(settings.buffer_line_height.value()),
23501 ..Default::default()
23502 };
23503 EditorElement::new(
23504 &self.prompt,
23505 EditorStyle {
23506 background: cx.theme().colors().editor_background,
23507 local_player: cx.theme().players().local(),
23508 text: text_style,
23509 ..Default::default()
23510 },
23511 )
23512 }
23513}
23514
23515impl Render for BreakpointPromptEditor {
23516 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23517 let editor_margins = *self.editor_margins.lock();
23518 let gutter_dimensions = editor_margins.gutter;
23519 h_flex()
23520 .key_context("Editor")
23521 .bg(cx.theme().colors().editor_background)
23522 .border_y_1()
23523 .border_color(cx.theme().status().info_border)
23524 .size_full()
23525 .py(window.line_height() / 2.5)
23526 .on_action(cx.listener(Self::confirm))
23527 .on_action(cx.listener(Self::cancel))
23528 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
23529 .child(div().flex_1().child(self.render_prompt_editor(cx)))
23530 }
23531}
23532
23533impl Focusable for BreakpointPromptEditor {
23534 fn focus_handle(&self, cx: &App) -> FocusHandle {
23535 self.prompt.focus_handle(cx)
23536 }
23537}
23538
23539fn all_edits_insertions_or_deletions(
23540 edits: &Vec<(Range<Anchor>, String)>,
23541 snapshot: &MultiBufferSnapshot,
23542) -> bool {
23543 let mut all_insertions = true;
23544 let mut all_deletions = true;
23545
23546 for (range, new_text) in edits.iter() {
23547 let range_is_empty = range.to_offset(&snapshot).is_empty();
23548 let text_is_empty = new_text.is_empty();
23549
23550 if range_is_empty != text_is_empty {
23551 if range_is_empty {
23552 all_deletions = false;
23553 } else {
23554 all_insertions = false;
23555 }
23556 } else {
23557 return false;
23558 }
23559
23560 if !all_insertions && !all_deletions {
23561 return false;
23562 }
23563 }
23564 all_insertions || all_deletions
23565}
23566
23567struct MissingEditPredictionKeybindingTooltip;
23568
23569impl Render for MissingEditPredictionKeybindingTooltip {
23570 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23571 ui::tooltip_container(window, cx, |container, _, cx| {
23572 container
23573 .flex_shrink_0()
23574 .max_w_80()
23575 .min_h(rems_from_px(124.))
23576 .justify_between()
23577 .child(
23578 v_flex()
23579 .flex_1()
23580 .text_ui_sm(cx)
23581 .child(Label::new("Conflict with Accept Keybinding"))
23582 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
23583 )
23584 .child(
23585 h_flex()
23586 .pb_1()
23587 .gap_1()
23588 .items_end()
23589 .w_full()
23590 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
23591 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
23592 }))
23593 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
23594 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
23595 })),
23596 )
23597 })
23598 }
23599}
23600
23601#[derive(Debug, Clone, Copy, PartialEq)]
23602pub struct LineHighlight {
23603 pub background: Background,
23604 pub border: Option<gpui::Hsla>,
23605 pub include_gutter: bool,
23606 pub type_id: Option<TypeId>,
23607}
23608
23609struct LineManipulationResult {
23610 pub new_text: String,
23611 pub line_count_before: usize,
23612 pub line_count_after: usize,
23613}
23614
23615fn render_diff_hunk_controls(
23616 row: u32,
23617 status: &DiffHunkStatus,
23618 hunk_range: Range<Anchor>,
23619 is_created_file: bool,
23620 line_height: Pixels,
23621 editor: &Entity<Editor>,
23622 _window: &mut Window,
23623 cx: &mut App,
23624) -> AnyElement {
23625 h_flex()
23626 .h(line_height)
23627 .mr_1()
23628 .gap_1()
23629 .px_0p5()
23630 .pb_1()
23631 .border_x_1()
23632 .border_b_1()
23633 .border_color(cx.theme().colors().border_variant)
23634 .rounded_b_lg()
23635 .bg(cx.theme().colors().editor_background)
23636 .gap_1()
23637 .block_mouse_except_scroll()
23638 .shadow_md()
23639 .child(if status.has_secondary_hunk() {
23640 Button::new(("stage", row as u64), "Stage")
23641 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
23642 .tooltip({
23643 let focus_handle = editor.focus_handle(cx);
23644 move |window, cx| {
23645 Tooltip::for_action_in(
23646 "Stage Hunk",
23647 &::git::ToggleStaged,
23648 &focus_handle,
23649 window,
23650 cx,
23651 )
23652 }
23653 })
23654 .on_click({
23655 let editor = editor.clone();
23656 move |_event, _window, cx| {
23657 editor.update(cx, |editor, cx| {
23658 editor.stage_or_unstage_diff_hunks(
23659 true,
23660 vec![hunk_range.start..hunk_range.start],
23661 cx,
23662 );
23663 });
23664 }
23665 })
23666 } else {
23667 Button::new(("unstage", row as u64), "Unstage")
23668 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
23669 .tooltip({
23670 let focus_handle = editor.focus_handle(cx);
23671 move |window, cx| {
23672 Tooltip::for_action_in(
23673 "Unstage Hunk",
23674 &::git::ToggleStaged,
23675 &focus_handle,
23676 window,
23677 cx,
23678 )
23679 }
23680 })
23681 .on_click({
23682 let editor = editor.clone();
23683 move |_event, _window, cx| {
23684 editor.update(cx, |editor, cx| {
23685 editor.stage_or_unstage_diff_hunks(
23686 false,
23687 vec![hunk_range.start..hunk_range.start],
23688 cx,
23689 );
23690 });
23691 }
23692 })
23693 })
23694 .child(
23695 Button::new(("restore", row as u64), "Restore")
23696 .tooltip({
23697 let focus_handle = editor.focus_handle(cx);
23698 move |window, cx| {
23699 Tooltip::for_action_in(
23700 "Restore Hunk",
23701 &::git::Restore,
23702 &focus_handle,
23703 window,
23704 cx,
23705 )
23706 }
23707 })
23708 .on_click({
23709 let editor = editor.clone();
23710 move |_event, window, cx| {
23711 editor.update(cx, |editor, cx| {
23712 let snapshot = editor.snapshot(window, cx);
23713 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
23714 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
23715 });
23716 }
23717 })
23718 .disabled(is_created_file),
23719 )
23720 .when(
23721 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
23722 |el| {
23723 el.child(
23724 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
23725 .shape(IconButtonShape::Square)
23726 .icon_size(IconSize::Small)
23727 // .disabled(!has_multiple_hunks)
23728 .tooltip({
23729 let focus_handle = editor.focus_handle(cx);
23730 move |window, cx| {
23731 Tooltip::for_action_in(
23732 "Next Hunk",
23733 &GoToHunk,
23734 &focus_handle,
23735 window,
23736 cx,
23737 )
23738 }
23739 })
23740 .on_click({
23741 let editor = editor.clone();
23742 move |_event, window, cx| {
23743 editor.update(cx, |editor, cx| {
23744 let snapshot = editor.snapshot(window, cx);
23745 let position =
23746 hunk_range.end.to_point(&snapshot.buffer_snapshot);
23747 editor.go_to_hunk_before_or_after_position(
23748 &snapshot,
23749 position,
23750 Direction::Next,
23751 window,
23752 cx,
23753 );
23754 editor.expand_selected_diff_hunks(cx);
23755 });
23756 }
23757 }),
23758 )
23759 .child(
23760 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
23761 .shape(IconButtonShape::Square)
23762 .icon_size(IconSize::Small)
23763 // .disabled(!has_multiple_hunks)
23764 .tooltip({
23765 let focus_handle = editor.focus_handle(cx);
23766 move |window, cx| {
23767 Tooltip::for_action_in(
23768 "Previous Hunk",
23769 &GoToPreviousHunk,
23770 &focus_handle,
23771 window,
23772 cx,
23773 )
23774 }
23775 })
23776 .on_click({
23777 let editor = editor.clone();
23778 move |_event, window, cx| {
23779 editor.update(cx, |editor, cx| {
23780 let snapshot = editor.snapshot(window, cx);
23781 let point =
23782 hunk_range.start.to_point(&snapshot.buffer_snapshot);
23783 editor.go_to_hunk_before_or_after_position(
23784 &snapshot,
23785 point,
23786 Direction::Prev,
23787 window,
23788 cx,
23789 );
23790 editor.expand_selected_diff_hunks(cx);
23791 });
23792 }
23793 }),
23794 )
23795 },
23796 )
23797 .into_any_element()
23798}