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, ShowMinimap, ShowScrollbar,
69};
70use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
71pub use editor_settings_controls::*;
72use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
73pub use element::{
74 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
75};
76use futures::{
77 FutureExt, StreamExt as _,
78 future::{self, Shared, join},
79 stream::FuturesUnordered,
80};
81use fuzzy::{StringMatch, StringMatchCandidate};
82use lsp_colors::LspColorData;
83
84use ::git::blame::BlameEntry;
85use ::git::{Restore, blame::ParsedCommitMessage};
86use code_context_menus::{
87 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
88 CompletionsMenu, ContextMenuOrigin,
89};
90use git::blame::{GitBlame, GlobalBlameRenderer};
91use gpui::{
92 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
93 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
94 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
95 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
96 MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render, ScrollHandle,
97 SharedString, Size, Stateful, Styled, Subscription, Task, TextStyle, TextStyleRefinement,
98 UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
99 div, point, prelude::*, pulsating_between, px, relative, size,
100};
101use highlight_matching_bracket::refresh_matching_bracket_highlights;
102use hover_links::{HoverLink, HoveredLinkState, InlayHighlight, find_file};
103pub use hover_popover::hover_markdown_style;
104use hover_popover::{HoverState, hide_hover};
105use indent_guides::ActiveIndentGuidesState;
106use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
107pub use inline_completion::Direction;
108use inline_completion::{EditPredictionProvider, InlineCompletionProviderHandle};
109pub use items::MAX_TAB_TITLE_LEN;
110use itertools::Itertools;
111use language::{
112 AutoindentMode, BlockCommentConfig, BracketMatch, BracketPair, Buffer, Capability, CharKind,
113 CodeLabel, CursorShape, DiagnosticEntry, DiffOptions, EditPredictionsMode, EditPreview,
114 HighlightedText, IndentKind, IndentSize, Language, OffsetRangeExt, Point, Selection,
115 SelectionGoal, TextObject, TransactionId, TreeSitterOptions, WordsQuery,
116 language_settings::{
117 self, InlayHintSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
118 all_language_settings, language_settings,
119 },
120 point_from_lsp, text_diff_with_options,
121};
122use language::{BufferRow, CharClassifier, Runnable, RunnableRange, point_to_lsp};
123use linked_editing_ranges::refresh_linked_ranges;
124use markdown::Markdown;
125use mouse_context_menu::MouseContextMenu;
126use persistence::DB;
127use project::{
128 BreakpointWithPosition, CompletionResponse, DisableAiSettings, ProjectPath,
129 debugger::{
130 breakpoint_store::{
131 BreakpointEditAction, BreakpointSessionState, BreakpointState, BreakpointStore,
132 BreakpointStoreEvent,
133 },
134 session::{Session, SessionEvent},
135 },
136 git_store::{GitStoreEvent, RepositoryEvent},
137 project_settings::{DiagnosticSeverity, GoToDiagnosticSeverityFilter},
138};
139
140pub use git::blame::BlameRenderer;
141pub use proposed_changes_editor::{
142 ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
143};
144use std::{cell::OnceCell, iter::Peekable, ops::Not};
145use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
146
147pub use lsp::CompletionContext;
148use lsp::{
149 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
150 LanguageServerId, LanguageServerName,
151};
152
153use language::BufferSnapshot;
154pub use lsp_ext::lsp_tasks;
155use movement::TextLayoutDetails;
156pub use multi_buffer::{
157 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, PathKey,
158 RowInfo, ToOffset, ToPoint,
159};
160use multi_buffer::{
161 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
162 MultiOrSingleBufferOffsetRange, ToOffsetUtf16,
163};
164use parking_lot::Mutex;
165use project::{
166 CodeAction, Completion, CompletionIntent, CompletionSource, DocumentHighlight, InlayHint,
167 Location, LocationLink, PrepareRenameResponse, Project, ProjectItem, ProjectTransaction,
168 TaskSourceKind,
169 debugger::breakpoint_store::Breakpoint,
170 lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
171 project_settings::{GitGutterSetting, ProjectSettings},
172};
173use rand::prelude::*;
174use rpc::{ErrorExt, proto::*};
175use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide};
176use selections_collection::{
177 MutableSelectionsCollection, SelectionsCollection, resolve_selections,
178};
179use serde::{Deserialize, Serialize};
180use settings::{Settings, SettingsLocation, SettingsStore, update_settings_file};
181use smallvec::{SmallVec, smallvec};
182use snippet::Snippet;
183use std::sync::Arc;
184use std::{
185 any::TypeId,
186 borrow::Cow,
187 cell::RefCell,
188 cmp::{self, Ordering, Reverse},
189 mem,
190 num::NonZeroU32,
191 ops::{ControlFlow, Deref, DerefMut, Range, RangeInclusive},
192 path::{Path, PathBuf},
193 rc::Rc,
194 time::{Duration, Instant},
195};
196pub use sum_tree::Bias;
197use sum_tree::TreeMap;
198use text::{BufferId, FromAnchor, OffsetUtf16, Rope};
199use theme::{
200 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, Theme, ThemeSettings,
201 observe_buffer_font_size_adjustment,
202};
203use ui::{
204 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
205 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*,
206};
207use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
208use workspace::{
209 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
210 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
211 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
212 item::{ItemHandle, PreviewTabsSettings, SaveOptions},
213 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
214 searchable::SearchEvent,
215};
216use zed_actions;
217
218use crate::{
219 code_context_menus::CompletionsMenuSource,
220 hover_links::{find_url, find_url_from_range},
221};
222use crate::{
223 editor_settings::MultiCursorModifier,
224 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
225};
226
227pub const FILE_HEADER_HEIGHT: u32 = 2;
228pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
229pub const DEFAULT_MULTIBUFFER_CONTEXT: u32 = 2;
230const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
231const MAX_LINE_LEN: usize = 1024;
232const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
233const MAX_SELECTION_HISTORY_LEN: usize = 1024;
234pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
235#[doc(hidden)]
236pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
237const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
238
239pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
240pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
241pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
242
243pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
244pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
245pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
246
247pub type RenderDiffHunkControlsFn = Arc<
248 dyn Fn(
249 u32,
250 &DiffHunkStatus,
251 Range<Anchor>,
252 bool,
253 Pixels,
254 &Entity<Editor>,
255 &mut Window,
256 &mut App,
257 ) -> AnyElement,
258>;
259
260struct InlineValueCache {
261 enabled: bool,
262 inlays: Vec<InlayId>,
263 refresh_task: Task<Option<()>>,
264}
265
266impl InlineValueCache {
267 fn new(enabled: bool) -> Self {
268 Self {
269 enabled,
270 inlays: Vec::new(),
271 refresh_task: Task::ready(None),
272 }
273 }
274}
275
276#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
277pub enum InlayId {
278 InlineCompletion(usize),
279 DebuggerValue(usize),
280 // LSP
281 Hint(usize),
282 Color(usize),
283}
284
285impl InlayId {
286 fn id(&self) -> usize {
287 match self {
288 Self::InlineCompletion(id) => *id,
289 Self::DebuggerValue(id) => *id,
290 Self::Hint(id) => *id,
291 Self::Color(id) => *id,
292 }
293 }
294}
295
296pub enum ActiveDebugLine {}
297pub enum DebugStackFrameLine {}
298enum DocumentHighlightRead {}
299enum DocumentHighlightWrite {}
300enum InputComposition {}
301pub enum PendingInput {}
302enum SelectedTextHighlight {}
303
304pub enum ConflictsOuter {}
305pub enum ConflictsOurs {}
306pub enum ConflictsTheirs {}
307pub enum ConflictsOursMarker {}
308pub enum ConflictsTheirsMarker {}
309
310#[derive(Debug, Copy, Clone, PartialEq, Eq)]
311pub enum Navigated {
312 Yes,
313 No,
314}
315
316impl Navigated {
317 pub fn from_bool(yes: bool) -> Navigated {
318 if yes { Navigated::Yes } else { Navigated::No }
319 }
320}
321
322#[derive(Debug, Clone, PartialEq, Eq)]
323enum DisplayDiffHunk {
324 Folded {
325 display_row: DisplayRow,
326 },
327 Unfolded {
328 is_created_file: bool,
329 diff_base_byte_range: Range<usize>,
330 display_row_range: Range<DisplayRow>,
331 multi_buffer_range: Range<Anchor>,
332 status: DiffHunkStatus,
333 },
334}
335
336pub enum HideMouseCursorOrigin {
337 TypingAction,
338 MovementAction,
339}
340
341pub fn init_settings(cx: &mut App) {
342 EditorSettings::register(cx);
343}
344
345pub fn init(cx: &mut App) {
346 init_settings(cx);
347
348 cx.set_global(GlobalBlameRenderer(Arc::new(())));
349
350 workspace::register_project_item::<Editor>(cx);
351 workspace::FollowableViewRegistry::register::<Editor>(cx);
352 workspace::register_serializable_item::<Editor>(cx);
353
354 cx.observe_new(
355 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
356 workspace.register_action(Editor::new_file);
357 workspace.register_action(Editor::new_file_vertical);
358 workspace.register_action(Editor::new_file_horizontal);
359 workspace.register_action(Editor::cancel_language_server_work);
360 workspace.register_action(Editor::toggle_focus);
361 },
362 )
363 .detach();
364
365 cx.on_action(move |_: &workspace::NewFile, cx| {
366 let app_state = workspace::AppState::global(cx);
367 if let Some(app_state) = app_state.upgrade() {
368 workspace::open_new(
369 Default::default(),
370 app_state,
371 cx,
372 |workspace, window, cx| {
373 Editor::new_file(workspace, &Default::default(), window, cx)
374 },
375 )
376 .detach();
377 }
378 });
379 cx.on_action(move |_: &workspace::NewWindow, cx| {
380 let app_state = workspace::AppState::global(cx);
381 if let Some(app_state) = app_state.upgrade() {
382 workspace::open_new(
383 Default::default(),
384 app_state,
385 cx,
386 |workspace, window, cx| {
387 cx.activate(true);
388 Editor::new_file(workspace, &Default::default(), window, cx)
389 },
390 )
391 .detach();
392 }
393 });
394}
395
396pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
397 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
398}
399
400pub trait DiagnosticRenderer {
401 fn render_group(
402 &self,
403 diagnostic_group: Vec<DiagnosticEntry<Point>>,
404 buffer_id: BufferId,
405 snapshot: EditorSnapshot,
406 editor: WeakEntity<Editor>,
407 cx: &mut App,
408 ) -> Vec<BlockProperties<Anchor>>;
409
410 fn render_hover(
411 &self,
412 diagnostic_group: Vec<DiagnosticEntry<Point>>,
413 range: Range<Point>,
414 buffer_id: BufferId,
415 cx: &mut App,
416 ) -> Option<Entity<markdown::Markdown>>;
417
418 fn open_link(
419 &self,
420 editor: &mut Editor,
421 link: SharedString,
422 window: &mut Window,
423 cx: &mut Context<Editor>,
424 );
425}
426
427pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
428
429impl GlobalDiagnosticRenderer {
430 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
431 cx.try_global::<Self>().map(|g| g.0.clone())
432 }
433}
434
435impl gpui::Global for GlobalDiagnosticRenderer {}
436pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
437 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
438}
439
440pub struct SearchWithinRange;
441
442trait InvalidationRegion {
443 fn ranges(&self) -> &[Range<Anchor>];
444}
445
446#[derive(Clone, Debug, PartialEq)]
447pub enum SelectPhase {
448 Begin {
449 position: DisplayPoint,
450 add: bool,
451 click_count: usize,
452 },
453 BeginColumnar {
454 position: DisplayPoint,
455 reset: bool,
456 mode: ColumnarMode,
457 goal_column: u32,
458 },
459 Extend {
460 position: DisplayPoint,
461 click_count: usize,
462 },
463 Update {
464 position: DisplayPoint,
465 goal_column: u32,
466 scroll_delta: gpui::Point<f32>,
467 },
468 End,
469}
470
471#[derive(Clone, Debug, PartialEq)]
472pub enum ColumnarMode {
473 FromMouse,
474 FromSelection,
475}
476
477#[derive(Clone, Debug)]
478pub enum SelectMode {
479 Character,
480 Word(Range<Anchor>),
481 Line(Range<Anchor>),
482 All,
483}
484
485#[derive(Clone, PartialEq, Eq, Debug)]
486pub enum EditorMode {
487 SingleLine,
488 AutoHeight {
489 min_lines: usize,
490 max_lines: Option<usize>,
491 },
492 Full {
493 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
494 scale_ui_elements_with_buffer_font_size: bool,
495 /// When set to `true`, the editor will render a background for the active line.
496 show_active_line_background: bool,
497 /// When set to `true`, the editor's height will be determined by its content.
498 sized_by_content: bool,
499 },
500 Minimap {
501 parent: WeakEntity<Editor>,
502 },
503}
504
505impl EditorMode {
506 pub fn full() -> Self {
507 Self::Full {
508 scale_ui_elements_with_buffer_font_size: true,
509 show_active_line_background: true,
510 sized_by_content: false,
511 }
512 }
513
514 #[inline]
515 pub fn is_full(&self) -> bool {
516 matches!(self, Self::Full { .. })
517 }
518
519 #[inline]
520 pub fn is_single_line(&self) -> bool {
521 matches!(self, Self::SingleLine { .. })
522 }
523
524 #[inline]
525 fn is_minimap(&self) -> bool {
526 matches!(self, Self::Minimap { .. })
527 }
528}
529
530#[derive(Copy, Clone, Debug)]
531pub enum SoftWrap {
532 /// Prefer not to wrap at all.
533 ///
534 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
535 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
536 GitDiff,
537 /// Prefer a single line generally, unless an overly long line is encountered.
538 None,
539 /// Soft wrap lines that exceed the editor width.
540 EditorWidth,
541 /// Soft wrap lines at the preferred line length.
542 Column(u32),
543 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
544 Bounded(u32),
545}
546
547#[derive(Clone)]
548pub struct EditorStyle {
549 pub background: Hsla,
550 pub border: Hsla,
551 pub local_player: PlayerColor,
552 pub text: TextStyle,
553 pub scrollbar_width: Pixels,
554 pub syntax: Arc<SyntaxTheme>,
555 pub status: StatusColors,
556 pub inlay_hints_style: HighlightStyle,
557 pub inline_completion_styles: InlineCompletionStyles,
558 pub unnecessary_code_fade: f32,
559 pub show_underlines: bool,
560}
561
562impl Default for EditorStyle {
563 fn default() -> Self {
564 Self {
565 background: Hsla::default(),
566 border: Hsla::default(),
567 local_player: PlayerColor::default(),
568 text: TextStyle::default(),
569 scrollbar_width: Pixels::default(),
570 syntax: Default::default(),
571 // HACK: Status colors don't have a real default.
572 // We should look into removing the status colors from the editor
573 // style and retrieve them directly from the theme.
574 status: StatusColors::dark(),
575 inlay_hints_style: HighlightStyle::default(),
576 inline_completion_styles: InlineCompletionStyles {
577 insertion: HighlightStyle::default(),
578 whitespace: HighlightStyle::default(),
579 },
580 unnecessary_code_fade: Default::default(),
581 show_underlines: true,
582 }
583 }
584}
585
586pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
587 let show_background = language_settings::language_settings(None, None, cx)
588 .inlay_hints
589 .show_background;
590
591 HighlightStyle {
592 color: Some(cx.theme().status().hint),
593 background_color: show_background.then(|| cx.theme().status().hint_background),
594 ..HighlightStyle::default()
595 }
596}
597
598pub fn make_suggestion_styles(cx: &mut App) -> InlineCompletionStyles {
599 InlineCompletionStyles {
600 insertion: HighlightStyle {
601 color: Some(cx.theme().status().predictive),
602 ..HighlightStyle::default()
603 },
604 whitespace: HighlightStyle {
605 background_color: Some(cx.theme().status().created_background),
606 ..HighlightStyle::default()
607 },
608 }
609}
610
611type CompletionId = usize;
612
613pub(crate) enum EditDisplayMode {
614 TabAccept,
615 DiffPopover,
616 Inline,
617}
618
619enum InlineCompletion {
620 Edit {
621 edits: Vec<(Range<Anchor>, String)>,
622 edit_preview: Option<EditPreview>,
623 display_mode: EditDisplayMode,
624 snapshot: BufferSnapshot,
625 },
626 Move {
627 target: Anchor,
628 snapshot: BufferSnapshot,
629 },
630}
631
632struct InlineCompletionState {
633 inlay_ids: Vec<InlayId>,
634 completion: InlineCompletion,
635 completion_id: Option<SharedString>,
636 invalidation_range: Range<Anchor>,
637}
638
639enum EditPredictionSettings {
640 Disabled,
641 Enabled {
642 show_in_menu: bool,
643 preview_requires_modifier: bool,
644 },
645}
646
647enum InlineCompletionHighlight {}
648
649#[derive(Debug, Clone)]
650struct InlineDiagnostic {
651 message: SharedString,
652 group_id: usize,
653 is_primary: bool,
654 start: Point,
655 severity: lsp::DiagnosticSeverity,
656}
657
658pub enum MenuInlineCompletionsPolicy {
659 Never,
660 ByProvider,
661}
662
663pub enum EditPredictionPreview {
664 /// Modifier is not pressed
665 Inactive { released_too_fast: bool },
666 /// Modifier pressed
667 Active {
668 since: Instant,
669 previous_scroll_position: Option<ScrollAnchor>,
670 },
671}
672
673impl EditPredictionPreview {
674 pub fn released_too_fast(&self) -> bool {
675 match self {
676 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
677 EditPredictionPreview::Active { .. } => false,
678 }
679 }
680
681 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
682 if let EditPredictionPreview::Active {
683 previous_scroll_position,
684 ..
685 } = self
686 {
687 *previous_scroll_position = scroll_position;
688 }
689 }
690}
691
692pub struct ContextMenuOptions {
693 pub min_entries_visible: usize,
694 pub max_entries_visible: usize,
695 pub placement: Option<ContextMenuPlacement>,
696}
697
698#[derive(Debug, Clone, PartialEq, Eq)]
699pub enum ContextMenuPlacement {
700 Above,
701 Below,
702}
703
704#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
705struct EditorActionId(usize);
706
707impl EditorActionId {
708 pub fn post_inc(&mut self) -> Self {
709 let answer = self.0;
710
711 *self = Self(answer + 1);
712
713 Self(answer)
714 }
715}
716
717// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
718// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
719
720type BackgroundHighlight = (fn(&Theme) -> Hsla, Arc<[Range<Anchor>]>);
721type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
722
723#[derive(Default)]
724struct ScrollbarMarkerState {
725 scrollbar_size: Size<Pixels>,
726 dirty: bool,
727 markers: Arc<[PaintQuad]>,
728 pending_refresh: Option<Task<Result<()>>>,
729}
730
731impl ScrollbarMarkerState {
732 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
733 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
734 }
735}
736
737#[derive(Clone, Copy, PartialEq, Eq)]
738pub enum MinimapVisibility {
739 Disabled,
740 Enabled {
741 /// The configuration currently present in the users settings.
742 setting_configuration: bool,
743 /// Whether to override the currently set visibility from the users setting.
744 toggle_override: bool,
745 },
746}
747
748impl MinimapVisibility {
749 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
750 if mode.is_full() {
751 Self::Enabled {
752 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
753 toggle_override: false,
754 }
755 } else {
756 Self::Disabled
757 }
758 }
759
760 fn hidden(&self) -> Self {
761 match *self {
762 Self::Enabled {
763 setting_configuration,
764 ..
765 } => Self::Enabled {
766 setting_configuration,
767 toggle_override: setting_configuration,
768 },
769 Self::Disabled => Self::Disabled,
770 }
771 }
772
773 fn disabled(&self) -> bool {
774 match *self {
775 Self::Disabled => true,
776 _ => false,
777 }
778 }
779
780 fn settings_visibility(&self) -> bool {
781 match *self {
782 Self::Enabled {
783 setting_configuration,
784 ..
785 } => setting_configuration,
786 _ => false,
787 }
788 }
789
790 fn visible(&self) -> bool {
791 match *self {
792 Self::Enabled {
793 setting_configuration,
794 toggle_override,
795 } => setting_configuration ^ toggle_override,
796 _ => false,
797 }
798 }
799
800 fn toggle_visibility(&self) -> Self {
801 match *self {
802 Self::Enabled {
803 toggle_override,
804 setting_configuration,
805 } => Self::Enabled {
806 setting_configuration,
807 toggle_override: !toggle_override,
808 },
809 Self::Disabled => Self::Disabled,
810 }
811 }
812}
813
814#[derive(Clone, Debug)]
815struct RunnableTasks {
816 templates: Vec<(TaskSourceKind, TaskTemplate)>,
817 offset: multi_buffer::Anchor,
818 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
819 column: u32,
820 // Values of all named captures, including those starting with '_'
821 extra_variables: HashMap<String, String>,
822 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
823 context_range: Range<BufferOffset>,
824}
825
826impl RunnableTasks {
827 fn resolve<'a>(
828 &'a self,
829 cx: &'a task::TaskContext,
830 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
831 self.templates.iter().filter_map(|(kind, template)| {
832 template
833 .resolve_task(&kind.to_id_base(), cx)
834 .map(|task| (kind.clone(), task))
835 })
836 }
837}
838
839#[derive(Clone)]
840pub struct ResolvedTasks {
841 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
842 position: Anchor,
843}
844
845#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
846struct BufferOffset(usize);
847
848// Addons allow storing per-editor state in other crates (e.g. Vim)
849pub trait Addon: 'static {
850 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
851
852 fn render_buffer_header_controls(
853 &self,
854 _: &ExcerptInfo,
855 _: &Window,
856 _: &App,
857 ) -> Option<AnyElement> {
858 None
859 }
860
861 fn to_any(&self) -> &dyn std::any::Any;
862
863 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
864 None
865 }
866}
867
868struct ChangeLocation {
869 current: Option<Vec<Anchor>>,
870 original: Vec<Anchor>,
871}
872impl ChangeLocation {
873 fn locations(&self) -> &[Anchor] {
874 self.current.as_ref().unwrap_or(&self.original)
875 }
876}
877
878/// A set of caret positions, registered when the editor was edited.
879pub struct ChangeList {
880 changes: Vec<ChangeLocation>,
881 /// Currently "selected" change.
882 position: Option<usize>,
883}
884
885impl ChangeList {
886 pub fn new() -> Self {
887 Self {
888 changes: Vec::new(),
889 position: None,
890 }
891 }
892
893 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
894 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
895 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
896 if self.changes.is_empty() {
897 return None;
898 }
899
900 let prev = self.position.unwrap_or(self.changes.len());
901 let next = if direction == Direction::Prev {
902 prev.saturating_sub(count)
903 } else {
904 (prev + count).min(self.changes.len() - 1)
905 };
906 self.position = Some(next);
907 self.changes.get(next).map(|change| change.locations())
908 }
909
910 /// Adds a new change to the list, resetting the change list position.
911 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
912 self.position.take();
913 if let Some(last) = self.changes.last_mut()
914 && group
915 {
916 last.current = Some(new_positions)
917 } else {
918 self.changes.push(ChangeLocation {
919 original: new_positions,
920 current: None,
921 });
922 }
923 }
924
925 pub fn last(&self) -> Option<&[Anchor]> {
926 self.changes.last().map(|change| change.locations())
927 }
928
929 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
930 self.changes.last().map(|change| change.original.as_slice())
931 }
932
933 pub fn invert_last_group(&mut self) {
934 if let Some(last) = self.changes.last_mut() {
935 if let Some(current) = last.current.as_mut() {
936 mem::swap(&mut last.original, current);
937 }
938 }
939 }
940}
941
942#[derive(Clone)]
943struct InlineBlamePopoverState {
944 scroll_handle: ScrollHandle,
945 commit_message: Option<ParsedCommitMessage>,
946 markdown: Entity<Markdown>,
947}
948
949struct InlineBlamePopover {
950 position: gpui::Point<Pixels>,
951 hide_task: Option<Task<()>>,
952 popover_bounds: Option<Bounds<Pixels>>,
953 popover_state: InlineBlamePopoverState,
954 keyboard_grace: bool,
955}
956
957enum SelectionDragState {
958 /// State when no drag related activity is detected.
959 None,
960 /// State when the mouse is down on a selection that is about to be dragged.
961 ReadyToDrag {
962 selection: Selection<Anchor>,
963 click_position: gpui::Point<Pixels>,
964 mouse_down_time: Instant,
965 },
966 /// State when the mouse is dragging the selection in the editor.
967 Dragging {
968 selection: Selection<Anchor>,
969 drop_cursor: Selection<Anchor>,
970 hide_drop_cursor: bool,
971 },
972}
973
974enum ColumnarSelectionState {
975 FromMouse {
976 selection_tail: Anchor,
977 display_point: Option<DisplayPoint>,
978 },
979 FromSelection {
980 selection_tail: Anchor,
981 },
982}
983
984/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
985/// a breakpoint on them.
986#[derive(Clone, Copy, Debug, PartialEq, Eq)]
987struct PhantomBreakpointIndicator {
988 display_row: DisplayRow,
989 /// There's a small debounce between hovering over the line and showing the indicator.
990 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
991 is_active: bool,
992 collides_with_existing_breakpoint: bool,
993}
994
995/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
996///
997/// See the [module level documentation](self) for more information.
998pub struct Editor {
999 focus_handle: FocusHandle,
1000 last_focused_descendant: Option<WeakFocusHandle>,
1001 /// The text buffer being edited
1002 buffer: Entity<MultiBuffer>,
1003 /// Map of how text in the buffer should be displayed.
1004 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1005 pub display_map: Entity<DisplayMap>,
1006 pub selections: SelectionsCollection,
1007 pub scroll_manager: ScrollManager,
1008 /// When inline assist editors are linked, they all render cursors because
1009 /// typing enters text into each of them, even the ones that aren't focused.
1010 pub(crate) show_cursor_when_unfocused: bool,
1011 columnar_selection_state: Option<ColumnarSelectionState>,
1012 add_selections_state: Option<AddSelectionsState>,
1013 select_next_state: Option<SelectNextState>,
1014 select_prev_state: Option<SelectNextState>,
1015 selection_history: SelectionHistory,
1016 defer_selection_effects: bool,
1017 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1018 autoclose_regions: Vec<AutocloseRegion>,
1019 snippet_stack: InvalidationStack<SnippetState>,
1020 select_syntax_node_history: SelectSyntaxNodeHistory,
1021 ime_transaction: Option<TransactionId>,
1022 pub diagnostics_max_severity: DiagnosticSeverity,
1023 active_diagnostics: ActiveDiagnostic,
1024 show_inline_diagnostics: bool,
1025 inline_diagnostics_update: Task<()>,
1026 inline_diagnostics_enabled: bool,
1027 diagnostics_enabled: bool,
1028 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1029 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1030 hard_wrap: Option<usize>,
1031
1032 // TODO: make this a access method
1033 pub project: Option<Entity<Project>>,
1034 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1035 completion_provider: Option<Rc<dyn CompletionProvider>>,
1036 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1037 blink_manager: Entity<BlinkManager>,
1038 show_cursor_names: bool,
1039 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1040 pub show_local_selections: bool,
1041 mode: EditorMode,
1042 show_breadcrumbs: bool,
1043 show_gutter: bool,
1044 show_scrollbars: ScrollbarAxes,
1045 minimap_visibility: MinimapVisibility,
1046 offset_content: bool,
1047 disable_expand_excerpt_buttons: bool,
1048 show_line_numbers: Option<bool>,
1049 use_relative_line_numbers: Option<bool>,
1050 show_git_diff_gutter: Option<bool>,
1051 show_code_actions: Option<bool>,
1052 show_runnables: Option<bool>,
1053 show_breakpoints: Option<bool>,
1054 show_wrap_guides: Option<bool>,
1055 show_indent_guides: Option<bool>,
1056 placeholder_text: Option<Arc<str>>,
1057 highlight_order: usize,
1058 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1059 background_highlights: TreeMap<HighlightKey, BackgroundHighlight>,
1060 gutter_highlights: TreeMap<TypeId, GutterHighlight>,
1061 scrollbar_marker_state: ScrollbarMarkerState,
1062 active_indent_guides_state: ActiveIndentGuidesState,
1063 nav_history: Option<ItemNavHistory>,
1064 context_menu: RefCell<Option<CodeContextMenu>>,
1065 context_menu_options: Option<ContextMenuOptions>,
1066 mouse_context_menu: Option<MouseContextMenu>,
1067 completion_tasks: Vec<(CompletionId, Task<()>)>,
1068 inline_blame_popover: Option<InlineBlamePopover>,
1069 inline_blame_popover_show_task: Option<Task<()>>,
1070 signature_help_state: SignatureHelpState,
1071 auto_signature_help: Option<bool>,
1072 find_all_references_task_sources: Vec<Anchor>,
1073 next_completion_id: CompletionId,
1074 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1075 code_actions_task: Option<Task<Result<()>>>,
1076 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1077 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1078 document_highlights_task: Option<Task<()>>,
1079 linked_editing_range_task: Option<Task<Option<()>>>,
1080 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1081 pending_rename: Option<RenameState>,
1082 searchable: bool,
1083 cursor_shape: CursorShape,
1084 current_line_highlight: Option<CurrentLineHighlight>,
1085 collapse_matches: bool,
1086 autoindent_mode: Option<AutoindentMode>,
1087 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1088 input_enabled: bool,
1089 use_modal_editing: bool,
1090 read_only: bool,
1091 leader_id: Option<CollaboratorId>,
1092 remote_id: Option<ViewId>,
1093 pub hover_state: HoverState,
1094 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1095 gutter_hovered: bool,
1096 hovered_link_state: Option<HoveredLinkState>,
1097 edit_prediction_provider: Option<RegisteredInlineCompletionProvider>,
1098 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1099 active_inline_completion: Option<InlineCompletionState>,
1100 /// Used to prevent flickering as the user types while the menu is open
1101 stale_inline_completion_in_menu: Option<InlineCompletionState>,
1102 edit_prediction_settings: EditPredictionSettings,
1103 inline_completions_hidden_for_vim_mode: bool,
1104 show_inline_completions_override: Option<bool>,
1105 menu_inline_completions_policy: MenuInlineCompletionsPolicy,
1106 edit_prediction_preview: EditPredictionPreview,
1107 edit_prediction_indent_conflict: bool,
1108 edit_prediction_requires_modifier_in_indent_conflict: bool,
1109 inlay_hint_cache: InlayHintCache,
1110 next_inlay_id: usize,
1111 _subscriptions: Vec<Subscription>,
1112 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1113 gutter_dimensions: GutterDimensions,
1114 style: Option<EditorStyle>,
1115 text_style_refinement: Option<TextStyleRefinement>,
1116 next_editor_action_id: EditorActionId,
1117 editor_actions: Rc<
1118 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1119 >,
1120 use_autoclose: bool,
1121 use_auto_surround: bool,
1122 auto_replace_emoji_shortcode: bool,
1123 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1124 show_git_blame_gutter: bool,
1125 show_git_blame_inline: bool,
1126 show_git_blame_inline_delay_task: Option<Task<()>>,
1127 git_blame_inline_enabled: bool,
1128 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1129 serialize_dirty_buffers: bool,
1130 show_selection_menu: Option<bool>,
1131 blame: Option<Entity<GitBlame>>,
1132 blame_subscription: Option<Subscription>,
1133 custom_context_menu: Option<
1134 Box<
1135 dyn 'static
1136 + Fn(
1137 &mut Self,
1138 DisplayPoint,
1139 &mut Window,
1140 &mut Context<Self>,
1141 ) -> Option<Entity<ui::ContextMenu>>,
1142 >,
1143 >,
1144 last_bounds: Option<Bounds<Pixels>>,
1145 last_position_map: Option<Rc<PositionMap>>,
1146 expect_bounds_change: Option<Bounds<Pixels>>,
1147 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1148 tasks_update_task: Option<Task<()>>,
1149 breakpoint_store: Option<Entity<BreakpointStore>>,
1150 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1151 hovered_diff_hunk_row: Option<DisplayRow>,
1152 pull_diagnostics_task: Task<()>,
1153 in_project_search: bool,
1154 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1155 breadcrumb_header: Option<String>,
1156 focused_block: Option<FocusedBlock>,
1157 next_scroll_position: NextScrollCursorCenterTopBottom,
1158 addons: HashMap<TypeId, Box<dyn Addon>>,
1159 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1160 load_diff_task: Option<Shared<Task<()>>>,
1161 /// Whether we are temporarily displaying a diff other than git's
1162 temporary_diff_override: bool,
1163 selection_mark_mode: bool,
1164 toggle_fold_multiple_buffers: Task<()>,
1165 _scroll_cursor_center_top_bottom_task: Task<()>,
1166 serialize_selections: Task<()>,
1167 serialize_folds: Task<()>,
1168 mouse_cursor_hidden: bool,
1169 minimap: Option<Entity<Self>>,
1170 hide_mouse_mode: HideMouseMode,
1171 pub change_list: ChangeList,
1172 inline_value_cache: InlineValueCache,
1173 selection_drag_state: SelectionDragState,
1174 next_color_inlay_id: usize,
1175 colors: Option<LspColorData>,
1176 folding_newlines: Task<()>,
1177}
1178
1179#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1180enum NextScrollCursorCenterTopBottom {
1181 #[default]
1182 Center,
1183 Top,
1184 Bottom,
1185}
1186
1187impl NextScrollCursorCenterTopBottom {
1188 fn next(&self) -> Self {
1189 match self {
1190 Self::Center => Self::Top,
1191 Self::Top => Self::Bottom,
1192 Self::Bottom => Self::Center,
1193 }
1194 }
1195}
1196
1197#[derive(Clone)]
1198pub struct EditorSnapshot {
1199 pub mode: EditorMode,
1200 show_gutter: bool,
1201 show_line_numbers: Option<bool>,
1202 show_git_diff_gutter: Option<bool>,
1203 show_code_actions: Option<bool>,
1204 show_runnables: Option<bool>,
1205 show_breakpoints: Option<bool>,
1206 git_blame_gutter_max_author_length: Option<usize>,
1207 pub display_snapshot: DisplaySnapshot,
1208 pub placeholder_text: Option<Arc<str>>,
1209 is_focused: bool,
1210 scroll_anchor: ScrollAnchor,
1211 ongoing_scroll: OngoingScroll,
1212 current_line_highlight: CurrentLineHighlight,
1213 gutter_hovered: bool,
1214}
1215
1216#[derive(Default, Debug, Clone, Copy)]
1217pub struct GutterDimensions {
1218 pub left_padding: Pixels,
1219 pub right_padding: Pixels,
1220 pub width: Pixels,
1221 pub margin: Pixels,
1222 pub git_blame_entries_width: Option<Pixels>,
1223}
1224
1225impl GutterDimensions {
1226 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1227 Self {
1228 margin: Self::default_gutter_margin(font_id, font_size, cx),
1229 ..Default::default()
1230 }
1231 }
1232
1233 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1234 -cx.text_system().descent(font_id, font_size)
1235 }
1236 /// The full width of the space taken up by the gutter.
1237 pub fn full_width(&self) -> Pixels {
1238 self.margin + self.width
1239 }
1240
1241 /// The width of the space reserved for the fold indicators,
1242 /// use alongside 'justify_end' and `gutter_width` to
1243 /// right align content with the line numbers
1244 pub fn fold_area_width(&self) -> Pixels {
1245 self.margin + self.right_padding
1246 }
1247}
1248
1249struct CharacterDimensions {
1250 em_width: Pixels,
1251 em_advance: Pixels,
1252 line_height: Pixels,
1253}
1254
1255#[derive(Debug)]
1256pub struct RemoteSelection {
1257 pub replica_id: ReplicaId,
1258 pub selection: Selection<Anchor>,
1259 pub cursor_shape: CursorShape,
1260 pub collaborator_id: CollaboratorId,
1261 pub line_mode: bool,
1262 pub user_name: Option<SharedString>,
1263 pub color: PlayerColor,
1264}
1265
1266#[derive(Clone, Debug)]
1267struct SelectionHistoryEntry {
1268 selections: Arc<[Selection<Anchor>]>,
1269 select_next_state: Option<SelectNextState>,
1270 select_prev_state: Option<SelectNextState>,
1271 add_selections_state: Option<AddSelectionsState>,
1272}
1273
1274#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1275enum SelectionHistoryMode {
1276 Normal,
1277 Undoing,
1278 Redoing,
1279 Skipping,
1280}
1281
1282#[derive(Clone, PartialEq, Eq, Hash)]
1283struct HoveredCursor {
1284 replica_id: u16,
1285 selection_id: usize,
1286}
1287
1288impl Default for SelectionHistoryMode {
1289 fn default() -> Self {
1290 Self::Normal
1291 }
1292}
1293
1294#[derive(Debug)]
1295/// SelectionEffects controls the side-effects of updating the selection.
1296///
1297/// The default behaviour does "what you mostly want":
1298/// - it pushes to the nav history if the cursor moved by >10 lines
1299/// - it re-triggers completion requests
1300/// - it scrolls to fit
1301///
1302/// You might want to modify these behaviours. For example when doing a "jump"
1303/// like go to definition, we always want to add to nav history; but when scrolling
1304/// in vim mode we never do.
1305///
1306/// Similarly, you might want to disable scrolling if you don't want the viewport to
1307/// move.
1308#[derive(Clone)]
1309pub struct SelectionEffects {
1310 nav_history: Option<bool>,
1311 completions: bool,
1312 scroll: Option<Autoscroll>,
1313}
1314
1315impl Default for SelectionEffects {
1316 fn default() -> Self {
1317 Self {
1318 nav_history: None,
1319 completions: true,
1320 scroll: Some(Autoscroll::fit()),
1321 }
1322 }
1323}
1324impl SelectionEffects {
1325 pub fn scroll(scroll: Autoscroll) -> Self {
1326 Self {
1327 scroll: Some(scroll),
1328 ..Default::default()
1329 }
1330 }
1331
1332 pub fn no_scroll() -> Self {
1333 Self {
1334 scroll: None,
1335 ..Default::default()
1336 }
1337 }
1338
1339 pub fn completions(self, completions: bool) -> Self {
1340 Self {
1341 completions,
1342 ..self
1343 }
1344 }
1345
1346 pub fn nav_history(self, nav_history: bool) -> Self {
1347 Self {
1348 nav_history: Some(nav_history),
1349 ..self
1350 }
1351 }
1352}
1353
1354struct DeferredSelectionEffectsState {
1355 changed: bool,
1356 effects: SelectionEffects,
1357 old_cursor_position: Anchor,
1358 history_entry: SelectionHistoryEntry,
1359}
1360
1361#[derive(Default)]
1362struct SelectionHistory {
1363 #[allow(clippy::type_complexity)]
1364 selections_by_transaction:
1365 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1366 mode: SelectionHistoryMode,
1367 undo_stack: VecDeque<SelectionHistoryEntry>,
1368 redo_stack: VecDeque<SelectionHistoryEntry>,
1369}
1370
1371impl SelectionHistory {
1372 #[track_caller]
1373 fn insert_transaction(
1374 &mut self,
1375 transaction_id: TransactionId,
1376 selections: Arc<[Selection<Anchor>]>,
1377 ) {
1378 if selections.is_empty() {
1379 log::error!(
1380 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1381 std::panic::Location::caller()
1382 );
1383 return;
1384 }
1385 self.selections_by_transaction
1386 .insert(transaction_id, (selections, None));
1387 }
1388
1389 #[allow(clippy::type_complexity)]
1390 fn transaction(
1391 &self,
1392 transaction_id: TransactionId,
1393 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1394 self.selections_by_transaction.get(&transaction_id)
1395 }
1396
1397 #[allow(clippy::type_complexity)]
1398 fn transaction_mut(
1399 &mut self,
1400 transaction_id: TransactionId,
1401 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1402 self.selections_by_transaction.get_mut(&transaction_id)
1403 }
1404
1405 fn push(&mut self, entry: SelectionHistoryEntry) {
1406 if !entry.selections.is_empty() {
1407 match self.mode {
1408 SelectionHistoryMode::Normal => {
1409 self.push_undo(entry);
1410 self.redo_stack.clear();
1411 }
1412 SelectionHistoryMode::Undoing => self.push_redo(entry),
1413 SelectionHistoryMode::Redoing => self.push_undo(entry),
1414 SelectionHistoryMode::Skipping => {}
1415 }
1416 }
1417 }
1418
1419 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1420 if self
1421 .undo_stack
1422 .back()
1423 .map_or(true, |e| e.selections != entry.selections)
1424 {
1425 self.undo_stack.push_back(entry);
1426 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1427 self.undo_stack.pop_front();
1428 }
1429 }
1430 }
1431
1432 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1433 if self
1434 .redo_stack
1435 .back()
1436 .map_or(true, |e| e.selections != entry.selections)
1437 {
1438 self.redo_stack.push_back(entry);
1439 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1440 self.redo_stack.pop_front();
1441 }
1442 }
1443 }
1444}
1445
1446#[derive(Clone, Copy)]
1447pub struct RowHighlightOptions {
1448 pub autoscroll: bool,
1449 pub include_gutter: bool,
1450}
1451
1452impl Default for RowHighlightOptions {
1453 fn default() -> Self {
1454 Self {
1455 autoscroll: Default::default(),
1456 include_gutter: true,
1457 }
1458 }
1459}
1460
1461struct RowHighlight {
1462 index: usize,
1463 range: Range<Anchor>,
1464 color: Hsla,
1465 options: RowHighlightOptions,
1466 type_id: TypeId,
1467}
1468
1469#[derive(Clone, Debug)]
1470struct AddSelectionsState {
1471 groups: Vec<AddSelectionsGroup>,
1472}
1473
1474#[derive(Clone, Debug)]
1475struct AddSelectionsGroup {
1476 above: bool,
1477 stack: Vec<usize>,
1478}
1479
1480#[derive(Clone)]
1481struct SelectNextState {
1482 query: AhoCorasick,
1483 wordwise: bool,
1484 done: bool,
1485}
1486
1487impl std::fmt::Debug for SelectNextState {
1488 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1489 f.debug_struct(std::any::type_name::<Self>())
1490 .field("wordwise", &self.wordwise)
1491 .field("done", &self.done)
1492 .finish()
1493 }
1494}
1495
1496#[derive(Debug)]
1497struct AutocloseRegion {
1498 selection_id: usize,
1499 range: Range<Anchor>,
1500 pair: BracketPair,
1501}
1502
1503#[derive(Debug)]
1504struct SnippetState {
1505 ranges: Vec<Vec<Range<Anchor>>>,
1506 active_index: usize,
1507 choices: Vec<Option<Vec<String>>>,
1508}
1509
1510#[doc(hidden)]
1511pub struct RenameState {
1512 pub range: Range<Anchor>,
1513 pub old_name: Arc<str>,
1514 pub editor: Entity<Editor>,
1515 block_id: CustomBlockId,
1516}
1517
1518struct InvalidationStack<T>(Vec<T>);
1519
1520struct RegisteredInlineCompletionProvider {
1521 provider: Arc<dyn InlineCompletionProviderHandle>,
1522 _subscription: Subscription,
1523}
1524
1525#[derive(Debug, PartialEq, Eq)]
1526pub struct ActiveDiagnosticGroup {
1527 pub active_range: Range<Anchor>,
1528 pub active_message: String,
1529 pub group_id: usize,
1530 pub blocks: HashSet<CustomBlockId>,
1531}
1532
1533#[derive(Debug, PartialEq, Eq)]
1534
1535pub(crate) enum ActiveDiagnostic {
1536 None,
1537 All,
1538 Group(ActiveDiagnosticGroup),
1539}
1540
1541#[derive(Serialize, Deserialize, Clone, Debug)]
1542pub struct ClipboardSelection {
1543 /// The number of bytes in this selection.
1544 pub len: usize,
1545 /// Whether this was a full-line selection.
1546 pub is_entire_line: bool,
1547 /// The indentation of the first line when this content was originally copied.
1548 pub first_line_indent: u32,
1549}
1550
1551// selections, scroll behavior, was newest selection reversed
1552type SelectSyntaxNodeHistoryState = (
1553 Box<[Selection<usize>]>,
1554 SelectSyntaxNodeScrollBehavior,
1555 bool,
1556);
1557
1558#[derive(Default)]
1559struct SelectSyntaxNodeHistory {
1560 stack: Vec<SelectSyntaxNodeHistoryState>,
1561 // disable temporarily to allow changing selections without losing the stack
1562 pub disable_clearing: bool,
1563}
1564
1565impl SelectSyntaxNodeHistory {
1566 pub fn try_clear(&mut self) {
1567 if !self.disable_clearing {
1568 self.stack.clear();
1569 }
1570 }
1571
1572 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1573 self.stack.push(selection);
1574 }
1575
1576 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1577 self.stack.pop()
1578 }
1579}
1580
1581enum SelectSyntaxNodeScrollBehavior {
1582 CursorTop,
1583 FitSelection,
1584 CursorBottom,
1585}
1586
1587#[derive(Debug)]
1588pub(crate) struct NavigationData {
1589 cursor_anchor: Anchor,
1590 cursor_position: Point,
1591 scroll_anchor: ScrollAnchor,
1592 scroll_top_row: u32,
1593}
1594
1595#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1596pub enum GotoDefinitionKind {
1597 Symbol,
1598 Declaration,
1599 Type,
1600 Implementation,
1601}
1602
1603#[derive(Debug, Clone)]
1604enum InlayHintRefreshReason {
1605 ModifiersChanged(bool),
1606 Toggle(bool),
1607 SettingsChange(InlayHintSettings),
1608 NewLinesShown,
1609 BufferEdited(HashSet<Arc<Language>>),
1610 RefreshRequested,
1611 ExcerptsRemoved(Vec<ExcerptId>),
1612}
1613
1614impl InlayHintRefreshReason {
1615 fn description(&self) -> &'static str {
1616 match self {
1617 Self::ModifiersChanged(_) => "modifiers changed",
1618 Self::Toggle(_) => "toggle",
1619 Self::SettingsChange(_) => "settings change",
1620 Self::NewLinesShown => "new lines shown",
1621 Self::BufferEdited(_) => "buffer edited",
1622 Self::RefreshRequested => "refresh requested",
1623 Self::ExcerptsRemoved(_) => "excerpts removed",
1624 }
1625 }
1626}
1627
1628pub enum FormatTarget {
1629 Buffers(HashSet<Entity<Buffer>>),
1630 Ranges(Vec<Range<MultiBufferPoint>>),
1631}
1632
1633pub(crate) struct FocusedBlock {
1634 id: BlockId,
1635 focus_handle: WeakFocusHandle,
1636}
1637
1638#[derive(Clone)]
1639enum JumpData {
1640 MultiBufferRow {
1641 row: MultiBufferRow,
1642 line_offset_from_top: u32,
1643 },
1644 MultiBufferPoint {
1645 excerpt_id: ExcerptId,
1646 position: Point,
1647 anchor: text::Anchor,
1648 line_offset_from_top: u32,
1649 },
1650}
1651
1652pub enum MultibufferSelectionMode {
1653 First,
1654 All,
1655}
1656
1657#[derive(Clone, Copy, Debug, Default)]
1658pub struct RewrapOptions {
1659 pub override_language_settings: bool,
1660 pub preserve_existing_whitespace: bool,
1661}
1662
1663impl Editor {
1664 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1665 let buffer = cx.new(|cx| Buffer::local("", cx));
1666 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1667 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1668 }
1669
1670 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1671 let buffer = cx.new(|cx| Buffer::local("", cx));
1672 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1673 Self::new(EditorMode::full(), buffer, None, window, cx)
1674 }
1675
1676 pub fn auto_height(
1677 min_lines: usize,
1678 max_lines: usize,
1679 window: &mut Window,
1680 cx: &mut Context<Self>,
1681 ) -> Self {
1682 let buffer = cx.new(|cx| Buffer::local("", cx));
1683 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1684 Self::new(
1685 EditorMode::AutoHeight {
1686 min_lines,
1687 max_lines: Some(max_lines),
1688 },
1689 buffer,
1690 None,
1691 window,
1692 cx,
1693 )
1694 }
1695
1696 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1697 /// The editor grows as tall as needed to fit its content.
1698 pub fn auto_height_unbounded(
1699 min_lines: usize,
1700 window: &mut Window,
1701 cx: &mut Context<Self>,
1702 ) -> Self {
1703 let buffer = cx.new(|cx| Buffer::local("", cx));
1704 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1705 Self::new(
1706 EditorMode::AutoHeight {
1707 min_lines,
1708 max_lines: None,
1709 },
1710 buffer,
1711 None,
1712 window,
1713 cx,
1714 )
1715 }
1716
1717 pub fn for_buffer(
1718 buffer: Entity<Buffer>,
1719 project: Option<Entity<Project>>,
1720 window: &mut Window,
1721 cx: &mut Context<Self>,
1722 ) -> Self {
1723 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1724 Self::new(EditorMode::full(), buffer, project, window, cx)
1725 }
1726
1727 pub fn for_multibuffer(
1728 buffer: Entity<MultiBuffer>,
1729 project: Option<Entity<Project>>,
1730 window: &mut Window,
1731 cx: &mut Context<Self>,
1732 ) -> Self {
1733 Self::new(EditorMode::full(), buffer, project, window, cx)
1734 }
1735
1736 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1737 let mut clone = Self::new(
1738 self.mode.clone(),
1739 self.buffer.clone(),
1740 self.project.clone(),
1741 window,
1742 cx,
1743 );
1744 self.display_map.update(cx, |display_map, cx| {
1745 let snapshot = display_map.snapshot(cx);
1746 clone.display_map.update(cx, |display_map, cx| {
1747 display_map.set_state(&snapshot, cx);
1748 });
1749 });
1750 clone.folds_did_change(cx);
1751 clone.selections.clone_state(&self.selections);
1752 clone.scroll_manager.clone_state(&self.scroll_manager);
1753 clone.searchable = self.searchable;
1754 clone.read_only = self.read_only;
1755 clone
1756 }
1757
1758 pub fn new(
1759 mode: EditorMode,
1760 buffer: Entity<MultiBuffer>,
1761 project: Option<Entity<Project>>,
1762 window: &mut Window,
1763 cx: &mut Context<Self>,
1764 ) -> Self {
1765 Editor::new_internal(mode, buffer, project, None, window, cx)
1766 }
1767
1768 fn new_internal(
1769 mode: EditorMode,
1770 buffer: Entity<MultiBuffer>,
1771 project: Option<Entity<Project>>,
1772 display_map: Option<Entity<DisplayMap>>,
1773 window: &mut Window,
1774 cx: &mut Context<Self>,
1775 ) -> Self {
1776 debug_assert!(
1777 display_map.is_none() || mode.is_minimap(),
1778 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1779 );
1780
1781 let full_mode = mode.is_full();
1782 let is_minimap = mode.is_minimap();
1783 let diagnostics_max_severity = if full_mode {
1784 EditorSettings::get_global(cx)
1785 .diagnostics_max_severity
1786 .unwrap_or(DiagnosticSeverity::Hint)
1787 } else {
1788 DiagnosticSeverity::Off
1789 };
1790 let style = window.text_style();
1791 let font_size = style.font_size.to_pixels(window.rem_size());
1792 let editor = cx.entity().downgrade();
1793 let fold_placeholder = FoldPlaceholder {
1794 constrain_width: true,
1795 render: Arc::new(move |fold_id, fold_range, cx| {
1796 let editor = editor.clone();
1797 div()
1798 .id(fold_id)
1799 .bg(cx.theme().colors().ghost_element_background)
1800 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1801 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1802 .rounded_xs()
1803 .size_full()
1804 .cursor_pointer()
1805 .child("⋯")
1806 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1807 .on_click(move |_, _window, cx| {
1808 editor
1809 .update(cx, |editor, cx| {
1810 editor.unfold_ranges(
1811 &[fold_range.start..fold_range.end],
1812 true,
1813 false,
1814 cx,
1815 );
1816 cx.stop_propagation();
1817 })
1818 .ok();
1819 })
1820 .into_any()
1821 }),
1822 merge_adjacent: true,
1823 ..FoldPlaceholder::default()
1824 };
1825 let display_map = display_map.unwrap_or_else(|| {
1826 cx.new(|cx| {
1827 DisplayMap::new(
1828 buffer.clone(),
1829 style.font(),
1830 font_size,
1831 None,
1832 FILE_HEADER_HEIGHT,
1833 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1834 fold_placeholder,
1835 diagnostics_max_severity,
1836 cx,
1837 )
1838 })
1839 });
1840
1841 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1842
1843 let blink_manager = cx.new(|cx| {
1844 let mut blink_manager = BlinkManager::new(CURSOR_BLINK_INTERVAL, cx);
1845 if is_minimap {
1846 blink_manager.disable(cx);
1847 }
1848 blink_manager
1849 });
1850
1851 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1852 .then(|| language_settings::SoftWrap::None);
1853
1854 let mut project_subscriptions = Vec::new();
1855 if full_mode {
1856 if let Some(project) = project.as_ref() {
1857 project_subscriptions.push(cx.subscribe_in(
1858 project,
1859 window,
1860 |editor, _, event, window, cx| match event {
1861 project::Event::RefreshCodeLens => {
1862 // we always query lens with actions, without storing them, always refreshing them
1863 }
1864 project::Event::RefreshInlayHints => {
1865 editor
1866 .refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1867 }
1868 project::Event::LanguageServerAdded(..)
1869 | project::Event::LanguageServerRemoved(..) => {
1870 if editor.tasks_update_task.is_none() {
1871 editor.tasks_update_task =
1872 Some(editor.refresh_runnables(window, cx));
1873 }
1874 editor.update_lsp_data(true, None, window, cx);
1875 }
1876 project::Event::SnippetEdit(id, snippet_edits) => {
1877 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1878 let focus_handle = editor.focus_handle(cx);
1879 if focus_handle.is_focused(window) {
1880 let snapshot = buffer.read(cx).snapshot();
1881 for (range, snippet) in snippet_edits {
1882 let editor_range =
1883 language::range_from_lsp(*range).to_offset(&snapshot);
1884 editor
1885 .insert_snippet(
1886 &[editor_range],
1887 snippet.clone(),
1888 window,
1889 cx,
1890 )
1891 .ok();
1892 }
1893 }
1894 }
1895 }
1896 _ => {}
1897 },
1898 ));
1899 if let Some(task_inventory) = project
1900 .read(cx)
1901 .task_store()
1902 .read(cx)
1903 .task_inventory()
1904 .cloned()
1905 {
1906 project_subscriptions.push(cx.observe_in(
1907 &task_inventory,
1908 window,
1909 |editor, _, window, cx| {
1910 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1911 },
1912 ));
1913 };
1914
1915 project_subscriptions.push(cx.subscribe_in(
1916 &project.read(cx).breakpoint_store(),
1917 window,
1918 |editor, _, event, window, cx| match event {
1919 BreakpointStoreEvent::ClearDebugLines => {
1920 editor.clear_row_highlights::<ActiveDebugLine>();
1921 editor.refresh_inline_values(cx);
1922 }
1923 BreakpointStoreEvent::SetDebugLine => {
1924 if editor.go_to_active_debug_line(window, cx) {
1925 cx.stop_propagation();
1926 }
1927
1928 editor.refresh_inline_values(cx);
1929 }
1930 _ => {}
1931 },
1932 ));
1933 let git_store = project.read(cx).git_store().clone();
1934 let project = project.clone();
1935 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
1936 match event {
1937 GitStoreEvent::RepositoryUpdated(
1938 _,
1939 RepositoryEvent::Updated {
1940 new_instance: true, ..
1941 },
1942 _,
1943 ) => {
1944 this.load_diff_task = Some(
1945 update_uncommitted_diff_for_buffer(
1946 cx.entity(),
1947 &project,
1948 this.buffer.read(cx).all_buffers(),
1949 this.buffer.clone(),
1950 cx,
1951 )
1952 .shared(),
1953 );
1954 }
1955 _ => {}
1956 }
1957 }));
1958 }
1959 }
1960
1961 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1962
1963 let inlay_hint_settings =
1964 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1965 let focus_handle = cx.focus_handle();
1966 if !is_minimap {
1967 cx.on_focus(&focus_handle, window, Self::handle_focus)
1968 .detach();
1969 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1970 .detach();
1971 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1972 .detach();
1973 cx.on_blur(&focus_handle, window, Self::handle_blur)
1974 .detach();
1975 cx.observe_pending_input(window, Self::observe_pending_input)
1976 .detach();
1977 }
1978
1979 let show_indent_guides = if matches!(
1980 mode,
1981 EditorMode::SingleLine { .. } | EditorMode::Minimap { .. }
1982 ) {
1983 Some(false)
1984 } else {
1985 None
1986 };
1987
1988 let breakpoint_store = match (&mode, project.as_ref()) {
1989 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
1990 _ => None,
1991 };
1992
1993 let mut code_action_providers = Vec::new();
1994 let mut load_uncommitted_diff = None;
1995 if let Some(project) = project.clone() {
1996 load_uncommitted_diff = Some(
1997 update_uncommitted_diff_for_buffer(
1998 cx.entity(),
1999 &project,
2000 buffer.read(cx).all_buffers(),
2001 buffer.clone(),
2002 cx,
2003 )
2004 .shared(),
2005 );
2006 code_action_providers.push(Rc::new(project) as Rc<_>);
2007 }
2008
2009 let mut editor = Self {
2010 focus_handle,
2011 show_cursor_when_unfocused: false,
2012 last_focused_descendant: None,
2013 buffer: buffer.clone(),
2014 display_map: display_map.clone(),
2015 selections,
2016 scroll_manager: ScrollManager::new(cx),
2017 columnar_selection_state: None,
2018 add_selections_state: None,
2019 select_next_state: None,
2020 select_prev_state: None,
2021 selection_history: SelectionHistory::default(),
2022 defer_selection_effects: false,
2023 deferred_selection_effects_state: None,
2024 autoclose_regions: Vec::new(),
2025 snippet_stack: InvalidationStack::default(),
2026 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2027 ime_transaction: None,
2028 active_diagnostics: ActiveDiagnostic::None,
2029 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2030 inline_diagnostics_update: Task::ready(()),
2031 inline_diagnostics: Vec::new(),
2032 soft_wrap_mode_override,
2033 diagnostics_max_severity,
2034 hard_wrap: None,
2035 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2036 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2037 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2038 project,
2039 blink_manager: blink_manager.clone(),
2040 show_local_selections: true,
2041 show_scrollbars: ScrollbarAxes {
2042 horizontal: full_mode,
2043 vertical: full_mode,
2044 },
2045 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2046 offset_content: !matches!(mode, EditorMode::SingleLine { .. }),
2047 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2048 show_gutter: full_mode,
2049 show_line_numbers: (!full_mode).then_some(false),
2050 use_relative_line_numbers: None,
2051 disable_expand_excerpt_buttons: !full_mode,
2052 show_git_diff_gutter: None,
2053 show_code_actions: None,
2054 show_runnables: None,
2055 show_breakpoints: None,
2056 show_wrap_guides: None,
2057 show_indent_guides,
2058 placeholder_text: None,
2059 highlight_order: 0,
2060 highlighted_rows: HashMap::default(),
2061 background_highlights: TreeMap::default(),
2062 gutter_highlights: TreeMap::default(),
2063 scrollbar_marker_state: ScrollbarMarkerState::default(),
2064 active_indent_guides_state: ActiveIndentGuidesState::default(),
2065 nav_history: None,
2066 context_menu: RefCell::new(None),
2067 context_menu_options: None,
2068 mouse_context_menu: None,
2069 completion_tasks: Vec::new(),
2070 inline_blame_popover: None,
2071 inline_blame_popover_show_task: None,
2072 signature_help_state: SignatureHelpState::default(),
2073 auto_signature_help: None,
2074 find_all_references_task_sources: Vec::new(),
2075 next_completion_id: 0,
2076 next_inlay_id: 0,
2077 code_action_providers,
2078 available_code_actions: None,
2079 code_actions_task: None,
2080 quick_selection_highlight_task: None,
2081 debounced_selection_highlight_task: None,
2082 document_highlights_task: None,
2083 linked_editing_range_task: None,
2084 pending_rename: None,
2085 searchable: !is_minimap,
2086 cursor_shape: EditorSettings::get_global(cx)
2087 .cursor_shape
2088 .unwrap_or_default(),
2089 current_line_highlight: None,
2090 autoindent_mode: Some(AutoindentMode::EachLine),
2091 collapse_matches: false,
2092 workspace: None,
2093 input_enabled: !is_minimap,
2094 use_modal_editing: full_mode,
2095 read_only: is_minimap,
2096 use_autoclose: true,
2097 use_auto_surround: true,
2098 auto_replace_emoji_shortcode: false,
2099 jsx_tag_auto_close_enabled_in_any_buffer: false,
2100 leader_id: None,
2101 remote_id: None,
2102 hover_state: HoverState::default(),
2103 pending_mouse_down: None,
2104 hovered_link_state: None,
2105 edit_prediction_provider: None,
2106 active_inline_completion: None,
2107 stale_inline_completion_in_menu: None,
2108 edit_prediction_preview: EditPredictionPreview::Inactive {
2109 released_too_fast: false,
2110 },
2111 inline_diagnostics_enabled: full_mode,
2112 diagnostics_enabled: full_mode,
2113 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2114 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
2115 gutter_hovered: false,
2116 pixel_position_of_newest_cursor: None,
2117 last_bounds: None,
2118 last_position_map: None,
2119 expect_bounds_change: None,
2120 gutter_dimensions: GutterDimensions::default(),
2121 style: None,
2122 show_cursor_names: false,
2123 hovered_cursors: HashMap::default(),
2124 next_editor_action_id: EditorActionId::default(),
2125 editor_actions: Rc::default(),
2126 inline_completions_hidden_for_vim_mode: false,
2127 show_inline_completions_override: None,
2128 menu_inline_completions_policy: MenuInlineCompletionsPolicy::ByProvider,
2129 edit_prediction_settings: EditPredictionSettings::Disabled,
2130 edit_prediction_indent_conflict: false,
2131 edit_prediction_requires_modifier_in_indent_conflict: true,
2132 custom_context_menu: None,
2133 show_git_blame_gutter: false,
2134 show_git_blame_inline: false,
2135 show_selection_menu: None,
2136 show_git_blame_inline_delay_task: None,
2137 git_blame_inline_enabled: full_mode
2138 && ProjectSettings::get_global(cx).git.inline_blame_enabled(),
2139 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2140 serialize_dirty_buffers: !is_minimap
2141 && ProjectSettings::get_global(cx)
2142 .session
2143 .restore_unsaved_buffers,
2144 blame: None,
2145 blame_subscription: None,
2146 tasks: BTreeMap::default(),
2147
2148 breakpoint_store,
2149 gutter_breakpoint_indicator: (None, None),
2150 hovered_diff_hunk_row: None,
2151 _subscriptions: (!is_minimap)
2152 .then(|| {
2153 vec![
2154 cx.observe(&buffer, Self::on_buffer_changed),
2155 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
2156 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2157 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2158 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2159 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2160 cx.observe_window_activation(window, |editor, window, cx| {
2161 let active = window.is_window_active();
2162 editor.blink_manager.update(cx, |blink_manager, cx| {
2163 if active {
2164 blink_manager.enable(cx);
2165 } else {
2166 blink_manager.disable(cx);
2167 }
2168 });
2169 if active {
2170 editor.show_mouse_cursor(cx);
2171 }
2172 }),
2173 ]
2174 })
2175 .unwrap_or_default(),
2176 tasks_update_task: None,
2177 pull_diagnostics_task: Task::ready(()),
2178 colors: None,
2179 next_color_inlay_id: 0,
2180 linked_edit_ranges: Default::default(),
2181 in_project_search: false,
2182 previous_search_ranges: None,
2183 breadcrumb_header: None,
2184 focused_block: None,
2185 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2186 addons: HashMap::default(),
2187 registered_buffers: HashMap::default(),
2188 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2189 selection_mark_mode: false,
2190 toggle_fold_multiple_buffers: Task::ready(()),
2191 serialize_selections: Task::ready(()),
2192 serialize_folds: Task::ready(()),
2193 text_style_refinement: None,
2194 load_diff_task: load_uncommitted_diff,
2195 temporary_diff_override: false,
2196 mouse_cursor_hidden: false,
2197 minimap: None,
2198 hide_mouse_mode: EditorSettings::get_global(cx)
2199 .hide_mouse
2200 .unwrap_or_default(),
2201 change_list: ChangeList::new(),
2202 mode,
2203 selection_drag_state: SelectionDragState::None,
2204 folding_newlines: Task::ready(()),
2205 };
2206
2207 if is_minimap {
2208 return editor;
2209 }
2210
2211 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2212 editor
2213 ._subscriptions
2214 .push(cx.observe(breakpoints, |_, _, cx| {
2215 cx.notify();
2216 }));
2217 }
2218 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2219 editor._subscriptions.extend(project_subscriptions);
2220
2221 editor._subscriptions.push(cx.subscribe_in(
2222 &cx.entity(),
2223 window,
2224 |editor, _, e: &EditorEvent, window, cx| match e {
2225 EditorEvent::ScrollPositionChanged { local, .. } => {
2226 if *local {
2227 let new_anchor = editor.scroll_manager.anchor();
2228 let snapshot = editor.snapshot(window, cx);
2229 editor.update_restoration_data(cx, move |data| {
2230 data.scroll_position = (
2231 new_anchor.top_row(&snapshot.buffer_snapshot),
2232 new_anchor.offset,
2233 );
2234 });
2235 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2236 editor.inline_blame_popover.take();
2237 }
2238 }
2239 EditorEvent::Edited { .. } => {
2240 if !vim_enabled(cx) {
2241 let (map, selections) = editor.selections.all_adjusted_display(cx);
2242 let pop_state = editor
2243 .change_list
2244 .last()
2245 .map(|previous| {
2246 previous.len() == selections.len()
2247 && previous.iter().enumerate().all(|(ix, p)| {
2248 p.to_display_point(&map).row()
2249 == selections[ix].head().row()
2250 })
2251 })
2252 .unwrap_or(false);
2253 let new_positions = selections
2254 .into_iter()
2255 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
2256 .collect();
2257 editor
2258 .change_list
2259 .push_to_change_list(pop_state, new_positions);
2260 }
2261 }
2262 _ => (),
2263 },
2264 ));
2265
2266 if let Some(dap_store) = editor
2267 .project
2268 .as_ref()
2269 .map(|project| project.read(cx).dap_store())
2270 {
2271 let weak_editor = cx.weak_entity();
2272
2273 editor
2274 ._subscriptions
2275 .push(
2276 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2277 let session_entity = cx.entity();
2278 weak_editor
2279 .update(cx, |editor, cx| {
2280 editor._subscriptions.push(
2281 cx.subscribe(&session_entity, Self::on_debug_session_event),
2282 );
2283 })
2284 .ok();
2285 }),
2286 );
2287
2288 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2289 editor
2290 ._subscriptions
2291 .push(cx.subscribe(&session, Self::on_debug_session_event));
2292 }
2293 }
2294
2295 // skip adding the initial selection to selection history
2296 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2297 editor.end_selection(window, cx);
2298 editor.selection_history.mode = SelectionHistoryMode::Normal;
2299
2300 editor.scroll_manager.show_scrollbars(window, cx);
2301 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &buffer, cx);
2302
2303 if full_mode {
2304 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2305 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2306
2307 if editor.git_blame_inline_enabled {
2308 editor.start_git_blame_inline(false, window, cx);
2309 }
2310
2311 editor.go_to_active_debug_line(window, cx);
2312
2313 if let Some(buffer) = buffer.read(cx).as_singleton() {
2314 if let Some(project) = editor.project.as_ref() {
2315 let handle = project.update(cx, |project, cx| {
2316 project.register_buffer_with_language_servers(&buffer, cx)
2317 });
2318 editor
2319 .registered_buffers
2320 .insert(buffer.read(cx).remote_id(), handle);
2321 }
2322 }
2323
2324 editor.minimap =
2325 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2326 editor.colors = Some(LspColorData::new(cx));
2327 editor.update_lsp_data(false, None, window, cx);
2328 }
2329
2330 if editor.mode.is_full() {
2331 editor.report_editor_event("Editor Opened", None, cx);
2332 }
2333
2334 editor
2335 }
2336
2337 pub fn deploy_mouse_context_menu(
2338 &mut self,
2339 position: gpui::Point<Pixels>,
2340 context_menu: Entity<ContextMenu>,
2341 window: &mut Window,
2342 cx: &mut Context<Self>,
2343 ) {
2344 self.mouse_context_menu = Some(MouseContextMenu::new(
2345 self,
2346 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2347 context_menu,
2348 window,
2349 cx,
2350 ));
2351 }
2352
2353 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2354 self.mouse_context_menu
2355 .as_ref()
2356 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2357 }
2358
2359 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2360 self.key_context_internal(self.has_active_inline_completion(), window, cx)
2361 }
2362
2363 fn key_context_internal(
2364 &self,
2365 has_active_edit_prediction: bool,
2366 window: &Window,
2367 cx: &App,
2368 ) -> KeyContext {
2369 let mut key_context = KeyContext::new_with_defaults();
2370 key_context.add("Editor");
2371 let mode = match self.mode {
2372 EditorMode::SingleLine { .. } => "single_line",
2373 EditorMode::AutoHeight { .. } => "auto_height",
2374 EditorMode::Minimap { .. } => "minimap",
2375 EditorMode::Full { .. } => "full",
2376 };
2377
2378 if EditorSettings::jupyter_enabled(cx) {
2379 key_context.add("jupyter");
2380 }
2381
2382 key_context.set("mode", mode);
2383 if self.pending_rename.is_some() {
2384 key_context.add("renaming");
2385 }
2386
2387 match self.context_menu.borrow().as_ref() {
2388 Some(CodeContextMenu::Completions(menu)) => {
2389 if menu.visible() {
2390 key_context.add("menu");
2391 key_context.add("showing_completions");
2392 }
2393 }
2394 Some(CodeContextMenu::CodeActions(menu)) => {
2395 if menu.visible() {
2396 key_context.add("menu");
2397 key_context.add("showing_code_actions")
2398 }
2399 }
2400 None => {}
2401 }
2402
2403 if self.signature_help_state.has_multiple_signatures() {
2404 key_context.add("showing_signature_help");
2405 }
2406
2407 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2408 if !self.focus_handle(cx).contains_focused(window, cx)
2409 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2410 {
2411 for addon in self.addons.values() {
2412 addon.extend_key_context(&mut key_context, cx)
2413 }
2414 }
2415
2416 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2417 if let Some(extension) = singleton_buffer
2418 .read(cx)
2419 .file()
2420 .and_then(|file| file.path().extension()?.to_str())
2421 {
2422 key_context.set("extension", extension.to_string());
2423 }
2424 } else {
2425 key_context.add("multibuffer");
2426 }
2427
2428 if has_active_edit_prediction {
2429 if self.edit_prediction_in_conflict() {
2430 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2431 } else {
2432 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2433 key_context.add("copilot_suggestion");
2434 }
2435 }
2436
2437 if self.selection_mark_mode {
2438 key_context.add("selection_mode");
2439 }
2440
2441 key_context
2442 }
2443
2444 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2445 if self.mouse_cursor_hidden {
2446 self.mouse_cursor_hidden = false;
2447 cx.notify();
2448 }
2449 }
2450
2451 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2452 let hide_mouse_cursor = match origin {
2453 HideMouseCursorOrigin::TypingAction => {
2454 matches!(
2455 self.hide_mouse_mode,
2456 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2457 )
2458 }
2459 HideMouseCursorOrigin::MovementAction => {
2460 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2461 }
2462 };
2463 if self.mouse_cursor_hidden != hide_mouse_cursor {
2464 self.mouse_cursor_hidden = hide_mouse_cursor;
2465 cx.notify();
2466 }
2467 }
2468
2469 pub fn edit_prediction_in_conflict(&self) -> bool {
2470 if !self.show_edit_predictions_in_menu() {
2471 return false;
2472 }
2473
2474 let showing_completions = self
2475 .context_menu
2476 .borrow()
2477 .as_ref()
2478 .map_or(false, |context| {
2479 matches!(context, CodeContextMenu::Completions(_))
2480 });
2481
2482 showing_completions
2483 || self.edit_prediction_requires_modifier()
2484 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2485 // bindings to insert tab characters.
2486 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2487 }
2488
2489 pub fn accept_edit_prediction_keybind(
2490 &self,
2491 accept_partial: bool,
2492 window: &Window,
2493 cx: &App,
2494 ) -> AcceptEditPredictionBinding {
2495 let key_context = self.key_context_internal(true, window, cx);
2496 let in_conflict = self.edit_prediction_in_conflict();
2497
2498 let bindings = if accept_partial {
2499 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2500 } else {
2501 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2502 };
2503
2504 // TODO: if the binding contains multiple keystrokes, display all of them, not
2505 // just the first one.
2506 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2507 !in_conflict
2508 || binding
2509 .keystrokes()
2510 .first()
2511 .map_or(false, |keystroke| keystroke.modifiers.modified())
2512 }))
2513 }
2514
2515 pub fn new_file(
2516 workspace: &mut Workspace,
2517 _: &workspace::NewFile,
2518 window: &mut Window,
2519 cx: &mut Context<Workspace>,
2520 ) {
2521 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2522 "Failed to create buffer",
2523 window,
2524 cx,
2525 |e, _, _| match e.error_code() {
2526 ErrorCode::RemoteUpgradeRequired => Some(format!(
2527 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2528 e.error_tag("required").unwrap_or("the latest version")
2529 )),
2530 _ => None,
2531 },
2532 );
2533 }
2534
2535 pub fn new_in_workspace(
2536 workspace: &mut Workspace,
2537 window: &mut Window,
2538 cx: &mut Context<Workspace>,
2539 ) -> Task<Result<Entity<Editor>>> {
2540 let project = workspace.project().clone();
2541 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2542
2543 cx.spawn_in(window, async move |workspace, cx| {
2544 let buffer = create.await?;
2545 workspace.update_in(cx, |workspace, window, cx| {
2546 let editor =
2547 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2548 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2549 editor
2550 })
2551 })
2552 }
2553
2554 fn new_file_vertical(
2555 workspace: &mut Workspace,
2556 _: &workspace::NewFileSplitVertical,
2557 window: &mut Window,
2558 cx: &mut Context<Workspace>,
2559 ) {
2560 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2561 }
2562
2563 fn new_file_horizontal(
2564 workspace: &mut Workspace,
2565 _: &workspace::NewFileSplitHorizontal,
2566 window: &mut Window,
2567 cx: &mut Context<Workspace>,
2568 ) {
2569 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2570 }
2571
2572 fn new_file_in_direction(
2573 workspace: &mut Workspace,
2574 direction: SplitDirection,
2575 window: &mut Window,
2576 cx: &mut Context<Workspace>,
2577 ) {
2578 let project = workspace.project().clone();
2579 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2580
2581 cx.spawn_in(window, async move |workspace, cx| {
2582 let buffer = create.await?;
2583 workspace.update_in(cx, move |workspace, window, cx| {
2584 workspace.split_item(
2585 direction,
2586 Box::new(
2587 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2588 ),
2589 window,
2590 cx,
2591 )
2592 })?;
2593 anyhow::Ok(())
2594 })
2595 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2596 match e.error_code() {
2597 ErrorCode::RemoteUpgradeRequired => Some(format!(
2598 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2599 e.error_tag("required").unwrap_or("the latest version")
2600 )),
2601 _ => None,
2602 }
2603 });
2604 }
2605
2606 pub fn leader_id(&self) -> Option<CollaboratorId> {
2607 self.leader_id
2608 }
2609
2610 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2611 &self.buffer
2612 }
2613
2614 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2615 self.workspace.as_ref()?.0.upgrade()
2616 }
2617
2618 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2619 self.buffer().read(cx).title(cx)
2620 }
2621
2622 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2623 let git_blame_gutter_max_author_length = self
2624 .render_git_blame_gutter(cx)
2625 .then(|| {
2626 if let Some(blame) = self.blame.as_ref() {
2627 let max_author_length =
2628 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2629 Some(max_author_length)
2630 } else {
2631 None
2632 }
2633 })
2634 .flatten();
2635
2636 EditorSnapshot {
2637 mode: self.mode.clone(),
2638 show_gutter: self.show_gutter,
2639 show_line_numbers: self.show_line_numbers,
2640 show_git_diff_gutter: self.show_git_diff_gutter,
2641 show_code_actions: self.show_code_actions,
2642 show_runnables: self.show_runnables,
2643 show_breakpoints: self.show_breakpoints,
2644 git_blame_gutter_max_author_length,
2645 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2646 scroll_anchor: self.scroll_manager.anchor(),
2647 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2648 placeholder_text: self.placeholder_text.clone(),
2649 is_focused: self.focus_handle.is_focused(window),
2650 current_line_highlight: self
2651 .current_line_highlight
2652 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2653 gutter_hovered: self.gutter_hovered,
2654 }
2655 }
2656
2657 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2658 self.buffer.read(cx).language_at(point, cx)
2659 }
2660
2661 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2662 self.buffer.read(cx).read(cx).file_at(point).cloned()
2663 }
2664
2665 pub fn active_excerpt(
2666 &self,
2667 cx: &App,
2668 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2669 self.buffer
2670 .read(cx)
2671 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2672 }
2673
2674 pub fn mode(&self) -> &EditorMode {
2675 &self.mode
2676 }
2677
2678 pub fn set_mode(&mut self, mode: EditorMode) {
2679 self.mode = mode;
2680 }
2681
2682 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2683 self.collaboration_hub.as_deref()
2684 }
2685
2686 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2687 self.collaboration_hub = Some(hub);
2688 }
2689
2690 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2691 self.in_project_search = in_project_search;
2692 }
2693
2694 pub fn set_custom_context_menu(
2695 &mut self,
2696 f: impl 'static
2697 + Fn(
2698 &mut Self,
2699 DisplayPoint,
2700 &mut Window,
2701 &mut Context<Self>,
2702 ) -> Option<Entity<ui::ContextMenu>>,
2703 ) {
2704 self.custom_context_menu = Some(Box::new(f))
2705 }
2706
2707 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2708 self.completion_provider = provider;
2709 }
2710
2711 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2712 self.semantics_provider.clone()
2713 }
2714
2715 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2716 self.semantics_provider = provider;
2717 }
2718
2719 pub fn set_edit_prediction_provider<T>(
2720 &mut self,
2721 provider: Option<Entity<T>>,
2722 window: &mut Window,
2723 cx: &mut Context<Self>,
2724 ) where
2725 T: EditPredictionProvider,
2726 {
2727 self.edit_prediction_provider =
2728 provider.map(|provider| RegisteredInlineCompletionProvider {
2729 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2730 if this.focus_handle.is_focused(window) {
2731 this.update_visible_inline_completion(window, cx);
2732 }
2733 }),
2734 provider: Arc::new(provider),
2735 });
2736 self.update_edit_prediction_settings(cx);
2737 self.refresh_inline_completion(false, false, window, cx);
2738 }
2739
2740 pub fn placeholder_text(&self) -> Option<&str> {
2741 self.placeholder_text.as_deref()
2742 }
2743
2744 pub fn set_placeholder_text(
2745 &mut self,
2746 placeholder_text: impl Into<Arc<str>>,
2747 cx: &mut Context<Self>,
2748 ) {
2749 let placeholder_text = Some(placeholder_text.into());
2750 if self.placeholder_text != placeholder_text {
2751 self.placeholder_text = placeholder_text;
2752 cx.notify();
2753 }
2754 }
2755
2756 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2757 self.cursor_shape = cursor_shape;
2758
2759 // Disrupt blink for immediate user feedback that the cursor shape has changed
2760 self.blink_manager.update(cx, BlinkManager::show_cursor);
2761
2762 cx.notify();
2763 }
2764
2765 pub fn set_current_line_highlight(
2766 &mut self,
2767 current_line_highlight: Option<CurrentLineHighlight>,
2768 ) {
2769 self.current_line_highlight = current_line_highlight;
2770 }
2771
2772 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2773 self.collapse_matches = collapse_matches;
2774 }
2775
2776 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2777 let buffers = self.buffer.read(cx).all_buffers();
2778 let Some(project) = self.project.as_ref() else {
2779 return;
2780 };
2781 project.update(cx, |project, cx| {
2782 for buffer in buffers {
2783 self.registered_buffers
2784 .entry(buffer.read(cx).remote_id())
2785 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2786 }
2787 })
2788 }
2789
2790 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2791 if self.collapse_matches {
2792 return range.start..range.start;
2793 }
2794 range.clone()
2795 }
2796
2797 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2798 if self.display_map.read(cx).clip_at_line_ends != clip {
2799 self.display_map
2800 .update(cx, |map, _| map.clip_at_line_ends = clip);
2801 }
2802 }
2803
2804 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2805 self.input_enabled = input_enabled;
2806 }
2807
2808 pub fn set_inline_completions_hidden_for_vim_mode(
2809 &mut self,
2810 hidden: bool,
2811 window: &mut Window,
2812 cx: &mut Context<Self>,
2813 ) {
2814 if hidden != self.inline_completions_hidden_for_vim_mode {
2815 self.inline_completions_hidden_for_vim_mode = hidden;
2816 if hidden {
2817 self.update_visible_inline_completion(window, cx);
2818 } else {
2819 self.refresh_inline_completion(true, false, window, cx);
2820 }
2821 }
2822 }
2823
2824 pub fn set_menu_inline_completions_policy(&mut self, value: MenuInlineCompletionsPolicy) {
2825 self.menu_inline_completions_policy = value;
2826 }
2827
2828 pub fn set_autoindent(&mut self, autoindent: bool) {
2829 if autoindent {
2830 self.autoindent_mode = Some(AutoindentMode::EachLine);
2831 } else {
2832 self.autoindent_mode = None;
2833 }
2834 }
2835
2836 pub fn read_only(&self, cx: &App) -> bool {
2837 self.read_only || self.buffer.read(cx).read_only()
2838 }
2839
2840 pub fn set_read_only(&mut self, read_only: bool) {
2841 self.read_only = read_only;
2842 }
2843
2844 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2845 self.use_autoclose = autoclose;
2846 }
2847
2848 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2849 self.use_auto_surround = auto_surround;
2850 }
2851
2852 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2853 self.auto_replace_emoji_shortcode = auto_replace;
2854 }
2855
2856 pub fn toggle_edit_predictions(
2857 &mut self,
2858 _: &ToggleEditPrediction,
2859 window: &mut Window,
2860 cx: &mut Context<Self>,
2861 ) {
2862 if self.show_inline_completions_override.is_some() {
2863 self.set_show_edit_predictions(None, window, cx);
2864 } else {
2865 let show_edit_predictions = !self.edit_predictions_enabled();
2866 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2867 }
2868 }
2869
2870 pub fn set_show_edit_predictions(
2871 &mut self,
2872 show_edit_predictions: Option<bool>,
2873 window: &mut Window,
2874 cx: &mut Context<Self>,
2875 ) {
2876 self.show_inline_completions_override = show_edit_predictions;
2877 self.update_edit_prediction_settings(cx);
2878
2879 if let Some(false) = show_edit_predictions {
2880 self.discard_inline_completion(false, cx);
2881 } else {
2882 self.refresh_inline_completion(false, true, window, cx);
2883 }
2884 }
2885
2886 fn inline_completions_disabled_in_scope(
2887 &self,
2888 buffer: &Entity<Buffer>,
2889 buffer_position: language::Anchor,
2890 cx: &App,
2891 ) -> bool {
2892 let snapshot = buffer.read(cx).snapshot();
2893 let settings = snapshot.settings_at(buffer_position, cx);
2894
2895 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2896 return false;
2897 };
2898
2899 scope.override_name().map_or(false, |scope_name| {
2900 settings
2901 .edit_predictions_disabled_in
2902 .iter()
2903 .any(|s| s == scope_name)
2904 })
2905 }
2906
2907 pub fn set_use_modal_editing(&mut self, to: bool) {
2908 self.use_modal_editing = to;
2909 }
2910
2911 pub fn use_modal_editing(&self) -> bool {
2912 self.use_modal_editing
2913 }
2914
2915 fn selections_did_change(
2916 &mut self,
2917 local: bool,
2918 old_cursor_position: &Anchor,
2919 effects: SelectionEffects,
2920 window: &mut Window,
2921 cx: &mut Context<Self>,
2922 ) {
2923 window.invalidate_character_coordinates();
2924
2925 // Copy selections to primary selection buffer
2926 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2927 if local {
2928 let selections = self.selections.all::<usize>(cx);
2929 let buffer_handle = self.buffer.read(cx).read(cx);
2930
2931 let mut text = String::new();
2932 for (index, selection) in selections.iter().enumerate() {
2933 let text_for_selection = buffer_handle
2934 .text_for_range(selection.start..selection.end)
2935 .collect::<String>();
2936
2937 text.push_str(&text_for_selection);
2938 if index != selections.len() - 1 {
2939 text.push('\n');
2940 }
2941 }
2942
2943 if !text.is_empty() {
2944 cx.write_to_primary(ClipboardItem::new_string(text));
2945 }
2946 }
2947
2948 let selection_anchors = self.selections.disjoint_anchors();
2949
2950 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
2951 self.buffer.update(cx, |buffer, cx| {
2952 buffer.set_active_selections(
2953 &selection_anchors,
2954 self.selections.line_mode,
2955 self.cursor_shape,
2956 cx,
2957 )
2958 });
2959 }
2960 let display_map = self
2961 .display_map
2962 .update(cx, |display_map, cx| display_map.snapshot(cx));
2963 let buffer = &display_map.buffer_snapshot;
2964 if self.selections.count() == 1 {
2965 self.add_selections_state = None;
2966 }
2967 self.select_next_state = None;
2968 self.select_prev_state = None;
2969 self.select_syntax_node_history.try_clear();
2970 self.invalidate_autoclose_regions(&selection_anchors, buffer);
2971 self.snippet_stack.invalidate(&selection_anchors, buffer);
2972 self.take_rename(false, window, cx);
2973
2974 let newest_selection = self.selections.newest_anchor();
2975 let new_cursor_position = newest_selection.head();
2976 let selection_start = newest_selection.start;
2977
2978 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
2979 self.push_to_nav_history(
2980 *old_cursor_position,
2981 Some(new_cursor_position.to_point(buffer)),
2982 false,
2983 effects.nav_history == Some(true),
2984 cx,
2985 );
2986 }
2987
2988 if local {
2989 if let Some(buffer_id) = new_cursor_position.buffer_id {
2990 if !self.registered_buffers.contains_key(&buffer_id) {
2991 if let Some(project) = self.project.as_ref() {
2992 project.update(cx, |project, cx| {
2993 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
2994 return;
2995 };
2996 self.registered_buffers.insert(
2997 buffer_id,
2998 project.register_buffer_with_language_servers(&buffer, cx),
2999 );
3000 })
3001 }
3002 }
3003 }
3004
3005 let mut context_menu = self.context_menu.borrow_mut();
3006 let completion_menu = match context_menu.as_ref() {
3007 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3008 Some(CodeContextMenu::CodeActions(_)) => {
3009 *context_menu = None;
3010 None
3011 }
3012 None => None,
3013 };
3014 let completion_position = completion_menu.map(|menu| menu.initial_position);
3015 drop(context_menu);
3016
3017 if effects.completions {
3018 if let Some(completion_position) = completion_position {
3019 let start_offset = selection_start.to_offset(buffer);
3020 let position_matches = start_offset == completion_position.to_offset(buffer);
3021 let continue_showing = if position_matches {
3022 if self.snippet_stack.is_empty() {
3023 buffer.char_kind_before(start_offset, true) == Some(CharKind::Word)
3024 } else {
3025 // Snippet choices can be shown even when the cursor is in whitespace.
3026 // Dismissing the menu with actions like backspace is handled by
3027 // invalidation regions.
3028 true
3029 }
3030 } else {
3031 false
3032 };
3033
3034 if continue_showing {
3035 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
3036 } else {
3037 self.hide_context_menu(window, cx);
3038 }
3039 }
3040 }
3041
3042 hide_hover(self, cx);
3043
3044 if old_cursor_position.to_display_point(&display_map).row()
3045 != new_cursor_position.to_display_point(&display_map).row()
3046 {
3047 self.available_code_actions.take();
3048 }
3049 self.refresh_code_actions(window, cx);
3050 self.refresh_document_highlights(cx);
3051 self.refresh_selected_text_highlights(false, window, cx);
3052 refresh_matching_bracket_highlights(self, window, cx);
3053 self.update_visible_inline_completion(window, cx);
3054 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3055 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
3056 self.inline_blame_popover.take();
3057 if self.git_blame_inline_enabled {
3058 self.start_inline_blame_timer(window, cx);
3059 }
3060 }
3061
3062 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3063 cx.emit(EditorEvent::SelectionsChanged { local });
3064
3065 let selections = &self.selections.disjoint;
3066 if selections.len() == 1 {
3067 cx.emit(SearchEvent::ActiveMatchChanged)
3068 }
3069 if local {
3070 if let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3071 let inmemory_selections = selections
3072 .iter()
3073 .map(|s| {
3074 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3075 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3076 })
3077 .collect();
3078 self.update_restoration_data(cx, |data| {
3079 data.selections = inmemory_selections;
3080 });
3081
3082 if WorkspaceSettings::get(None, cx).restore_on_startup
3083 != RestoreOnStartupBehavior::None
3084 {
3085 if let Some(workspace_id) =
3086 self.workspace.as_ref().and_then(|workspace| workspace.1)
3087 {
3088 let snapshot = self.buffer().read(cx).snapshot(cx);
3089 let selections = selections.clone();
3090 let background_executor = cx.background_executor().clone();
3091 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3092 self.serialize_selections = cx.background_spawn(async move {
3093 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3094 let db_selections = selections
3095 .iter()
3096 .map(|selection| {
3097 (
3098 selection.start.to_offset(&snapshot),
3099 selection.end.to_offset(&snapshot),
3100 )
3101 })
3102 .collect();
3103
3104 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3105 .await
3106 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
3107 .log_err();
3108 });
3109 }
3110 }
3111 }
3112 }
3113
3114 cx.notify();
3115 }
3116
3117 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3118 use text::ToOffset as _;
3119 use text::ToPoint as _;
3120
3121 if self.mode.is_minimap()
3122 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3123 {
3124 return;
3125 }
3126
3127 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
3128 return;
3129 };
3130
3131 let snapshot = singleton.read(cx).snapshot();
3132 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
3133 let display_snapshot = display_map.snapshot(cx);
3134
3135 display_snapshot
3136 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
3137 .map(|fold| {
3138 fold.range.start.text_anchor.to_point(&snapshot)
3139 ..fold.range.end.text_anchor.to_point(&snapshot)
3140 })
3141 .collect()
3142 });
3143 self.update_restoration_data(cx, |data| {
3144 data.folds = inmemory_folds;
3145 });
3146
3147 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
3148 return;
3149 };
3150 let background_executor = cx.background_executor().clone();
3151 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3152 let db_folds = self.display_map.update(cx, |display_map, cx| {
3153 display_map
3154 .snapshot(cx)
3155 .folds_in_range(0..snapshot.len())
3156 .map(|fold| {
3157 (
3158 fold.range.start.text_anchor.to_offset(&snapshot),
3159 fold.range.end.text_anchor.to_offset(&snapshot),
3160 )
3161 })
3162 .collect()
3163 });
3164 self.serialize_folds = cx.background_spawn(async move {
3165 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3166 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3167 .await
3168 .with_context(|| {
3169 format!(
3170 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3171 )
3172 })
3173 .log_err();
3174 });
3175 }
3176
3177 pub fn sync_selections(
3178 &mut self,
3179 other: Entity<Editor>,
3180 cx: &mut Context<Self>,
3181 ) -> gpui::Subscription {
3182 let other_selections = other.read(cx).selections.disjoint.to_vec();
3183 self.selections.change_with(cx, |selections| {
3184 selections.select_anchors(other_selections);
3185 });
3186
3187 let other_subscription =
3188 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
3189 EditorEvent::SelectionsChanged { local: true } => {
3190 let other_selections = other.read(cx).selections.disjoint.to_vec();
3191 if other_selections.is_empty() {
3192 return;
3193 }
3194 this.selections.change_with(cx, |selections| {
3195 selections.select_anchors(other_selections);
3196 });
3197 }
3198 _ => {}
3199 });
3200
3201 let this_subscription =
3202 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
3203 EditorEvent::SelectionsChanged { local: true } => {
3204 let these_selections = this.selections.disjoint.to_vec();
3205 if these_selections.is_empty() {
3206 return;
3207 }
3208 other.update(cx, |other_editor, cx| {
3209 other_editor.selections.change_with(cx, |selections| {
3210 selections.select_anchors(these_selections);
3211 })
3212 });
3213 }
3214 _ => {}
3215 });
3216
3217 Subscription::join(other_subscription, this_subscription)
3218 }
3219
3220 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3221 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3222 /// effects of selection change occur at the end of the transaction.
3223 pub fn change_selections<R>(
3224 &mut self,
3225 effects: SelectionEffects,
3226 window: &mut Window,
3227 cx: &mut Context<Self>,
3228 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3229 ) -> R {
3230 if let Some(state) = &mut self.deferred_selection_effects_state {
3231 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3232 state.effects.completions = effects.completions;
3233 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3234 let (changed, result) = self.selections.change_with(cx, change);
3235 state.changed |= changed;
3236 return result;
3237 }
3238 let mut state = DeferredSelectionEffectsState {
3239 changed: false,
3240 effects,
3241 old_cursor_position: self.selections.newest_anchor().head(),
3242 history_entry: SelectionHistoryEntry {
3243 selections: self.selections.disjoint_anchors(),
3244 select_next_state: self.select_next_state.clone(),
3245 select_prev_state: self.select_prev_state.clone(),
3246 add_selections_state: self.add_selections_state.clone(),
3247 },
3248 };
3249 let (changed, result) = self.selections.change_with(cx, change);
3250 state.changed = state.changed || changed;
3251 if self.defer_selection_effects {
3252 self.deferred_selection_effects_state = Some(state);
3253 } else {
3254 self.apply_selection_effects(state, window, cx);
3255 }
3256 result
3257 }
3258
3259 /// Defers the effects of selection change, so that the effects of multiple calls to
3260 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3261 /// to selection history and the state of popovers based on selection position aren't
3262 /// erroneously updated.
3263 pub fn with_selection_effects_deferred<R>(
3264 &mut self,
3265 window: &mut Window,
3266 cx: &mut Context<Self>,
3267 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3268 ) -> R {
3269 let already_deferred = self.defer_selection_effects;
3270 self.defer_selection_effects = true;
3271 let result = update(self, window, cx);
3272 if !already_deferred {
3273 self.defer_selection_effects = false;
3274 if let Some(state) = self.deferred_selection_effects_state.take() {
3275 self.apply_selection_effects(state, window, cx);
3276 }
3277 }
3278 result
3279 }
3280
3281 fn apply_selection_effects(
3282 &mut self,
3283 state: DeferredSelectionEffectsState,
3284 window: &mut Window,
3285 cx: &mut Context<Self>,
3286 ) {
3287 if state.changed {
3288 self.selection_history.push(state.history_entry);
3289
3290 if let Some(autoscroll) = state.effects.scroll {
3291 self.request_autoscroll(autoscroll, cx);
3292 }
3293
3294 let old_cursor_position = &state.old_cursor_position;
3295
3296 self.selections_did_change(true, &old_cursor_position, state.effects, window, cx);
3297
3298 if self.should_open_signature_help_automatically(&old_cursor_position, cx) {
3299 self.show_signature_help(&ShowSignatureHelp, window, cx);
3300 }
3301 }
3302 }
3303
3304 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3305 where
3306 I: IntoIterator<Item = (Range<S>, T)>,
3307 S: ToOffset,
3308 T: Into<Arc<str>>,
3309 {
3310 if self.read_only(cx) {
3311 return;
3312 }
3313
3314 self.buffer
3315 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3316 }
3317
3318 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3319 where
3320 I: IntoIterator<Item = (Range<S>, T)>,
3321 S: ToOffset,
3322 T: Into<Arc<str>>,
3323 {
3324 if self.read_only(cx) {
3325 return;
3326 }
3327
3328 self.buffer.update(cx, |buffer, cx| {
3329 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3330 });
3331 }
3332
3333 pub fn edit_with_block_indent<I, S, T>(
3334 &mut self,
3335 edits: I,
3336 original_indent_columns: Vec<Option<u32>>,
3337 cx: &mut Context<Self>,
3338 ) where
3339 I: IntoIterator<Item = (Range<S>, T)>,
3340 S: ToOffset,
3341 T: Into<Arc<str>>,
3342 {
3343 if self.read_only(cx) {
3344 return;
3345 }
3346
3347 self.buffer.update(cx, |buffer, cx| {
3348 buffer.edit(
3349 edits,
3350 Some(AutoindentMode::Block {
3351 original_indent_columns,
3352 }),
3353 cx,
3354 )
3355 });
3356 }
3357
3358 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3359 self.hide_context_menu(window, cx);
3360
3361 match phase {
3362 SelectPhase::Begin {
3363 position,
3364 add,
3365 click_count,
3366 } => self.begin_selection(position, add, click_count, window, cx),
3367 SelectPhase::BeginColumnar {
3368 position,
3369 goal_column,
3370 reset,
3371 mode,
3372 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3373 SelectPhase::Extend {
3374 position,
3375 click_count,
3376 } => self.extend_selection(position, click_count, window, cx),
3377 SelectPhase::Update {
3378 position,
3379 goal_column,
3380 scroll_delta,
3381 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3382 SelectPhase::End => self.end_selection(window, cx),
3383 }
3384 }
3385
3386 fn extend_selection(
3387 &mut self,
3388 position: DisplayPoint,
3389 click_count: usize,
3390 window: &mut Window,
3391 cx: &mut Context<Self>,
3392 ) {
3393 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3394 let tail = self.selections.newest::<usize>(cx).tail();
3395 self.begin_selection(position, false, click_count, window, cx);
3396
3397 let position = position.to_offset(&display_map, Bias::Left);
3398 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3399
3400 let mut pending_selection = self
3401 .selections
3402 .pending_anchor()
3403 .expect("extend_selection not called with pending selection");
3404 if position >= tail {
3405 pending_selection.start = tail_anchor;
3406 } else {
3407 pending_selection.end = tail_anchor;
3408 pending_selection.reversed = true;
3409 }
3410
3411 let mut pending_mode = self.selections.pending_mode().unwrap();
3412 match &mut pending_mode {
3413 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3414 _ => {}
3415 }
3416
3417 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3418 SelectionEffects::scroll(Autoscroll::fit())
3419 } else {
3420 SelectionEffects::no_scroll()
3421 };
3422
3423 self.change_selections(effects, window, cx, |s| {
3424 s.set_pending(pending_selection, pending_mode)
3425 });
3426 }
3427
3428 fn begin_selection(
3429 &mut self,
3430 position: DisplayPoint,
3431 add: bool,
3432 click_count: usize,
3433 window: &mut Window,
3434 cx: &mut Context<Self>,
3435 ) {
3436 if !self.focus_handle.is_focused(window) {
3437 self.last_focused_descendant = None;
3438 window.focus(&self.focus_handle);
3439 }
3440
3441 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3442 let buffer = &display_map.buffer_snapshot;
3443 let position = display_map.clip_point(position, Bias::Left);
3444
3445 let start;
3446 let end;
3447 let mode;
3448 let mut auto_scroll;
3449 match click_count {
3450 1 => {
3451 start = buffer.anchor_before(position.to_point(&display_map));
3452 end = start;
3453 mode = SelectMode::Character;
3454 auto_scroll = true;
3455 }
3456 2 => {
3457 let position = display_map
3458 .clip_point(position, Bias::Left)
3459 .to_offset(&display_map, Bias::Left);
3460 let (range, _) = buffer.surrounding_word(position, false);
3461 start = buffer.anchor_before(range.start);
3462 end = buffer.anchor_before(range.end);
3463 mode = SelectMode::Word(start..end);
3464 auto_scroll = true;
3465 }
3466 3 => {
3467 let position = display_map
3468 .clip_point(position, Bias::Left)
3469 .to_point(&display_map);
3470 let line_start = display_map.prev_line_boundary(position).0;
3471 let next_line_start = buffer.clip_point(
3472 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3473 Bias::Left,
3474 );
3475 start = buffer.anchor_before(line_start);
3476 end = buffer.anchor_before(next_line_start);
3477 mode = SelectMode::Line(start..end);
3478 auto_scroll = true;
3479 }
3480 _ => {
3481 start = buffer.anchor_before(0);
3482 end = buffer.anchor_before(buffer.len());
3483 mode = SelectMode::All;
3484 auto_scroll = false;
3485 }
3486 }
3487 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3488
3489 let point_to_delete: Option<usize> = {
3490 let selected_points: Vec<Selection<Point>> =
3491 self.selections.disjoint_in_range(start..end, cx);
3492
3493 if !add || click_count > 1 {
3494 None
3495 } else if !selected_points.is_empty() {
3496 Some(selected_points[0].id)
3497 } else {
3498 let clicked_point_already_selected =
3499 self.selections.disjoint.iter().find(|selection| {
3500 selection.start.to_point(buffer) == start.to_point(buffer)
3501 || selection.end.to_point(buffer) == end.to_point(buffer)
3502 });
3503
3504 clicked_point_already_selected.map(|selection| selection.id)
3505 }
3506 };
3507
3508 let selections_count = self.selections.count();
3509 let effects = if auto_scroll {
3510 SelectionEffects::default()
3511 } else {
3512 SelectionEffects::no_scroll()
3513 };
3514
3515 self.change_selections(effects, window, cx, |s| {
3516 if let Some(point_to_delete) = point_to_delete {
3517 s.delete(point_to_delete);
3518
3519 if selections_count == 1 {
3520 s.set_pending_anchor_range(start..end, mode);
3521 }
3522 } else {
3523 if !add {
3524 s.clear_disjoint();
3525 }
3526
3527 s.set_pending_anchor_range(start..end, mode);
3528 }
3529 });
3530 }
3531
3532 fn begin_columnar_selection(
3533 &mut self,
3534 position: DisplayPoint,
3535 goal_column: u32,
3536 reset: bool,
3537 mode: ColumnarMode,
3538 window: &mut Window,
3539 cx: &mut Context<Self>,
3540 ) {
3541 if !self.focus_handle.is_focused(window) {
3542 self.last_focused_descendant = None;
3543 window.focus(&self.focus_handle);
3544 }
3545
3546 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3547
3548 if reset {
3549 let pointer_position = display_map
3550 .buffer_snapshot
3551 .anchor_before(position.to_point(&display_map));
3552
3553 self.change_selections(
3554 SelectionEffects::scroll(Autoscroll::newest()),
3555 window,
3556 cx,
3557 |s| {
3558 s.clear_disjoint();
3559 s.set_pending_anchor_range(
3560 pointer_position..pointer_position,
3561 SelectMode::Character,
3562 );
3563 },
3564 );
3565 };
3566
3567 let tail = self.selections.newest::<Point>(cx).tail();
3568 let selection_anchor = display_map.buffer_snapshot.anchor_before(tail);
3569 self.columnar_selection_state = match mode {
3570 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3571 selection_tail: selection_anchor,
3572 display_point: if reset {
3573 if position.column() != goal_column {
3574 Some(DisplayPoint::new(position.row(), goal_column))
3575 } else {
3576 None
3577 }
3578 } else {
3579 None
3580 },
3581 }),
3582 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3583 selection_tail: selection_anchor,
3584 }),
3585 };
3586
3587 if !reset {
3588 self.select_columns(position, goal_column, &display_map, window, cx);
3589 }
3590 }
3591
3592 fn update_selection(
3593 &mut self,
3594 position: DisplayPoint,
3595 goal_column: u32,
3596 scroll_delta: gpui::Point<f32>,
3597 window: &mut Window,
3598 cx: &mut Context<Self>,
3599 ) {
3600 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3601
3602 if self.columnar_selection_state.is_some() {
3603 self.select_columns(position, goal_column, &display_map, window, cx);
3604 } else if let Some(mut pending) = self.selections.pending_anchor() {
3605 let buffer = &display_map.buffer_snapshot;
3606 let head;
3607 let tail;
3608 let mode = self.selections.pending_mode().unwrap();
3609 match &mode {
3610 SelectMode::Character => {
3611 head = position.to_point(&display_map);
3612 tail = pending.tail().to_point(buffer);
3613 }
3614 SelectMode::Word(original_range) => {
3615 let offset = display_map
3616 .clip_point(position, Bias::Left)
3617 .to_offset(&display_map, Bias::Left);
3618 let original_range = original_range.to_offset(buffer);
3619
3620 let head_offset = if buffer.is_inside_word(offset, false)
3621 || original_range.contains(&offset)
3622 {
3623 let (word_range, _) = buffer.surrounding_word(offset, false);
3624 if word_range.start < original_range.start {
3625 word_range.start
3626 } else {
3627 word_range.end
3628 }
3629 } else {
3630 offset
3631 };
3632
3633 head = head_offset.to_point(buffer);
3634 if head_offset <= original_range.start {
3635 tail = original_range.end.to_point(buffer);
3636 } else {
3637 tail = original_range.start.to_point(buffer);
3638 }
3639 }
3640 SelectMode::Line(original_range) => {
3641 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3642
3643 let position = display_map
3644 .clip_point(position, Bias::Left)
3645 .to_point(&display_map);
3646 let line_start = display_map.prev_line_boundary(position).0;
3647 let next_line_start = buffer.clip_point(
3648 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3649 Bias::Left,
3650 );
3651
3652 if line_start < original_range.start {
3653 head = line_start
3654 } else {
3655 head = next_line_start
3656 }
3657
3658 if head <= original_range.start {
3659 tail = original_range.end;
3660 } else {
3661 tail = original_range.start;
3662 }
3663 }
3664 SelectMode::All => {
3665 return;
3666 }
3667 };
3668
3669 if head < tail {
3670 pending.start = buffer.anchor_before(head);
3671 pending.end = buffer.anchor_before(tail);
3672 pending.reversed = true;
3673 } else {
3674 pending.start = buffer.anchor_before(tail);
3675 pending.end = buffer.anchor_before(head);
3676 pending.reversed = false;
3677 }
3678
3679 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3680 s.set_pending(pending, mode);
3681 });
3682 } else {
3683 log::error!("update_selection dispatched with no pending selection");
3684 return;
3685 }
3686
3687 self.apply_scroll_delta(scroll_delta, window, cx);
3688 cx.notify();
3689 }
3690
3691 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3692 self.columnar_selection_state.take();
3693 if self.selections.pending_anchor().is_some() {
3694 let selections = self.selections.all::<usize>(cx);
3695 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3696 s.select(selections);
3697 s.clear_pending();
3698 });
3699 }
3700 }
3701
3702 fn select_columns(
3703 &mut self,
3704 head: DisplayPoint,
3705 goal_column: u32,
3706 display_map: &DisplaySnapshot,
3707 window: &mut Window,
3708 cx: &mut Context<Self>,
3709 ) {
3710 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3711 return;
3712 };
3713
3714 let tail = match columnar_state {
3715 ColumnarSelectionState::FromMouse {
3716 selection_tail,
3717 display_point,
3718 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(&display_map)),
3719 ColumnarSelectionState::FromSelection { selection_tail } => {
3720 selection_tail.to_display_point(&display_map)
3721 }
3722 };
3723
3724 let start_row = cmp::min(tail.row(), head.row());
3725 let end_row = cmp::max(tail.row(), head.row());
3726 let start_column = cmp::min(tail.column(), goal_column);
3727 let end_column = cmp::max(tail.column(), goal_column);
3728 let reversed = start_column < tail.column();
3729
3730 let selection_ranges = (start_row.0..=end_row.0)
3731 .map(DisplayRow)
3732 .filter_map(|row| {
3733 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3734 || start_column <= display_map.line_len(row))
3735 && !display_map.is_block_line(row)
3736 {
3737 let start = display_map
3738 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3739 .to_point(display_map);
3740 let end = display_map
3741 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3742 .to_point(display_map);
3743 if reversed {
3744 Some(end..start)
3745 } else {
3746 Some(start..end)
3747 }
3748 } else {
3749 None
3750 }
3751 })
3752 .collect::<Vec<_>>();
3753
3754 let ranges = match columnar_state {
3755 ColumnarSelectionState::FromMouse { .. } => {
3756 let mut non_empty_ranges = selection_ranges
3757 .iter()
3758 .filter(|selection_range| selection_range.start != selection_range.end)
3759 .peekable();
3760 if non_empty_ranges.peek().is_some() {
3761 non_empty_ranges.cloned().collect()
3762 } else {
3763 selection_ranges
3764 }
3765 }
3766 _ => selection_ranges,
3767 };
3768
3769 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3770 s.select_ranges(ranges);
3771 });
3772 cx.notify();
3773 }
3774
3775 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3776 self.selections
3777 .all_adjusted(cx)
3778 .iter()
3779 .any(|selection| !selection.is_empty())
3780 }
3781
3782 pub fn has_pending_nonempty_selection(&self) -> bool {
3783 let pending_nonempty_selection = match self.selections.pending_anchor() {
3784 Some(Selection { start, end, .. }) => start != end,
3785 None => false,
3786 };
3787
3788 pending_nonempty_selection
3789 || (self.columnar_selection_state.is_some() && self.selections.disjoint.len() > 1)
3790 }
3791
3792 pub fn has_pending_selection(&self) -> bool {
3793 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
3794 }
3795
3796 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3797 self.selection_mark_mode = false;
3798 self.selection_drag_state = SelectionDragState::None;
3799
3800 if self.clear_expanded_diff_hunks(cx) {
3801 cx.notify();
3802 return;
3803 }
3804 if self.dismiss_menus_and_popups(true, window, cx) {
3805 return;
3806 }
3807
3808 if self.mode.is_full()
3809 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
3810 {
3811 return;
3812 }
3813
3814 cx.propagate();
3815 }
3816
3817 pub fn dismiss_menus_and_popups(
3818 &mut self,
3819 is_user_requested: bool,
3820 window: &mut Window,
3821 cx: &mut Context<Self>,
3822 ) -> bool {
3823 if self.take_rename(false, window, cx).is_some() {
3824 return true;
3825 }
3826
3827 if hide_hover(self, cx) {
3828 return true;
3829 }
3830
3831 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3832 return true;
3833 }
3834
3835 if self.hide_context_menu(window, cx).is_some() {
3836 return true;
3837 }
3838
3839 if self.mouse_context_menu.take().is_some() {
3840 return true;
3841 }
3842
3843 if is_user_requested && self.discard_inline_completion(true, cx) {
3844 return true;
3845 }
3846
3847 if self.snippet_stack.pop().is_some() {
3848 return true;
3849 }
3850
3851 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3852 self.dismiss_diagnostics(cx);
3853 return true;
3854 }
3855
3856 false
3857 }
3858
3859 fn linked_editing_ranges_for(
3860 &self,
3861 selection: Range<text::Anchor>,
3862 cx: &App,
3863 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3864 if self.linked_edit_ranges.is_empty() {
3865 return None;
3866 }
3867 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3868 selection.end.buffer_id.and_then(|end_buffer_id| {
3869 if selection.start.buffer_id != Some(end_buffer_id) {
3870 return None;
3871 }
3872 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3873 let snapshot = buffer.read(cx).snapshot();
3874 self.linked_edit_ranges
3875 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3876 .map(|ranges| (ranges, snapshot, buffer))
3877 })?;
3878 use text::ToOffset as TO;
3879 // find offset from the start of current range to current cursor position
3880 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3881
3882 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3883 let start_difference = start_offset - start_byte_offset;
3884 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3885 let end_difference = end_offset - start_byte_offset;
3886 // Current range has associated linked ranges.
3887 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3888 for range in linked_ranges.iter() {
3889 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3890 let end_offset = start_offset + end_difference;
3891 let start_offset = start_offset + start_difference;
3892 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3893 continue;
3894 }
3895 if self.selections.disjoint_anchor_ranges().any(|s| {
3896 if s.start.buffer_id != selection.start.buffer_id
3897 || s.end.buffer_id != selection.end.buffer_id
3898 {
3899 return false;
3900 }
3901 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3902 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3903 }) {
3904 continue;
3905 }
3906 let start = buffer_snapshot.anchor_after(start_offset);
3907 let end = buffer_snapshot.anchor_after(end_offset);
3908 linked_edits
3909 .entry(buffer.clone())
3910 .or_default()
3911 .push(start..end);
3912 }
3913 Some(linked_edits)
3914 }
3915
3916 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3917 let text: Arc<str> = text.into();
3918
3919 if self.read_only(cx) {
3920 return;
3921 }
3922
3923 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
3924
3925 let selections = self.selections.all_adjusted(cx);
3926 let mut bracket_inserted = false;
3927 let mut edits = Vec::new();
3928 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3929 let mut new_selections = Vec::with_capacity(selections.len());
3930 let mut new_autoclose_regions = Vec::new();
3931 let snapshot = self.buffer.read(cx).read(cx);
3932 let mut clear_linked_edit_ranges = false;
3933
3934 for (selection, autoclose_region) in
3935 self.selections_with_autoclose_regions(selections, &snapshot)
3936 {
3937 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3938 // Determine if the inserted text matches the opening or closing
3939 // bracket of any of this language's bracket pairs.
3940 let mut bracket_pair = None;
3941 let mut is_bracket_pair_start = false;
3942 let mut is_bracket_pair_end = false;
3943 if !text.is_empty() {
3944 let mut bracket_pair_matching_end = None;
3945 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3946 // and they are removing the character that triggered IME popup.
3947 for (pair, enabled) in scope.brackets() {
3948 if !pair.close && !pair.surround {
3949 continue;
3950 }
3951
3952 if enabled && pair.start.ends_with(text.as_ref()) {
3953 let prefix_len = pair.start.len() - text.len();
3954 let preceding_text_matches_prefix = prefix_len == 0
3955 || (selection.start.column >= (prefix_len as u32)
3956 && snapshot.contains_str_at(
3957 Point::new(
3958 selection.start.row,
3959 selection.start.column - (prefix_len as u32),
3960 ),
3961 &pair.start[..prefix_len],
3962 ));
3963 if preceding_text_matches_prefix {
3964 bracket_pair = Some(pair.clone());
3965 is_bracket_pair_start = true;
3966 break;
3967 }
3968 }
3969 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
3970 {
3971 // take first bracket pair matching end, but don't break in case a later bracket
3972 // pair matches start
3973 bracket_pair_matching_end = Some(pair.clone());
3974 }
3975 }
3976 if let Some(end) = bracket_pair_matching_end
3977 && bracket_pair.is_none()
3978 {
3979 bracket_pair = Some(end);
3980 is_bracket_pair_end = true;
3981 }
3982 }
3983
3984 if let Some(bracket_pair) = bracket_pair {
3985 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
3986 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
3987 let auto_surround =
3988 self.use_auto_surround && snapshot_settings.use_auto_surround;
3989 if selection.is_empty() {
3990 if is_bracket_pair_start {
3991 // If the inserted text is a suffix of an opening bracket and the
3992 // selection is preceded by the rest of the opening bracket, then
3993 // insert the closing bracket.
3994 let following_text_allows_autoclose = snapshot
3995 .chars_at(selection.start)
3996 .next()
3997 .map_or(true, |c| scope.should_autoclose_before(c));
3998
3999 let preceding_text_allows_autoclose = selection.start.column == 0
4000 || snapshot.reversed_chars_at(selection.start).next().map_or(
4001 true,
4002 |c| {
4003 bracket_pair.start != bracket_pair.end
4004 || !snapshot
4005 .char_classifier_at(selection.start)
4006 .is_word(c)
4007 },
4008 );
4009
4010 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4011 && bracket_pair.start.len() == 1
4012 {
4013 let target = bracket_pair.start.chars().next().unwrap();
4014 let current_line_count = snapshot
4015 .reversed_chars_at(selection.start)
4016 .take_while(|&c| c != '\n')
4017 .filter(|&c| c == target)
4018 .count();
4019 current_line_count % 2 == 1
4020 } else {
4021 false
4022 };
4023
4024 if autoclose
4025 && bracket_pair.close
4026 && following_text_allows_autoclose
4027 && preceding_text_allows_autoclose
4028 && !is_closing_quote
4029 {
4030 let anchor = snapshot.anchor_before(selection.end);
4031 new_selections.push((selection.map(|_| anchor), text.len()));
4032 new_autoclose_regions.push((
4033 anchor,
4034 text.len(),
4035 selection.id,
4036 bracket_pair.clone(),
4037 ));
4038 edits.push((
4039 selection.range(),
4040 format!("{}{}", text, bracket_pair.end).into(),
4041 ));
4042 bracket_inserted = true;
4043 continue;
4044 }
4045 }
4046
4047 if let Some(region) = autoclose_region {
4048 // If the selection is followed by an auto-inserted closing bracket,
4049 // then don't insert that closing bracket again; just move the selection
4050 // past the closing bracket.
4051 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4052 && text.as_ref() == region.pair.end.as_str()
4053 && snapshot.contains_str_at(region.range.end, text.as_ref());
4054 if should_skip {
4055 let anchor = snapshot.anchor_after(selection.end);
4056 new_selections
4057 .push((selection.map(|_| anchor), region.pair.end.len()));
4058 continue;
4059 }
4060 }
4061
4062 let always_treat_brackets_as_autoclosed = snapshot
4063 .language_settings_at(selection.start, cx)
4064 .always_treat_brackets_as_autoclosed;
4065 if always_treat_brackets_as_autoclosed
4066 && is_bracket_pair_end
4067 && snapshot.contains_str_at(selection.end, text.as_ref())
4068 {
4069 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4070 // and the inserted text is a closing bracket and the selection is followed
4071 // by the closing bracket then move the selection past the closing bracket.
4072 let anchor = snapshot.anchor_after(selection.end);
4073 new_selections.push((selection.map(|_| anchor), text.len()));
4074 continue;
4075 }
4076 }
4077 // If an opening bracket is 1 character long and is typed while
4078 // text is selected, then surround that text with the bracket pair.
4079 else if auto_surround
4080 && bracket_pair.surround
4081 && is_bracket_pair_start
4082 && bracket_pair.start.chars().count() == 1
4083 {
4084 edits.push((selection.start..selection.start, text.clone()));
4085 edits.push((
4086 selection.end..selection.end,
4087 bracket_pair.end.as_str().into(),
4088 ));
4089 bracket_inserted = true;
4090 new_selections.push((
4091 Selection {
4092 id: selection.id,
4093 start: snapshot.anchor_after(selection.start),
4094 end: snapshot.anchor_before(selection.end),
4095 reversed: selection.reversed,
4096 goal: selection.goal,
4097 },
4098 0,
4099 ));
4100 continue;
4101 }
4102 }
4103 }
4104
4105 if self.auto_replace_emoji_shortcode
4106 && selection.is_empty()
4107 && text.as_ref().ends_with(':')
4108 {
4109 if let Some(possible_emoji_short_code) =
4110 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4111 {
4112 if !possible_emoji_short_code.is_empty() {
4113 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
4114 let emoji_shortcode_start = Point::new(
4115 selection.start.row,
4116 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4117 );
4118
4119 // Remove shortcode from buffer
4120 edits.push((
4121 emoji_shortcode_start..selection.start,
4122 "".to_string().into(),
4123 ));
4124 new_selections.push((
4125 Selection {
4126 id: selection.id,
4127 start: snapshot.anchor_after(emoji_shortcode_start),
4128 end: snapshot.anchor_before(selection.start),
4129 reversed: selection.reversed,
4130 goal: selection.goal,
4131 },
4132 0,
4133 ));
4134
4135 // Insert emoji
4136 let selection_start_anchor = snapshot.anchor_after(selection.start);
4137 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4138 edits.push((selection.start..selection.end, emoji.to_string().into()));
4139
4140 continue;
4141 }
4142 }
4143 }
4144 }
4145
4146 // If not handling any auto-close operation, then just replace the selected
4147 // text with the given input and move the selection to the end of the
4148 // newly inserted text.
4149 let anchor = snapshot.anchor_after(selection.end);
4150 if !self.linked_edit_ranges.is_empty() {
4151 let start_anchor = snapshot.anchor_before(selection.start);
4152
4153 let is_word_char = text.chars().next().map_or(true, |char| {
4154 let classifier = snapshot
4155 .char_classifier_at(start_anchor.to_offset(&snapshot))
4156 .ignore_punctuation(true);
4157 classifier.is_word(char)
4158 });
4159
4160 if is_word_char {
4161 if let Some(ranges) = self
4162 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4163 {
4164 for (buffer, edits) in ranges {
4165 linked_edits
4166 .entry(buffer.clone())
4167 .or_default()
4168 .extend(edits.into_iter().map(|range| (range, text.clone())));
4169 }
4170 }
4171 } else {
4172 clear_linked_edit_ranges = true;
4173 }
4174 }
4175
4176 new_selections.push((selection.map(|_| anchor), 0));
4177 edits.push((selection.start..selection.end, text.clone()));
4178 }
4179
4180 drop(snapshot);
4181
4182 self.transact(window, cx, |this, window, cx| {
4183 if clear_linked_edit_ranges {
4184 this.linked_edit_ranges.clear();
4185 }
4186 let initial_buffer_versions =
4187 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4188
4189 this.buffer.update(cx, |buffer, cx| {
4190 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4191 });
4192 for (buffer, edits) in linked_edits {
4193 buffer.update(cx, |buffer, cx| {
4194 let snapshot = buffer.snapshot();
4195 let edits = edits
4196 .into_iter()
4197 .map(|(range, text)| {
4198 use text::ToPoint as TP;
4199 let end_point = TP::to_point(&range.end, &snapshot);
4200 let start_point = TP::to_point(&range.start, &snapshot);
4201 (start_point..end_point, text)
4202 })
4203 .sorted_by_key(|(range, _)| range.start);
4204 buffer.edit(edits, None, cx);
4205 })
4206 }
4207 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4208 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4209 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4210 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
4211 .zip(new_selection_deltas)
4212 .map(|(selection, delta)| Selection {
4213 id: selection.id,
4214 start: selection.start + delta,
4215 end: selection.end + delta,
4216 reversed: selection.reversed,
4217 goal: SelectionGoal::None,
4218 })
4219 .collect::<Vec<_>>();
4220
4221 let mut i = 0;
4222 for (position, delta, selection_id, pair) in new_autoclose_regions {
4223 let position = position.to_offset(&map.buffer_snapshot) + delta;
4224 let start = map.buffer_snapshot.anchor_before(position);
4225 let end = map.buffer_snapshot.anchor_after(position);
4226 while let Some(existing_state) = this.autoclose_regions.get(i) {
4227 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
4228 Ordering::Less => i += 1,
4229 Ordering::Greater => break,
4230 Ordering::Equal => {
4231 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
4232 Ordering::Less => i += 1,
4233 Ordering::Equal => break,
4234 Ordering::Greater => break,
4235 }
4236 }
4237 }
4238 }
4239 this.autoclose_regions.insert(
4240 i,
4241 AutocloseRegion {
4242 selection_id,
4243 range: start..end,
4244 pair,
4245 },
4246 );
4247 }
4248
4249 let had_active_inline_completion = this.has_active_inline_completion();
4250 this.change_selections(
4251 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4252 window,
4253 cx,
4254 |s| s.select(new_selections),
4255 );
4256
4257 if !bracket_inserted {
4258 if let Some(on_type_format_task) =
4259 this.trigger_on_type_formatting(text.to_string(), window, cx)
4260 {
4261 on_type_format_task.detach_and_log_err(cx);
4262 }
4263 }
4264
4265 let editor_settings = EditorSettings::get_global(cx);
4266 if bracket_inserted
4267 && (editor_settings.auto_signature_help
4268 || editor_settings.show_signature_help_after_edits)
4269 {
4270 this.show_signature_help(&ShowSignatureHelp, window, cx);
4271 }
4272
4273 let trigger_in_words =
4274 this.show_edit_predictions_in_menu() || !had_active_inline_completion;
4275 if this.hard_wrap.is_some() {
4276 let latest: Range<Point> = this.selections.newest(cx).range();
4277 if latest.is_empty()
4278 && this
4279 .buffer()
4280 .read(cx)
4281 .snapshot(cx)
4282 .line_len(MultiBufferRow(latest.start.row))
4283 == latest.start.column
4284 {
4285 this.rewrap_impl(
4286 RewrapOptions {
4287 override_language_settings: true,
4288 preserve_existing_whitespace: true,
4289 },
4290 cx,
4291 )
4292 }
4293 }
4294 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4295 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
4296 this.refresh_inline_completion(true, false, window, cx);
4297 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4298 });
4299 }
4300
4301 fn find_possible_emoji_shortcode_at_position(
4302 snapshot: &MultiBufferSnapshot,
4303 position: Point,
4304 ) -> Option<String> {
4305 let mut chars = Vec::new();
4306 let mut found_colon = false;
4307 for char in snapshot.reversed_chars_at(position).take(100) {
4308 // Found a possible emoji shortcode in the middle of the buffer
4309 if found_colon {
4310 if char.is_whitespace() {
4311 chars.reverse();
4312 return Some(chars.iter().collect());
4313 }
4314 // If the previous character is not a whitespace, we are in the middle of a word
4315 // and we only want to complete the shortcode if the word is made up of other emojis
4316 let mut containing_word = String::new();
4317 for ch in snapshot
4318 .reversed_chars_at(position)
4319 .skip(chars.len() + 1)
4320 .take(100)
4321 {
4322 if ch.is_whitespace() {
4323 break;
4324 }
4325 containing_word.push(ch);
4326 }
4327 let containing_word = containing_word.chars().rev().collect::<String>();
4328 if util::word_consists_of_emojis(containing_word.as_str()) {
4329 chars.reverse();
4330 return Some(chars.iter().collect());
4331 }
4332 }
4333
4334 if char.is_whitespace() || !char.is_ascii() {
4335 return None;
4336 }
4337 if char == ':' {
4338 found_colon = true;
4339 } else {
4340 chars.push(char);
4341 }
4342 }
4343 // Found a possible emoji shortcode at the beginning of the buffer
4344 chars.reverse();
4345 Some(chars.iter().collect())
4346 }
4347
4348 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4349 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4350 self.transact(window, cx, |this, window, cx| {
4351 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4352 let selections = this.selections.all::<usize>(cx);
4353 let multi_buffer = this.buffer.read(cx);
4354 let buffer = multi_buffer.snapshot(cx);
4355 selections
4356 .iter()
4357 .map(|selection| {
4358 let start_point = selection.start.to_point(&buffer);
4359 let mut existing_indent =
4360 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4361 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4362 let start = selection.start;
4363 let end = selection.end;
4364 let selection_is_empty = start == end;
4365 let language_scope = buffer.language_scope_at(start);
4366 let (
4367 comment_delimiter,
4368 doc_delimiter,
4369 insert_extra_newline,
4370 indent_on_newline,
4371 indent_on_extra_newline,
4372 ) = if let Some(language) = &language_scope {
4373 let mut insert_extra_newline =
4374 insert_extra_newline_brackets(&buffer, start..end, language)
4375 || insert_extra_newline_tree_sitter(&buffer, start..end);
4376
4377 // Comment extension on newline is allowed only for cursor selections
4378 let comment_delimiter = maybe!({
4379 if !selection_is_empty {
4380 return None;
4381 }
4382
4383 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4384 return None;
4385 }
4386
4387 let delimiters = language.line_comment_prefixes();
4388 let max_len_of_delimiter =
4389 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4390 let (snapshot, range) =
4391 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4392
4393 let num_of_whitespaces = snapshot
4394 .chars_for_range(range.clone())
4395 .take_while(|c| c.is_whitespace())
4396 .count();
4397 let comment_candidate = snapshot
4398 .chars_for_range(range.clone())
4399 .skip(num_of_whitespaces)
4400 .take(max_len_of_delimiter)
4401 .collect::<String>();
4402 let (delimiter, trimmed_len) = delimiters
4403 .iter()
4404 .filter_map(|delimiter| {
4405 let prefix = delimiter.trim_end();
4406 if comment_candidate.starts_with(prefix) {
4407 Some((delimiter, prefix.len()))
4408 } else {
4409 None
4410 }
4411 })
4412 .max_by_key(|(_, len)| *len)?;
4413
4414 if let Some(BlockCommentConfig {
4415 start: block_start, ..
4416 }) = language.block_comment()
4417 {
4418 let block_start_trimmed = block_start.trim_end();
4419 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4420 let line_content = snapshot
4421 .chars_for_range(range)
4422 .skip(num_of_whitespaces)
4423 .take(block_start_trimmed.len())
4424 .collect::<String>();
4425
4426 if line_content.starts_with(block_start_trimmed) {
4427 return None;
4428 }
4429 }
4430 }
4431
4432 let cursor_is_placed_after_comment_marker =
4433 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4434 if cursor_is_placed_after_comment_marker {
4435 Some(delimiter.clone())
4436 } else {
4437 None
4438 }
4439 });
4440
4441 let mut indent_on_newline = IndentSize::spaces(0);
4442 let mut indent_on_extra_newline = IndentSize::spaces(0);
4443
4444 let doc_delimiter = maybe!({
4445 if !selection_is_empty {
4446 return None;
4447 }
4448
4449 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4450 return None;
4451 }
4452
4453 let BlockCommentConfig {
4454 start: start_tag,
4455 end: end_tag,
4456 prefix: delimiter,
4457 tab_size: len,
4458 } = language.documentation_comment()?;
4459 let is_within_block_comment = buffer
4460 .language_scope_at(start_point)
4461 .is_some_and(|scope| scope.override_name() == Some("comment"));
4462 if !is_within_block_comment {
4463 return None;
4464 }
4465
4466 let (snapshot, range) =
4467 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4468
4469 let num_of_whitespaces = snapshot
4470 .chars_for_range(range.clone())
4471 .take_while(|c| c.is_whitespace())
4472 .count();
4473
4474 // It is safe to use a column from MultiBufferPoint in context of a single buffer ranges, because we're only ever looking at a single line at a time.
4475 let column = start_point.column;
4476 let cursor_is_after_start_tag = {
4477 let start_tag_len = start_tag.len();
4478 let start_tag_line = snapshot
4479 .chars_for_range(range.clone())
4480 .skip(num_of_whitespaces)
4481 .take(start_tag_len)
4482 .collect::<String>();
4483 if start_tag_line.starts_with(start_tag.as_ref()) {
4484 num_of_whitespaces + start_tag_len <= column as usize
4485 } else {
4486 false
4487 }
4488 };
4489
4490 let cursor_is_after_delimiter = {
4491 let delimiter_trim = delimiter.trim_end();
4492 let delimiter_line = snapshot
4493 .chars_for_range(range.clone())
4494 .skip(num_of_whitespaces)
4495 .take(delimiter_trim.len())
4496 .collect::<String>();
4497 if delimiter_line.starts_with(delimiter_trim) {
4498 num_of_whitespaces + delimiter_trim.len() <= column as usize
4499 } else {
4500 false
4501 }
4502 };
4503
4504 let cursor_is_before_end_tag_if_exists = {
4505 let mut char_position = 0u32;
4506 let mut end_tag_offset = None;
4507
4508 'outer: for chunk in snapshot.text_for_range(range.clone()) {
4509 if let Some(byte_pos) = chunk.find(&**end_tag) {
4510 let chars_before_match =
4511 chunk[..byte_pos].chars().count() as u32;
4512 end_tag_offset =
4513 Some(char_position + chars_before_match);
4514 break 'outer;
4515 }
4516 char_position += chunk.chars().count() as u32;
4517 }
4518
4519 if let Some(end_tag_offset) = end_tag_offset {
4520 let cursor_is_before_end_tag = column <= end_tag_offset;
4521 if cursor_is_after_start_tag {
4522 if cursor_is_before_end_tag {
4523 insert_extra_newline = true;
4524 }
4525 let cursor_is_at_start_of_end_tag =
4526 column == end_tag_offset;
4527 if cursor_is_at_start_of_end_tag {
4528 indent_on_extra_newline.len = *len;
4529 }
4530 }
4531 cursor_is_before_end_tag
4532 } else {
4533 true
4534 }
4535 };
4536
4537 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4538 && cursor_is_before_end_tag_if_exists
4539 {
4540 if cursor_is_after_start_tag {
4541 indent_on_newline.len = *len;
4542 }
4543 Some(delimiter.clone())
4544 } else {
4545 None
4546 }
4547 });
4548
4549 (
4550 comment_delimiter,
4551 doc_delimiter,
4552 insert_extra_newline,
4553 indent_on_newline,
4554 indent_on_extra_newline,
4555 )
4556 } else {
4557 (
4558 None,
4559 None,
4560 false,
4561 IndentSize::default(),
4562 IndentSize::default(),
4563 )
4564 };
4565
4566 let prevent_auto_indent = doc_delimiter.is_some();
4567 let delimiter = comment_delimiter.or(doc_delimiter);
4568
4569 let capacity_for_delimiter =
4570 delimiter.as_deref().map(str::len).unwrap_or_default();
4571 let mut new_text = String::with_capacity(
4572 1 + capacity_for_delimiter
4573 + existing_indent.len as usize
4574 + indent_on_newline.len as usize
4575 + indent_on_extra_newline.len as usize,
4576 );
4577 new_text.push('\n');
4578 new_text.extend(existing_indent.chars());
4579 new_text.extend(indent_on_newline.chars());
4580
4581 if let Some(delimiter) = &delimiter {
4582 new_text.push_str(delimiter);
4583 }
4584
4585 if insert_extra_newline {
4586 new_text.push('\n');
4587 new_text.extend(existing_indent.chars());
4588 new_text.extend(indent_on_extra_newline.chars());
4589 }
4590
4591 let anchor = buffer.anchor_after(end);
4592 let new_selection = selection.map(|_| anchor);
4593 (
4594 ((start..end, new_text), prevent_auto_indent),
4595 (insert_extra_newline, new_selection),
4596 )
4597 })
4598 .unzip()
4599 };
4600
4601 let mut auto_indent_edits = Vec::new();
4602 let mut edits = Vec::new();
4603 for (edit, prevent_auto_indent) in edits_with_flags {
4604 if prevent_auto_indent {
4605 edits.push(edit);
4606 } else {
4607 auto_indent_edits.push(edit);
4608 }
4609 }
4610 if !edits.is_empty() {
4611 this.edit(edits, cx);
4612 }
4613 if !auto_indent_edits.is_empty() {
4614 this.edit_with_autoindent(auto_indent_edits, cx);
4615 }
4616
4617 let buffer = this.buffer.read(cx).snapshot(cx);
4618 let new_selections = selection_info
4619 .into_iter()
4620 .map(|(extra_newline_inserted, new_selection)| {
4621 let mut cursor = new_selection.end.to_point(&buffer);
4622 if extra_newline_inserted {
4623 cursor.row -= 1;
4624 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4625 }
4626 new_selection.map(|_| cursor)
4627 })
4628 .collect();
4629
4630 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4631 this.refresh_inline_completion(true, false, window, cx);
4632 });
4633 }
4634
4635 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4636 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4637
4638 let buffer = self.buffer.read(cx);
4639 let snapshot = buffer.snapshot(cx);
4640
4641 let mut edits = Vec::new();
4642 let mut rows = Vec::new();
4643
4644 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4645 let cursor = selection.head();
4646 let row = cursor.row;
4647
4648 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4649
4650 let newline = "\n".to_string();
4651 edits.push((start_of_line..start_of_line, newline));
4652
4653 rows.push(row + rows_inserted as u32);
4654 }
4655
4656 self.transact(window, cx, |editor, window, cx| {
4657 editor.edit(edits, cx);
4658
4659 editor.change_selections(Default::default(), window, cx, |s| {
4660 let mut index = 0;
4661 s.move_cursors_with(|map, _, _| {
4662 let row = rows[index];
4663 index += 1;
4664
4665 let point = Point::new(row, 0);
4666 let boundary = map.next_line_boundary(point).1;
4667 let clipped = map.clip_point(boundary, Bias::Left);
4668
4669 (clipped, SelectionGoal::None)
4670 });
4671 });
4672
4673 let mut indent_edits = Vec::new();
4674 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4675 for row in rows {
4676 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4677 for (row, indent) in indents {
4678 if indent.len == 0 {
4679 continue;
4680 }
4681
4682 let text = match indent.kind {
4683 IndentKind::Space => " ".repeat(indent.len as usize),
4684 IndentKind::Tab => "\t".repeat(indent.len as usize),
4685 };
4686 let point = Point::new(row.0, 0);
4687 indent_edits.push((point..point, text));
4688 }
4689 }
4690 editor.edit(indent_edits, cx);
4691 });
4692 }
4693
4694 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4695 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4696
4697 let buffer = self.buffer.read(cx);
4698 let snapshot = buffer.snapshot(cx);
4699
4700 let mut edits = Vec::new();
4701 let mut rows = Vec::new();
4702 let mut rows_inserted = 0;
4703
4704 for selection in self.selections.all_adjusted(cx) {
4705 let cursor = selection.head();
4706 let row = cursor.row;
4707
4708 let point = Point::new(row + 1, 0);
4709 let start_of_line = snapshot.clip_point(point, Bias::Left);
4710
4711 let newline = "\n".to_string();
4712 edits.push((start_of_line..start_of_line, newline));
4713
4714 rows_inserted += 1;
4715 rows.push(row + rows_inserted);
4716 }
4717
4718 self.transact(window, cx, |editor, window, cx| {
4719 editor.edit(edits, cx);
4720
4721 editor.change_selections(Default::default(), window, cx, |s| {
4722 let mut index = 0;
4723 s.move_cursors_with(|map, _, _| {
4724 let row = rows[index];
4725 index += 1;
4726
4727 let point = Point::new(row, 0);
4728 let boundary = map.next_line_boundary(point).1;
4729 let clipped = map.clip_point(boundary, Bias::Left);
4730
4731 (clipped, SelectionGoal::None)
4732 });
4733 });
4734
4735 let mut indent_edits = Vec::new();
4736 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4737 for row in rows {
4738 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4739 for (row, indent) in indents {
4740 if indent.len == 0 {
4741 continue;
4742 }
4743
4744 let text = match indent.kind {
4745 IndentKind::Space => " ".repeat(indent.len as usize),
4746 IndentKind::Tab => "\t".repeat(indent.len as usize),
4747 };
4748 let point = Point::new(row.0, 0);
4749 indent_edits.push((point..point, text));
4750 }
4751 }
4752 editor.edit(indent_edits, cx);
4753 });
4754 }
4755
4756 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4757 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4758 original_indent_columns: Vec::new(),
4759 });
4760 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4761 }
4762
4763 fn insert_with_autoindent_mode(
4764 &mut self,
4765 text: &str,
4766 autoindent_mode: Option<AutoindentMode>,
4767 window: &mut Window,
4768 cx: &mut Context<Self>,
4769 ) {
4770 if self.read_only(cx) {
4771 return;
4772 }
4773
4774 let text: Arc<str> = text.into();
4775 self.transact(window, cx, |this, window, cx| {
4776 let old_selections = this.selections.all_adjusted(cx);
4777 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4778 let anchors = {
4779 let snapshot = buffer.read(cx);
4780 old_selections
4781 .iter()
4782 .map(|s| {
4783 let anchor = snapshot.anchor_after(s.head());
4784 s.map(|_| anchor)
4785 })
4786 .collect::<Vec<_>>()
4787 };
4788 buffer.edit(
4789 old_selections
4790 .iter()
4791 .map(|s| (s.start..s.end, text.clone())),
4792 autoindent_mode,
4793 cx,
4794 );
4795 anchors
4796 });
4797
4798 this.change_selections(Default::default(), window, cx, |s| {
4799 s.select_anchors(selection_anchors);
4800 });
4801
4802 cx.notify();
4803 });
4804 }
4805
4806 fn trigger_completion_on_input(
4807 &mut self,
4808 text: &str,
4809 trigger_in_words: bool,
4810 window: &mut Window,
4811 cx: &mut Context<Self>,
4812 ) {
4813 let completions_source = self
4814 .context_menu
4815 .borrow()
4816 .as_ref()
4817 .and_then(|menu| match menu {
4818 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4819 CodeContextMenu::CodeActions(_) => None,
4820 });
4821
4822 match completions_source {
4823 Some(CompletionsMenuSource::Words) => {
4824 self.show_word_completions(&ShowWordCompletions, window, cx)
4825 }
4826 Some(CompletionsMenuSource::Normal)
4827 | Some(CompletionsMenuSource::SnippetChoices)
4828 | None
4829 if self.is_completion_trigger(
4830 text,
4831 trigger_in_words,
4832 completions_source.is_some(),
4833 cx,
4834 ) =>
4835 {
4836 self.show_completions(
4837 &ShowCompletions {
4838 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4839 },
4840 window,
4841 cx,
4842 )
4843 }
4844 _ => {
4845 self.hide_context_menu(window, cx);
4846 }
4847 }
4848 }
4849
4850 fn is_completion_trigger(
4851 &self,
4852 text: &str,
4853 trigger_in_words: bool,
4854 menu_is_open: bool,
4855 cx: &mut Context<Self>,
4856 ) -> bool {
4857 let position = self.selections.newest_anchor().head();
4858 let multibuffer = self.buffer.read(cx);
4859 let Some(buffer) = position
4860 .buffer_id
4861 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
4862 else {
4863 return false;
4864 };
4865
4866 if let Some(completion_provider) = &self.completion_provider {
4867 completion_provider.is_completion_trigger(
4868 &buffer,
4869 position.text_anchor,
4870 text,
4871 trigger_in_words,
4872 menu_is_open,
4873 cx,
4874 )
4875 } else {
4876 false
4877 }
4878 }
4879
4880 /// If any empty selections is touching the start of its innermost containing autoclose
4881 /// region, expand it to select the brackets.
4882 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4883 let selections = self.selections.all::<usize>(cx);
4884 let buffer = self.buffer.read(cx).read(cx);
4885 let new_selections = self
4886 .selections_with_autoclose_regions(selections, &buffer)
4887 .map(|(mut selection, region)| {
4888 if !selection.is_empty() {
4889 return selection;
4890 }
4891
4892 if let Some(region) = region {
4893 let mut range = region.range.to_offset(&buffer);
4894 if selection.start == range.start && range.start >= region.pair.start.len() {
4895 range.start -= region.pair.start.len();
4896 if buffer.contains_str_at(range.start, ®ion.pair.start)
4897 && buffer.contains_str_at(range.end, ®ion.pair.end)
4898 {
4899 range.end += region.pair.end.len();
4900 selection.start = range.start;
4901 selection.end = range.end;
4902
4903 return selection;
4904 }
4905 }
4906 }
4907
4908 let always_treat_brackets_as_autoclosed = buffer
4909 .language_settings_at(selection.start, cx)
4910 .always_treat_brackets_as_autoclosed;
4911
4912 if !always_treat_brackets_as_autoclosed {
4913 return selection;
4914 }
4915
4916 if let Some(scope) = buffer.language_scope_at(selection.start) {
4917 for (pair, enabled) in scope.brackets() {
4918 if !enabled || !pair.close {
4919 continue;
4920 }
4921
4922 if buffer.contains_str_at(selection.start, &pair.end) {
4923 let pair_start_len = pair.start.len();
4924 if buffer.contains_str_at(
4925 selection.start.saturating_sub(pair_start_len),
4926 &pair.start,
4927 ) {
4928 selection.start -= pair_start_len;
4929 selection.end += pair.end.len();
4930
4931 return selection;
4932 }
4933 }
4934 }
4935 }
4936
4937 selection
4938 })
4939 .collect();
4940
4941 drop(buffer);
4942 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
4943 selections.select(new_selections)
4944 });
4945 }
4946
4947 /// Iterate the given selections, and for each one, find the smallest surrounding
4948 /// autoclose region. This uses the ordering of the selections and the autoclose
4949 /// regions to avoid repeated comparisons.
4950 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
4951 &'a self,
4952 selections: impl IntoIterator<Item = Selection<D>>,
4953 buffer: &'a MultiBufferSnapshot,
4954 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
4955 let mut i = 0;
4956 let mut regions = self.autoclose_regions.as_slice();
4957 selections.into_iter().map(move |selection| {
4958 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
4959
4960 let mut enclosing = None;
4961 while let Some(pair_state) = regions.get(i) {
4962 if pair_state.range.end.to_offset(buffer) < range.start {
4963 regions = ®ions[i + 1..];
4964 i = 0;
4965 } else if pair_state.range.start.to_offset(buffer) > range.end {
4966 break;
4967 } else {
4968 if pair_state.selection_id == selection.id {
4969 enclosing = Some(pair_state);
4970 }
4971 i += 1;
4972 }
4973 }
4974
4975 (selection, enclosing)
4976 })
4977 }
4978
4979 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
4980 fn invalidate_autoclose_regions(
4981 &mut self,
4982 mut selections: &[Selection<Anchor>],
4983 buffer: &MultiBufferSnapshot,
4984 ) {
4985 self.autoclose_regions.retain(|state| {
4986 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
4987 return false;
4988 }
4989
4990 let mut i = 0;
4991 while let Some(selection) = selections.get(i) {
4992 if selection.end.cmp(&state.range.start, buffer).is_lt() {
4993 selections = &selections[1..];
4994 continue;
4995 }
4996 if selection.start.cmp(&state.range.end, buffer).is_gt() {
4997 break;
4998 }
4999 if selection.id == state.selection_id {
5000 return true;
5001 } else {
5002 i += 1;
5003 }
5004 }
5005 false
5006 });
5007 }
5008
5009 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5010 let offset = position.to_offset(buffer);
5011 let (word_range, kind) = buffer.surrounding_word(offset, true);
5012 if offset > word_range.start && kind == Some(CharKind::Word) {
5013 Some(
5014 buffer
5015 .text_for_range(word_range.start..offset)
5016 .collect::<String>(),
5017 )
5018 } else {
5019 None
5020 }
5021 }
5022
5023 pub fn toggle_inline_values(
5024 &mut self,
5025 _: &ToggleInlineValues,
5026 _: &mut Window,
5027 cx: &mut Context<Self>,
5028 ) {
5029 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
5030
5031 self.refresh_inline_values(cx);
5032 }
5033
5034 pub fn toggle_inlay_hints(
5035 &mut self,
5036 _: &ToggleInlayHints,
5037 _: &mut Window,
5038 cx: &mut Context<Self>,
5039 ) {
5040 self.refresh_inlay_hints(
5041 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
5042 cx,
5043 );
5044 }
5045
5046 pub fn inlay_hints_enabled(&self) -> bool {
5047 self.inlay_hint_cache.enabled
5048 }
5049
5050 pub fn inline_values_enabled(&self) -> bool {
5051 self.inline_value_cache.enabled
5052 }
5053
5054 #[cfg(any(test, feature = "test-support"))]
5055 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
5056 self.display_map
5057 .read(cx)
5058 .current_inlays()
5059 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
5060 .cloned()
5061 .collect()
5062 }
5063
5064 #[cfg(any(test, feature = "test-support"))]
5065 pub fn all_inlays(&self, cx: &App) -> Vec<Inlay> {
5066 self.display_map
5067 .read(cx)
5068 .current_inlays()
5069 .cloned()
5070 .collect()
5071 }
5072
5073 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
5074 if self.semantics_provider.is_none() || !self.mode.is_full() {
5075 return;
5076 }
5077
5078 let reason_description = reason.description();
5079 let ignore_debounce = matches!(
5080 reason,
5081 InlayHintRefreshReason::SettingsChange(_)
5082 | InlayHintRefreshReason::Toggle(_)
5083 | InlayHintRefreshReason::ExcerptsRemoved(_)
5084 | InlayHintRefreshReason::ModifiersChanged(_)
5085 );
5086 let (invalidate_cache, required_languages) = match reason {
5087 InlayHintRefreshReason::ModifiersChanged(enabled) => {
5088 match self.inlay_hint_cache.modifiers_override(enabled) {
5089 Some(enabled) => {
5090 if enabled {
5091 (InvalidationStrategy::RefreshRequested, None)
5092 } else {
5093 self.splice_inlays(
5094 &self
5095 .visible_inlay_hints(cx)
5096 .iter()
5097 .map(|inlay| inlay.id)
5098 .collect::<Vec<InlayId>>(),
5099 Vec::new(),
5100 cx,
5101 );
5102 return;
5103 }
5104 }
5105 None => return,
5106 }
5107 }
5108 InlayHintRefreshReason::Toggle(enabled) => {
5109 if self.inlay_hint_cache.toggle(enabled) {
5110 if enabled {
5111 (InvalidationStrategy::RefreshRequested, None)
5112 } else {
5113 self.splice_inlays(
5114 &self
5115 .visible_inlay_hints(cx)
5116 .iter()
5117 .map(|inlay| inlay.id)
5118 .collect::<Vec<InlayId>>(),
5119 Vec::new(),
5120 cx,
5121 );
5122 return;
5123 }
5124 } else {
5125 return;
5126 }
5127 }
5128 InlayHintRefreshReason::SettingsChange(new_settings) => {
5129 match self.inlay_hint_cache.update_settings(
5130 &self.buffer,
5131 new_settings,
5132 self.visible_inlay_hints(cx),
5133 cx,
5134 ) {
5135 ControlFlow::Break(Some(InlaySplice {
5136 to_remove,
5137 to_insert,
5138 })) => {
5139 self.splice_inlays(&to_remove, to_insert, cx);
5140 return;
5141 }
5142 ControlFlow::Break(None) => return,
5143 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
5144 }
5145 }
5146 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
5147 if let Some(InlaySplice {
5148 to_remove,
5149 to_insert,
5150 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
5151 {
5152 self.splice_inlays(&to_remove, to_insert, cx);
5153 }
5154 self.display_map.update(cx, |display_map, _| {
5155 display_map.remove_inlays_for_excerpts(&excerpts_removed)
5156 });
5157 return;
5158 }
5159 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
5160 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
5161 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
5162 }
5163 InlayHintRefreshReason::RefreshRequested => {
5164 (InvalidationStrategy::RefreshRequested, None)
5165 }
5166 };
5167
5168 if let Some(InlaySplice {
5169 to_remove,
5170 to_insert,
5171 }) = self.inlay_hint_cache.spawn_hint_refresh(
5172 reason_description,
5173 self.visible_excerpts(required_languages.as_ref(), cx),
5174 invalidate_cache,
5175 ignore_debounce,
5176 cx,
5177 ) {
5178 self.splice_inlays(&to_remove, to_insert, cx);
5179 }
5180 }
5181
5182 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
5183 self.display_map
5184 .read(cx)
5185 .current_inlays()
5186 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
5187 .cloned()
5188 .collect()
5189 }
5190
5191 pub fn visible_excerpts(
5192 &self,
5193 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
5194 cx: &mut Context<Editor>,
5195 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5196 let Some(project) = self.project.as_ref() else {
5197 return HashMap::default();
5198 };
5199 let project = project.read(cx);
5200 let multi_buffer = self.buffer().read(cx);
5201 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5202 let multi_buffer_visible_start = self
5203 .scroll_manager
5204 .anchor()
5205 .anchor
5206 .to_point(&multi_buffer_snapshot);
5207 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5208 multi_buffer_visible_start
5209 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5210 Bias::Left,
5211 );
5212 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
5213 multi_buffer_snapshot
5214 .range_to_buffer_ranges(multi_buffer_visible_range)
5215 .into_iter()
5216 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5217 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5218 let buffer_file = project::File::from_dyn(buffer.file())?;
5219 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5220 let worktree_entry = buffer_worktree
5221 .read(cx)
5222 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
5223 if worktree_entry.is_ignored {
5224 return None;
5225 }
5226
5227 let language = buffer.language()?;
5228 if let Some(restrict_to_languages) = restrict_to_languages {
5229 if !restrict_to_languages.contains(language) {
5230 return None;
5231 }
5232 }
5233 Some((
5234 excerpt_id,
5235 (
5236 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5237 buffer.version().clone(),
5238 excerpt_visible_range,
5239 ),
5240 ))
5241 })
5242 .collect()
5243 }
5244
5245 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5246 TextLayoutDetails {
5247 text_system: window.text_system().clone(),
5248 editor_style: self.style.clone().unwrap(),
5249 rem_size: window.rem_size(),
5250 scroll_anchor: self.scroll_manager.anchor(),
5251 visible_rows: self.visible_line_count(),
5252 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5253 }
5254 }
5255
5256 pub fn splice_inlays(
5257 &self,
5258 to_remove: &[InlayId],
5259 to_insert: Vec<Inlay>,
5260 cx: &mut Context<Self>,
5261 ) {
5262 self.display_map.update(cx, |display_map, cx| {
5263 display_map.splice_inlays(to_remove, to_insert, cx)
5264 });
5265 cx.notify();
5266 }
5267
5268 fn trigger_on_type_formatting(
5269 &self,
5270 input: String,
5271 window: &mut Window,
5272 cx: &mut Context<Self>,
5273 ) -> Option<Task<Result<()>>> {
5274 if input.len() != 1 {
5275 return None;
5276 }
5277
5278 let project = self.project.as_ref()?;
5279 let position = self.selections.newest_anchor().head();
5280 let (buffer, buffer_position) = self
5281 .buffer
5282 .read(cx)
5283 .text_anchor_for_position(position, cx)?;
5284
5285 let settings = language_settings::language_settings(
5286 buffer
5287 .read(cx)
5288 .language_at(buffer_position)
5289 .map(|l| l.name()),
5290 buffer.read(cx).file(),
5291 cx,
5292 );
5293 if !settings.use_on_type_format {
5294 return None;
5295 }
5296
5297 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5298 // hence we do LSP request & edit on host side only — add formats to host's history.
5299 let push_to_lsp_host_history = true;
5300 // If this is not the host, append its history with new edits.
5301 let push_to_client_history = project.read(cx).is_via_collab();
5302
5303 let on_type_formatting = project.update(cx, |project, cx| {
5304 project.on_type_format(
5305 buffer.clone(),
5306 buffer_position,
5307 input,
5308 push_to_lsp_host_history,
5309 cx,
5310 )
5311 });
5312 Some(cx.spawn_in(window, async move |editor, cx| {
5313 if let Some(transaction) = on_type_formatting.await? {
5314 if push_to_client_history {
5315 buffer
5316 .update(cx, |buffer, _| {
5317 buffer.push_transaction(transaction, Instant::now());
5318 buffer.finalize_last_transaction();
5319 })
5320 .ok();
5321 }
5322 editor.update(cx, |editor, cx| {
5323 editor.refresh_document_highlights(cx);
5324 })?;
5325 }
5326 Ok(())
5327 }))
5328 }
5329
5330 pub fn show_word_completions(
5331 &mut self,
5332 _: &ShowWordCompletions,
5333 window: &mut Window,
5334 cx: &mut Context<Self>,
5335 ) {
5336 self.open_or_update_completions_menu(Some(CompletionsMenuSource::Words), None, window, cx);
5337 }
5338
5339 pub fn show_completions(
5340 &mut self,
5341 options: &ShowCompletions,
5342 window: &mut Window,
5343 cx: &mut Context<Self>,
5344 ) {
5345 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5346 }
5347
5348 fn open_or_update_completions_menu(
5349 &mut self,
5350 requested_source: Option<CompletionsMenuSource>,
5351 trigger: Option<&str>,
5352 window: &mut Window,
5353 cx: &mut Context<Self>,
5354 ) {
5355 if self.pending_rename.is_some() {
5356 return;
5357 }
5358
5359 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5360
5361 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5362 // inserted and selected. To handle that case, the start of the selection is used so that
5363 // the menu starts with all choices.
5364 let position = self
5365 .selections
5366 .newest_anchor()
5367 .start
5368 .bias_right(&multibuffer_snapshot);
5369 if position.diff_base_anchor.is_some() {
5370 return;
5371 }
5372 let (buffer, buffer_position) =
5373 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
5374 output
5375 } else {
5376 return;
5377 };
5378 let buffer_snapshot = buffer.read(cx).snapshot();
5379
5380 let query: Option<Arc<String>> =
5381 Self::completion_query(&multibuffer_snapshot, position).map(|query| query.into());
5382
5383 drop(multibuffer_snapshot);
5384
5385 let provider = match requested_source {
5386 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5387 Some(CompletionsMenuSource::Words) => None,
5388 Some(CompletionsMenuSource::SnippetChoices) => {
5389 log::error!("bug: SnippetChoices requested_source is not handled");
5390 None
5391 }
5392 };
5393
5394 let sort_completions = provider
5395 .as_ref()
5396 .map_or(false, |provider| provider.sort_completions());
5397
5398 let filter_completions = provider
5399 .as_ref()
5400 .map_or(true, |provider| provider.filter_completions());
5401
5402 let trigger_kind = match trigger {
5403 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5404 CompletionTriggerKind::TRIGGER_CHARACTER
5405 }
5406 _ => CompletionTriggerKind::INVOKED,
5407 };
5408 let completion_context = CompletionContext {
5409 trigger_character: trigger.and_then(|trigger| {
5410 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5411 Some(String::from(trigger))
5412 } else {
5413 None
5414 }
5415 }),
5416 trigger_kind,
5417 };
5418
5419 // Hide the current completions menu when a trigger char is typed. Without this, cached
5420 // completions from before the trigger char may be reused (#32774). Snippet choices could
5421 // involve trigger chars, so this is skipped in that case.
5422 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER && self.snippet_stack.is_empty()
5423 {
5424 let menu_is_open = matches!(
5425 self.context_menu.borrow().as_ref(),
5426 Some(CodeContextMenu::Completions(_))
5427 );
5428 if menu_is_open {
5429 self.hide_context_menu(window, cx);
5430 }
5431 }
5432
5433 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5434 if filter_completions {
5435 menu.filter(query.clone(), provider.clone(), window, cx);
5436 }
5437 // When `is_incomplete` is false, no need to re-query completions when the current query
5438 // is a suffix of the initial query.
5439 if !menu.is_incomplete {
5440 // If the new query is a suffix of the old query (typing more characters) and
5441 // the previous result was complete, the existing completions can be filtered.
5442 //
5443 // Note that this is always true for snippet completions.
5444 let query_matches = match (&menu.initial_query, &query) {
5445 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5446 (None, _) => true,
5447 _ => false,
5448 };
5449 if query_matches {
5450 let position_matches = if menu.initial_position == position {
5451 true
5452 } else {
5453 let snapshot = self.buffer.read(cx).read(cx);
5454 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5455 };
5456 if position_matches {
5457 return;
5458 }
5459 }
5460 }
5461 };
5462
5463 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5464 buffer_snapshot.surrounding_word(buffer_position, false)
5465 {
5466 let word_to_exclude = buffer_snapshot
5467 .text_for_range(word_range.clone())
5468 .collect::<String>();
5469 (
5470 buffer_snapshot.anchor_before(word_range.start)
5471 ..buffer_snapshot.anchor_after(buffer_position),
5472 Some(word_to_exclude),
5473 )
5474 } else {
5475 (buffer_position..buffer_position, None)
5476 };
5477
5478 let language = buffer_snapshot
5479 .language_at(buffer_position)
5480 .map(|language| language.name());
5481
5482 let completion_settings =
5483 language_settings(language.clone(), buffer_snapshot.file(), cx).completions;
5484
5485 let show_completion_documentation = buffer_snapshot
5486 .settings_at(buffer_position, cx)
5487 .show_completion_documentation;
5488
5489 // The document can be large, so stay in reasonable bounds when searching for words,
5490 // otherwise completion pop-up might be slow to appear.
5491 const WORD_LOOKUP_ROWS: u32 = 5_000;
5492 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5493 let min_word_search = buffer_snapshot.clip_point(
5494 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5495 Bias::Left,
5496 );
5497 let max_word_search = buffer_snapshot.clip_point(
5498 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5499 Bias::Right,
5500 );
5501 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5502 ..buffer_snapshot.point_to_offset(max_word_search);
5503
5504 let skip_digits = query
5505 .as_ref()
5506 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
5507
5508 let (mut words, provider_responses) = match &provider {
5509 Some(provider) => {
5510 let provider_responses = provider.completions(
5511 position.excerpt_id,
5512 &buffer,
5513 buffer_position,
5514 completion_context,
5515 window,
5516 cx,
5517 );
5518
5519 let words = match completion_settings.words {
5520 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
5521 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
5522 .background_spawn(async move {
5523 buffer_snapshot.words_in_range(WordsQuery {
5524 fuzzy_contents: None,
5525 range: word_search_range,
5526 skip_digits,
5527 })
5528 }),
5529 };
5530
5531 (words, provider_responses)
5532 }
5533 None => (
5534 cx.background_spawn(async move {
5535 buffer_snapshot.words_in_range(WordsQuery {
5536 fuzzy_contents: None,
5537 range: word_search_range,
5538 skip_digits,
5539 })
5540 }),
5541 Task::ready(Ok(Vec::new())),
5542 ),
5543 };
5544
5545 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5546
5547 let id = post_inc(&mut self.next_completion_id);
5548 let task = cx.spawn_in(window, async move |editor, cx| {
5549 let Ok(()) = editor.update(cx, |this, _| {
5550 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5551 }) else {
5552 return;
5553 };
5554
5555 // TODO: Ideally completions from different sources would be selectively re-queried, so
5556 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5557 let mut completions = Vec::new();
5558 let mut is_incomplete = false;
5559 if let Some(provider_responses) = provider_responses.await.log_err() {
5560 if !provider_responses.is_empty() {
5561 for response in provider_responses {
5562 completions.extend(response.completions);
5563 is_incomplete = is_incomplete || response.is_incomplete;
5564 }
5565 if completion_settings.words == WordsCompletionMode::Fallback {
5566 words = Task::ready(BTreeMap::default());
5567 }
5568 }
5569 }
5570
5571 let mut words = words.await;
5572 if let Some(word_to_exclude) = &word_to_exclude {
5573 words.remove(word_to_exclude);
5574 }
5575 for lsp_completion in &completions {
5576 words.remove(&lsp_completion.new_text);
5577 }
5578 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5579 replace_range: word_replace_range.clone(),
5580 new_text: word.clone(),
5581 label: CodeLabel::plain(word, None),
5582 icon_path: None,
5583 documentation: None,
5584 source: CompletionSource::BufferWord {
5585 word_range,
5586 resolved: false,
5587 },
5588 insert_text_mode: Some(InsertTextMode::AS_IS),
5589 confirm: None,
5590 }));
5591
5592 let menu = if completions.is_empty() {
5593 None
5594 } else {
5595 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5596 let languages = editor
5597 .workspace
5598 .as_ref()
5599 .and_then(|(workspace, _)| workspace.upgrade())
5600 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5601 let menu = CompletionsMenu::new(
5602 id,
5603 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5604 sort_completions,
5605 show_completion_documentation,
5606 position,
5607 query.clone(),
5608 is_incomplete,
5609 buffer.clone(),
5610 completions.into(),
5611 snippet_sort_order,
5612 languages,
5613 language,
5614 cx,
5615 );
5616
5617 let query = if filter_completions { query } else { None };
5618 let matches_task = if let Some(query) = query {
5619 menu.do_async_filtering(query, cx)
5620 } else {
5621 Task::ready(menu.unfiltered_matches())
5622 };
5623 (menu, matches_task)
5624 }) else {
5625 return;
5626 };
5627
5628 let matches = matches_task.await;
5629
5630 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5631 // Newer menu already set, so exit.
5632 match editor.context_menu.borrow().as_ref() {
5633 Some(CodeContextMenu::Completions(prev_menu)) => {
5634 if prev_menu.id > id {
5635 return;
5636 }
5637 }
5638 _ => {}
5639 };
5640
5641 // Only valid to take prev_menu because it the new menu is immediately set
5642 // below, or the menu is hidden.
5643 match editor.context_menu.borrow_mut().take() {
5644 Some(CodeContextMenu::Completions(prev_menu)) => {
5645 let position_matches =
5646 if prev_menu.initial_position == menu.initial_position {
5647 true
5648 } else {
5649 let snapshot = editor.buffer.read(cx).read(cx);
5650 prev_menu.initial_position.to_offset(&snapshot)
5651 == menu.initial_position.to_offset(&snapshot)
5652 };
5653 if position_matches {
5654 // Preserve markdown cache before `set_filter_results` because it will
5655 // try to populate the documentation cache.
5656 menu.preserve_markdown_cache(prev_menu);
5657 }
5658 }
5659 _ => {}
5660 };
5661
5662 menu.set_filter_results(matches, provider, window, cx);
5663 }) else {
5664 return;
5665 };
5666
5667 menu.visible().then_some(menu)
5668 };
5669
5670 editor
5671 .update_in(cx, |editor, window, cx| {
5672 if editor.focus_handle.is_focused(window) {
5673 if let Some(menu) = menu {
5674 *editor.context_menu.borrow_mut() =
5675 Some(CodeContextMenu::Completions(menu));
5676
5677 crate::hover_popover::hide_hover(editor, cx);
5678 if editor.show_edit_predictions_in_menu() {
5679 editor.update_visible_inline_completion(window, cx);
5680 } else {
5681 editor.discard_inline_completion(false, cx);
5682 }
5683
5684 cx.notify();
5685 return;
5686 }
5687 }
5688
5689 if editor.completion_tasks.len() <= 1 {
5690 // If there are no more completion tasks and the last menu was empty, we should hide it.
5691 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5692 // If it was already hidden and we don't show inline completions in the menu, we should
5693 // also show the inline-completion when available.
5694 if was_hidden && editor.show_edit_predictions_in_menu() {
5695 editor.update_visible_inline_completion(window, cx);
5696 }
5697 }
5698 })
5699 .ok();
5700 });
5701
5702 self.completion_tasks.push((id, task));
5703 }
5704
5705 #[cfg(feature = "test-support")]
5706 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5707 let menu = self.context_menu.borrow();
5708 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5709 let completions = menu.completions.borrow();
5710 Some(completions.to_vec())
5711 } else {
5712 None
5713 }
5714 }
5715
5716 pub fn with_completions_menu_matching_id<R>(
5717 &self,
5718 id: CompletionId,
5719 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5720 ) -> R {
5721 let mut context_menu = self.context_menu.borrow_mut();
5722 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5723 return f(None);
5724 };
5725 if completions_menu.id != id {
5726 return f(None);
5727 }
5728 f(Some(completions_menu))
5729 }
5730
5731 pub fn confirm_completion(
5732 &mut self,
5733 action: &ConfirmCompletion,
5734 window: &mut Window,
5735 cx: &mut Context<Self>,
5736 ) -> Option<Task<Result<()>>> {
5737 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5738 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5739 }
5740
5741 pub fn confirm_completion_insert(
5742 &mut self,
5743 _: &ConfirmCompletionInsert,
5744 window: &mut Window,
5745 cx: &mut Context<Self>,
5746 ) -> Option<Task<Result<()>>> {
5747 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5748 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5749 }
5750
5751 pub fn confirm_completion_replace(
5752 &mut self,
5753 _: &ConfirmCompletionReplace,
5754 window: &mut Window,
5755 cx: &mut Context<Self>,
5756 ) -> Option<Task<Result<()>>> {
5757 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5758 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5759 }
5760
5761 pub fn compose_completion(
5762 &mut self,
5763 action: &ComposeCompletion,
5764 window: &mut Window,
5765 cx: &mut Context<Self>,
5766 ) -> Option<Task<Result<()>>> {
5767 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5768 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5769 }
5770
5771 fn do_completion(
5772 &mut self,
5773 item_ix: Option<usize>,
5774 intent: CompletionIntent,
5775 window: &mut Window,
5776 cx: &mut Context<Editor>,
5777 ) -> Option<Task<Result<()>>> {
5778 use language::ToOffset as _;
5779
5780 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5781 else {
5782 return None;
5783 };
5784
5785 let candidate_id = {
5786 let entries = completions_menu.entries.borrow();
5787 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5788 if self.show_edit_predictions_in_menu() {
5789 self.discard_inline_completion(true, cx);
5790 }
5791 mat.candidate_id
5792 };
5793
5794 let completion = completions_menu
5795 .completions
5796 .borrow()
5797 .get(candidate_id)?
5798 .clone();
5799 cx.stop_propagation();
5800
5801 let buffer_handle = completions_menu.buffer.clone();
5802
5803 let CompletionEdit {
5804 new_text,
5805 snippet,
5806 replace_range,
5807 } = process_completion_for_edit(
5808 &completion,
5809 intent,
5810 &buffer_handle,
5811 &completions_menu.initial_position.text_anchor,
5812 cx,
5813 );
5814
5815 let buffer = buffer_handle.read(cx);
5816 let snapshot = self.buffer.read(cx).snapshot(cx);
5817 let newest_anchor = self.selections.newest_anchor();
5818 let replace_range_multibuffer = {
5819 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5820 let multibuffer_anchor = snapshot
5821 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5822 .unwrap()
5823 ..snapshot
5824 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5825 .unwrap();
5826 multibuffer_anchor.start.to_offset(&snapshot)
5827 ..multibuffer_anchor.end.to_offset(&snapshot)
5828 };
5829 if newest_anchor.head().buffer_id != Some(buffer.remote_id()) {
5830 return None;
5831 }
5832
5833 let old_text = buffer
5834 .text_for_range(replace_range.clone())
5835 .collect::<String>();
5836 let lookbehind = newest_anchor
5837 .start
5838 .text_anchor
5839 .to_offset(buffer)
5840 .saturating_sub(replace_range.start);
5841 let lookahead = replace_range
5842 .end
5843 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5844 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5845 let suffix = &old_text[lookbehind.min(old_text.len())..];
5846
5847 let selections = self.selections.all::<usize>(cx);
5848 let mut ranges = Vec::new();
5849 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5850
5851 for selection in &selections {
5852 let range = if selection.id == newest_anchor.id {
5853 replace_range_multibuffer.clone()
5854 } else {
5855 let mut range = selection.range();
5856
5857 // if prefix is present, don't duplicate it
5858 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5859 range.start = range.start.saturating_sub(lookbehind);
5860
5861 // if suffix is also present, mimic the newest cursor and replace it
5862 if selection.id != newest_anchor.id
5863 && snapshot.contains_str_at(range.end, suffix)
5864 {
5865 range.end += lookahead;
5866 }
5867 }
5868 range
5869 };
5870
5871 ranges.push(range.clone());
5872
5873 if !self.linked_edit_ranges.is_empty() {
5874 let start_anchor = snapshot.anchor_before(range.start);
5875 let end_anchor = snapshot.anchor_after(range.end);
5876 if let Some(ranges) = self
5877 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5878 {
5879 for (buffer, edits) in ranges {
5880 linked_edits
5881 .entry(buffer.clone())
5882 .or_default()
5883 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5884 }
5885 }
5886 }
5887 }
5888
5889 let common_prefix_len = old_text
5890 .chars()
5891 .zip(new_text.chars())
5892 .take_while(|(a, b)| a == b)
5893 .map(|(a, _)| a.len_utf8())
5894 .sum::<usize>();
5895
5896 cx.emit(EditorEvent::InputHandled {
5897 utf16_range_to_replace: None,
5898 text: new_text[common_prefix_len..].into(),
5899 });
5900
5901 self.transact(window, cx, |editor, window, cx| {
5902 if let Some(mut snippet) = snippet {
5903 snippet.text = new_text.to_string();
5904 editor
5905 .insert_snippet(&ranges, snippet, window, cx)
5906 .log_err();
5907 } else {
5908 editor.buffer.update(cx, |multi_buffer, cx| {
5909 let auto_indent = match completion.insert_text_mode {
5910 Some(InsertTextMode::AS_IS) => None,
5911 _ => editor.autoindent_mode.clone(),
5912 };
5913 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5914 multi_buffer.edit(edits, auto_indent, cx);
5915 });
5916 }
5917 for (buffer, edits) in linked_edits {
5918 buffer.update(cx, |buffer, cx| {
5919 let snapshot = buffer.snapshot();
5920 let edits = edits
5921 .into_iter()
5922 .map(|(range, text)| {
5923 use text::ToPoint as TP;
5924 let end_point = TP::to_point(&range.end, &snapshot);
5925 let start_point = TP::to_point(&range.start, &snapshot);
5926 (start_point..end_point, text)
5927 })
5928 .sorted_by_key(|(range, _)| range.start);
5929 buffer.edit(edits, None, cx);
5930 })
5931 }
5932
5933 editor.refresh_inline_completion(true, false, window, cx);
5934 });
5935 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), &snapshot);
5936
5937 let show_new_completions_on_confirm = completion
5938 .confirm
5939 .as_ref()
5940 .map_or(false, |confirm| confirm(intent, window, cx));
5941 if show_new_completions_on_confirm {
5942 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5943 }
5944
5945 let provider = self.completion_provider.as_ref()?;
5946 drop(completion);
5947 let apply_edits = provider.apply_additional_edits_for_completion(
5948 buffer_handle,
5949 completions_menu.completions.clone(),
5950 candidate_id,
5951 true,
5952 cx,
5953 );
5954
5955 let editor_settings = EditorSettings::get_global(cx);
5956 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5957 // After the code completion is finished, users often want to know what signatures are needed.
5958 // so we should automatically call signature_help
5959 self.show_signature_help(&ShowSignatureHelp, window, cx);
5960 }
5961
5962 Some(cx.foreground_executor().spawn(async move {
5963 apply_edits.await?;
5964 Ok(())
5965 }))
5966 }
5967
5968 pub fn toggle_code_actions(
5969 &mut self,
5970 action: &ToggleCodeActions,
5971 window: &mut Window,
5972 cx: &mut Context<Self>,
5973 ) {
5974 let quick_launch = action.quick_launch;
5975 let mut context_menu = self.context_menu.borrow_mut();
5976 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5977 if code_actions.deployed_from == action.deployed_from {
5978 // Toggle if we're selecting the same one
5979 *context_menu = None;
5980 cx.notify();
5981 return;
5982 } else {
5983 // Otherwise, clear it and start a new one
5984 *context_menu = None;
5985 cx.notify();
5986 }
5987 }
5988 drop(context_menu);
5989 let snapshot = self.snapshot(window, cx);
5990 let deployed_from = action.deployed_from.clone();
5991 let action = action.clone();
5992 self.completion_tasks.clear();
5993 self.discard_inline_completion(false, cx);
5994
5995 let multibuffer_point = match &action.deployed_from {
5996 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
5997 DisplayPoint::new(*row, 0).to_point(&snapshot)
5998 }
5999 _ => self.selections.newest::<Point>(cx).head(),
6000 };
6001 let Some((buffer, buffer_row)) = snapshot
6002 .buffer_snapshot
6003 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6004 .and_then(|(buffer_snapshot, range)| {
6005 self.buffer()
6006 .read(cx)
6007 .buffer(buffer_snapshot.remote_id())
6008 .map(|buffer| (buffer, range.start.row))
6009 })
6010 else {
6011 return;
6012 };
6013 let buffer_id = buffer.read(cx).remote_id();
6014 let tasks = self
6015 .tasks
6016 .get(&(buffer_id, buffer_row))
6017 .map(|t| Arc::new(t.to_owned()));
6018
6019 if !self.focus_handle.is_focused(window) {
6020 return;
6021 }
6022 let project = self.project.clone();
6023
6024 let code_actions_task = match deployed_from {
6025 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6026 _ => self.code_actions(buffer_row, window, cx),
6027 };
6028
6029 let runnable_task = match deployed_from {
6030 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6031 _ => {
6032 let mut task_context_task = Task::ready(None);
6033 if let Some(tasks) = &tasks {
6034 if let Some(project) = project {
6035 task_context_task =
6036 Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
6037 }
6038 }
6039
6040 cx.spawn_in(window, {
6041 let buffer = buffer.clone();
6042 async move |editor, cx| {
6043 let task_context = task_context_task.await;
6044
6045 let resolved_tasks =
6046 tasks
6047 .zip(task_context.clone())
6048 .map(|(tasks, task_context)| ResolvedTasks {
6049 templates: tasks.resolve(&task_context).collect(),
6050 position: snapshot.buffer_snapshot.anchor_before(Point::new(
6051 multibuffer_point.row,
6052 tasks.column,
6053 )),
6054 });
6055 let debug_scenarios = editor
6056 .update(cx, |editor, cx| {
6057 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6058 })?
6059 .await;
6060 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6061 }
6062 })
6063 }
6064 };
6065
6066 cx.spawn_in(window, async move |editor, cx| {
6067 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6068 let code_actions = code_actions_task.await;
6069 let spawn_straight_away = quick_launch
6070 && resolved_tasks
6071 .as_ref()
6072 .map_or(false, |tasks| tasks.templates.len() == 1)
6073 && code_actions
6074 .as_ref()
6075 .map_or(true, |actions| actions.is_empty())
6076 && debug_scenarios.is_empty();
6077
6078 editor.update_in(cx, |editor, window, cx| {
6079 crate::hover_popover::hide_hover(editor, cx);
6080 let actions = CodeActionContents::new(
6081 resolved_tasks,
6082 code_actions,
6083 debug_scenarios,
6084 task_context.unwrap_or_default(),
6085 );
6086
6087 // Don't show the menu if there are no actions available
6088 if actions.is_empty() {
6089 cx.notify();
6090 return Task::ready(Ok(()));
6091 }
6092
6093 *editor.context_menu.borrow_mut() =
6094 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6095 buffer,
6096 actions,
6097 selected_item: Default::default(),
6098 scroll_handle: UniformListScrollHandle::default(),
6099 deployed_from,
6100 }));
6101 cx.notify();
6102 if spawn_straight_away {
6103 if let Some(task) = editor.confirm_code_action(
6104 &ConfirmCodeAction { item_ix: Some(0) },
6105 window,
6106 cx,
6107 ) {
6108 return task;
6109 }
6110 }
6111
6112 Task::ready(Ok(()))
6113 })
6114 })
6115 .detach_and_log_err(cx);
6116 }
6117
6118 fn debug_scenarios(
6119 &mut self,
6120 resolved_tasks: &Option<ResolvedTasks>,
6121 buffer: &Entity<Buffer>,
6122 cx: &mut App,
6123 ) -> Task<Vec<task::DebugScenario>> {
6124 maybe!({
6125 let project = self.project.as_ref()?;
6126 let dap_store = project.read(cx).dap_store();
6127 let mut scenarios = vec![];
6128 let resolved_tasks = resolved_tasks.as_ref()?;
6129 let buffer = buffer.read(cx);
6130 let language = buffer.language()?;
6131 let file = buffer.file();
6132 let debug_adapter = language_settings(language.name().into(), file, cx)
6133 .debuggers
6134 .first()
6135 .map(SharedString::from)
6136 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6137
6138 dap_store.update(cx, |dap_store, cx| {
6139 for (_, task) in &resolved_tasks.templates {
6140 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6141 task.original_task().clone(),
6142 debug_adapter.clone().into(),
6143 task.display_label().to_owned().into(),
6144 cx,
6145 );
6146 scenarios.push(maybe_scenario);
6147 }
6148 });
6149 Some(cx.background_spawn(async move {
6150 let scenarios = futures::future::join_all(scenarios)
6151 .await
6152 .into_iter()
6153 .flatten()
6154 .collect::<Vec<_>>();
6155 scenarios
6156 }))
6157 })
6158 .unwrap_or_else(|| Task::ready(vec![]))
6159 }
6160
6161 fn code_actions(
6162 &mut self,
6163 buffer_row: u32,
6164 window: &mut Window,
6165 cx: &mut Context<Self>,
6166 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6167 let mut task = self.code_actions_task.take();
6168 cx.spawn_in(window, async move |editor, cx| {
6169 while let Some(prev_task) = task {
6170 prev_task.await.log_err();
6171 task = editor
6172 .update(cx, |this, _| this.code_actions_task.take())
6173 .ok()?;
6174 }
6175
6176 editor
6177 .update(cx, |editor, cx| {
6178 editor
6179 .available_code_actions
6180 .clone()
6181 .and_then(|(location, code_actions)| {
6182 let snapshot = location.buffer.read(cx).snapshot();
6183 let point_range = location.range.to_point(&snapshot);
6184 let point_range = point_range.start.row..=point_range.end.row;
6185 if point_range.contains(&buffer_row) {
6186 Some(code_actions)
6187 } else {
6188 None
6189 }
6190 })
6191 })
6192 .ok()
6193 .flatten()
6194 })
6195 }
6196
6197 pub fn confirm_code_action(
6198 &mut self,
6199 action: &ConfirmCodeAction,
6200 window: &mut Window,
6201 cx: &mut Context<Self>,
6202 ) -> Option<Task<Result<()>>> {
6203 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6204
6205 let actions_menu =
6206 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6207 menu
6208 } else {
6209 return None;
6210 };
6211
6212 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6213 let action = actions_menu.actions.get(action_ix)?;
6214 let title = action.label();
6215 let buffer = actions_menu.buffer;
6216 let workspace = self.workspace()?;
6217
6218 match action {
6219 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6220 workspace.update(cx, |workspace, cx| {
6221 workspace.schedule_resolved_task(
6222 task_source_kind,
6223 resolved_task,
6224 false,
6225 window,
6226 cx,
6227 );
6228
6229 Some(Task::ready(Ok(())))
6230 })
6231 }
6232 CodeActionsItem::CodeAction {
6233 excerpt_id,
6234 action,
6235 provider,
6236 } => {
6237 let apply_code_action =
6238 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6239 let workspace = workspace.downgrade();
6240 Some(cx.spawn_in(window, async move |editor, cx| {
6241 let project_transaction = apply_code_action.await?;
6242 Self::open_project_transaction(
6243 &editor,
6244 workspace,
6245 project_transaction,
6246 title,
6247 cx,
6248 )
6249 .await
6250 }))
6251 }
6252 CodeActionsItem::DebugScenario(scenario) => {
6253 let context = actions_menu.actions.context.clone();
6254
6255 workspace.update(cx, |workspace, cx| {
6256 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6257 workspace.start_debug_session(
6258 scenario,
6259 context,
6260 Some(buffer),
6261 None,
6262 window,
6263 cx,
6264 );
6265 });
6266 Some(Task::ready(Ok(())))
6267 }
6268 }
6269 }
6270
6271 pub async fn open_project_transaction(
6272 this: &WeakEntity<Editor>,
6273 workspace: WeakEntity<Workspace>,
6274 transaction: ProjectTransaction,
6275 title: String,
6276 cx: &mut AsyncWindowContext,
6277 ) -> Result<()> {
6278 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6279 cx.update(|_, cx| {
6280 entries.sort_unstable_by_key(|(buffer, _)| {
6281 buffer.read(cx).file().map(|f| f.path().clone())
6282 });
6283 })?;
6284
6285 // If the project transaction's edits are all contained within this editor, then
6286 // avoid opening a new editor to display them.
6287
6288 if let Some((buffer, transaction)) = entries.first() {
6289 if entries.len() == 1 {
6290 let excerpt = this.update(cx, |editor, cx| {
6291 editor
6292 .buffer()
6293 .read(cx)
6294 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6295 })?;
6296 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
6297 if excerpted_buffer == *buffer {
6298 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6299 let excerpt_range = excerpt_range.to_offset(buffer);
6300 buffer
6301 .edited_ranges_for_transaction::<usize>(transaction)
6302 .all(|range| {
6303 excerpt_range.start <= range.start
6304 && excerpt_range.end >= range.end
6305 })
6306 })?;
6307
6308 if all_edits_within_excerpt {
6309 return Ok(());
6310 }
6311 }
6312 }
6313 }
6314 } else {
6315 return Ok(());
6316 }
6317
6318 let mut ranges_to_highlight = Vec::new();
6319 let excerpt_buffer = cx.new(|cx| {
6320 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6321 for (buffer_handle, transaction) in &entries {
6322 let edited_ranges = buffer_handle
6323 .read(cx)
6324 .edited_ranges_for_transaction::<Point>(transaction)
6325 .collect::<Vec<_>>();
6326 let (ranges, _) = multibuffer.set_excerpts_for_path(
6327 PathKey::for_buffer(buffer_handle, cx),
6328 buffer_handle.clone(),
6329 edited_ranges,
6330 DEFAULT_MULTIBUFFER_CONTEXT,
6331 cx,
6332 );
6333
6334 ranges_to_highlight.extend(ranges);
6335 }
6336 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6337 multibuffer
6338 })?;
6339
6340 workspace.update_in(cx, |workspace, window, cx| {
6341 let project = workspace.project().clone();
6342 let editor =
6343 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6344 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6345 editor.update(cx, |editor, cx| {
6346 editor.highlight_background::<Self>(
6347 &ranges_to_highlight,
6348 |theme| theme.colors().editor_highlighted_line_background,
6349 cx,
6350 );
6351 });
6352 })?;
6353
6354 Ok(())
6355 }
6356
6357 pub fn clear_code_action_providers(&mut self) {
6358 self.code_action_providers.clear();
6359 self.available_code_actions.take();
6360 }
6361
6362 pub fn add_code_action_provider(
6363 &mut self,
6364 provider: Rc<dyn CodeActionProvider>,
6365 window: &mut Window,
6366 cx: &mut Context<Self>,
6367 ) {
6368 if self
6369 .code_action_providers
6370 .iter()
6371 .any(|existing_provider| existing_provider.id() == provider.id())
6372 {
6373 return;
6374 }
6375
6376 self.code_action_providers.push(provider);
6377 self.refresh_code_actions(window, cx);
6378 }
6379
6380 pub fn remove_code_action_provider(
6381 &mut self,
6382 id: Arc<str>,
6383 window: &mut Window,
6384 cx: &mut Context<Self>,
6385 ) {
6386 self.code_action_providers
6387 .retain(|provider| provider.id() != id);
6388 self.refresh_code_actions(window, cx);
6389 }
6390
6391 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6392 !self.code_action_providers.is_empty()
6393 && EditorSettings::get_global(cx).toolbar.code_actions
6394 }
6395
6396 pub fn has_available_code_actions(&self) -> bool {
6397 self.available_code_actions
6398 .as_ref()
6399 .is_some_and(|(_, actions)| !actions.is_empty())
6400 }
6401
6402 fn render_inline_code_actions(
6403 &self,
6404 icon_size: ui::IconSize,
6405 display_row: DisplayRow,
6406 is_active: bool,
6407 cx: &mut Context<Self>,
6408 ) -> AnyElement {
6409 let show_tooltip = !self.context_menu_visible();
6410 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6411 .icon_size(icon_size)
6412 .shape(ui::IconButtonShape::Square)
6413 .style(ButtonStyle::Transparent)
6414 .icon_color(ui::Color::Hidden)
6415 .toggle_state(is_active)
6416 .when(show_tooltip, |this| {
6417 this.tooltip({
6418 let focus_handle = self.focus_handle.clone();
6419 move |window, cx| {
6420 Tooltip::for_action_in(
6421 "Toggle Code Actions",
6422 &ToggleCodeActions {
6423 deployed_from: None,
6424 quick_launch: false,
6425 },
6426 &focus_handle,
6427 window,
6428 cx,
6429 )
6430 }
6431 })
6432 })
6433 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6434 window.focus(&editor.focus_handle(cx));
6435 editor.toggle_code_actions(
6436 &crate::actions::ToggleCodeActions {
6437 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6438 display_row,
6439 )),
6440 quick_launch: false,
6441 },
6442 window,
6443 cx,
6444 );
6445 }))
6446 .into_any_element()
6447 }
6448
6449 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6450 &self.context_menu
6451 }
6452
6453 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
6454 let newest_selection = self.selections.newest_anchor().clone();
6455 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
6456 let buffer = self.buffer.read(cx);
6457 if newest_selection.head().diff_base_anchor.is_some() {
6458 return None;
6459 }
6460 let (start_buffer, start) =
6461 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6462 let (end_buffer, end) =
6463 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6464 if start_buffer != end_buffer {
6465 return None;
6466 }
6467
6468 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6469 cx.background_executor()
6470 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6471 .await;
6472
6473 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6474 let providers = this.code_action_providers.clone();
6475 let tasks = this
6476 .code_action_providers
6477 .iter()
6478 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6479 .collect::<Vec<_>>();
6480 (providers, tasks)
6481 })?;
6482
6483 let mut actions = Vec::new();
6484 for (provider, provider_actions) in
6485 providers.into_iter().zip(future::join_all(tasks).await)
6486 {
6487 if let Some(provider_actions) = provider_actions.log_err() {
6488 actions.extend(provider_actions.into_iter().map(|action| {
6489 AvailableCodeAction {
6490 excerpt_id: newest_selection.start.excerpt_id,
6491 action,
6492 provider: provider.clone(),
6493 }
6494 }));
6495 }
6496 }
6497
6498 this.update(cx, |this, cx| {
6499 this.available_code_actions = if actions.is_empty() {
6500 None
6501 } else {
6502 Some((
6503 Location {
6504 buffer: start_buffer,
6505 range: start..end,
6506 },
6507 actions.into(),
6508 ))
6509 };
6510 cx.notify();
6511 })
6512 }));
6513 None
6514 }
6515
6516 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6517 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6518 self.show_git_blame_inline = false;
6519
6520 self.show_git_blame_inline_delay_task =
6521 Some(cx.spawn_in(window, async move |this, cx| {
6522 cx.background_executor().timer(delay).await;
6523
6524 this.update(cx, |this, cx| {
6525 this.show_git_blame_inline = true;
6526 cx.notify();
6527 })
6528 .log_err();
6529 }));
6530 }
6531 }
6532
6533 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6534 let snapshot = self.snapshot(window, cx);
6535 let cursor = self.selections.newest::<Point>(cx).head();
6536 let Some((buffer, point, _)) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)
6537 else {
6538 return;
6539 };
6540
6541 let Some(blame) = self.blame.as_ref() else {
6542 return;
6543 };
6544
6545 let row_info = RowInfo {
6546 buffer_id: Some(buffer.remote_id()),
6547 buffer_row: Some(point.row),
6548 ..Default::default()
6549 };
6550 let Some(blame_entry) = blame
6551 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6552 .flatten()
6553 else {
6554 return;
6555 };
6556
6557 let anchor = self.selections.newest_anchor().head();
6558 let position = self.to_pixel_point(anchor, &snapshot, window);
6559 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6560 self.show_blame_popover(&blame_entry, position + last_bounds.origin, true, cx);
6561 };
6562 }
6563
6564 fn show_blame_popover(
6565 &mut self,
6566 blame_entry: &BlameEntry,
6567 position: gpui::Point<Pixels>,
6568 ignore_timeout: bool,
6569 cx: &mut Context<Self>,
6570 ) {
6571 if let Some(state) = &mut self.inline_blame_popover {
6572 state.hide_task.take();
6573 } else {
6574 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay;
6575 let blame_entry = blame_entry.clone();
6576 let show_task = cx.spawn(async move |editor, cx| {
6577 if !ignore_timeout {
6578 cx.background_executor()
6579 .timer(std::time::Duration::from_millis(blame_popover_delay))
6580 .await;
6581 }
6582 editor
6583 .update(cx, |editor, cx| {
6584 editor.inline_blame_popover_show_task.take();
6585 let Some(blame) = editor.blame.as_ref() else {
6586 return;
6587 };
6588 let blame = blame.read(cx);
6589 let details = blame.details_for_entry(&blame_entry);
6590 let markdown = cx.new(|cx| {
6591 Markdown::new(
6592 details
6593 .as_ref()
6594 .map(|message| message.message.clone())
6595 .unwrap_or_default(),
6596 None,
6597 None,
6598 cx,
6599 )
6600 });
6601 editor.inline_blame_popover = Some(InlineBlamePopover {
6602 position,
6603 hide_task: None,
6604 popover_bounds: None,
6605 popover_state: InlineBlamePopoverState {
6606 scroll_handle: ScrollHandle::new(),
6607 commit_message: details,
6608 markdown,
6609 },
6610 keyboard_grace: ignore_timeout,
6611 });
6612 cx.notify();
6613 })
6614 .ok();
6615 });
6616 self.inline_blame_popover_show_task = Some(show_task);
6617 }
6618 }
6619
6620 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6621 self.inline_blame_popover_show_task.take();
6622 if let Some(state) = &mut self.inline_blame_popover {
6623 let hide_task = cx.spawn(async move |editor, cx| {
6624 cx.background_executor()
6625 .timer(std::time::Duration::from_millis(100))
6626 .await;
6627 editor
6628 .update(cx, |editor, cx| {
6629 editor.inline_blame_popover.take();
6630 cx.notify();
6631 })
6632 .ok();
6633 });
6634 state.hide_task = Some(hide_task);
6635 }
6636 }
6637
6638 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6639 if self.pending_rename.is_some() {
6640 return None;
6641 }
6642
6643 let provider = self.semantics_provider.clone()?;
6644 let buffer = self.buffer.read(cx);
6645 let newest_selection = self.selections.newest_anchor().clone();
6646 let cursor_position = newest_selection.head();
6647 let (cursor_buffer, cursor_buffer_position) =
6648 buffer.text_anchor_for_position(cursor_position, cx)?;
6649 let (tail_buffer, tail_buffer_position) =
6650 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6651 if cursor_buffer != tail_buffer {
6652 return None;
6653 }
6654
6655 let snapshot = cursor_buffer.read(cx).snapshot();
6656 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, false);
6657 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, false);
6658 if start_word_range != end_word_range {
6659 self.document_highlights_task.take();
6660 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6661 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6662 return None;
6663 }
6664
6665 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6666 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6667 cx.background_executor()
6668 .timer(Duration::from_millis(debounce))
6669 .await;
6670
6671 let highlights = if let Some(highlights) = cx
6672 .update(|cx| {
6673 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6674 })
6675 .ok()
6676 .flatten()
6677 {
6678 highlights.await.log_err()
6679 } else {
6680 None
6681 };
6682
6683 if let Some(highlights) = highlights {
6684 this.update(cx, |this, cx| {
6685 if this.pending_rename.is_some() {
6686 return;
6687 }
6688
6689 let buffer_id = cursor_position.buffer_id;
6690 let buffer = this.buffer.read(cx);
6691 if !buffer
6692 .text_anchor_for_position(cursor_position, cx)
6693 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
6694 {
6695 return;
6696 }
6697
6698 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6699 let mut write_ranges = Vec::new();
6700 let mut read_ranges = Vec::new();
6701 for highlight in highlights {
6702 for (excerpt_id, excerpt_range) in
6703 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
6704 {
6705 let start = highlight
6706 .range
6707 .start
6708 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6709 let end = highlight
6710 .range
6711 .end
6712 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6713 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6714 continue;
6715 }
6716
6717 let range = Anchor {
6718 buffer_id,
6719 excerpt_id,
6720 text_anchor: start,
6721 diff_base_anchor: None,
6722 }..Anchor {
6723 buffer_id,
6724 excerpt_id,
6725 text_anchor: end,
6726 diff_base_anchor: None,
6727 };
6728 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6729 write_ranges.push(range);
6730 } else {
6731 read_ranges.push(range);
6732 }
6733 }
6734 }
6735
6736 this.highlight_background::<DocumentHighlightRead>(
6737 &read_ranges,
6738 |theme| theme.colors().editor_document_highlight_read_background,
6739 cx,
6740 );
6741 this.highlight_background::<DocumentHighlightWrite>(
6742 &write_ranges,
6743 |theme| theme.colors().editor_document_highlight_write_background,
6744 cx,
6745 );
6746 cx.notify();
6747 })
6748 .log_err();
6749 }
6750 }));
6751 None
6752 }
6753
6754 fn prepare_highlight_query_from_selection(
6755 &mut self,
6756 cx: &mut Context<Editor>,
6757 ) -> Option<(String, Range<Anchor>)> {
6758 if matches!(self.mode, EditorMode::SingleLine { .. }) {
6759 return None;
6760 }
6761 if !EditorSettings::get_global(cx).selection_highlight {
6762 return None;
6763 }
6764 if self.selections.count() != 1 || self.selections.line_mode {
6765 return None;
6766 }
6767 let selection = self.selections.newest::<Point>(cx);
6768 if selection.is_empty() || selection.start.row != selection.end.row {
6769 return None;
6770 }
6771 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6772 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6773 let query = multi_buffer_snapshot
6774 .text_for_range(selection_anchor_range.clone())
6775 .collect::<String>();
6776 if query.trim().is_empty() {
6777 return None;
6778 }
6779 Some((query, selection_anchor_range))
6780 }
6781
6782 fn update_selection_occurrence_highlights(
6783 &mut self,
6784 query_text: String,
6785 query_range: Range<Anchor>,
6786 multi_buffer_range_to_query: Range<Point>,
6787 use_debounce: bool,
6788 window: &mut Window,
6789 cx: &mut Context<Editor>,
6790 ) -> Task<()> {
6791 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6792 cx.spawn_in(window, async move |editor, cx| {
6793 if use_debounce {
6794 cx.background_executor()
6795 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6796 .await;
6797 }
6798 let match_task = cx.background_spawn(async move {
6799 let buffer_ranges = multi_buffer_snapshot
6800 .range_to_buffer_ranges(multi_buffer_range_to_query)
6801 .into_iter()
6802 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6803 let mut match_ranges = Vec::new();
6804 let Ok(regex) = project::search::SearchQuery::text(
6805 query_text.clone(),
6806 false,
6807 false,
6808 false,
6809 Default::default(),
6810 Default::default(),
6811 false,
6812 None,
6813 ) else {
6814 return Vec::default();
6815 };
6816 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6817 match_ranges.extend(
6818 regex
6819 .search(&buffer_snapshot, Some(search_range.clone()))
6820 .await
6821 .into_iter()
6822 .filter_map(|match_range| {
6823 let match_start = buffer_snapshot
6824 .anchor_after(search_range.start + match_range.start);
6825 let match_end = buffer_snapshot
6826 .anchor_before(search_range.start + match_range.end);
6827 let match_anchor_range = Anchor::range_in_buffer(
6828 excerpt_id,
6829 buffer_snapshot.remote_id(),
6830 match_start..match_end,
6831 );
6832 (match_anchor_range != query_range).then_some(match_anchor_range)
6833 }),
6834 );
6835 }
6836 match_ranges
6837 });
6838 let match_ranges = match_task.await;
6839 editor
6840 .update_in(cx, |editor, _, cx| {
6841 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6842 if !match_ranges.is_empty() {
6843 editor.highlight_background::<SelectedTextHighlight>(
6844 &match_ranges,
6845 |theme| theme.colors().editor_document_highlight_bracket_background,
6846 cx,
6847 )
6848 }
6849 })
6850 .log_err();
6851 })
6852 }
6853
6854 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
6855 struct NewlineFold;
6856 let type_id = std::any::TypeId::of::<NewlineFold>();
6857 if !self.mode.is_single_line() {
6858 return;
6859 }
6860 let snapshot = self.snapshot(window, cx);
6861 if snapshot.buffer_snapshot.max_point().row == 0 {
6862 return;
6863 }
6864 let task = cx.background_spawn(async move {
6865 let new_newlines = snapshot
6866 .buffer_chars_at(0)
6867 .filter_map(|(c, i)| {
6868 if c == '\n' {
6869 Some(
6870 snapshot.buffer_snapshot.anchor_after(i)
6871 ..snapshot.buffer_snapshot.anchor_before(i + 1),
6872 )
6873 } else {
6874 None
6875 }
6876 })
6877 .collect::<Vec<_>>();
6878 let existing_newlines = snapshot
6879 .folds_in_range(0..snapshot.buffer_snapshot.len())
6880 .filter_map(|fold| {
6881 if fold.placeholder.type_tag == Some(type_id) {
6882 Some(fold.range.start..fold.range.end)
6883 } else {
6884 None
6885 }
6886 })
6887 .collect::<Vec<_>>();
6888
6889 (new_newlines, existing_newlines)
6890 });
6891 self.folding_newlines = cx.spawn(async move |this, cx| {
6892 let (new_newlines, existing_newlines) = task.await;
6893 if new_newlines == existing_newlines {
6894 return;
6895 }
6896 let placeholder = FoldPlaceholder {
6897 render: Arc::new(move |_, _, cx| {
6898 div()
6899 .bg(cx.theme().status().hint_background)
6900 .border_b_1()
6901 .size_full()
6902 .font(ThemeSettings::get_global(cx).buffer_font.clone())
6903 .border_color(cx.theme().status().hint)
6904 .child("\\n")
6905 .into_any()
6906 }),
6907 constrain_width: false,
6908 merge_adjacent: false,
6909 type_tag: Some(type_id),
6910 };
6911 let creases = new_newlines
6912 .into_iter()
6913 .map(|range| Crease::simple(range, placeholder.clone()))
6914 .collect();
6915 this.update(cx, |this, cx| {
6916 this.display_map.update(cx, |display_map, cx| {
6917 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
6918 display_map.fold(creases, cx);
6919 });
6920 })
6921 .ok();
6922 });
6923 }
6924
6925 fn refresh_selected_text_highlights(
6926 &mut self,
6927 on_buffer_edit: bool,
6928 window: &mut Window,
6929 cx: &mut Context<Editor>,
6930 ) {
6931 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
6932 else {
6933 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6934 self.quick_selection_highlight_task.take();
6935 self.debounced_selection_highlight_task.take();
6936 return;
6937 };
6938 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6939 if on_buffer_edit
6940 || self
6941 .quick_selection_highlight_task
6942 .as_ref()
6943 .map_or(true, |(prev_anchor_range, _)| {
6944 prev_anchor_range != &query_range
6945 })
6946 {
6947 let multi_buffer_visible_start = self
6948 .scroll_manager
6949 .anchor()
6950 .anchor
6951 .to_point(&multi_buffer_snapshot);
6952 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
6953 multi_buffer_visible_start
6954 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
6955 Bias::Left,
6956 );
6957 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
6958 self.quick_selection_highlight_task = Some((
6959 query_range.clone(),
6960 self.update_selection_occurrence_highlights(
6961 query_text.clone(),
6962 query_range.clone(),
6963 multi_buffer_visible_range,
6964 false,
6965 window,
6966 cx,
6967 ),
6968 ));
6969 }
6970 if on_buffer_edit
6971 || self
6972 .debounced_selection_highlight_task
6973 .as_ref()
6974 .map_or(true, |(prev_anchor_range, _)| {
6975 prev_anchor_range != &query_range
6976 })
6977 {
6978 let multi_buffer_start = multi_buffer_snapshot
6979 .anchor_before(0)
6980 .to_point(&multi_buffer_snapshot);
6981 let multi_buffer_end = multi_buffer_snapshot
6982 .anchor_after(multi_buffer_snapshot.len())
6983 .to_point(&multi_buffer_snapshot);
6984 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
6985 self.debounced_selection_highlight_task = Some((
6986 query_range.clone(),
6987 self.update_selection_occurrence_highlights(
6988 query_text,
6989 query_range,
6990 multi_buffer_full_range,
6991 true,
6992 window,
6993 cx,
6994 ),
6995 ));
6996 }
6997 }
6998
6999 pub fn refresh_inline_completion(
7000 &mut self,
7001 debounce: bool,
7002 user_requested: bool,
7003 window: &mut Window,
7004 cx: &mut Context<Self>,
7005 ) -> Option<()> {
7006 if DisableAiSettings::get_global(cx).disable_ai {
7007 return None;
7008 }
7009
7010 let provider = self.edit_prediction_provider()?;
7011 let cursor = self.selections.newest_anchor().head();
7012 let (buffer, cursor_buffer_position) =
7013 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7014
7015 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7016 self.discard_inline_completion(false, cx);
7017 return None;
7018 }
7019
7020 if !user_requested
7021 && (!self.should_show_edit_predictions()
7022 || !self.is_focused(window)
7023 || buffer.read(cx).is_empty())
7024 {
7025 self.discard_inline_completion(false, cx);
7026 return None;
7027 }
7028
7029 self.update_visible_inline_completion(window, cx);
7030 provider.refresh(
7031 self.project.clone(),
7032 buffer,
7033 cursor_buffer_position,
7034 debounce,
7035 cx,
7036 );
7037 Some(())
7038 }
7039
7040 fn show_edit_predictions_in_menu(&self) -> bool {
7041 match self.edit_prediction_settings {
7042 EditPredictionSettings::Disabled => false,
7043 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7044 }
7045 }
7046
7047 pub fn edit_predictions_enabled(&self) -> bool {
7048 match self.edit_prediction_settings {
7049 EditPredictionSettings::Disabled => false,
7050 EditPredictionSettings::Enabled { .. } => true,
7051 }
7052 }
7053
7054 fn edit_prediction_requires_modifier(&self) -> bool {
7055 match self.edit_prediction_settings {
7056 EditPredictionSettings::Disabled => false,
7057 EditPredictionSettings::Enabled {
7058 preview_requires_modifier,
7059 ..
7060 } => preview_requires_modifier,
7061 }
7062 }
7063
7064 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7065 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7066 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7067 self.discard_inline_completion(false, cx);
7068 } else {
7069 let selection = self.selections.newest_anchor();
7070 let cursor = selection.head();
7071
7072 if let Some((buffer, cursor_buffer_position)) =
7073 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7074 {
7075 self.edit_prediction_settings =
7076 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7077 }
7078 }
7079 }
7080
7081 fn edit_prediction_settings_at_position(
7082 &self,
7083 buffer: &Entity<Buffer>,
7084 buffer_position: language::Anchor,
7085 cx: &App,
7086 ) -> EditPredictionSettings {
7087 if !self.mode.is_full()
7088 || !self.show_inline_completions_override.unwrap_or(true)
7089 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
7090 {
7091 return EditPredictionSettings::Disabled;
7092 }
7093
7094 let buffer = buffer.read(cx);
7095
7096 let file = buffer.file();
7097
7098 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7099 return EditPredictionSettings::Disabled;
7100 };
7101
7102 let by_provider = matches!(
7103 self.menu_inline_completions_policy,
7104 MenuInlineCompletionsPolicy::ByProvider
7105 );
7106
7107 let show_in_menu = by_provider
7108 && self
7109 .edit_prediction_provider
7110 .as_ref()
7111 .map_or(false, |provider| {
7112 provider.provider.show_completions_in_menu()
7113 });
7114
7115 let preview_requires_modifier =
7116 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7117
7118 EditPredictionSettings::Enabled {
7119 show_in_menu,
7120 preview_requires_modifier,
7121 }
7122 }
7123
7124 fn should_show_edit_predictions(&self) -> bool {
7125 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7126 }
7127
7128 pub fn edit_prediction_preview_is_active(&self) -> bool {
7129 matches!(
7130 self.edit_prediction_preview,
7131 EditPredictionPreview::Active { .. }
7132 )
7133 }
7134
7135 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7136 let cursor = self.selections.newest_anchor().head();
7137 if let Some((buffer, cursor_position)) =
7138 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7139 {
7140 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7141 } else {
7142 false
7143 }
7144 }
7145
7146 pub fn supports_minimap(&self, cx: &App) -> bool {
7147 !self.minimap_visibility.disabled() && self.is_singleton(cx)
7148 }
7149
7150 fn edit_predictions_enabled_in_buffer(
7151 &self,
7152 buffer: &Entity<Buffer>,
7153 buffer_position: language::Anchor,
7154 cx: &App,
7155 ) -> bool {
7156 maybe!({
7157 if self.read_only(cx) {
7158 return Some(false);
7159 }
7160 let provider = self.edit_prediction_provider()?;
7161 if !provider.is_enabled(&buffer, buffer_position, cx) {
7162 return Some(false);
7163 }
7164 let buffer = buffer.read(cx);
7165 let Some(file) = buffer.file() else {
7166 return Some(true);
7167 };
7168 let settings = all_language_settings(Some(file), cx);
7169 Some(settings.edit_predictions_enabled_for_file(file, cx))
7170 })
7171 .unwrap_or(false)
7172 }
7173
7174 fn cycle_inline_completion(
7175 &mut self,
7176 direction: Direction,
7177 window: &mut Window,
7178 cx: &mut Context<Self>,
7179 ) -> Option<()> {
7180 let provider = self.edit_prediction_provider()?;
7181 let cursor = self.selections.newest_anchor().head();
7182 let (buffer, cursor_buffer_position) =
7183 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7184 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7185 return None;
7186 }
7187
7188 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7189 self.update_visible_inline_completion(window, cx);
7190
7191 Some(())
7192 }
7193
7194 pub fn show_inline_completion(
7195 &mut self,
7196 _: &ShowEditPrediction,
7197 window: &mut Window,
7198 cx: &mut Context<Self>,
7199 ) {
7200 if !self.has_active_inline_completion() {
7201 self.refresh_inline_completion(false, true, window, cx);
7202 return;
7203 }
7204
7205 self.update_visible_inline_completion(window, cx);
7206 }
7207
7208 pub fn display_cursor_names(
7209 &mut self,
7210 _: &DisplayCursorNames,
7211 window: &mut Window,
7212 cx: &mut Context<Self>,
7213 ) {
7214 self.show_cursor_names(window, cx);
7215 }
7216
7217 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7218 self.show_cursor_names = true;
7219 cx.notify();
7220 cx.spawn_in(window, async move |this, cx| {
7221 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7222 this.update(cx, |this, cx| {
7223 this.show_cursor_names = false;
7224 cx.notify()
7225 })
7226 .ok()
7227 })
7228 .detach();
7229 }
7230
7231 pub fn next_edit_prediction(
7232 &mut self,
7233 _: &NextEditPrediction,
7234 window: &mut Window,
7235 cx: &mut Context<Self>,
7236 ) {
7237 if self.has_active_inline_completion() {
7238 self.cycle_inline_completion(Direction::Next, window, cx);
7239 } else {
7240 let is_copilot_disabled = self
7241 .refresh_inline_completion(false, true, window, cx)
7242 .is_none();
7243 if is_copilot_disabled {
7244 cx.propagate();
7245 }
7246 }
7247 }
7248
7249 pub fn previous_edit_prediction(
7250 &mut self,
7251 _: &PreviousEditPrediction,
7252 window: &mut Window,
7253 cx: &mut Context<Self>,
7254 ) {
7255 if self.has_active_inline_completion() {
7256 self.cycle_inline_completion(Direction::Prev, window, cx);
7257 } else {
7258 let is_copilot_disabled = self
7259 .refresh_inline_completion(false, true, window, cx)
7260 .is_none();
7261 if is_copilot_disabled {
7262 cx.propagate();
7263 }
7264 }
7265 }
7266
7267 pub fn accept_edit_prediction(
7268 &mut self,
7269 _: &AcceptEditPrediction,
7270 window: &mut Window,
7271 cx: &mut Context<Self>,
7272 ) {
7273 if self.show_edit_predictions_in_menu() {
7274 self.hide_context_menu(window, cx);
7275 }
7276
7277 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
7278 return;
7279 };
7280
7281 self.report_inline_completion_event(
7282 active_inline_completion.completion_id.clone(),
7283 true,
7284 cx,
7285 );
7286
7287 match &active_inline_completion.completion {
7288 InlineCompletion::Move { target, .. } => {
7289 let target = *target;
7290
7291 if let Some(position_map) = &self.last_position_map {
7292 if position_map
7293 .visible_row_range
7294 .contains(&target.to_display_point(&position_map.snapshot).row())
7295 || !self.edit_prediction_requires_modifier()
7296 {
7297 self.unfold_ranges(&[target..target], true, false, cx);
7298 // Note that this is also done in vim's handler of the Tab action.
7299 self.change_selections(
7300 SelectionEffects::scroll(Autoscroll::newest()),
7301 window,
7302 cx,
7303 |selections| {
7304 selections.select_anchor_ranges([target..target]);
7305 },
7306 );
7307 self.clear_row_highlights::<EditPredictionPreview>();
7308
7309 self.edit_prediction_preview
7310 .set_previous_scroll_position(None);
7311 } else {
7312 self.edit_prediction_preview
7313 .set_previous_scroll_position(Some(
7314 position_map.snapshot.scroll_anchor,
7315 ));
7316
7317 self.highlight_rows::<EditPredictionPreview>(
7318 target..target,
7319 cx.theme().colors().editor_highlighted_line_background,
7320 RowHighlightOptions {
7321 autoscroll: true,
7322 ..Default::default()
7323 },
7324 cx,
7325 );
7326 self.request_autoscroll(Autoscroll::fit(), cx);
7327 }
7328 }
7329 }
7330 InlineCompletion::Edit { edits, .. } => {
7331 if let Some(provider) = self.edit_prediction_provider() {
7332 provider.accept(cx);
7333 }
7334
7335 // Store the transaction ID and selections before applying the edit
7336 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7337
7338 let snapshot = self.buffer.read(cx).snapshot(cx);
7339 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7340
7341 self.buffer.update(cx, |buffer, cx| {
7342 buffer.edit(edits.iter().cloned(), None, cx)
7343 });
7344
7345 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7346 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7347 });
7348
7349 let selections = self.selections.disjoint_anchors();
7350 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7351 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7352 if has_new_transaction {
7353 self.selection_history
7354 .insert_transaction(transaction_id_now, selections);
7355 }
7356 }
7357
7358 self.update_visible_inline_completion(window, cx);
7359 if self.active_inline_completion.is_none() {
7360 self.refresh_inline_completion(true, true, window, cx);
7361 }
7362
7363 cx.notify();
7364 }
7365 }
7366
7367 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7368 }
7369
7370 pub fn accept_partial_inline_completion(
7371 &mut self,
7372 _: &AcceptPartialEditPrediction,
7373 window: &mut Window,
7374 cx: &mut Context<Self>,
7375 ) {
7376 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
7377 return;
7378 };
7379 if self.selections.count() != 1 {
7380 return;
7381 }
7382
7383 self.report_inline_completion_event(
7384 active_inline_completion.completion_id.clone(),
7385 true,
7386 cx,
7387 );
7388
7389 match &active_inline_completion.completion {
7390 InlineCompletion::Move { target, .. } => {
7391 let target = *target;
7392 self.change_selections(
7393 SelectionEffects::scroll(Autoscroll::newest()),
7394 window,
7395 cx,
7396 |selections| {
7397 selections.select_anchor_ranges([target..target]);
7398 },
7399 );
7400 }
7401 InlineCompletion::Edit { edits, .. } => {
7402 // Find an insertion that starts at the cursor position.
7403 let snapshot = self.buffer.read(cx).snapshot(cx);
7404 let cursor_offset = self.selections.newest::<usize>(cx).head();
7405 let insertion = edits.iter().find_map(|(range, text)| {
7406 let range = range.to_offset(&snapshot);
7407 if range.is_empty() && range.start == cursor_offset {
7408 Some(text)
7409 } else {
7410 None
7411 }
7412 });
7413
7414 if let Some(text) = insertion {
7415 let mut partial_completion = text
7416 .chars()
7417 .by_ref()
7418 .take_while(|c| c.is_alphabetic())
7419 .collect::<String>();
7420 if partial_completion.is_empty() {
7421 partial_completion = text
7422 .chars()
7423 .by_ref()
7424 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7425 .collect::<String>();
7426 }
7427
7428 cx.emit(EditorEvent::InputHandled {
7429 utf16_range_to_replace: None,
7430 text: partial_completion.clone().into(),
7431 });
7432
7433 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7434
7435 self.refresh_inline_completion(true, true, window, cx);
7436 cx.notify();
7437 } else {
7438 self.accept_edit_prediction(&Default::default(), window, cx);
7439 }
7440 }
7441 }
7442 }
7443
7444 fn discard_inline_completion(
7445 &mut self,
7446 should_report_inline_completion_event: bool,
7447 cx: &mut Context<Self>,
7448 ) -> bool {
7449 if should_report_inline_completion_event {
7450 let completion_id = self
7451 .active_inline_completion
7452 .as_ref()
7453 .and_then(|active_completion| active_completion.completion_id.clone());
7454
7455 self.report_inline_completion_event(completion_id, false, cx);
7456 }
7457
7458 if let Some(provider) = self.edit_prediction_provider() {
7459 provider.discard(cx);
7460 }
7461
7462 self.take_active_inline_completion(cx)
7463 }
7464
7465 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7466 let Some(provider) = self.edit_prediction_provider() else {
7467 return;
7468 };
7469
7470 let Some((_, buffer, _)) = self
7471 .buffer
7472 .read(cx)
7473 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7474 else {
7475 return;
7476 };
7477
7478 let extension = buffer
7479 .read(cx)
7480 .file()
7481 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
7482
7483 let event_type = match accepted {
7484 true => "Edit Prediction Accepted",
7485 false => "Edit Prediction Discarded",
7486 };
7487 telemetry::event!(
7488 event_type,
7489 provider = provider.name(),
7490 prediction_id = id,
7491 suggestion_accepted = accepted,
7492 file_extension = extension,
7493 );
7494 }
7495
7496 pub fn has_active_inline_completion(&self) -> bool {
7497 self.active_inline_completion.is_some()
7498 }
7499
7500 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
7501 let Some(active_inline_completion) = self.active_inline_completion.take() else {
7502 return false;
7503 };
7504
7505 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
7506 self.clear_highlights::<InlineCompletionHighlight>(cx);
7507 self.stale_inline_completion_in_menu = Some(active_inline_completion);
7508 true
7509 }
7510
7511 /// Returns true when we're displaying the edit prediction popover below the cursor
7512 /// like we are not previewing and the LSP autocomplete menu is visible
7513 /// or we are in `when_holding_modifier` mode.
7514 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7515 if self.edit_prediction_preview_is_active()
7516 || !self.show_edit_predictions_in_menu()
7517 || !self.edit_predictions_enabled()
7518 {
7519 return false;
7520 }
7521
7522 if self.has_visible_completions_menu() {
7523 return true;
7524 }
7525
7526 has_completion && self.edit_prediction_requires_modifier()
7527 }
7528
7529 fn handle_modifiers_changed(
7530 &mut self,
7531 modifiers: Modifiers,
7532 position_map: &PositionMap,
7533 window: &mut Window,
7534 cx: &mut Context<Self>,
7535 ) {
7536 if self.show_edit_predictions_in_menu() {
7537 self.update_edit_prediction_preview(&modifiers, window, cx);
7538 }
7539
7540 self.update_selection_mode(&modifiers, position_map, window, cx);
7541
7542 let mouse_position = window.mouse_position();
7543 if !position_map.text_hitbox.is_hovered(window) {
7544 return;
7545 }
7546
7547 self.update_hovered_link(
7548 position_map.point_for_position(mouse_position),
7549 &position_map.snapshot,
7550 modifiers,
7551 window,
7552 cx,
7553 )
7554 }
7555
7556 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7557 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7558 if invert {
7559 match multi_cursor_setting {
7560 MultiCursorModifier::Alt => modifiers.alt,
7561 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7562 }
7563 } else {
7564 match multi_cursor_setting {
7565 MultiCursorModifier::Alt => modifiers.secondary(),
7566 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7567 }
7568 }
7569 }
7570
7571 fn columnar_selection_mode(
7572 modifiers: &Modifiers,
7573 cx: &mut Context<Self>,
7574 ) -> Option<ColumnarMode> {
7575 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7576 if Self::multi_cursor_modifier(false, modifiers, cx) {
7577 Some(ColumnarMode::FromMouse)
7578 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7579 Some(ColumnarMode::FromSelection)
7580 } else {
7581 None
7582 }
7583 } else {
7584 None
7585 }
7586 }
7587
7588 fn update_selection_mode(
7589 &mut self,
7590 modifiers: &Modifiers,
7591 position_map: &PositionMap,
7592 window: &mut Window,
7593 cx: &mut Context<Self>,
7594 ) {
7595 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7596 return;
7597 };
7598 if self.selections.pending.is_none() {
7599 return;
7600 }
7601
7602 let mouse_position = window.mouse_position();
7603 let point_for_position = position_map.point_for_position(mouse_position);
7604 let position = point_for_position.previous_valid;
7605
7606 self.select(
7607 SelectPhase::BeginColumnar {
7608 position,
7609 reset: false,
7610 mode,
7611 goal_column: point_for_position.exact_unclipped.column(),
7612 },
7613 window,
7614 cx,
7615 );
7616 }
7617
7618 fn update_edit_prediction_preview(
7619 &mut self,
7620 modifiers: &Modifiers,
7621 window: &mut Window,
7622 cx: &mut Context<Self>,
7623 ) {
7624 let mut modifiers_held = false;
7625 if let Some(accept_keystroke) = self
7626 .accept_edit_prediction_keybind(false, window, cx)
7627 .keystroke()
7628 {
7629 modifiers_held = modifiers_held
7630 || (&accept_keystroke.modifiers == modifiers
7631 && accept_keystroke.modifiers.modified());
7632 };
7633 if let Some(accept_partial_keystroke) = self
7634 .accept_edit_prediction_keybind(true, window, cx)
7635 .keystroke()
7636 {
7637 modifiers_held = modifiers_held
7638 || (&accept_partial_keystroke.modifiers == modifiers
7639 && accept_partial_keystroke.modifiers.modified());
7640 }
7641
7642 if modifiers_held {
7643 if matches!(
7644 self.edit_prediction_preview,
7645 EditPredictionPreview::Inactive { .. }
7646 ) {
7647 self.edit_prediction_preview = EditPredictionPreview::Active {
7648 previous_scroll_position: None,
7649 since: Instant::now(),
7650 };
7651
7652 self.update_visible_inline_completion(window, cx);
7653 cx.notify();
7654 }
7655 } else if let EditPredictionPreview::Active {
7656 previous_scroll_position,
7657 since,
7658 } = self.edit_prediction_preview
7659 {
7660 if let (Some(previous_scroll_position), Some(position_map)) =
7661 (previous_scroll_position, self.last_position_map.as_ref())
7662 {
7663 self.set_scroll_position(
7664 previous_scroll_position
7665 .scroll_position(&position_map.snapshot.display_snapshot),
7666 window,
7667 cx,
7668 );
7669 }
7670
7671 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7672 released_too_fast: since.elapsed() < Duration::from_millis(200),
7673 };
7674 self.clear_row_highlights::<EditPredictionPreview>();
7675 self.update_visible_inline_completion(window, cx);
7676 cx.notify();
7677 }
7678 }
7679
7680 fn update_visible_inline_completion(
7681 &mut self,
7682 _window: &mut Window,
7683 cx: &mut Context<Self>,
7684 ) -> Option<()> {
7685 if DisableAiSettings::get_global(cx).disable_ai {
7686 return None;
7687 }
7688
7689 let selection = self.selections.newest_anchor();
7690 let cursor = selection.head();
7691 let multibuffer = self.buffer.read(cx).snapshot(cx);
7692 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7693 let excerpt_id = cursor.excerpt_id;
7694
7695 let show_in_menu = self.show_edit_predictions_in_menu();
7696 let completions_menu_has_precedence = !show_in_menu
7697 && (self.context_menu.borrow().is_some()
7698 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
7699
7700 if completions_menu_has_precedence
7701 || !offset_selection.is_empty()
7702 || self
7703 .active_inline_completion
7704 .as_ref()
7705 .map_or(false, |completion| {
7706 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
7707 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7708 !invalidation_range.contains(&offset_selection.head())
7709 })
7710 {
7711 self.discard_inline_completion(false, cx);
7712 return None;
7713 }
7714
7715 self.take_active_inline_completion(cx);
7716 let Some(provider) = self.edit_prediction_provider() else {
7717 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7718 return None;
7719 };
7720
7721 let (buffer, cursor_buffer_position) =
7722 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7723
7724 self.edit_prediction_settings =
7725 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7726
7727 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7728
7729 if self.edit_prediction_indent_conflict {
7730 let cursor_point = cursor.to_point(&multibuffer);
7731
7732 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7733
7734 if let Some((_, indent)) = indents.iter().next() {
7735 if indent.len == cursor_point.column {
7736 self.edit_prediction_indent_conflict = false;
7737 }
7738 }
7739 }
7740
7741 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7742 let edits = inline_completion
7743 .edits
7744 .into_iter()
7745 .flat_map(|(range, new_text)| {
7746 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7747 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7748 Some((start..end, new_text))
7749 })
7750 .collect::<Vec<_>>();
7751 if edits.is_empty() {
7752 return None;
7753 }
7754
7755 let first_edit_start = edits.first().unwrap().0.start;
7756 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7757 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7758
7759 let last_edit_end = edits.last().unwrap().0.end;
7760 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7761 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7762
7763 let cursor_row = cursor.to_point(&multibuffer).row;
7764
7765 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7766
7767 let mut inlay_ids = Vec::new();
7768 let invalidation_row_range;
7769 let move_invalidation_row_range = if cursor_row < edit_start_row {
7770 Some(cursor_row..edit_end_row)
7771 } else if cursor_row > edit_end_row {
7772 Some(edit_start_row..cursor_row)
7773 } else {
7774 None
7775 };
7776 let is_move =
7777 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
7778 let completion = if is_move {
7779 invalidation_row_range =
7780 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7781 let target = first_edit_start;
7782 InlineCompletion::Move { target, snapshot }
7783 } else {
7784 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7785 && !self.inline_completions_hidden_for_vim_mode;
7786
7787 if show_completions_in_buffer {
7788 if edits
7789 .iter()
7790 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7791 {
7792 let mut inlays = Vec::new();
7793 for (range, new_text) in &edits {
7794 let inlay = Inlay::inline_completion(
7795 post_inc(&mut self.next_inlay_id),
7796 range.start,
7797 new_text.as_str(),
7798 );
7799 inlay_ids.push(inlay.id);
7800 inlays.push(inlay);
7801 }
7802
7803 self.splice_inlays(&[], inlays, cx);
7804 } else {
7805 let background_color = cx.theme().status().deleted_background;
7806 self.highlight_text::<InlineCompletionHighlight>(
7807 edits.iter().map(|(range, _)| range.clone()).collect(),
7808 HighlightStyle {
7809 background_color: Some(background_color),
7810 ..Default::default()
7811 },
7812 cx,
7813 );
7814 }
7815 }
7816
7817 invalidation_row_range = edit_start_row..edit_end_row;
7818
7819 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7820 if provider.show_tab_accept_marker() {
7821 EditDisplayMode::TabAccept
7822 } else {
7823 EditDisplayMode::Inline
7824 }
7825 } else {
7826 EditDisplayMode::DiffPopover
7827 };
7828
7829 InlineCompletion::Edit {
7830 edits,
7831 edit_preview: inline_completion.edit_preview,
7832 display_mode,
7833 snapshot,
7834 }
7835 };
7836
7837 let invalidation_range = multibuffer
7838 .anchor_before(Point::new(invalidation_row_range.start, 0))
7839 ..multibuffer.anchor_after(Point::new(
7840 invalidation_row_range.end,
7841 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7842 ));
7843
7844 self.stale_inline_completion_in_menu = None;
7845 self.active_inline_completion = Some(InlineCompletionState {
7846 inlay_ids,
7847 completion,
7848 completion_id: inline_completion.id,
7849 invalidation_range,
7850 });
7851
7852 cx.notify();
7853
7854 Some(())
7855 }
7856
7857 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
7858 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7859 }
7860
7861 fn clear_tasks(&mut self) {
7862 self.tasks.clear()
7863 }
7864
7865 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7866 if self.tasks.insert(key, value).is_some() {
7867 // This case should hopefully be rare, but just in case...
7868 log::error!(
7869 "multiple different run targets found on a single line, only the last target will be rendered"
7870 )
7871 }
7872 }
7873
7874 /// Get all display points of breakpoints that will be rendered within editor
7875 ///
7876 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7877 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7878 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7879 fn active_breakpoints(
7880 &self,
7881 range: Range<DisplayRow>,
7882 window: &mut Window,
7883 cx: &mut Context<Self>,
7884 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7885 let mut breakpoint_display_points = HashMap::default();
7886
7887 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7888 return breakpoint_display_points;
7889 };
7890
7891 let snapshot = self.snapshot(window, cx);
7892
7893 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
7894 let Some(project) = self.project.as_ref() else {
7895 return breakpoint_display_points;
7896 };
7897
7898 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7899 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7900
7901 for (buffer_snapshot, range, excerpt_id) in
7902 multi_buffer_snapshot.range_to_buffer_ranges(range)
7903 {
7904 let Some(buffer) = project
7905 .read(cx)
7906 .buffer_for_id(buffer_snapshot.remote_id(), cx)
7907 else {
7908 continue;
7909 };
7910 let breakpoints = breakpoint_store.read(cx).breakpoints(
7911 &buffer,
7912 Some(
7913 buffer_snapshot.anchor_before(range.start)
7914 ..buffer_snapshot.anchor_after(range.end),
7915 ),
7916 buffer_snapshot,
7917 cx,
7918 );
7919 for (breakpoint, state) in breakpoints {
7920 let multi_buffer_anchor =
7921 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
7922 let position = multi_buffer_anchor
7923 .to_point(&multi_buffer_snapshot)
7924 .to_display_point(&snapshot);
7925
7926 breakpoint_display_points.insert(
7927 position.row(),
7928 (multi_buffer_anchor, breakpoint.bp.clone(), state),
7929 );
7930 }
7931 }
7932
7933 breakpoint_display_points
7934 }
7935
7936 fn breakpoint_context_menu(
7937 &self,
7938 anchor: Anchor,
7939 window: &mut Window,
7940 cx: &mut Context<Self>,
7941 ) -> Entity<ui::ContextMenu> {
7942 let weak_editor = cx.weak_entity();
7943 let focus_handle = self.focus_handle(cx);
7944
7945 let row = self
7946 .buffer
7947 .read(cx)
7948 .snapshot(cx)
7949 .summary_for_anchor::<Point>(&anchor)
7950 .row;
7951
7952 let breakpoint = self
7953 .breakpoint_at_row(row, window, cx)
7954 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
7955
7956 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
7957 "Edit Log Breakpoint"
7958 } else {
7959 "Set Log Breakpoint"
7960 };
7961
7962 let condition_breakpoint_msg = if breakpoint
7963 .as_ref()
7964 .is_some_and(|bp| bp.1.condition.is_some())
7965 {
7966 "Edit Condition Breakpoint"
7967 } else {
7968 "Set Condition Breakpoint"
7969 };
7970
7971 let hit_condition_breakpoint_msg = if breakpoint
7972 .as_ref()
7973 .is_some_and(|bp| bp.1.hit_condition.is_some())
7974 {
7975 "Edit Hit Condition Breakpoint"
7976 } else {
7977 "Set Hit Condition Breakpoint"
7978 };
7979
7980 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
7981 "Unset Breakpoint"
7982 } else {
7983 "Set Breakpoint"
7984 };
7985
7986 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
7987
7988 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
7989 BreakpointState::Enabled => Some("Disable"),
7990 BreakpointState::Disabled => Some("Enable"),
7991 });
7992
7993 let (anchor, breakpoint) =
7994 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
7995
7996 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
7997 menu.on_blur_subscription(Subscription::new(|| {}))
7998 .context(focus_handle)
7999 .when(run_to_cursor, |this| {
8000 let weak_editor = weak_editor.clone();
8001 this.entry("Run to cursor", None, move |window, cx| {
8002 weak_editor
8003 .update(cx, |editor, cx| {
8004 editor.change_selections(
8005 SelectionEffects::no_scroll(),
8006 window,
8007 cx,
8008 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8009 );
8010 })
8011 .ok();
8012
8013 window.dispatch_action(Box::new(RunToCursor), cx);
8014 })
8015 .separator()
8016 })
8017 .when_some(toggle_state_msg, |this, msg| {
8018 this.entry(msg, None, {
8019 let weak_editor = weak_editor.clone();
8020 let breakpoint = breakpoint.clone();
8021 move |_window, cx| {
8022 weak_editor
8023 .update(cx, |this, cx| {
8024 this.edit_breakpoint_at_anchor(
8025 anchor,
8026 breakpoint.as_ref().clone(),
8027 BreakpointEditAction::InvertState,
8028 cx,
8029 );
8030 })
8031 .log_err();
8032 }
8033 })
8034 })
8035 .entry(set_breakpoint_msg, None, {
8036 let weak_editor = weak_editor.clone();
8037 let breakpoint = breakpoint.clone();
8038 move |_window, cx| {
8039 weak_editor
8040 .update(cx, |this, cx| {
8041 this.edit_breakpoint_at_anchor(
8042 anchor,
8043 breakpoint.as_ref().clone(),
8044 BreakpointEditAction::Toggle,
8045 cx,
8046 );
8047 })
8048 .log_err();
8049 }
8050 })
8051 .entry(log_breakpoint_msg, None, {
8052 let breakpoint = breakpoint.clone();
8053 let weak_editor = weak_editor.clone();
8054 move |window, cx| {
8055 weak_editor
8056 .update(cx, |this, cx| {
8057 this.add_edit_breakpoint_block(
8058 anchor,
8059 breakpoint.as_ref(),
8060 BreakpointPromptEditAction::Log,
8061 window,
8062 cx,
8063 );
8064 })
8065 .log_err();
8066 }
8067 })
8068 .entry(condition_breakpoint_msg, None, {
8069 let breakpoint = breakpoint.clone();
8070 let weak_editor = weak_editor.clone();
8071 move |window, cx| {
8072 weak_editor
8073 .update(cx, |this, cx| {
8074 this.add_edit_breakpoint_block(
8075 anchor,
8076 breakpoint.as_ref(),
8077 BreakpointPromptEditAction::Condition,
8078 window,
8079 cx,
8080 );
8081 })
8082 .log_err();
8083 }
8084 })
8085 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8086 weak_editor
8087 .update(cx, |this, cx| {
8088 this.add_edit_breakpoint_block(
8089 anchor,
8090 breakpoint.as_ref(),
8091 BreakpointPromptEditAction::HitCondition,
8092 window,
8093 cx,
8094 );
8095 })
8096 .log_err();
8097 })
8098 })
8099 }
8100
8101 fn render_breakpoint(
8102 &self,
8103 position: Anchor,
8104 row: DisplayRow,
8105 breakpoint: &Breakpoint,
8106 state: Option<BreakpointSessionState>,
8107 cx: &mut Context<Self>,
8108 ) -> IconButton {
8109 let is_rejected = state.is_some_and(|s| !s.verified);
8110 // Is it a breakpoint that shows up when hovering over gutter?
8111 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8112 (false, false),
8113 |PhantomBreakpointIndicator {
8114 is_active,
8115 display_row,
8116 collides_with_existing_breakpoint,
8117 }| {
8118 (
8119 is_active && display_row == row,
8120 collides_with_existing_breakpoint,
8121 )
8122 },
8123 );
8124
8125 let (color, icon) = {
8126 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8127 (false, false) => ui::IconName::DebugBreakpoint,
8128 (true, false) => ui::IconName::DebugLogBreakpoint,
8129 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8130 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8131 };
8132
8133 let color = if is_phantom {
8134 Color::Hint
8135 } else if is_rejected {
8136 Color::Disabled
8137 } else {
8138 Color::Debugger
8139 };
8140
8141 (color, icon)
8142 };
8143
8144 let breakpoint = Arc::from(breakpoint.clone());
8145
8146 let alt_as_text = gpui::Keystroke {
8147 modifiers: Modifiers::secondary_key(),
8148 ..Default::default()
8149 };
8150 let primary_action_text = if breakpoint.is_disabled() {
8151 "Enable breakpoint"
8152 } else if is_phantom && !collides_with_existing {
8153 "Set breakpoint"
8154 } else {
8155 "Unset breakpoint"
8156 };
8157 let focus_handle = self.focus_handle.clone();
8158
8159 let meta = if is_rejected {
8160 SharedString::from("No executable code is associated with this line.")
8161 } else if collides_with_existing && !breakpoint.is_disabled() {
8162 SharedString::from(format!(
8163 "{alt_as_text}-click to disable,\nright-click for more options."
8164 ))
8165 } else {
8166 SharedString::from("Right-click for more options.")
8167 };
8168 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8169 .icon_size(IconSize::XSmall)
8170 .size(ui::ButtonSize::None)
8171 .when(is_rejected, |this| {
8172 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8173 })
8174 .icon_color(color)
8175 .style(ButtonStyle::Transparent)
8176 .on_click(cx.listener({
8177 let breakpoint = breakpoint.clone();
8178
8179 move |editor, event: &ClickEvent, window, cx| {
8180 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8181 BreakpointEditAction::InvertState
8182 } else {
8183 BreakpointEditAction::Toggle
8184 };
8185
8186 window.focus(&editor.focus_handle(cx));
8187 editor.edit_breakpoint_at_anchor(
8188 position,
8189 breakpoint.as_ref().clone(),
8190 edit_action,
8191 cx,
8192 );
8193 }
8194 }))
8195 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8196 editor.set_breakpoint_context_menu(
8197 row,
8198 Some(position),
8199 event.down.position,
8200 window,
8201 cx,
8202 );
8203 }))
8204 .tooltip(move |window, cx| {
8205 Tooltip::with_meta_in(
8206 primary_action_text,
8207 Some(&ToggleBreakpoint),
8208 meta.clone(),
8209 &focus_handle,
8210 window,
8211 cx,
8212 )
8213 })
8214 }
8215
8216 fn build_tasks_context(
8217 project: &Entity<Project>,
8218 buffer: &Entity<Buffer>,
8219 buffer_row: u32,
8220 tasks: &Arc<RunnableTasks>,
8221 cx: &mut Context<Self>,
8222 ) -> Task<Option<task::TaskContext>> {
8223 let position = Point::new(buffer_row, tasks.column);
8224 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8225 let location = Location {
8226 buffer: buffer.clone(),
8227 range: range_start..range_start,
8228 };
8229 // Fill in the environmental variables from the tree-sitter captures
8230 let mut captured_task_variables = TaskVariables::default();
8231 for (capture_name, value) in tasks.extra_variables.clone() {
8232 captured_task_variables.insert(
8233 task::VariableName::Custom(capture_name.into()),
8234 value.clone(),
8235 );
8236 }
8237 project.update(cx, |project, cx| {
8238 project.task_store().update(cx, |task_store, cx| {
8239 task_store.task_context_for_location(captured_task_variables, location, cx)
8240 })
8241 })
8242 }
8243
8244 pub fn spawn_nearest_task(
8245 &mut self,
8246 action: &SpawnNearestTask,
8247 window: &mut Window,
8248 cx: &mut Context<Self>,
8249 ) {
8250 let Some((workspace, _)) = self.workspace.clone() else {
8251 return;
8252 };
8253 let Some(project) = self.project.clone() else {
8254 return;
8255 };
8256
8257 // Try to find a closest, enclosing node using tree-sitter that has a task
8258 let Some((buffer, buffer_row, tasks)) = self
8259 .find_enclosing_node_task(cx)
8260 // Or find the task that's closest in row-distance.
8261 .or_else(|| self.find_closest_task(cx))
8262 else {
8263 return;
8264 };
8265
8266 let reveal_strategy = action.reveal;
8267 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8268 cx.spawn_in(window, async move |_, cx| {
8269 let context = task_context.await?;
8270 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8271
8272 let resolved = &mut resolved_task.resolved;
8273 resolved.reveal = reveal_strategy;
8274
8275 workspace
8276 .update_in(cx, |workspace, window, cx| {
8277 workspace.schedule_resolved_task(
8278 task_source_kind,
8279 resolved_task,
8280 false,
8281 window,
8282 cx,
8283 );
8284 })
8285 .ok()
8286 })
8287 .detach();
8288 }
8289
8290 fn find_closest_task(
8291 &mut self,
8292 cx: &mut Context<Self>,
8293 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8294 let cursor_row = self.selections.newest_adjusted(cx).head().row;
8295
8296 let ((buffer_id, row), tasks) = self
8297 .tasks
8298 .iter()
8299 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8300
8301 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8302 let tasks = Arc::new(tasks.to_owned());
8303 Some((buffer, *row, tasks))
8304 }
8305
8306 fn find_enclosing_node_task(
8307 &mut self,
8308 cx: &mut Context<Self>,
8309 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8310 let snapshot = self.buffer.read(cx).snapshot(cx);
8311 let offset = self.selections.newest::<usize>(cx).head();
8312 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8313 let buffer_id = excerpt.buffer().remote_id();
8314
8315 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8316 let mut cursor = layer.node().walk();
8317
8318 while cursor.goto_first_child_for_byte(offset).is_some() {
8319 if cursor.node().end_byte() == offset {
8320 cursor.goto_next_sibling();
8321 }
8322 }
8323
8324 // Ascend to the smallest ancestor that contains the range and has a task.
8325 loop {
8326 let node = cursor.node();
8327 let node_range = node.byte_range();
8328 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8329
8330 // Check if this node contains our offset
8331 if node_range.start <= offset && node_range.end >= offset {
8332 // If it contains offset, check for task
8333 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8334 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8335 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8336 }
8337 }
8338
8339 if !cursor.goto_parent() {
8340 break;
8341 }
8342 }
8343 None
8344 }
8345
8346 fn render_run_indicator(
8347 &self,
8348 _style: &EditorStyle,
8349 is_active: bool,
8350 row: DisplayRow,
8351 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8352 cx: &mut Context<Self>,
8353 ) -> IconButton {
8354 let color = Color::Muted;
8355 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8356
8357 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
8358 .shape(ui::IconButtonShape::Square)
8359 .icon_size(IconSize::XSmall)
8360 .icon_color(color)
8361 .toggle_state(is_active)
8362 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8363 let quick_launch = e.down.button == MouseButton::Left;
8364 window.focus(&editor.focus_handle(cx));
8365 editor.toggle_code_actions(
8366 &ToggleCodeActions {
8367 deployed_from: Some(CodeActionSource::RunMenu(row)),
8368 quick_launch,
8369 },
8370 window,
8371 cx,
8372 );
8373 }))
8374 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8375 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
8376 }))
8377 }
8378
8379 pub fn context_menu_visible(&self) -> bool {
8380 !self.edit_prediction_preview_is_active()
8381 && self
8382 .context_menu
8383 .borrow()
8384 .as_ref()
8385 .map_or(false, |menu| menu.visible())
8386 }
8387
8388 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8389 self.context_menu
8390 .borrow()
8391 .as_ref()
8392 .map(|menu| menu.origin())
8393 }
8394
8395 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8396 self.context_menu_options = Some(options);
8397 }
8398
8399 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
8400 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
8401
8402 fn render_edit_prediction_popover(
8403 &mut self,
8404 text_bounds: &Bounds<Pixels>,
8405 content_origin: gpui::Point<Pixels>,
8406 right_margin: Pixels,
8407 editor_snapshot: &EditorSnapshot,
8408 visible_row_range: Range<DisplayRow>,
8409 scroll_top: f32,
8410 scroll_bottom: f32,
8411 line_layouts: &[LineWithInvisibles],
8412 line_height: Pixels,
8413 scroll_pixel_position: gpui::Point<Pixels>,
8414 newest_selection_head: Option<DisplayPoint>,
8415 editor_width: Pixels,
8416 style: &EditorStyle,
8417 window: &mut Window,
8418 cx: &mut App,
8419 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8420 if self.mode().is_minimap() {
8421 return None;
8422 }
8423 let active_inline_completion = self.active_inline_completion.as_ref()?;
8424
8425 if self.edit_prediction_visible_in_cursor_popover(true) {
8426 return None;
8427 }
8428
8429 match &active_inline_completion.completion {
8430 InlineCompletion::Move { target, .. } => {
8431 let target_display_point = target.to_display_point(editor_snapshot);
8432
8433 if self.edit_prediction_requires_modifier() {
8434 if !self.edit_prediction_preview_is_active() {
8435 return None;
8436 }
8437
8438 self.render_edit_prediction_modifier_jump_popover(
8439 text_bounds,
8440 content_origin,
8441 visible_row_range,
8442 line_layouts,
8443 line_height,
8444 scroll_pixel_position,
8445 newest_selection_head,
8446 target_display_point,
8447 window,
8448 cx,
8449 )
8450 } else {
8451 self.render_edit_prediction_eager_jump_popover(
8452 text_bounds,
8453 content_origin,
8454 editor_snapshot,
8455 visible_row_range,
8456 scroll_top,
8457 scroll_bottom,
8458 line_height,
8459 scroll_pixel_position,
8460 target_display_point,
8461 editor_width,
8462 window,
8463 cx,
8464 )
8465 }
8466 }
8467 InlineCompletion::Edit {
8468 display_mode: EditDisplayMode::Inline,
8469 ..
8470 } => None,
8471 InlineCompletion::Edit {
8472 display_mode: EditDisplayMode::TabAccept,
8473 edits,
8474 ..
8475 } => {
8476 let range = &edits.first()?.0;
8477 let target_display_point = range.end.to_display_point(editor_snapshot);
8478
8479 self.render_edit_prediction_end_of_line_popover(
8480 "Accept",
8481 editor_snapshot,
8482 visible_row_range,
8483 target_display_point,
8484 line_height,
8485 scroll_pixel_position,
8486 content_origin,
8487 editor_width,
8488 window,
8489 cx,
8490 )
8491 }
8492 InlineCompletion::Edit {
8493 edits,
8494 edit_preview,
8495 display_mode: EditDisplayMode::DiffPopover,
8496 snapshot,
8497 } => self.render_edit_prediction_diff_popover(
8498 text_bounds,
8499 content_origin,
8500 right_margin,
8501 editor_snapshot,
8502 visible_row_range,
8503 line_layouts,
8504 line_height,
8505 scroll_pixel_position,
8506 newest_selection_head,
8507 editor_width,
8508 style,
8509 edits,
8510 edit_preview,
8511 snapshot,
8512 window,
8513 cx,
8514 ),
8515 }
8516 }
8517
8518 fn render_edit_prediction_modifier_jump_popover(
8519 &mut self,
8520 text_bounds: &Bounds<Pixels>,
8521 content_origin: gpui::Point<Pixels>,
8522 visible_row_range: Range<DisplayRow>,
8523 line_layouts: &[LineWithInvisibles],
8524 line_height: Pixels,
8525 scroll_pixel_position: gpui::Point<Pixels>,
8526 newest_selection_head: Option<DisplayPoint>,
8527 target_display_point: DisplayPoint,
8528 window: &mut Window,
8529 cx: &mut App,
8530 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8531 let scrolled_content_origin =
8532 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
8533
8534 const SCROLL_PADDING_Y: Pixels = px(12.);
8535
8536 if target_display_point.row() < visible_row_range.start {
8537 return self.render_edit_prediction_scroll_popover(
8538 |_| SCROLL_PADDING_Y,
8539 IconName::ArrowUp,
8540 visible_row_range,
8541 line_layouts,
8542 newest_selection_head,
8543 scrolled_content_origin,
8544 window,
8545 cx,
8546 );
8547 } else if target_display_point.row() >= visible_row_range.end {
8548 return self.render_edit_prediction_scroll_popover(
8549 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8550 IconName::ArrowDown,
8551 visible_row_range,
8552 line_layouts,
8553 newest_selection_head,
8554 scrolled_content_origin,
8555 window,
8556 cx,
8557 );
8558 }
8559
8560 const POLE_WIDTH: Pixels = px(2.);
8561
8562 let line_layout =
8563 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8564 let target_column = target_display_point.column() as usize;
8565
8566 let target_x = line_layout.x_for_index(target_column);
8567 let target_y =
8568 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
8569
8570 let flag_on_right = target_x < text_bounds.size.width / 2.;
8571
8572 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8573 border_color.l += 0.001;
8574
8575 let mut element = v_flex()
8576 .items_end()
8577 .when(flag_on_right, |el| el.items_start())
8578 .child(if flag_on_right {
8579 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8580 .rounded_bl(px(0.))
8581 .rounded_tl(px(0.))
8582 .border_l_2()
8583 .border_color(border_color)
8584 } else {
8585 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8586 .rounded_br(px(0.))
8587 .rounded_tr(px(0.))
8588 .border_r_2()
8589 .border_color(border_color)
8590 })
8591 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8592 .into_any();
8593
8594 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8595
8596 let mut origin = scrolled_content_origin + point(target_x, target_y)
8597 - point(
8598 if flag_on_right {
8599 POLE_WIDTH
8600 } else {
8601 size.width - POLE_WIDTH
8602 },
8603 size.height - line_height,
8604 );
8605
8606 origin.x = origin.x.max(content_origin.x);
8607
8608 element.prepaint_at(origin, window, cx);
8609
8610 Some((element, origin))
8611 }
8612
8613 fn render_edit_prediction_scroll_popover(
8614 &mut self,
8615 to_y: impl Fn(Size<Pixels>) -> Pixels,
8616 scroll_icon: IconName,
8617 visible_row_range: Range<DisplayRow>,
8618 line_layouts: &[LineWithInvisibles],
8619 newest_selection_head: Option<DisplayPoint>,
8620 scrolled_content_origin: gpui::Point<Pixels>,
8621 window: &mut Window,
8622 cx: &mut App,
8623 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8624 let mut element = self
8625 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
8626 .into_any();
8627
8628 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8629
8630 let cursor = newest_selection_head?;
8631 let cursor_row_layout =
8632 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8633 let cursor_column = cursor.column() as usize;
8634
8635 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8636
8637 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8638
8639 element.prepaint_at(origin, window, cx);
8640 Some((element, origin))
8641 }
8642
8643 fn render_edit_prediction_eager_jump_popover(
8644 &mut self,
8645 text_bounds: &Bounds<Pixels>,
8646 content_origin: gpui::Point<Pixels>,
8647 editor_snapshot: &EditorSnapshot,
8648 visible_row_range: Range<DisplayRow>,
8649 scroll_top: f32,
8650 scroll_bottom: f32,
8651 line_height: Pixels,
8652 scroll_pixel_position: gpui::Point<Pixels>,
8653 target_display_point: DisplayPoint,
8654 editor_width: Pixels,
8655 window: &mut Window,
8656 cx: &mut App,
8657 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8658 if target_display_point.row().as_f32() < scroll_top {
8659 let mut element = self
8660 .render_edit_prediction_line_popover(
8661 "Jump to Edit",
8662 Some(IconName::ArrowUp),
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 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 if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
8678 let mut element = self
8679 .render_edit_prediction_line_popover(
8680 "Jump to Edit",
8681 Some(IconName::ArrowDown),
8682 window,
8683 cx,
8684 )?
8685 .into_any();
8686
8687 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8688 let offset = point(
8689 (text_bounds.size.width - size.width) / 2.,
8690 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8691 );
8692
8693 let origin = text_bounds.origin + offset;
8694 element.prepaint_at(origin, window, cx);
8695 Some((element, origin))
8696 } else {
8697 self.render_edit_prediction_end_of_line_popover(
8698 "Jump to Edit",
8699 editor_snapshot,
8700 visible_row_range,
8701 target_display_point,
8702 line_height,
8703 scroll_pixel_position,
8704 content_origin,
8705 editor_width,
8706 window,
8707 cx,
8708 )
8709 }
8710 }
8711
8712 fn render_edit_prediction_end_of_line_popover(
8713 self: &mut Editor,
8714 label: &'static str,
8715 editor_snapshot: &EditorSnapshot,
8716 visible_row_range: Range<DisplayRow>,
8717 target_display_point: DisplayPoint,
8718 line_height: Pixels,
8719 scroll_pixel_position: gpui::Point<Pixels>,
8720 content_origin: gpui::Point<Pixels>,
8721 editor_width: Pixels,
8722 window: &mut Window,
8723 cx: &mut App,
8724 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8725 let target_line_end = DisplayPoint::new(
8726 target_display_point.row(),
8727 editor_snapshot.line_len(target_display_point.row()),
8728 );
8729
8730 let mut element = self
8731 .render_edit_prediction_line_popover(label, None, window, cx)?
8732 .into_any();
8733
8734 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8735
8736 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8737
8738 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
8739 let mut origin = start_point
8740 + line_origin
8741 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8742 origin.x = origin.x.max(content_origin.x);
8743
8744 let max_x = content_origin.x + editor_width - size.width;
8745
8746 if origin.x > max_x {
8747 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8748
8749 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8750 origin.y += offset;
8751 IconName::ArrowUp
8752 } else {
8753 origin.y -= offset;
8754 IconName::ArrowDown
8755 };
8756
8757 element = self
8758 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
8759 .into_any();
8760
8761 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8762
8763 origin.x = content_origin.x + editor_width - size.width - px(2.);
8764 }
8765
8766 element.prepaint_at(origin, window, cx);
8767 Some((element, origin))
8768 }
8769
8770 fn render_edit_prediction_diff_popover(
8771 self: &Editor,
8772 text_bounds: &Bounds<Pixels>,
8773 content_origin: gpui::Point<Pixels>,
8774 right_margin: Pixels,
8775 editor_snapshot: &EditorSnapshot,
8776 visible_row_range: Range<DisplayRow>,
8777 line_layouts: &[LineWithInvisibles],
8778 line_height: Pixels,
8779 scroll_pixel_position: gpui::Point<Pixels>,
8780 newest_selection_head: Option<DisplayPoint>,
8781 editor_width: Pixels,
8782 style: &EditorStyle,
8783 edits: &Vec<(Range<Anchor>, String)>,
8784 edit_preview: &Option<language::EditPreview>,
8785 snapshot: &language::BufferSnapshot,
8786 window: &mut Window,
8787 cx: &mut App,
8788 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8789 let edit_start = edits
8790 .first()
8791 .unwrap()
8792 .0
8793 .start
8794 .to_display_point(editor_snapshot);
8795 let edit_end = edits
8796 .last()
8797 .unwrap()
8798 .0
8799 .end
8800 .to_display_point(editor_snapshot);
8801
8802 let is_visible = visible_row_range.contains(&edit_start.row())
8803 || visible_row_range.contains(&edit_end.row());
8804 if !is_visible {
8805 return None;
8806 }
8807
8808 let highlighted_edits =
8809 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
8810
8811 let styled_text = highlighted_edits.to_styled_text(&style.text);
8812 let line_count = highlighted_edits.text.lines().count();
8813
8814 const BORDER_WIDTH: Pixels = px(1.);
8815
8816 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8817 let has_keybind = keybind.is_some();
8818
8819 let mut element = h_flex()
8820 .items_start()
8821 .child(
8822 h_flex()
8823 .bg(cx.theme().colors().editor_background)
8824 .border(BORDER_WIDTH)
8825 .shadow_xs()
8826 .border_color(cx.theme().colors().border)
8827 .rounded_l_lg()
8828 .when(line_count > 1, |el| el.rounded_br_lg())
8829 .pr_1()
8830 .child(styled_text),
8831 )
8832 .child(
8833 h_flex()
8834 .h(line_height + BORDER_WIDTH * 2.)
8835 .px_1p5()
8836 .gap_1()
8837 // Workaround: For some reason, there's a gap if we don't do this
8838 .ml(-BORDER_WIDTH)
8839 .shadow(vec![gpui::BoxShadow {
8840 color: gpui::black().opacity(0.05),
8841 offset: point(px(1.), px(1.)),
8842 blur_radius: px(2.),
8843 spread_radius: px(0.),
8844 }])
8845 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8846 .border(BORDER_WIDTH)
8847 .border_color(cx.theme().colors().border)
8848 .rounded_r_lg()
8849 .id("edit_prediction_diff_popover_keybind")
8850 .when(!has_keybind, |el| {
8851 let status_colors = cx.theme().status();
8852
8853 el.bg(status_colors.error_background)
8854 .border_color(status_colors.error.opacity(0.6))
8855 .child(Icon::new(IconName::Info).color(Color::Error))
8856 .cursor_default()
8857 .hoverable_tooltip(move |_window, cx| {
8858 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8859 })
8860 })
8861 .children(keybind),
8862 )
8863 .into_any();
8864
8865 let longest_row =
8866 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8867 let longest_line_width = if visible_row_range.contains(&longest_row) {
8868 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8869 } else {
8870 layout_line(
8871 longest_row,
8872 editor_snapshot,
8873 style,
8874 editor_width,
8875 |_| false,
8876 window,
8877 cx,
8878 )
8879 .width
8880 };
8881
8882 let viewport_bounds =
8883 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8884 right: -right_margin,
8885 ..Default::default()
8886 });
8887
8888 let x_after_longest =
8889 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
8890 - scroll_pixel_position.x;
8891
8892 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8893
8894 // Fully visible if it can be displayed within the window (allow overlapping other
8895 // panes). However, this is only allowed if the popover starts within text_bounds.
8896 let can_position_to_the_right = x_after_longest < text_bounds.right()
8897 && x_after_longest + element_bounds.width < viewport_bounds.right();
8898
8899 let mut origin = if can_position_to_the_right {
8900 point(
8901 x_after_longest,
8902 text_bounds.origin.y + edit_start.row().as_f32() * line_height
8903 - scroll_pixel_position.y,
8904 )
8905 } else {
8906 let cursor_row = newest_selection_head.map(|head| head.row());
8907 let above_edit = edit_start
8908 .row()
8909 .0
8910 .checked_sub(line_count as u32)
8911 .map(DisplayRow);
8912 let below_edit = Some(edit_end.row() + 1);
8913 let above_cursor =
8914 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
8915 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
8916
8917 // Place the edit popover adjacent to the edit if there is a location
8918 // available that is onscreen and does not obscure the cursor. Otherwise,
8919 // place it adjacent to the cursor.
8920 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
8921 .into_iter()
8922 .flatten()
8923 .find(|&start_row| {
8924 let end_row = start_row + line_count as u32;
8925 visible_row_range.contains(&start_row)
8926 && visible_row_range.contains(&end_row)
8927 && cursor_row.map_or(true, |cursor_row| {
8928 !((start_row..end_row).contains(&cursor_row))
8929 })
8930 })?;
8931
8932 content_origin
8933 + point(
8934 -scroll_pixel_position.x,
8935 row_target.as_f32() * line_height - scroll_pixel_position.y,
8936 )
8937 };
8938
8939 origin.x -= BORDER_WIDTH;
8940
8941 window.defer_draw(element, origin, 1);
8942
8943 // Do not return an element, since it will already be drawn due to defer_draw.
8944 None
8945 }
8946
8947 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
8948 px(30.)
8949 }
8950
8951 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
8952 if self.read_only(cx) {
8953 cx.theme().players().read_only()
8954 } else {
8955 self.style.as_ref().unwrap().local_player
8956 }
8957 }
8958
8959 fn render_edit_prediction_accept_keybind(
8960 &self,
8961 window: &mut Window,
8962 cx: &App,
8963 ) -> Option<AnyElement> {
8964 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
8965 let accept_keystroke = accept_binding.keystroke()?;
8966
8967 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8968
8969 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
8970 Color::Accent
8971 } else {
8972 Color::Muted
8973 };
8974
8975 h_flex()
8976 .px_0p5()
8977 .when(is_platform_style_mac, |parent| parent.gap_0p5())
8978 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8979 .text_size(TextSize::XSmall.rems(cx))
8980 .child(h_flex().children(ui::render_modifiers(
8981 &accept_keystroke.modifiers,
8982 PlatformStyle::platform(),
8983 Some(modifiers_color),
8984 Some(IconSize::XSmall.rems().into()),
8985 true,
8986 )))
8987 .when(is_platform_style_mac, |parent| {
8988 parent.child(accept_keystroke.key.clone())
8989 })
8990 .when(!is_platform_style_mac, |parent| {
8991 parent.child(
8992 Key::new(
8993 util::capitalize(&accept_keystroke.key),
8994 Some(Color::Default),
8995 )
8996 .size(Some(IconSize::XSmall.rems().into())),
8997 )
8998 })
8999 .into_any()
9000 .into()
9001 }
9002
9003 fn render_edit_prediction_line_popover(
9004 &self,
9005 label: impl Into<SharedString>,
9006 icon: Option<IconName>,
9007 window: &mut Window,
9008 cx: &App,
9009 ) -> Option<Stateful<Div>> {
9010 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9011
9012 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9013 let has_keybind = keybind.is_some();
9014
9015 let result = h_flex()
9016 .id("ep-line-popover")
9017 .py_0p5()
9018 .pl_1()
9019 .pr(padding_right)
9020 .gap_1()
9021 .rounded_md()
9022 .border_1()
9023 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9024 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9025 .shadow_xs()
9026 .when(!has_keybind, |el| {
9027 let status_colors = cx.theme().status();
9028
9029 el.bg(status_colors.error_background)
9030 .border_color(status_colors.error.opacity(0.6))
9031 .pl_2()
9032 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9033 .cursor_default()
9034 .hoverable_tooltip(move |_window, cx| {
9035 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9036 })
9037 })
9038 .children(keybind)
9039 .child(
9040 Label::new(label)
9041 .size(LabelSize::Small)
9042 .when(!has_keybind, |el| {
9043 el.color(cx.theme().status().error.into()).strikethrough()
9044 }),
9045 )
9046 .when(!has_keybind, |el| {
9047 el.child(
9048 h_flex().ml_1().child(
9049 Icon::new(IconName::Info)
9050 .size(IconSize::Small)
9051 .color(cx.theme().status().error.into()),
9052 ),
9053 )
9054 })
9055 .when_some(icon, |element, icon| {
9056 element.child(
9057 div()
9058 .mt(px(1.5))
9059 .child(Icon::new(icon).size(IconSize::Small)),
9060 )
9061 });
9062
9063 Some(result)
9064 }
9065
9066 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9067 let accent_color = cx.theme().colors().text_accent;
9068 let editor_bg_color = cx.theme().colors().editor_background;
9069 editor_bg_color.blend(accent_color.opacity(0.1))
9070 }
9071
9072 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9073 let accent_color = cx.theme().colors().text_accent;
9074 let editor_bg_color = cx.theme().colors().editor_background;
9075 editor_bg_color.blend(accent_color.opacity(0.6))
9076 }
9077
9078 fn render_edit_prediction_cursor_popover(
9079 &self,
9080 min_width: Pixels,
9081 max_width: Pixels,
9082 cursor_point: Point,
9083 style: &EditorStyle,
9084 accept_keystroke: Option<&gpui::Keystroke>,
9085 _window: &Window,
9086 cx: &mut Context<Editor>,
9087 ) -> Option<AnyElement> {
9088 let provider = self.edit_prediction_provider.as_ref()?;
9089
9090 if provider.provider.needs_terms_acceptance(cx) {
9091 return Some(
9092 h_flex()
9093 .min_w(min_width)
9094 .flex_1()
9095 .px_2()
9096 .py_1()
9097 .gap_3()
9098 .elevation_2(cx)
9099 .hover(|style| style.bg(cx.theme().colors().element_hover))
9100 .id("accept-terms")
9101 .cursor_pointer()
9102 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
9103 .on_click(cx.listener(|this, _event, window, cx| {
9104 cx.stop_propagation();
9105 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
9106 window.dispatch_action(
9107 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
9108 cx,
9109 );
9110 }))
9111 .child(
9112 h_flex()
9113 .flex_1()
9114 .gap_2()
9115 .child(Icon::new(IconName::ZedPredict))
9116 .child(Label::new("Accept Terms of Service"))
9117 .child(div().w_full())
9118 .child(
9119 Icon::new(IconName::ArrowUpRight)
9120 .color(Color::Muted)
9121 .size(IconSize::Small),
9122 )
9123 .into_any_element(),
9124 )
9125 .into_any(),
9126 );
9127 }
9128
9129 let is_refreshing = provider.provider.is_refreshing(cx);
9130
9131 fn pending_completion_container() -> Div {
9132 h_flex()
9133 .h_full()
9134 .flex_1()
9135 .gap_2()
9136 .child(Icon::new(IconName::ZedPredict))
9137 }
9138
9139 let completion = match &self.active_inline_completion {
9140 Some(prediction) => {
9141 if !self.has_visible_completions_menu() {
9142 const RADIUS: Pixels = px(6.);
9143 const BORDER_WIDTH: Pixels = px(1.);
9144
9145 return Some(
9146 h_flex()
9147 .elevation_2(cx)
9148 .border(BORDER_WIDTH)
9149 .border_color(cx.theme().colors().border)
9150 .when(accept_keystroke.is_none(), |el| {
9151 el.border_color(cx.theme().status().error)
9152 })
9153 .rounded(RADIUS)
9154 .rounded_tl(px(0.))
9155 .overflow_hidden()
9156 .child(div().px_1p5().child(match &prediction.completion {
9157 InlineCompletion::Move { target, snapshot } => {
9158 use text::ToPoint as _;
9159 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
9160 {
9161 Icon::new(IconName::ZedPredictDown)
9162 } else {
9163 Icon::new(IconName::ZedPredictUp)
9164 }
9165 }
9166 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
9167 }))
9168 .child(
9169 h_flex()
9170 .gap_1()
9171 .py_1()
9172 .px_2()
9173 .rounded_r(RADIUS - BORDER_WIDTH)
9174 .border_l_1()
9175 .border_color(cx.theme().colors().border)
9176 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9177 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9178 el.child(
9179 Label::new("Hold")
9180 .size(LabelSize::Small)
9181 .when(accept_keystroke.is_none(), |el| {
9182 el.strikethrough()
9183 })
9184 .line_height_style(LineHeightStyle::UiLabel),
9185 )
9186 })
9187 .id("edit_prediction_cursor_popover_keybind")
9188 .when(accept_keystroke.is_none(), |el| {
9189 let status_colors = cx.theme().status();
9190
9191 el.bg(status_colors.error_background)
9192 .border_color(status_colors.error.opacity(0.6))
9193 .child(Icon::new(IconName::Info).color(Color::Error))
9194 .cursor_default()
9195 .hoverable_tooltip(move |_window, cx| {
9196 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9197 .into()
9198 })
9199 })
9200 .when_some(
9201 accept_keystroke.as_ref(),
9202 |el, accept_keystroke| {
9203 el.child(h_flex().children(ui::render_modifiers(
9204 &accept_keystroke.modifiers,
9205 PlatformStyle::platform(),
9206 Some(Color::Default),
9207 Some(IconSize::XSmall.rems().into()),
9208 false,
9209 )))
9210 },
9211 ),
9212 )
9213 .into_any(),
9214 );
9215 }
9216
9217 self.render_edit_prediction_cursor_popover_preview(
9218 prediction,
9219 cursor_point,
9220 style,
9221 cx,
9222 )?
9223 }
9224
9225 None if is_refreshing => match &self.stale_inline_completion_in_menu {
9226 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9227 stale_completion,
9228 cursor_point,
9229 style,
9230 cx,
9231 )?,
9232
9233 None => {
9234 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
9235 }
9236 },
9237
9238 None => pending_completion_container().child(Label::new("No Prediction")),
9239 };
9240
9241 let completion = if is_refreshing {
9242 completion
9243 .with_animation(
9244 "loading-completion",
9245 Animation::new(Duration::from_secs(2))
9246 .repeat()
9247 .with_easing(pulsating_between(0.4, 0.8)),
9248 |label, delta| label.opacity(delta),
9249 )
9250 .into_any_element()
9251 } else {
9252 completion.into_any_element()
9253 };
9254
9255 let has_completion = self.active_inline_completion.is_some();
9256
9257 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9258 Some(
9259 h_flex()
9260 .min_w(min_width)
9261 .max_w(max_width)
9262 .flex_1()
9263 .elevation_2(cx)
9264 .border_color(cx.theme().colors().border)
9265 .child(
9266 div()
9267 .flex_1()
9268 .py_1()
9269 .px_2()
9270 .overflow_hidden()
9271 .child(completion),
9272 )
9273 .when_some(accept_keystroke, |el, accept_keystroke| {
9274 if !accept_keystroke.modifiers.modified() {
9275 return el;
9276 }
9277
9278 el.child(
9279 h_flex()
9280 .h_full()
9281 .border_l_1()
9282 .rounded_r_lg()
9283 .border_color(cx.theme().colors().border)
9284 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9285 .gap_1()
9286 .py_1()
9287 .px_2()
9288 .child(
9289 h_flex()
9290 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9291 .when(is_platform_style_mac, |parent| parent.gap_1())
9292 .child(h_flex().children(ui::render_modifiers(
9293 &accept_keystroke.modifiers,
9294 PlatformStyle::platform(),
9295 Some(if !has_completion {
9296 Color::Muted
9297 } else {
9298 Color::Default
9299 }),
9300 None,
9301 false,
9302 ))),
9303 )
9304 .child(Label::new("Preview").into_any_element())
9305 .opacity(if has_completion { 1.0 } else { 0.4 }),
9306 )
9307 })
9308 .into_any(),
9309 )
9310 }
9311
9312 fn render_edit_prediction_cursor_popover_preview(
9313 &self,
9314 completion: &InlineCompletionState,
9315 cursor_point: Point,
9316 style: &EditorStyle,
9317 cx: &mut Context<Editor>,
9318 ) -> Option<Div> {
9319 use text::ToPoint as _;
9320
9321 fn render_relative_row_jump(
9322 prefix: impl Into<String>,
9323 current_row: u32,
9324 target_row: u32,
9325 ) -> Div {
9326 let (row_diff, arrow) = if target_row < current_row {
9327 (current_row - target_row, IconName::ArrowUp)
9328 } else {
9329 (target_row - current_row, IconName::ArrowDown)
9330 };
9331
9332 h_flex()
9333 .child(
9334 Label::new(format!("{}{}", prefix.into(), row_diff))
9335 .color(Color::Muted)
9336 .size(LabelSize::Small),
9337 )
9338 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9339 }
9340
9341 match &completion.completion {
9342 InlineCompletion::Move {
9343 target, snapshot, ..
9344 } => Some(
9345 h_flex()
9346 .px_2()
9347 .gap_2()
9348 .flex_1()
9349 .child(
9350 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
9351 Icon::new(IconName::ZedPredictDown)
9352 } else {
9353 Icon::new(IconName::ZedPredictUp)
9354 },
9355 )
9356 .child(Label::new("Jump to Edit")),
9357 ),
9358
9359 InlineCompletion::Edit {
9360 edits,
9361 edit_preview,
9362 snapshot,
9363 display_mode: _,
9364 } => {
9365 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
9366
9367 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
9368 &snapshot,
9369 &edits,
9370 edit_preview.as_ref()?,
9371 true,
9372 cx,
9373 )
9374 .first_line_preview();
9375
9376 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9377 .with_default_highlights(&style.text, highlighted_edits.highlights);
9378
9379 let preview = h_flex()
9380 .gap_1()
9381 .min_w_16()
9382 .child(styled_text)
9383 .when(has_more_lines, |parent| parent.child("…"));
9384
9385 let left = if first_edit_row != cursor_point.row {
9386 render_relative_row_jump("", cursor_point.row, first_edit_row)
9387 .into_any_element()
9388 } else {
9389 Icon::new(IconName::ZedPredict).into_any_element()
9390 };
9391
9392 Some(
9393 h_flex()
9394 .h_full()
9395 .flex_1()
9396 .gap_2()
9397 .pr_1()
9398 .overflow_x_hidden()
9399 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9400 .child(left)
9401 .child(preview),
9402 )
9403 }
9404 }
9405 }
9406
9407 pub fn render_context_menu(
9408 &self,
9409 style: &EditorStyle,
9410 max_height_in_lines: u32,
9411 window: &mut Window,
9412 cx: &mut Context<Editor>,
9413 ) -> Option<AnyElement> {
9414 let menu = self.context_menu.borrow();
9415 let menu = menu.as_ref()?;
9416 if !menu.visible() {
9417 return None;
9418 };
9419 Some(menu.render(style, max_height_in_lines, window, cx))
9420 }
9421
9422 fn render_context_menu_aside(
9423 &mut self,
9424 max_size: Size<Pixels>,
9425 window: &mut Window,
9426 cx: &mut Context<Editor>,
9427 ) -> Option<AnyElement> {
9428 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9429 if menu.visible() {
9430 menu.render_aside(max_size, window, cx)
9431 } else {
9432 None
9433 }
9434 })
9435 }
9436
9437 fn hide_context_menu(
9438 &mut self,
9439 window: &mut Window,
9440 cx: &mut Context<Self>,
9441 ) -> Option<CodeContextMenu> {
9442 cx.notify();
9443 self.completion_tasks.clear();
9444 let context_menu = self.context_menu.borrow_mut().take();
9445 self.stale_inline_completion_in_menu.take();
9446 self.update_visible_inline_completion(window, cx);
9447 if let Some(CodeContextMenu::Completions(_)) = &context_menu {
9448 if let Some(completion_provider) = &self.completion_provider {
9449 completion_provider.selection_changed(None, window, cx);
9450 }
9451 }
9452 context_menu
9453 }
9454
9455 fn show_snippet_choices(
9456 &mut self,
9457 choices: &Vec<String>,
9458 selection: Range<Anchor>,
9459 cx: &mut Context<Self>,
9460 ) {
9461 let buffer_id = match (&selection.start.buffer_id, &selection.end.buffer_id) {
9462 (Some(a), Some(b)) if a == b => a,
9463 _ => {
9464 log::error!("expected anchor range to have matching buffer IDs");
9465 return;
9466 }
9467 };
9468 let multi_buffer = self.buffer().read(cx);
9469 let Some(buffer) = multi_buffer.buffer(*buffer_id) else {
9470 return;
9471 };
9472
9473 let id = post_inc(&mut self.next_completion_id);
9474 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9475 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9476 CompletionsMenu::new_snippet_choices(
9477 id,
9478 true,
9479 choices,
9480 selection,
9481 buffer,
9482 snippet_sort_order,
9483 ),
9484 ));
9485 }
9486
9487 pub fn insert_snippet(
9488 &mut self,
9489 insertion_ranges: &[Range<usize>],
9490 snippet: Snippet,
9491 window: &mut Window,
9492 cx: &mut Context<Self>,
9493 ) -> Result<()> {
9494 struct Tabstop<T> {
9495 is_end_tabstop: bool,
9496 ranges: Vec<Range<T>>,
9497 choices: Option<Vec<String>>,
9498 }
9499
9500 let tabstops = self.buffer.update(cx, |buffer, cx| {
9501 let snippet_text: Arc<str> = snippet.text.clone().into();
9502 let edits = insertion_ranges
9503 .iter()
9504 .cloned()
9505 .map(|range| (range, snippet_text.clone()));
9506 let autoindent_mode = AutoindentMode::Block {
9507 original_indent_columns: Vec::new(),
9508 };
9509 buffer.edit(edits, Some(autoindent_mode), cx);
9510
9511 let snapshot = &*buffer.read(cx);
9512 let snippet = &snippet;
9513 snippet
9514 .tabstops
9515 .iter()
9516 .map(|tabstop| {
9517 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
9518 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9519 });
9520 let mut tabstop_ranges = tabstop
9521 .ranges
9522 .iter()
9523 .flat_map(|tabstop_range| {
9524 let mut delta = 0_isize;
9525 insertion_ranges.iter().map(move |insertion_range| {
9526 let insertion_start = insertion_range.start as isize + delta;
9527 delta +=
9528 snippet.text.len() as isize - insertion_range.len() as isize;
9529
9530 let start = ((insertion_start + tabstop_range.start) as usize)
9531 .min(snapshot.len());
9532 let end = ((insertion_start + tabstop_range.end) as usize)
9533 .min(snapshot.len());
9534 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9535 })
9536 })
9537 .collect::<Vec<_>>();
9538 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9539
9540 Tabstop {
9541 is_end_tabstop,
9542 ranges: tabstop_ranges,
9543 choices: tabstop.choices.clone(),
9544 }
9545 })
9546 .collect::<Vec<_>>()
9547 });
9548 if let Some(tabstop) = tabstops.first() {
9549 self.change_selections(Default::default(), window, cx, |s| {
9550 // Reverse order so that the first range is the newest created selection.
9551 // Completions will use it and autoscroll will prioritize it.
9552 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9553 });
9554
9555 if let Some(choices) = &tabstop.choices {
9556 if let Some(selection) = tabstop.ranges.first() {
9557 self.show_snippet_choices(choices, selection.clone(), cx)
9558 }
9559 }
9560
9561 // If we're already at the last tabstop and it's at the end of the snippet,
9562 // we're done, we don't need to keep the state around.
9563 if !tabstop.is_end_tabstop {
9564 let choices = tabstops
9565 .iter()
9566 .map(|tabstop| tabstop.choices.clone())
9567 .collect();
9568
9569 let ranges = tabstops
9570 .into_iter()
9571 .map(|tabstop| tabstop.ranges)
9572 .collect::<Vec<_>>();
9573
9574 self.snippet_stack.push(SnippetState {
9575 active_index: 0,
9576 ranges,
9577 choices,
9578 });
9579 }
9580
9581 // Check whether the just-entered snippet ends with an auto-closable bracket.
9582 if self.autoclose_regions.is_empty() {
9583 let snapshot = self.buffer.read(cx).snapshot(cx);
9584 let mut all_selections = self.selections.all::<Point>(cx);
9585 for selection in &mut all_selections {
9586 let selection_head = selection.head();
9587 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9588 continue;
9589 };
9590
9591 let mut bracket_pair = None;
9592 let max_lookup_length = scope
9593 .brackets()
9594 .map(|(pair, _)| {
9595 pair.start
9596 .as_str()
9597 .chars()
9598 .count()
9599 .max(pair.end.as_str().chars().count())
9600 })
9601 .max();
9602 if let Some(max_lookup_length) = max_lookup_length {
9603 let next_text = snapshot
9604 .chars_at(selection_head)
9605 .take(max_lookup_length)
9606 .collect::<String>();
9607 let prev_text = snapshot
9608 .reversed_chars_at(selection_head)
9609 .take(max_lookup_length)
9610 .collect::<String>();
9611
9612 for (pair, enabled) in scope.brackets() {
9613 if enabled
9614 && pair.close
9615 && prev_text.starts_with(pair.start.as_str())
9616 && next_text.starts_with(pair.end.as_str())
9617 {
9618 bracket_pair = Some(pair.clone());
9619 break;
9620 }
9621 }
9622 }
9623
9624 if let Some(pair) = bracket_pair {
9625 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9626 let autoclose_enabled =
9627 self.use_autoclose && snapshot_settings.use_autoclose;
9628 if autoclose_enabled {
9629 let start = snapshot.anchor_after(selection_head);
9630 let end = snapshot.anchor_after(selection_head);
9631 self.autoclose_regions.push(AutocloseRegion {
9632 selection_id: selection.id,
9633 range: start..end,
9634 pair,
9635 });
9636 }
9637 }
9638 }
9639 }
9640 }
9641 Ok(())
9642 }
9643
9644 pub fn move_to_next_snippet_tabstop(
9645 &mut self,
9646 window: &mut Window,
9647 cx: &mut Context<Self>,
9648 ) -> bool {
9649 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9650 }
9651
9652 pub fn move_to_prev_snippet_tabstop(
9653 &mut self,
9654 window: &mut Window,
9655 cx: &mut Context<Self>,
9656 ) -> bool {
9657 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9658 }
9659
9660 pub fn move_to_snippet_tabstop(
9661 &mut self,
9662 bias: Bias,
9663 window: &mut Window,
9664 cx: &mut Context<Self>,
9665 ) -> bool {
9666 if let Some(mut snippet) = self.snippet_stack.pop() {
9667 match bias {
9668 Bias::Left => {
9669 if snippet.active_index > 0 {
9670 snippet.active_index -= 1;
9671 } else {
9672 self.snippet_stack.push(snippet);
9673 return false;
9674 }
9675 }
9676 Bias::Right => {
9677 if snippet.active_index + 1 < snippet.ranges.len() {
9678 snippet.active_index += 1;
9679 } else {
9680 self.snippet_stack.push(snippet);
9681 return false;
9682 }
9683 }
9684 }
9685 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9686 self.change_selections(Default::default(), window, cx, |s| {
9687 // Reverse order so that the first range is the newest created selection.
9688 // Completions will use it and autoscroll will prioritize it.
9689 s.select_ranges(current_ranges.iter().rev().cloned())
9690 });
9691
9692 if let Some(choices) = &snippet.choices[snippet.active_index] {
9693 if let Some(selection) = current_ranges.first() {
9694 self.show_snippet_choices(&choices, selection.clone(), cx);
9695 }
9696 }
9697
9698 // If snippet state is not at the last tabstop, push it back on the stack
9699 if snippet.active_index + 1 < snippet.ranges.len() {
9700 self.snippet_stack.push(snippet);
9701 }
9702 return true;
9703 }
9704 }
9705
9706 false
9707 }
9708
9709 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9710 self.transact(window, cx, |this, window, cx| {
9711 this.select_all(&SelectAll, window, cx);
9712 this.insert("", window, cx);
9713 });
9714 }
9715
9716 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9717 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9718 self.transact(window, cx, |this, window, cx| {
9719 this.select_autoclose_pair(window, cx);
9720 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9721 if !this.linked_edit_ranges.is_empty() {
9722 let selections = this.selections.all::<MultiBufferPoint>(cx);
9723 let snapshot = this.buffer.read(cx).snapshot(cx);
9724
9725 for selection in selections.iter() {
9726 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9727 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9728 if selection_start.buffer_id != selection_end.buffer_id {
9729 continue;
9730 }
9731 if let Some(ranges) =
9732 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9733 {
9734 for (buffer, entries) in ranges {
9735 linked_ranges.entry(buffer).or_default().extend(entries);
9736 }
9737 }
9738 }
9739 }
9740
9741 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
9742 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9743 for selection in &mut selections {
9744 if selection.is_empty() {
9745 let old_head = selection.head();
9746 let mut new_head =
9747 movement::left(&display_map, old_head.to_display_point(&display_map))
9748 .to_point(&display_map);
9749 if let Some((buffer, line_buffer_range)) = display_map
9750 .buffer_snapshot
9751 .buffer_line_for_row(MultiBufferRow(old_head.row))
9752 {
9753 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9754 let indent_len = match indent_size.kind {
9755 IndentKind::Space => {
9756 buffer.settings_at(line_buffer_range.start, cx).tab_size
9757 }
9758 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9759 };
9760 if old_head.column <= indent_size.len && old_head.column > 0 {
9761 let indent_len = indent_len.get();
9762 new_head = cmp::min(
9763 new_head,
9764 MultiBufferPoint::new(
9765 old_head.row,
9766 ((old_head.column - 1) / indent_len) * indent_len,
9767 ),
9768 );
9769 }
9770 }
9771
9772 selection.set_head(new_head, SelectionGoal::None);
9773 }
9774 }
9775
9776 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9777 this.insert("", window, cx);
9778 let empty_str: Arc<str> = Arc::from("");
9779 for (buffer, edits) in linked_ranges {
9780 let snapshot = buffer.read(cx).snapshot();
9781 use text::ToPoint as TP;
9782
9783 let edits = edits
9784 .into_iter()
9785 .map(|range| {
9786 let end_point = TP::to_point(&range.end, &snapshot);
9787 let mut start_point = TP::to_point(&range.start, &snapshot);
9788
9789 if end_point == start_point {
9790 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9791 .saturating_sub(1);
9792 start_point =
9793 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9794 };
9795
9796 (start_point..end_point, empty_str.clone())
9797 })
9798 .sorted_by_key(|(range, _)| range.start)
9799 .collect::<Vec<_>>();
9800 buffer.update(cx, |this, cx| {
9801 this.edit(edits, None, cx);
9802 })
9803 }
9804 this.refresh_inline_completion(true, false, window, cx);
9805 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
9806 });
9807 }
9808
9809 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9810 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9811 self.transact(window, cx, |this, window, cx| {
9812 this.change_selections(Default::default(), window, cx, |s| {
9813 s.move_with(|map, selection| {
9814 if selection.is_empty() {
9815 let cursor = movement::right(map, selection.head());
9816 selection.end = cursor;
9817 selection.reversed = true;
9818 selection.goal = SelectionGoal::None;
9819 }
9820 })
9821 });
9822 this.insert("", window, cx);
9823 this.refresh_inline_completion(true, false, window, cx);
9824 });
9825 }
9826
9827 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9828 if self.mode.is_single_line() {
9829 cx.propagate();
9830 return;
9831 }
9832
9833 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9834 if self.move_to_prev_snippet_tabstop(window, cx) {
9835 return;
9836 }
9837 self.outdent(&Outdent, window, cx);
9838 }
9839
9840 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9841 if self.mode.is_single_line() {
9842 cx.propagate();
9843 return;
9844 }
9845
9846 if self.move_to_next_snippet_tabstop(window, cx) {
9847 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9848 return;
9849 }
9850 if self.read_only(cx) {
9851 return;
9852 }
9853 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9854 let mut selections = self.selections.all_adjusted(cx);
9855 let buffer = self.buffer.read(cx);
9856 let snapshot = buffer.snapshot(cx);
9857 let rows_iter = selections.iter().map(|s| s.head().row);
9858 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9859
9860 let has_some_cursor_in_whitespace = selections
9861 .iter()
9862 .filter(|selection| selection.is_empty())
9863 .any(|selection| {
9864 let cursor = selection.head();
9865 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9866 cursor.column < current_indent.len
9867 });
9868
9869 let mut edits = Vec::new();
9870 let mut prev_edited_row = 0;
9871 let mut row_delta = 0;
9872 for selection in &mut selections {
9873 if selection.start.row != prev_edited_row {
9874 row_delta = 0;
9875 }
9876 prev_edited_row = selection.end.row;
9877
9878 // If the selection is non-empty, then increase the indentation of the selected lines.
9879 if !selection.is_empty() {
9880 row_delta =
9881 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9882 continue;
9883 }
9884
9885 let cursor = selection.head();
9886 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9887 if let Some(suggested_indent) =
9888 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
9889 {
9890 // Don't do anything if already at suggested indent
9891 // and there is any other cursor which is not
9892 if has_some_cursor_in_whitespace
9893 && cursor.column == current_indent.len
9894 && current_indent.len == suggested_indent.len
9895 {
9896 continue;
9897 }
9898
9899 // Adjust line and move cursor to suggested indent
9900 // if cursor is not at suggested indent
9901 if cursor.column < suggested_indent.len
9902 && cursor.column <= current_indent.len
9903 && current_indent.len <= suggested_indent.len
9904 {
9905 selection.start = Point::new(cursor.row, suggested_indent.len);
9906 selection.end = selection.start;
9907 if row_delta == 0 {
9908 edits.extend(Buffer::edit_for_indent_size_adjustment(
9909 cursor.row,
9910 current_indent,
9911 suggested_indent,
9912 ));
9913 row_delta = suggested_indent.len - current_indent.len;
9914 }
9915 continue;
9916 }
9917
9918 // If current indent is more than suggested indent
9919 // only move cursor to current indent and skip indent
9920 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
9921 selection.start = Point::new(cursor.row, current_indent.len);
9922 selection.end = selection.start;
9923 continue;
9924 }
9925 }
9926
9927 // Otherwise, insert a hard or soft tab.
9928 let settings = buffer.language_settings_at(cursor, cx);
9929 let tab_size = if settings.hard_tabs {
9930 IndentSize::tab()
9931 } else {
9932 let tab_size = settings.tab_size.get();
9933 let indent_remainder = snapshot
9934 .text_for_range(Point::new(cursor.row, 0)..cursor)
9935 .flat_map(str::chars)
9936 .fold(row_delta % tab_size, |counter: u32, c| {
9937 if c == '\t' {
9938 0
9939 } else {
9940 (counter + 1) % tab_size
9941 }
9942 });
9943
9944 let chars_to_next_tab_stop = tab_size - indent_remainder;
9945 IndentSize::spaces(chars_to_next_tab_stop)
9946 };
9947 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
9948 selection.end = selection.start;
9949 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
9950 row_delta += tab_size.len;
9951 }
9952
9953 self.transact(window, cx, |this, window, cx| {
9954 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9955 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9956 this.refresh_inline_completion(true, false, window, cx);
9957 });
9958 }
9959
9960 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
9961 if self.read_only(cx) {
9962 return;
9963 }
9964 if self.mode.is_single_line() {
9965 cx.propagate();
9966 return;
9967 }
9968
9969 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9970 let mut selections = self.selections.all::<Point>(cx);
9971 let mut prev_edited_row = 0;
9972 let mut row_delta = 0;
9973 let mut edits = Vec::new();
9974 let buffer = self.buffer.read(cx);
9975 let snapshot = buffer.snapshot(cx);
9976 for selection in &mut selections {
9977 if selection.start.row != prev_edited_row {
9978 row_delta = 0;
9979 }
9980 prev_edited_row = selection.end.row;
9981
9982 row_delta =
9983 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9984 }
9985
9986 self.transact(window, cx, |this, window, cx| {
9987 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9988 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9989 });
9990 }
9991
9992 fn indent_selection(
9993 buffer: &MultiBuffer,
9994 snapshot: &MultiBufferSnapshot,
9995 selection: &mut Selection<Point>,
9996 edits: &mut Vec<(Range<Point>, String)>,
9997 delta_for_start_row: u32,
9998 cx: &App,
9999 ) -> u32 {
10000 let settings = buffer.language_settings_at(selection.start, cx);
10001 let tab_size = settings.tab_size.get();
10002 let indent_kind = if settings.hard_tabs {
10003 IndentKind::Tab
10004 } else {
10005 IndentKind::Space
10006 };
10007 let mut start_row = selection.start.row;
10008 let mut end_row = selection.end.row + 1;
10009
10010 // If a selection ends at the beginning of a line, don't indent
10011 // that last line.
10012 if selection.end.column == 0 && selection.end.row > selection.start.row {
10013 end_row -= 1;
10014 }
10015
10016 // Avoid re-indenting a row that has already been indented by a
10017 // previous selection, but still update this selection's column
10018 // to reflect that indentation.
10019 if delta_for_start_row > 0 {
10020 start_row += 1;
10021 selection.start.column += delta_for_start_row;
10022 if selection.end.row == selection.start.row {
10023 selection.end.column += delta_for_start_row;
10024 }
10025 }
10026
10027 let mut delta_for_end_row = 0;
10028 let has_multiple_rows = start_row + 1 != end_row;
10029 for row in start_row..end_row {
10030 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10031 let indent_delta = match (current_indent.kind, indent_kind) {
10032 (IndentKind::Space, IndentKind::Space) => {
10033 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10034 IndentSize::spaces(columns_to_next_tab_stop)
10035 }
10036 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10037 (_, IndentKind::Tab) => IndentSize::tab(),
10038 };
10039
10040 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10041 0
10042 } else {
10043 selection.start.column
10044 };
10045 let row_start = Point::new(row, start);
10046 edits.push((
10047 row_start..row_start,
10048 indent_delta.chars().collect::<String>(),
10049 ));
10050
10051 // Update this selection's endpoints to reflect the indentation.
10052 if row == selection.start.row {
10053 selection.start.column += indent_delta.len;
10054 }
10055 if row == selection.end.row {
10056 selection.end.column += indent_delta.len;
10057 delta_for_end_row = indent_delta.len;
10058 }
10059 }
10060
10061 if selection.start.row == selection.end.row {
10062 delta_for_start_row + delta_for_end_row
10063 } else {
10064 delta_for_end_row
10065 }
10066 }
10067
10068 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10069 if self.read_only(cx) {
10070 return;
10071 }
10072 if self.mode.is_single_line() {
10073 cx.propagate();
10074 return;
10075 }
10076
10077 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10078 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10079 let selections = self.selections.all::<Point>(cx);
10080 let mut deletion_ranges = Vec::new();
10081 let mut last_outdent = None;
10082 {
10083 let buffer = self.buffer.read(cx);
10084 let snapshot = buffer.snapshot(cx);
10085 for selection in &selections {
10086 let settings = buffer.language_settings_at(selection.start, cx);
10087 let tab_size = settings.tab_size.get();
10088 let mut rows = selection.spanned_rows(false, &display_map);
10089
10090 // Avoid re-outdenting a row that has already been outdented by a
10091 // previous selection.
10092 if let Some(last_row) = last_outdent {
10093 if last_row == rows.start {
10094 rows.start = rows.start.next_row();
10095 }
10096 }
10097 let has_multiple_rows = rows.len() > 1;
10098 for row in rows.iter_rows() {
10099 let indent_size = snapshot.indent_size_for_line(row);
10100 if indent_size.len > 0 {
10101 let deletion_len = match indent_size.kind {
10102 IndentKind::Space => {
10103 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10104 if columns_to_prev_tab_stop == 0 {
10105 tab_size
10106 } else {
10107 columns_to_prev_tab_stop
10108 }
10109 }
10110 IndentKind::Tab => 1,
10111 };
10112 let start = if has_multiple_rows
10113 || deletion_len > selection.start.column
10114 || indent_size.len < selection.start.column
10115 {
10116 0
10117 } else {
10118 selection.start.column - deletion_len
10119 };
10120 deletion_ranges.push(
10121 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10122 );
10123 last_outdent = Some(row);
10124 }
10125 }
10126 }
10127 }
10128
10129 self.transact(window, cx, |this, window, cx| {
10130 this.buffer.update(cx, |buffer, cx| {
10131 let empty_str: Arc<str> = Arc::default();
10132 buffer.edit(
10133 deletion_ranges
10134 .into_iter()
10135 .map(|range| (range, empty_str.clone())),
10136 None,
10137 cx,
10138 );
10139 });
10140 let selections = this.selections.all::<usize>(cx);
10141 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10142 });
10143 }
10144
10145 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10146 if self.read_only(cx) {
10147 return;
10148 }
10149 if self.mode.is_single_line() {
10150 cx.propagate();
10151 return;
10152 }
10153
10154 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10155 let selections = self
10156 .selections
10157 .all::<usize>(cx)
10158 .into_iter()
10159 .map(|s| s.range());
10160
10161 self.transact(window, cx, |this, window, cx| {
10162 this.buffer.update(cx, |buffer, cx| {
10163 buffer.autoindent_ranges(selections, cx);
10164 });
10165 let selections = this.selections.all::<usize>(cx);
10166 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10167 });
10168 }
10169
10170 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10171 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10172 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10173 let selections = self.selections.all::<Point>(cx);
10174
10175 let mut new_cursors = Vec::new();
10176 let mut edit_ranges = Vec::new();
10177 let mut selections = selections.iter().peekable();
10178 while let Some(selection) = selections.next() {
10179 let mut rows = selection.spanned_rows(false, &display_map);
10180 let goal_display_column = selection.head().to_display_point(&display_map).column();
10181
10182 // Accumulate contiguous regions of rows that we want to delete.
10183 while let Some(next_selection) = selections.peek() {
10184 let next_rows = next_selection.spanned_rows(false, &display_map);
10185 if next_rows.start <= rows.end {
10186 rows.end = next_rows.end;
10187 selections.next().unwrap();
10188 } else {
10189 break;
10190 }
10191 }
10192
10193 let buffer = &display_map.buffer_snapshot;
10194 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
10195 let edit_end;
10196 let cursor_buffer_row;
10197 if buffer.max_point().row >= rows.end.0 {
10198 // If there's a line after the range, delete the \n from the end of the row range
10199 // and position the cursor on the next line.
10200 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
10201 cursor_buffer_row = rows.end;
10202 } else {
10203 // If there isn't a line after the range, delete the \n from the line before the
10204 // start of the row range and position the cursor there.
10205 edit_start = edit_start.saturating_sub(1);
10206 edit_end = buffer.len();
10207 cursor_buffer_row = rows.start.previous_row();
10208 }
10209
10210 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
10211 *cursor.column_mut() =
10212 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
10213
10214 new_cursors.push((
10215 selection.id,
10216 buffer.anchor_after(cursor.to_point(&display_map)),
10217 ));
10218 edit_ranges.push(edit_start..edit_end);
10219 }
10220
10221 self.transact(window, cx, |this, window, cx| {
10222 let buffer = this.buffer.update(cx, |buffer, cx| {
10223 let empty_str: Arc<str> = Arc::default();
10224 buffer.edit(
10225 edit_ranges
10226 .into_iter()
10227 .map(|range| (range, empty_str.clone())),
10228 None,
10229 cx,
10230 );
10231 buffer.snapshot(cx)
10232 });
10233 let new_selections = new_cursors
10234 .into_iter()
10235 .map(|(id, cursor)| {
10236 let cursor = cursor.to_point(&buffer);
10237 Selection {
10238 id,
10239 start: cursor,
10240 end: cursor,
10241 reversed: false,
10242 goal: SelectionGoal::None,
10243 }
10244 })
10245 .collect();
10246
10247 this.change_selections(Default::default(), window, cx, |s| {
10248 s.select(new_selections);
10249 });
10250 });
10251 }
10252
10253 pub fn join_lines_impl(
10254 &mut self,
10255 insert_whitespace: bool,
10256 window: &mut Window,
10257 cx: &mut Context<Self>,
10258 ) {
10259 if self.read_only(cx) {
10260 return;
10261 }
10262 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10263 for selection in self.selections.all::<Point>(cx) {
10264 let start = MultiBufferRow(selection.start.row);
10265 // Treat single line selections as if they include the next line. Otherwise this action
10266 // would do nothing for single line selections individual cursors.
10267 let end = if selection.start.row == selection.end.row {
10268 MultiBufferRow(selection.start.row + 1)
10269 } else {
10270 MultiBufferRow(selection.end.row)
10271 };
10272
10273 if let Some(last_row_range) = row_ranges.last_mut() {
10274 if start <= last_row_range.end {
10275 last_row_range.end = end;
10276 continue;
10277 }
10278 }
10279 row_ranges.push(start..end);
10280 }
10281
10282 let snapshot = self.buffer.read(cx).snapshot(cx);
10283 let mut cursor_positions = Vec::new();
10284 for row_range in &row_ranges {
10285 let anchor = snapshot.anchor_before(Point::new(
10286 row_range.end.previous_row().0,
10287 snapshot.line_len(row_range.end.previous_row()),
10288 ));
10289 cursor_positions.push(anchor..anchor);
10290 }
10291
10292 self.transact(window, cx, |this, window, cx| {
10293 for row_range in row_ranges.into_iter().rev() {
10294 for row in row_range.iter_rows().rev() {
10295 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10296 let next_line_row = row.next_row();
10297 let indent = snapshot.indent_size_for_line(next_line_row);
10298 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10299
10300 let replace =
10301 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10302 " "
10303 } else {
10304 ""
10305 };
10306
10307 this.buffer.update(cx, |buffer, cx| {
10308 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10309 });
10310 }
10311 }
10312
10313 this.change_selections(Default::default(), window, cx, |s| {
10314 s.select_anchor_ranges(cursor_positions)
10315 });
10316 });
10317 }
10318
10319 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10320 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10321 self.join_lines_impl(true, window, cx);
10322 }
10323
10324 pub fn sort_lines_case_sensitive(
10325 &mut self,
10326 _: &SortLinesCaseSensitive,
10327 window: &mut Window,
10328 cx: &mut Context<Self>,
10329 ) {
10330 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10331 }
10332
10333 pub fn sort_lines_by_length(
10334 &mut self,
10335 _: &SortLinesByLength,
10336 window: &mut Window,
10337 cx: &mut Context<Self>,
10338 ) {
10339 self.manipulate_immutable_lines(window, cx, |lines| {
10340 lines.sort_by_key(|&line| line.chars().count())
10341 })
10342 }
10343
10344 pub fn sort_lines_case_insensitive(
10345 &mut self,
10346 _: &SortLinesCaseInsensitive,
10347 window: &mut Window,
10348 cx: &mut Context<Self>,
10349 ) {
10350 self.manipulate_immutable_lines(window, cx, |lines| {
10351 lines.sort_by_key(|line| line.to_lowercase())
10352 })
10353 }
10354
10355 pub fn unique_lines_case_insensitive(
10356 &mut self,
10357 _: &UniqueLinesCaseInsensitive,
10358 window: &mut Window,
10359 cx: &mut Context<Self>,
10360 ) {
10361 self.manipulate_immutable_lines(window, cx, |lines| {
10362 let mut seen = HashSet::default();
10363 lines.retain(|line| seen.insert(line.to_lowercase()));
10364 })
10365 }
10366
10367 pub fn unique_lines_case_sensitive(
10368 &mut self,
10369 _: &UniqueLinesCaseSensitive,
10370 window: &mut Window,
10371 cx: &mut Context<Self>,
10372 ) {
10373 self.manipulate_immutable_lines(window, cx, |lines| {
10374 let mut seen = HashSet::default();
10375 lines.retain(|line| seen.insert(*line));
10376 })
10377 }
10378
10379 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10380 let Some(project) = self.project.clone() else {
10381 return;
10382 };
10383 self.reload(project, window, cx)
10384 .detach_and_notify_err(window, cx);
10385 }
10386
10387 pub fn restore_file(
10388 &mut self,
10389 _: &::git::RestoreFile,
10390 window: &mut Window,
10391 cx: &mut Context<Self>,
10392 ) {
10393 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10394 let mut buffer_ids = HashSet::default();
10395 let snapshot = self.buffer().read(cx).snapshot(cx);
10396 for selection in self.selections.all::<usize>(cx) {
10397 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10398 }
10399
10400 let buffer = self.buffer().read(cx);
10401 let ranges = buffer_ids
10402 .into_iter()
10403 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10404 .collect::<Vec<_>>();
10405
10406 self.restore_hunks_in_ranges(ranges, window, cx);
10407 }
10408
10409 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10410 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10411 let selections = self
10412 .selections
10413 .all(cx)
10414 .into_iter()
10415 .map(|s| s.range())
10416 .collect();
10417 self.restore_hunks_in_ranges(selections, window, cx);
10418 }
10419
10420 pub fn restore_hunks_in_ranges(
10421 &mut self,
10422 ranges: Vec<Range<Point>>,
10423 window: &mut Window,
10424 cx: &mut Context<Editor>,
10425 ) {
10426 let mut revert_changes = HashMap::default();
10427 let chunk_by = self
10428 .snapshot(window, cx)
10429 .hunks_for_ranges(ranges)
10430 .into_iter()
10431 .chunk_by(|hunk| hunk.buffer_id);
10432 for (buffer_id, hunks) in &chunk_by {
10433 let hunks = hunks.collect::<Vec<_>>();
10434 for hunk in &hunks {
10435 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10436 }
10437 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10438 }
10439 drop(chunk_by);
10440 if !revert_changes.is_empty() {
10441 self.transact(window, cx, |editor, window, cx| {
10442 editor.restore(revert_changes, window, cx);
10443 });
10444 }
10445 }
10446
10447 pub fn open_active_item_in_terminal(
10448 &mut self,
10449 _: &OpenInTerminal,
10450 window: &mut Window,
10451 cx: &mut Context<Self>,
10452 ) {
10453 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10454 let project_path = buffer.read(cx).project_path(cx)?;
10455 let project = self.project.as_ref()?.read(cx);
10456 let entry = project.entry_for_path(&project_path, cx)?;
10457 let parent = match &entry.canonical_path {
10458 Some(canonical_path) => canonical_path.to_path_buf(),
10459 None => project.absolute_path(&project_path, cx)?,
10460 }
10461 .parent()?
10462 .to_path_buf();
10463 Some(parent)
10464 }) {
10465 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10466 }
10467 }
10468
10469 fn set_breakpoint_context_menu(
10470 &mut self,
10471 display_row: DisplayRow,
10472 position: Option<Anchor>,
10473 clicked_point: gpui::Point<Pixels>,
10474 window: &mut Window,
10475 cx: &mut Context<Self>,
10476 ) {
10477 let source = self
10478 .buffer
10479 .read(cx)
10480 .snapshot(cx)
10481 .anchor_before(Point::new(display_row.0, 0u32));
10482
10483 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10484
10485 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10486 self,
10487 source,
10488 clicked_point,
10489 context_menu,
10490 window,
10491 cx,
10492 );
10493 }
10494
10495 fn add_edit_breakpoint_block(
10496 &mut self,
10497 anchor: Anchor,
10498 breakpoint: &Breakpoint,
10499 edit_action: BreakpointPromptEditAction,
10500 window: &mut Window,
10501 cx: &mut Context<Self>,
10502 ) {
10503 let weak_editor = cx.weak_entity();
10504 let bp_prompt = cx.new(|cx| {
10505 BreakpointPromptEditor::new(
10506 weak_editor,
10507 anchor,
10508 breakpoint.clone(),
10509 edit_action,
10510 window,
10511 cx,
10512 )
10513 });
10514
10515 let height = bp_prompt.update(cx, |this, cx| {
10516 this.prompt
10517 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10518 });
10519 let cloned_prompt = bp_prompt.clone();
10520 let blocks = vec![BlockProperties {
10521 style: BlockStyle::Sticky,
10522 placement: BlockPlacement::Above(anchor),
10523 height: Some(height),
10524 render: Arc::new(move |cx| {
10525 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10526 cloned_prompt.clone().into_any_element()
10527 }),
10528 priority: 0,
10529 }];
10530
10531 let focus_handle = bp_prompt.focus_handle(cx);
10532 window.focus(&focus_handle);
10533
10534 let block_ids = self.insert_blocks(blocks, None, cx);
10535 bp_prompt.update(cx, |prompt, _| {
10536 prompt.add_block_ids(block_ids);
10537 });
10538 }
10539
10540 pub(crate) fn breakpoint_at_row(
10541 &self,
10542 row: u32,
10543 window: &mut Window,
10544 cx: &mut Context<Self>,
10545 ) -> Option<(Anchor, Breakpoint)> {
10546 let snapshot = self.snapshot(window, cx);
10547 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
10548
10549 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10550 }
10551
10552 pub(crate) fn breakpoint_at_anchor(
10553 &self,
10554 breakpoint_position: Anchor,
10555 snapshot: &EditorSnapshot,
10556 cx: &mut Context<Self>,
10557 ) -> Option<(Anchor, Breakpoint)> {
10558 let project = self.project.clone()?;
10559
10560 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
10561 snapshot
10562 .buffer_snapshot
10563 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
10564 })?;
10565
10566 let enclosing_excerpt = breakpoint_position.excerpt_id;
10567 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
10568 let buffer_snapshot = buffer.read(cx).snapshot();
10569
10570 let row = buffer_snapshot
10571 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10572 .row;
10573
10574 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
10575 let anchor_end = snapshot
10576 .buffer_snapshot
10577 .anchor_after(Point::new(row, line_len));
10578
10579 let bp = self
10580 .breakpoint_store
10581 .as_ref()?
10582 .read_with(cx, |breakpoint_store, cx| {
10583 breakpoint_store
10584 .breakpoints(
10585 &buffer,
10586 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10587 &buffer_snapshot,
10588 cx,
10589 )
10590 .next()
10591 .and_then(|(bp, _)| {
10592 let breakpoint_row = buffer_snapshot
10593 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10594 .row;
10595
10596 if breakpoint_row == row {
10597 snapshot
10598 .buffer_snapshot
10599 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10600 .map(|position| (position, bp.bp.clone()))
10601 } else {
10602 None
10603 }
10604 })
10605 });
10606 bp
10607 }
10608
10609 pub fn edit_log_breakpoint(
10610 &mut self,
10611 _: &EditLogBreakpoint,
10612 window: &mut Window,
10613 cx: &mut Context<Self>,
10614 ) {
10615 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10616 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10617 message: None,
10618 state: BreakpointState::Enabled,
10619 condition: None,
10620 hit_condition: None,
10621 });
10622
10623 self.add_edit_breakpoint_block(
10624 anchor,
10625 &breakpoint,
10626 BreakpointPromptEditAction::Log,
10627 window,
10628 cx,
10629 );
10630 }
10631 }
10632
10633 fn breakpoints_at_cursors(
10634 &self,
10635 window: &mut Window,
10636 cx: &mut Context<Self>,
10637 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10638 let snapshot = self.snapshot(window, cx);
10639 let cursors = self
10640 .selections
10641 .disjoint_anchors()
10642 .into_iter()
10643 .map(|selection| {
10644 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
10645
10646 let breakpoint_position = self
10647 .breakpoint_at_row(cursor_position.row, window, cx)
10648 .map(|bp| bp.0)
10649 .unwrap_or_else(|| {
10650 snapshot
10651 .display_snapshot
10652 .buffer_snapshot
10653 .anchor_after(Point::new(cursor_position.row, 0))
10654 });
10655
10656 let breakpoint = self
10657 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10658 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10659
10660 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10661 })
10662 // 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.
10663 .collect::<HashMap<Anchor, _>>();
10664
10665 cursors.into_iter().collect()
10666 }
10667
10668 pub fn enable_breakpoint(
10669 &mut self,
10670 _: &crate::actions::EnableBreakpoint,
10671 window: &mut Window,
10672 cx: &mut Context<Self>,
10673 ) {
10674 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10675 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10676 continue;
10677 };
10678 self.edit_breakpoint_at_anchor(
10679 anchor,
10680 breakpoint,
10681 BreakpointEditAction::InvertState,
10682 cx,
10683 );
10684 }
10685 }
10686
10687 pub fn disable_breakpoint(
10688 &mut self,
10689 _: &crate::actions::DisableBreakpoint,
10690 window: &mut Window,
10691 cx: &mut Context<Self>,
10692 ) {
10693 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10694 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10695 continue;
10696 };
10697 self.edit_breakpoint_at_anchor(
10698 anchor,
10699 breakpoint,
10700 BreakpointEditAction::InvertState,
10701 cx,
10702 );
10703 }
10704 }
10705
10706 pub fn toggle_breakpoint(
10707 &mut self,
10708 _: &crate::actions::ToggleBreakpoint,
10709 window: &mut Window,
10710 cx: &mut Context<Self>,
10711 ) {
10712 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10713 if let Some(breakpoint) = breakpoint {
10714 self.edit_breakpoint_at_anchor(
10715 anchor,
10716 breakpoint,
10717 BreakpointEditAction::Toggle,
10718 cx,
10719 );
10720 } else {
10721 self.edit_breakpoint_at_anchor(
10722 anchor,
10723 Breakpoint::new_standard(),
10724 BreakpointEditAction::Toggle,
10725 cx,
10726 );
10727 }
10728 }
10729 }
10730
10731 pub fn edit_breakpoint_at_anchor(
10732 &mut self,
10733 breakpoint_position: Anchor,
10734 breakpoint: Breakpoint,
10735 edit_action: BreakpointEditAction,
10736 cx: &mut Context<Self>,
10737 ) {
10738 let Some(breakpoint_store) = &self.breakpoint_store else {
10739 return;
10740 };
10741
10742 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
10743 if breakpoint_position == Anchor::min() {
10744 self.buffer()
10745 .read(cx)
10746 .excerpt_buffer_ids()
10747 .into_iter()
10748 .next()
10749 } else {
10750 None
10751 }
10752 }) else {
10753 return;
10754 };
10755
10756 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
10757 return;
10758 };
10759
10760 breakpoint_store.update(cx, |breakpoint_store, cx| {
10761 breakpoint_store.toggle_breakpoint(
10762 buffer,
10763 BreakpointWithPosition {
10764 position: breakpoint_position.text_anchor,
10765 bp: breakpoint,
10766 },
10767 edit_action,
10768 cx,
10769 );
10770 });
10771
10772 cx.notify();
10773 }
10774
10775 #[cfg(any(test, feature = "test-support"))]
10776 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10777 self.breakpoint_store.clone()
10778 }
10779
10780 pub fn prepare_restore_change(
10781 &self,
10782 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10783 hunk: &MultiBufferDiffHunk,
10784 cx: &mut App,
10785 ) -> Option<()> {
10786 if hunk.is_created_file() {
10787 return None;
10788 }
10789 let buffer = self.buffer.read(cx);
10790 let diff = buffer.diff_for(hunk.buffer_id)?;
10791 let buffer = buffer.buffer(hunk.buffer_id)?;
10792 let buffer = buffer.read(cx);
10793 let original_text = diff
10794 .read(cx)
10795 .base_text()
10796 .as_rope()
10797 .slice(hunk.diff_base_byte_range.clone());
10798 let buffer_snapshot = buffer.snapshot();
10799 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
10800 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
10801 probe
10802 .0
10803 .start
10804 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
10805 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
10806 }) {
10807 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
10808 Some(())
10809 } else {
10810 None
10811 }
10812 }
10813
10814 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
10815 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
10816 }
10817
10818 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
10819 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
10820 }
10821
10822 fn manipulate_lines<M>(
10823 &mut self,
10824 window: &mut Window,
10825 cx: &mut Context<Self>,
10826 mut manipulate: M,
10827 ) where
10828 M: FnMut(&str) -> LineManipulationResult,
10829 {
10830 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10831
10832 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10833 let buffer = self.buffer.read(cx).snapshot(cx);
10834
10835 let mut edits = Vec::new();
10836
10837 let selections = self.selections.all::<Point>(cx);
10838 let mut selections = selections.iter().peekable();
10839 let mut contiguous_row_selections = Vec::new();
10840 let mut new_selections = Vec::new();
10841 let mut added_lines = 0;
10842 let mut removed_lines = 0;
10843
10844 while let Some(selection) = selections.next() {
10845 let (start_row, end_row) = consume_contiguous_rows(
10846 &mut contiguous_row_selections,
10847 selection,
10848 &display_map,
10849 &mut selections,
10850 );
10851
10852 let start_point = Point::new(start_row.0, 0);
10853 let end_point = Point::new(
10854 end_row.previous_row().0,
10855 buffer.line_len(end_row.previous_row()),
10856 );
10857 let text = buffer
10858 .text_for_range(start_point..end_point)
10859 .collect::<String>();
10860
10861 let LineManipulationResult {
10862 new_text,
10863 line_count_before,
10864 line_count_after,
10865 } = manipulate(&text);
10866
10867 edits.push((start_point..end_point, new_text));
10868
10869 // Selections must change based on added and removed line count
10870 let start_row =
10871 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
10872 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
10873 new_selections.push(Selection {
10874 id: selection.id,
10875 start: start_row,
10876 end: end_row,
10877 goal: SelectionGoal::None,
10878 reversed: selection.reversed,
10879 });
10880
10881 if line_count_after > line_count_before {
10882 added_lines += line_count_after - line_count_before;
10883 } else if line_count_before > line_count_after {
10884 removed_lines += line_count_before - line_count_after;
10885 }
10886 }
10887
10888 self.transact(window, cx, |this, window, cx| {
10889 let buffer = this.buffer.update(cx, |buffer, cx| {
10890 buffer.edit(edits, None, cx);
10891 buffer.snapshot(cx)
10892 });
10893
10894 // Recalculate offsets on newly edited buffer
10895 let new_selections = new_selections
10896 .iter()
10897 .map(|s| {
10898 let start_point = Point::new(s.start.0, 0);
10899 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
10900 Selection {
10901 id: s.id,
10902 start: buffer.point_to_offset(start_point),
10903 end: buffer.point_to_offset(end_point),
10904 goal: s.goal,
10905 reversed: s.reversed,
10906 }
10907 })
10908 .collect();
10909
10910 this.change_selections(Default::default(), window, cx, |s| {
10911 s.select(new_selections);
10912 });
10913
10914 this.request_autoscroll(Autoscroll::fit(), cx);
10915 });
10916 }
10917
10918 fn manipulate_immutable_lines<Fn>(
10919 &mut self,
10920 window: &mut Window,
10921 cx: &mut Context<Self>,
10922 mut callback: Fn,
10923 ) where
10924 Fn: FnMut(&mut Vec<&str>),
10925 {
10926 self.manipulate_lines(window, cx, |text| {
10927 let mut lines: Vec<&str> = text.split('\n').collect();
10928 let line_count_before = lines.len();
10929
10930 callback(&mut lines);
10931
10932 LineManipulationResult {
10933 new_text: lines.join("\n"),
10934 line_count_before,
10935 line_count_after: lines.len(),
10936 }
10937 });
10938 }
10939
10940 fn manipulate_mutable_lines<Fn>(
10941 &mut self,
10942 window: &mut Window,
10943 cx: &mut Context<Self>,
10944 mut callback: Fn,
10945 ) where
10946 Fn: FnMut(&mut Vec<Cow<'_, str>>),
10947 {
10948 self.manipulate_lines(window, cx, |text| {
10949 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
10950 let line_count_before = lines.len();
10951
10952 callback(&mut lines);
10953
10954 LineManipulationResult {
10955 new_text: lines.join("\n"),
10956 line_count_before,
10957 line_count_after: lines.len(),
10958 }
10959 });
10960 }
10961
10962 pub fn convert_indentation_to_spaces(
10963 &mut self,
10964 _: &ConvertIndentationToSpaces,
10965 window: &mut Window,
10966 cx: &mut Context<Self>,
10967 ) {
10968 let settings = self.buffer.read(cx).language_settings(cx);
10969 let tab_size = settings.tab_size.get() as usize;
10970
10971 self.manipulate_mutable_lines(window, cx, |lines| {
10972 // Allocates a reasonably sized scratch buffer once for the whole loop
10973 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
10974 // Avoids recomputing spaces that could be inserted many times
10975 let space_cache: Vec<Vec<char>> = (1..=tab_size)
10976 .map(|n| IndentSize::spaces(n as u32).chars().collect())
10977 .collect();
10978
10979 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
10980 let mut chars = line.as_ref().chars();
10981 let mut col = 0;
10982 let mut changed = false;
10983
10984 while let Some(ch) = chars.next() {
10985 match ch {
10986 ' ' => {
10987 reindented_line.push(' ');
10988 col += 1;
10989 }
10990 '\t' => {
10991 // \t are converted to spaces depending on the current column
10992 let spaces_len = tab_size - (col % tab_size);
10993 reindented_line.extend(&space_cache[spaces_len - 1]);
10994 col += spaces_len;
10995 changed = true;
10996 }
10997 _ => {
10998 // If we dont append before break, the character is consumed
10999 reindented_line.push(ch);
11000 break;
11001 }
11002 }
11003 }
11004
11005 if !changed {
11006 reindented_line.clear();
11007 continue;
11008 }
11009 // Append the rest of the line and replace old reference with new one
11010 reindented_line.extend(chars);
11011 *line = Cow::Owned(reindented_line.clone());
11012 reindented_line.clear();
11013 }
11014 });
11015 }
11016
11017 pub fn convert_indentation_to_tabs(
11018 &mut self,
11019 _: &ConvertIndentationToTabs,
11020 window: &mut Window,
11021 cx: &mut Context<Self>,
11022 ) {
11023 let settings = self.buffer.read(cx).language_settings(cx);
11024 let tab_size = settings.tab_size.get() as usize;
11025
11026 self.manipulate_mutable_lines(window, cx, |lines| {
11027 // Allocates a reasonably sized buffer once for the whole loop
11028 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11029 // Avoids recomputing spaces that could be inserted many times
11030 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11031 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11032 .collect();
11033
11034 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11035 let mut chars = line.chars();
11036 let mut spaces_count = 0;
11037 let mut first_non_indent_char = None;
11038 let mut changed = false;
11039
11040 while let Some(ch) = chars.next() {
11041 match ch {
11042 ' ' => {
11043 // Keep track of spaces. Append \t when we reach tab_size
11044 spaces_count += 1;
11045 changed = true;
11046 if spaces_count == tab_size {
11047 reindented_line.push('\t');
11048 spaces_count = 0;
11049 }
11050 }
11051 '\t' => {
11052 reindented_line.push('\t');
11053 spaces_count = 0;
11054 }
11055 _ => {
11056 // Dont append it yet, we might have remaining spaces
11057 first_non_indent_char = Some(ch);
11058 break;
11059 }
11060 }
11061 }
11062
11063 if !changed {
11064 reindented_line.clear();
11065 continue;
11066 }
11067 // Remaining spaces that didn't make a full tab stop
11068 if spaces_count > 0 {
11069 reindented_line.extend(&space_cache[spaces_count - 1]);
11070 }
11071 // If we consume an extra character that was not indentation, add it back
11072 if let Some(extra_char) = first_non_indent_char {
11073 reindented_line.push(extra_char);
11074 }
11075 // Append the rest of the line and replace old reference with new one
11076 reindented_line.extend(chars);
11077 *line = Cow::Owned(reindented_line.clone());
11078 reindented_line.clear();
11079 }
11080 });
11081 }
11082
11083 pub fn convert_to_upper_case(
11084 &mut self,
11085 _: &ConvertToUpperCase,
11086 window: &mut Window,
11087 cx: &mut Context<Self>,
11088 ) {
11089 self.manipulate_text(window, cx, |text| text.to_uppercase())
11090 }
11091
11092 pub fn convert_to_lower_case(
11093 &mut self,
11094 _: &ConvertToLowerCase,
11095 window: &mut Window,
11096 cx: &mut Context<Self>,
11097 ) {
11098 self.manipulate_text(window, cx, |text| text.to_lowercase())
11099 }
11100
11101 pub fn convert_to_title_case(
11102 &mut self,
11103 _: &ConvertToTitleCase,
11104 window: &mut Window,
11105 cx: &mut Context<Self>,
11106 ) {
11107 self.manipulate_text(window, cx, |text| {
11108 text.split('\n')
11109 .map(|line| line.to_case(Case::Title))
11110 .join("\n")
11111 })
11112 }
11113
11114 pub fn convert_to_snake_case(
11115 &mut self,
11116 _: &ConvertToSnakeCase,
11117 window: &mut Window,
11118 cx: &mut Context<Self>,
11119 ) {
11120 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11121 }
11122
11123 pub fn convert_to_kebab_case(
11124 &mut self,
11125 _: &ConvertToKebabCase,
11126 window: &mut Window,
11127 cx: &mut Context<Self>,
11128 ) {
11129 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11130 }
11131
11132 pub fn convert_to_upper_camel_case(
11133 &mut self,
11134 _: &ConvertToUpperCamelCase,
11135 window: &mut Window,
11136 cx: &mut Context<Self>,
11137 ) {
11138 self.manipulate_text(window, cx, |text| {
11139 text.split('\n')
11140 .map(|line| line.to_case(Case::UpperCamel))
11141 .join("\n")
11142 })
11143 }
11144
11145 pub fn convert_to_lower_camel_case(
11146 &mut self,
11147 _: &ConvertToLowerCamelCase,
11148 window: &mut Window,
11149 cx: &mut Context<Self>,
11150 ) {
11151 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11152 }
11153
11154 pub fn convert_to_opposite_case(
11155 &mut self,
11156 _: &ConvertToOppositeCase,
11157 window: &mut Window,
11158 cx: &mut Context<Self>,
11159 ) {
11160 self.manipulate_text(window, cx, |text| {
11161 text.chars()
11162 .fold(String::with_capacity(text.len()), |mut t, c| {
11163 if c.is_uppercase() {
11164 t.extend(c.to_lowercase());
11165 } else {
11166 t.extend(c.to_uppercase());
11167 }
11168 t
11169 })
11170 })
11171 }
11172
11173 pub fn convert_to_sentence_case(
11174 &mut self,
11175 _: &ConvertToSentenceCase,
11176 window: &mut Window,
11177 cx: &mut Context<Self>,
11178 ) {
11179 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11180 }
11181
11182 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11183 self.manipulate_text(window, cx, |text| {
11184 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11185 if has_upper_case_characters {
11186 text.to_lowercase()
11187 } else {
11188 text.to_uppercase()
11189 }
11190 })
11191 }
11192
11193 pub fn convert_to_rot13(
11194 &mut self,
11195 _: &ConvertToRot13,
11196 window: &mut Window,
11197 cx: &mut Context<Self>,
11198 ) {
11199 self.manipulate_text(window, cx, |text| {
11200 text.chars()
11201 .map(|c| match c {
11202 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11203 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11204 _ => c,
11205 })
11206 .collect()
11207 })
11208 }
11209
11210 pub fn convert_to_rot47(
11211 &mut self,
11212 _: &ConvertToRot47,
11213 window: &mut Window,
11214 cx: &mut Context<Self>,
11215 ) {
11216 self.manipulate_text(window, cx, |text| {
11217 text.chars()
11218 .map(|c| {
11219 let code_point = c as u32;
11220 if code_point >= 33 && code_point <= 126 {
11221 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11222 }
11223 c
11224 })
11225 .collect()
11226 })
11227 }
11228
11229 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11230 where
11231 Fn: FnMut(&str) -> String,
11232 {
11233 let buffer = self.buffer.read(cx).snapshot(cx);
11234
11235 let mut new_selections = Vec::new();
11236 let mut edits = Vec::new();
11237 let mut selection_adjustment = 0i32;
11238
11239 for selection in self.selections.all::<usize>(cx) {
11240 let selection_is_empty = selection.is_empty();
11241
11242 let (start, end) = if selection_is_empty {
11243 let (word_range, _) = buffer.surrounding_word(selection.start, false);
11244 (word_range.start, word_range.end)
11245 } else {
11246 (selection.start, selection.end)
11247 };
11248
11249 let text = buffer.text_for_range(start..end).collect::<String>();
11250 let old_length = text.len() as i32;
11251 let text = callback(&text);
11252
11253 new_selections.push(Selection {
11254 start: (start as i32 - selection_adjustment) as usize,
11255 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11256 goal: SelectionGoal::None,
11257 ..selection
11258 });
11259
11260 selection_adjustment += old_length - text.len() as i32;
11261
11262 edits.push((start..end, text));
11263 }
11264
11265 self.transact(window, cx, |this, window, cx| {
11266 this.buffer.update(cx, |buffer, cx| {
11267 buffer.edit(edits, None, cx);
11268 });
11269
11270 this.change_selections(Default::default(), window, cx, |s| {
11271 s.select(new_selections);
11272 });
11273
11274 this.request_autoscroll(Autoscroll::fit(), cx);
11275 });
11276 }
11277
11278 pub fn move_selection_on_drop(
11279 &mut self,
11280 selection: &Selection<Anchor>,
11281 target: DisplayPoint,
11282 is_cut: bool,
11283 window: &mut Window,
11284 cx: &mut Context<Self>,
11285 ) {
11286 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11287 let buffer = &display_map.buffer_snapshot;
11288 let mut edits = Vec::new();
11289 let insert_point = display_map
11290 .clip_point(target, Bias::Left)
11291 .to_point(&display_map);
11292 let text = buffer
11293 .text_for_range(selection.start..selection.end)
11294 .collect::<String>();
11295 if is_cut {
11296 edits.push(((selection.start..selection.end), String::new()));
11297 }
11298 let insert_anchor = buffer.anchor_before(insert_point);
11299 edits.push(((insert_anchor..insert_anchor), text));
11300 let last_edit_start = insert_anchor.bias_left(buffer);
11301 let last_edit_end = insert_anchor.bias_right(buffer);
11302 self.transact(window, cx, |this, window, cx| {
11303 this.buffer.update(cx, |buffer, cx| {
11304 buffer.edit(edits, None, cx);
11305 });
11306 this.change_selections(Default::default(), window, cx, |s| {
11307 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11308 });
11309 });
11310 }
11311
11312 pub fn clear_selection_drag_state(&mut self) {
11313 self.selection_drag_state = SelectionDragState::None;
11314 }
11315
11316 pub fn duplicate(
11317 &mut self,
11318 upwards: bool,
11319 whole_lines: bool,
11320 window: &mut Window,
11321 cx: &mut Context<Self>,
11322 ) {
11323 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11324
11325 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11326 let buffer = &display_map.buffer_snapshot;
11327 let selections = self.selections.all::<Point>(cx);
11328
11329 let mut edits = Vec::new();
11330 let mut selections_iter = selections.iter().peekable();
11331 while let Some(selection) = selections_iter.next() {
11332 let mut rows = selection.spanned_rows(false, &display_map);
11333 // duplicate line-wise
11334 if whole_lines || selection.start == selection.end {
11335 // Avoid duplicating the same lines twice.
11336 while let Some(next_selection) = selections_iter.peek() {
11337 let next_rows = next_selection.spanned_rows(false, &display_map);
11338 if next_rows.start < rows.end {
11339 rows.end = next_rows.end;
11340 selections_iter.next().unwrap();
11341 } else {
11342 break;
11343 }
11344 }
11345
11346 // Copy the text from the selected row region and splice it either at the start
11347 // or end of the region.
11348 let start = Point::new(rows.start.0, 0);
11349 let end = Point::new(
11350 rows.end.previous_row().0,
11351 buffer.line_len(rows.end.previous_row()),
11352 );
11353 let text = buffer
11354 .text_for_range(start..end)
11355 .chain(Some("\n"))
11356 .collect::<String>();
11357 let insert_location = if upwards {
11358 Point::new(rows.end.0, 0)
11359 } else {
11360 start
11361 };
11362 edits.push((insert_location..insert_location, text));
11363 } else {
11364 // duplicate character-wise
11365 let start = selection.start;
11366 let end = selection.end;
11367 let text = buffer.text_for_range(start..end).collect::<String>();
11368 edits.push((selection.end..selection.end, text));
11369 }
11370 }
11371
11372 self.transact(window, cx, |this, _, cx| {
11373 this.buffer.update(cx, |buffer, cx| {
11374 buffer.edit(edits, None, cx);
11375 });
11376
11377 this.request_autoscroll(Autoscroll::fit(), cx);
11378 });
11379 }
11380
11381 pub fn duplicate_line_up(
11382 &mut self,
11383 _: &DuplicateLineUp,
11384 window: &mut Window,
11385 cx: &mut Context<Self>,
11386 ) {
11387 self.duplicate(true, true, window, cx);
11388 }
11389
11390 pub fn duplicate_line_down(
11391 &mut self,
11392 _: &DuplicateLineDown,
11393 window: &mut Window,
11394 cx: &mut Context<Self>,
11395 ) {
11396 self.duplicate(false, true, window, cx);
11397 }
11398
11399 pub fn duplicate_selection(
11400 &mut self,
11401 _: &DuplicateSelection,
11402 window: &mut Window,
11403 cx: &mut Context<Self>,
11404 ) {
11405 self.duplicate(false, false, window, cx);
11406 }
11407
11408 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11409 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11410 if self.mode.is_single_line() {
11411 cx.propagate();
11412 return;
11413 }
11414
11415 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11416 let buffer = self.buffer.read(cx).snapshot(cx);
11417
11418 let mut edits = Vec::new();
11419 let mut unfold_ranges = Vec::new();
11420 let mut refold_creases = Vec::new();
11421
11422 let selections = self.selections.all::<Point>(cx);
11423 let mut selections = selections.iter().peekable();
11424 let mut contiguous_row_selections = Vec::new();
11425 let mut new_selections = Vec::new();
11426
11427 while let Some(selection) = selections.next() {
11428 // Find all the selections that span a contiguous row range
11429 let (start_row, end_row) = consume_contiguous_rows(
11430 &mut contiguous_row_selections,
11431 selection,
11432 &display_map,
11433 &mut selections,
11434 );
11435
11436 // Move the text spanned by the row range to be before the line preceding the row range
11437 if start_row.0 > 0 {
11438 let range_to_move = Point::new(
11439 start_row.previous_row().0,
11440 buffer.line_len(start_row.previous_row()),
11441 )
11442 ..Point::new(
11443 end_row.previous_row().0,
11444 buffer.line_len(end_row.previous_row()),
11445 );
11446 let insertion_point = display_map
11447 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11448 .0;
11449
11450 // Don't move lines across excerpts
11451 if buffer
11452 .excerpt_containing(insertion_point..range_to_move.end)
11453 .is_some()
11454 {
11455 let text = buffer
11456 .text_for_range(range_to_move.clone())
11457 .flat_map(|s| s.chars())
11458 .skip(1)
11459 .chain(['\n'])
11460 .collect::<String>();
11461
11462 edits.push((
11463 buffer.anchor_after(range_to_move.start)
11464 ..buffer.anchor_before(range_to_move.end),
11465 String::new(),
11466 ));
11467 let insertion_anchor = buffer.anchor_after(insertion_point);
11468 edits.push((insertion_anchor..insertion_anchor, text));
11469
11470 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11471
11472 // Move selections up
11473 new_selections.extend(contiguous_row_selections.drain(..).map(
11474 |mut selection| {
11475 selection.start.row -= row_delta;
11476 selection.end.row -= row_delta;
11477 selection
11478 },
11479 ));
11480
11481 // Move folds up
11482 unfold_ranges.push(range_to_move.clone());
11483 for fold in display_map.folds_in_range(
11484 buffer.anchor_before(range_to_move.start)
11485 ..buffer.anchor_after(range_to_move.end),
11486 ) {
11487 let mut start = fold.range.start.to_point(&buffer);
11488 let mut end = fold.range.end.to_point(&buffer);
11489 start.row -= row_delta;
11490 end.row -= row_delta;
11491 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11492 }
11493 }
11494 }
11495
11496 // If we didn't move line(s), preserve the existing selections
11497 new_selections.append(&mut contiguous_row_selections);
11498 }
11499
11500 self.transact(window, cx, |this, window, cx| {
11501 this.unfold_ranges(&unfold_ranges, true, true, cx);
11502 this.buffer.update(cx, |buffer, cx| {
11503 for (range, text) in edits {
11504 buffer.edit([(range, text)], None, cx);
11505 }
11506 });
11507 this.fold_creases(refold_creases, true, window, cx);
11508 this.change_selections(Default::default(), window, cx, |s| {
11509 s.select(new_selections);
11510 })
11511 });
11512 }
11513
11514 pub fn move_line_down(
11515 &mut self,
11516 _: &MoveLineDown,
11517 window: &mut Window,
11518 cx: &mut Context<Self>,
11519 ) {
11520 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11521 if self.mode.is_single_line() {
11522 cx.propagate();
11523 return;
11524 }
11525
11526 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11527 let buffer = self.buffer.read(cx).snapshot(cx);
11528
11529 let mut edits = Vec::new();
11530 let mut unfold_ranges = Vec::new();
11531 let mut refold_creases = Vec::new();
11532
11533 let selections = self.selections.all::<Point>(cx);
11534 let mut selections = selections.iter().peekable();
11535 let mut contiguous_row_selections = Vec::new();
11536 let mut new_selections = Vec::new();
11537
11538 while let Some(selection) = selections.next() {
11539 // Find all the selections that span a contiguous row range
11540 let (start_row, end_row) = consume_contiguous_rows(
11541 &mut contiguous_row_selections,
11542 selection,
11543 &display_map,
11544 &mut selections,
11545 );
11546
11547 // Move the text spanned by the row range to be after the last line of the row range
11548 if end_row.0 <= buffer.max_point().row {
11549 let range_to_move =
11550 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11551 let insertion_point = display_map
11552 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11553 .0;
11554
11555 // Don't move lines across excerpt boundaries
11556 if buffer
11557 .excerpt_containing(range_to_move.start..insertion_point)
11558 .is_some()
11559 {
11560 let mut text = String::from("\n");
11561 text.extend(buffer.text_for_range(range_to_move.clone()));
11562 text.pop(); // Drop trailing newline
11563 edits.push((
11564 buffer.anchor_after(range_to_move.start)
11565 ..buffer.anchor_before(range_to_move.end),
11566 String::new(),
11567 ));
11568 let insertion_anchor = buffer.anchor_after(insertion_point);
11569 edits.push((insertion_anchor..insertion_anchor, text));
11570
11571 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11572
11573 // Move selections down
11574 new_selections.extend(contiguous_row_selections.drain(..).map(
11575 |mut selection| {
11576 selection.start.row += row_delta;
11577 selection.end.row += row_delta;
11578 selection
11579 },
11580 ));
11581
11582 // Move folds down
11583 unfold_ranges.push(range_to_move.clone());
11584 for fold in display_map.folds_in_range(
11585 buffer.anchor_before(range_to_move.start)
11586 ..buffer.anchor_after(range_to_move.end),
11587 ) {
11588 let mut start = fold.range.start.to_point(&buffer);
11589 let mut end = fold.range.end.to_point(&buffer);
11590 start.row += row_delta;
11591 end.row += row_delta;
11592 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11593 }
11594 }
11595 }
11596
11597 // If we didn't move line(s), preserve the existing selections
11598 new_selections.append(&mut contiguous_row_selections);
11599 }
11600
11601 self.transact(window, cx, |this, window, cx| {
11602 this.unfold_ranges(&unfold_ranges, true, true, cx);
11603 this.buffer.update(cx, |buffer, cx| {
11604 for (range, text) in edits {
11605 buffer.edit([(range, text)], None, cx);
11606 }
11607 });
11608 this.fold_creases(refold_creases, true, window, cx);
11609 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
11610 });
11611 }
11612
11613 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11614 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11615 let text_layout_details = &self.text_layout_details(window);
11616 self.transact(window, cx, |this, window, cx| {
11617 let edits = this.change_selections(Default::default(), window, cx, |s| {
11618 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11619 s.move_with(|display_map, selection| {
11620 if !selection.is_empty() {
11621 return;
11622 }
11623
11624 let mut head = selection.head();
11625 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11626 if head.column() == display_map.line_len(head.row()) {
11627 transpose_offset = display_map
11628 .buffer_snapshot
11629 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11630 }
11631
11632 if transpose_offset == 0 {
11633 return;
11634 }
11635
11636 *head.column_mut() += 1;
11637 head = display_map.clip_point(head, Bias::Right);
11638 let goal = SelectionGoal::HorizontalPosition(
11639 display_map
11640 .x_for_display_point(head, text_layout_details)
11641 .into(),
11642 );
11643 selection.collapse_to(head, goal);
11644
11645 let transpose_start = display_map
11646 .buffer_snapshot
11647 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11648 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
11649 let transpose_end = display_map
11650 .buffer_snapshot
11651 .clip_offset(transpose_offset + 1, Bias::Right);
11652 if let Some(ch) =
11653 display_map.buffer_snapshot.chars_at(transpose_start).next()
11654 {
11655 edits.push((transpose_start..transpose_offset, String::new()));
11656 edits.push((transpose_end..transpose_end, ch.to_string()));
11657 }
11658 }
11659 });
11660 edits
11661 });
11662 this.buffer
11663 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11664 let selections = this.selections.all::<usize>(cx);
11665 this.change_selections(Default::default(), window, cx, |s| {
11666 s.select(selections);
11667 });
11668 });
11669 }
11670
11671 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
11672 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11673 if self.mode.is_single_line() {
11674 cx.propagate();
11675 return;
11676 }
11677
11678 self.rewrap_impl(RewrapOptions::default(), cx)
11679 }
11680
11681 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
11682 let buffer = self.buffer.read(cx).snapshot(cx);
11683 let selections = self.selections.all::<Point>(cx);
11684
11685 // Split selections to respect paragraph, indent, and comment prefix boundaries.
11686 let wrap_ranges = selections.into_iter().flat_map(|selection| {
11687 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
11688 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11689 .peekable();
11690
11691 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
11692 row
11693 } else {
11694 return Vec::new();
11695 };
11696
11697 let language_settings = buffer.language_settings_at(selection.head(), cx);
11698 let language_scope = buffer.language_scope_at(selection.head());
11699
11700 let indent_and_prefix_for_row =
11701 |row: u32| -> (IndentSize, Option<String>, Option<String>) {
11702 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
11703 let (comment_prefix, rewrap_prefix) =
11704 if let Some(language_scope) = &language_scope {
11705 let indent_end = Point::new(row, indent.len);
11706 let comment_prefix = language_scope
11707 .line_comment_prefixes()
11708 .iter()
11709 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11710 .map(|prefix| prefix.to_string());
11711 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11712 let line_text_after_indent = buffer
11713 .text_for_range(indent_end..line_end)
11714 .collect::<String>();
11715 let rewrap_prefix = language_scope
11716 .rewrap_prefixes()
11717 .iter()
11718 .find_map(|prefix_regex| {
11719 prefix_regex.find(&line_text_after_indent).map(|mat| {
11720 if mat.start() == 0 {
11721 Some(mat.as_str().to_string())
11722 } else {
11723 None
11724 }
11725 })
11726 })
11727 .flatten();
11728 (comment_prefix, rewrap_prefix)
11729 } else {
11730 (None, None)
11731 };
11732 (indent, comment_prefix, rewrap_prefix)
11733 };
11734
11735 let mut ranges = Vec::new();
11736 let from_empty_selection = selection.is_empty();
11737
11738 let mut current_range_start = first_row;
11739 let mut prev_row = first_row;
11740 let (
11741 mut current_range_indent,
11742 mut current_range_comment_prefix,
11743 mut current_range_rewrap_prefix,
11744 ) = indent_and_prefix_for_row(first_row);
11745
11746 for row in non_blank_rows_iter.skip(1) {
11747 let has_paragraph_break = row > prev_row + 1;
11748
11749 let (row_indent, row_comment_prefix, row_rewrap_prefix) =
11750 indent_and_prefix_for_row(row);
11751
11752 let has_indent_change = row_indent != current_range_indent;
11753 let has_comment_change = row_comment_prefix != current_range_comment_prefix;
11754
11755 let has_boundary_change = has_comment_change
11756 || row_rewrap_prefix.is_some()
11757 || (has_indent_change && current_range_comment_prefix.is_some());
11758
11759 if has_paragraph_break || has_boundary_change {
11760 ranges.push((
11761 language_settings.clone(),
11762 Point::new(current_range_start, 0)
11763 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
11764 current_range_indent,
11765 current_range_comment_prefix.clone(),
11766 current_range_rewrap_prefix.clone(),
11767 from_empty_selection,
11768 ));
11769 current_range_start = row;
11770 current_range_indent = row_indent;
11771 current_range_comment_prefix = row_comment_prefix;
11772 current_range_rewrap_prefix = row_rewrap_prefix;
11773 }
11774 prev_row = row;
11775 }
11776
11777 ranges.push((
11778 language_settings.clone(),
11779 Point::new(current_range_start, 0)
11780 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
11781 current_range_indent,
11782 current_range_comment_prefix,
11783 current_range_rewrap_prefix,
11784 from_empty_selection,
11785 ));
11786
11787 ranges
11788 });
11789
11790 let mut edits = Vec::new();
11791 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
11792
11793 for (
11794 language_settings,
11795 wrap_range,
11796 indent_size,
11797 comment_prefix,
11798 rewrap_prefix,
11799 from_empty_selection,
11800 ) in wrap_ranges
11801 {
11802 let mut start_row = wrap_range.start.row;
11803 let mut end_row = wrap_range.end.row;
11804
11805 // Skip selections that overlap with a range that has already been rewrapped.
11806 let selection_range = start_row..end_row;
11807 if rewrapped_row_ranges
11808 .iter()
11809 .any(|range| range.overlaps(&selection_range))
11810 {
11811 continue;
11812 }
11813
11814 let tab_size = language_settings.tab_size;
11815
11816 let indent_prefix = indent_size.chars().collect::<String>();
11817 let mut line_prefix = indent_prefix.clone();
11818 let mut inside_comment = false;
11819 if let Some(prefix) = &comment_prefix {
11820 line_prefix.push_str(prefix);
11821 inside_comment = true;
11822 }
11823 if let Some(prefix) = &rewrap_prefix {
11824 line_prefix.push_str(prefix);
11825 }
11826
11827 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
11828 RewrapBehavior::InComments => inside_comment,
11829 RewrapBehavior::InSelections => !wrap_range.is_empty(),
11830 RewrapBehavior::Anywhere => true,
11831 };
11832
11833 let should_rewrap = options.override_language_settings
11834 || allow_rewrap_based_on_language
11835 || self.hard_wrap.is_some();
11836 if !should_rewrap {
11837 continue;
11838 }
11839
11840 if from_empty_selection {
11841 'expand_upwards: while start_row > 0 {
11842 let prev_row = start_row - 1;
11843 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
11844 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
11845 && !buffer.is_line_blank(MultiBufferRow(prev_row))
11846 {
11847 start_row = prev_row;
11848 } else {
11849 break 'expand_upwards;
11850 }
11851 }
11852
11853 'expand_downwards: while end_row < buffer.max_point().row {
11854 let next_row = end_row + 1;
11855 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
11856 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
11857 && !buffer.is_line_blank(MultiBufferRow(next_row))
11858 {
11859 end_row = next_row;
11860 } else {
11861 break 'expand_downwards;
11862 }
11863 }
11864 }
11865
11866 let start = Point::new(start_row, 0);
11867 let start_offset = start.to_offset(&buffer);
11868 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
11869 let selection_text = buffer.text_for_range(start..end).collect::<String>();
11870 let Some(lines_without_prefixes) = selection_text
11871 .lines()
11872 .enumerate()
11873 .map(|(ix, line)| {
11874 let line_trimmed = line.trim_start();
11875 if rewrap_prefix.is_some() && ix > 0 {
11876 Ok(line_trimmed)
11877 } else {
11878 line_trimmed
11879 .strip_prefix(&line_prefix.trim_start())
11880 .with_context(|| {
11881 format!("line did not start with prefix {line_prefix:?}: {line:?}")
11882 })
11883 }
11884 })
11885 .collect::<Result<Vec<_>, _>>()
11886 .log_err()
11887 else {
11888 continue;
11889 };
11890
11891 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
11892 buffer
11893 .language_settings_at(Point::new(start_row, 0), cx)
11894 .preferred_line_length as usize
11895 });
11896
11897 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
11898 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
11899 } else {
11900 line_prefix.clone()
11901 };
11902
11903 let wrapped_text = wrap_with_prefix(
11904 line_prefix,
11905 subsequent_lines_prefix,
11906 lines_without_prefixes.join("\n"),
11907 wrap_column,
11908 tab_size,
11909 options.preserve_existing_whitespace,
11910 );
11911
11912 // TODO: should always use char-based diff while still supporting cursor behavior that
11913 // matches vim.
11914 let mut diff_options = DiffOptions::default();
11915 if options.override_language_settings {
11916 diff_options.max_word_diff_len = 0;
11917 diff_options.max_word_diff_line_count = 0;
11918 } else {
11919 diff_options.max_word_diff_len = usize::MAX;
11920 diff_options.max_word_diff_line_count = usize::MAX;
11921 }
11922
11923 for (old_range, new_text) in
11924 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
11925 {
11926 let edit_start = buffer.anchor_after(start_offset + old_range.start);
11927 let edit_end = buffer.anchor_after(start_offset + old_range.end);
11928 edits.push((edit_start..edit_end, new_text));
11929 }
11930
11931 rewrapped_row_ranges.push(start_row..=end_row);
11932 }
11933
11934 self.buffer
11935 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11936 }
11937
11938 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
11939 let mut text = String::new();
11940 let buffer = self.buffer.read(cx).snapshot(cx);
11941 let mut selections = self.selections.all::<Point>(cx);
11942 let mut clipboard_selections = Vec::with_capacity(selections.len());
11943 {
11944 let max_point = buffer.max_point();
11945 let mut is_first = true;
11946 for selection in &mut selections {
11947 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11948 if is_entire_line {
11949 selection.start = Point::new(selection.start.row, 0);
11950 if !selection.is_empty() && selection.end.column == 0 {
11951 selection.end = cmp::min(max_point, selection.end);
11952 } else {
11953 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
11954 }
11955 selection.goal = SelectionGoal::None;
11956 }
11957 if is_first {
11958 is_first = false;
11959 } else {
11960 text += "\n";
11961 }
11962 let mut len = 0;
11963 for chunk in buffer.text_for_range(selection.start..selection.end) {
11964 text.push_str(chunk);
11965 len += chunk.len();
11966 }
11967 clipboard_selections.push(ClipboardSelection {
11968 len,
11969 is_entire_line,
11970 first_line_indent: buffer
11971 .indent_size_for_line(MultiBufferRow(selection.start.row))
11972 .len,
11973 });
11974 }
11975 }
11976
11977 self.transact(window, cx, |this, window, cx| {
11978 this.change_selections(Default::default(), window, cx, |s| {
11979 s.select(selections);
11980 });
11981 this.insert("", window, cx);
11982 });
11983 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
11984 }
11985
11986 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
11987 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11988 let item = self.cut_common(window, cx);
11989 cx.write_to_clipboard(item);
11990 }
11991
11992 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
11993 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11994 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11995 s.move_with(|snapshot, sel| {
11996 if sel.is_empty() {
11997 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
11998 }
11999 });
12000 });
12001 let item = self.cut_common(window, cx);
12002 cx.set_global(KillRing(item))
12003 }
12004
12005 pub fn kill_ring_yank(
12006 &mut self,
12007 _: &KillRingYank,
12008 window: &mut Window,
12009 cx: &mut Context<Self>,
12010 ) {
12011 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12012 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12013 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12014 (kill_ring.text().to_string(), kill_ring.metadata_json())
12015 } else {
12016 return;
12017 }
12018 } else {
12019 return;
12020 };
12021 self.do_paste(&text, metadata, false, window, cx);
12022 }
12023
12024 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12025 self.do_copy(true, cx);
12026 }
12027
12028 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12029 self.do_copy(false, cx);
12030 }
12031
12032 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12033 let selections = self.selections.all::<Point>(cx);
12034 let buffer = self.buffer.read(cx).read(cx);
12035 let mut text = String::new();
12036
12037 let mut clipboard_selections = Vec::with_capacity(selections.len());
12038 {
12039 let max_point = buffer.max_point();
12040 let mut is_first = true;
12041 for selection in &selections {
12042 let mut start = selection.start;
12043 let mut end = selection.end;
12044 let is_entire_line = selection.is_empty() || self.selections.line_mode;
12045 if is_entire_line {
12046 start = Point::new(start.row, 0);
12047 end = cmp::min(max_point, Point::new(end.row + 1, 0));
12048 }
12049
12050 let mut trimmed_selections = Vec::new();
12051 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12052 let row = MultiBufferRow(start.row);
12053 let first_indent = buffer.indent_size_for_line(row);
12054 if first_indent.len == 0 || start.column > first_indent.len {
12055 trimmed_selections.push(start..end);
12056 } else {
12057 trimmed_selections.push(
12058 Point::new(row.0, first_indent.len)
12059 ..Point::new(row.0, buffer.line_len(row)),
12060 );
12061 for row in start.row + 1..=end.row {
12062 let mut line_len = buffer.line_len(MultiBufferRow(row));
12063 if row == end.row {
12064 line_len = end.column;
12065 }
12066 if line_len == 0 {
12067 trimmed_selections
12068 .push(Point::new(row, 0)..Point::new(row, line_len));
12069 continue;
12070 }
12071 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12072 if row_indent_size.len >= first_indent.len {
12073 trimmed_selections.push(
12074 Point::new(row, first_indent.len)..Point::new(row, line_len),
12075 );
12076 } else {
12077 trimmed_selections.clear();
12078 trimmed_selections.push(start..end);
12079 break;
12080 }
12081 }
12082 }
12083 } else {
12084 trimmed_selections.push(start..end);
12085 }
12086
12087 for trimmed_range in trimmed_selections {
12088 if is_first {
12089 is_first = false;
12090 } else {
12091 text += "\n";
12092 }
12093 let mut len = 0;
12094 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12095 text.push_str(chunk);
12096 len += chunk.len();
12097 }
12098 clipboard_selections.push(ClipboardSelection {
12099 len,
12100 is_entire_line,
12101 first_line_indent: buffer
12102 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12103 .len,
12104 });
12105 }
12106 }
12107 }
12108
12109 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12110 text,
12111 clipboard_selections,
12112 ));
12113 }
12114
12115 pub fn do_paste(
12116 &mut self,
12117 text: &String,
12118 clipboard_selections: Option<Vec<ClipboardSelection>>,
12119 handle_entire_lines: bool,
12120 window: &mut Window,
12121 cx: &mut Context<Self>,
12122 ) {
12123 if self.read_only(cx) {
12124 return;
12125 }
12126
12127 let clipboard_text = Cow::Borrowed(text);
12128
12129 self.transact(window, cx, |this, window, cx| {
12130 if let Some(mut clipboard_selections) = clipboard_selections {
12131 let old_selections = this.selections.all::<usize>(cx);
12132 let all_selections_were_entire_line =
12133 clipboard_selections.iter().all(|s| s.is_entire_line);
12134 let first_selection_indent_column =
12135 clipboard_selections.first().map(|s| s.first_line_indent);
12136 if clipboard_selections.len() != old_selections.len() {
12137 clipboard_selections.drain(..);
12138 }
12139 let cursor_offset = this.selections.last::<usize>(cx).head();
12140 let mut auto_indent_on_paste = true;
12141
12142 this.buffer.update(cx, |buffer, cx| {
12143 let snapshot = buffer.read(cx);
12144 auto_indent_on_paste = snapshot
12145 .language_settings_at(cursor_offset, cx)
12146 .auto_indent_on_paste;
12147
12148 let mut start_offset = 0;
12149 let mut edits = Vec::new();
12150 let mut original_indent_columns = Vec::new();
12151 for (ix, selection) in old_selections.iter().enumerate() {
12152 let to_insert;
12153 let entire_line;
12154 let original_indent_column;
12155 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12156 let end_offset = start_offset + clipboard_selection.len;
12157 to_insert = &clipboard_text[start_offset..end_offset];
12158 entire_line = clipboard_selection.is_entire_line;
12159 start_offset = end_offset + 1;
12160 original_indent_column = Some(clipboard_selection.first_line_indent);
12161 } else {
12162 to_insert = clipboard_text.as_str();
12163 entire_line = all_selections_were_entire_line;
12164 original_indent_column = first_selection_indent_column
12165 }
12166
12167 // If the corresponding selection was empty when this slice of the
12168 // clipboard text was written, then the entire line containing the
12169 // selection was copied. If this selection is also currently empty,
12170 // then paste the line before the current line of the buffer.
12171 let range = if selection.is_empty() && handle_entire_lines && entire_line {
12172 let column = selection.start.to_point(&snapshot).column as usize;
12173 let line_start = selection.start - column;
12174 line_start..line_start
12175 } else {
12176 selection.range()
12177 };
12178
12179 edits.push((range, to_insert));
12180 original_indent_columns.push(original_indent_column);
12181 }
12182 drop(snapshot);
12183
12184 buffer.edit(
12185 edits,
12186 if auto_indent_on_paste {
12187 Some(AutoindentMode::Block {
12188 original_indent_columns,
12189 })
12190 } else {
12191 None
12192 },
12193 cx,
12194 );
12195 });
12196
12197 let selections = this.selections.all::<usize>(cx);
12198 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12199 } else {
12200 this.insert(&clipboard_text, window, cx);
12201 }
12202 });
12203 }
12204
12205 pub fn diff_clipboard_with_selection(
12206 &mut self,
12207 _: &DiffClipboardWithSelection,
12208 window: &mut Window,
12209 cx: &mut Context<Self>,
12210 ) {
12211 let selections = self.selections.all::<usize>(cx);
12212
12213 if selections.is_empty() {
12214 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12215 return;
12216 };
12217
12218 let clipboard_text = match cx.read_from_clipboard() {
12219 Some(item) => match item.entries().first() {
12220 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
12221 _ => None,
12222 },
12223 None => None,
12224 };
12225
12226 let Some(clipboard_text) = clipboard_text else {
12227 log::warn!("Clipboard doesn't contain text.");
12228 return;
12229 };
12230
12231 window.dispatch_action(
12232 Box::new(DiffClipboardWithSelectionData {
12233 clipboard_text,
12234 editor: cx.entity(),
12235 }),
12236 cx,
12237 );
12238 }
12239
12240 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12241 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12242 if let Some(item) = cx.read_from_clipboard() {
12243 let entries = item.entries();
12244
12245 match entries.first() {
12246 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12247 // of all the pasted entries.
12248 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12249 .do_paste(
12250 clipboard_string.text(),
12251 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12252 true,
12253 window,
12254 cx,
12255 ),
12256 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12257 }
12258 }
12259 }
12260
12261 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12262 if self.read_only(cx) {
12263 return;
12264 }
12265
12266 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12267
12268 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12269 if let Some((selections, _)) =
12270 self.selection_history.transaction(transaction_id).cloned()
12271 {
12272 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12273 s.select_anchors(selections.to_vec());
12274 });
12275 } else {
12276 log::error!(
12277 "No entry in selection_history found for undo. \
12278 This may correspond to a bug where undo does not update the selection. \
12279 If this is occurring, please add details to \
12280 https://github.com/zed-industries/zed/issues/22692"
12281 );
12282 }
12283 self.request_autoscroll(Autoscroll::fit(), cx);
12284 self.unmark_text(window, cx);
12285 self.refresh_inline_completion(true, false, window, cx);
12286 cx.emit(EditorEvent::Edited { transaction_id });
12287 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12288 }
12289 }
12290
12291 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12292 if self.read_only(cx) {
12293 return;
12294 }
12295
12296 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12297
12298 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12299 if let Some((_, Some(selections))) =
12300 self.selection_history.transaction(transaction_id).cloned()
12301 {
12302 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12303 s.select_anchors(selections.to_vec());
12304 });
12305 } else {
12306 log::error!(
12307 "No entry in selection_history found for redo. \
12308 This may correspond to a bug where undo does not update the selection. \
12309 If this is occurring, please add details to \
12310 https://github.com/zed-industries/zed/issues/22692"
12311 );
12312 }
12313 self.request_autoscroll(Autoscroll::fit(), cx);
12314 self.unmark_text(window, cx);
12315 self.refresh_inline_completion(true, false, window, cx);
12316 cx.emit(EditorEvent::Edited { transaction_id });
12317 }
12318 }
12319
12320 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12321 self.buffer
12322 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12323 }
12324
12325 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12326 self.buffer
12327 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12328 }
12329
12330 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
12331 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12332 self.change_selections(Default::default(), window, cx, |s| {
12333 s.move_with(|map, selection| {
12334 let cursor = if selection.is_empty() {
12335 movement::left(map, selection.start)
12336 } else {
12337 selection.start
12338 };
12339 selection.collapse_to(cursor, SelectionGoal::None);
12340 });
12341 })
12342 }
12343
12344 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12345 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12346 self.change_selections(Default::default(), window, cx, |s| {
12347 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12348 })
12349 }
12350
12351 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
12352 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12353 self.change_selections(Default::default(), window, cx, |s| {
12354 s.move_with(|map, selection| {
12355 let cursor = if selection.is_empty() {
12356 movement::right(map, selection.end)
12357 } else {
12358 selection.end
12359 };
12360 selection.collapse_to(cursor, SelectionGoal::None)
12361 });
12362 })
12363 }
12364
12365 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12366 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12367 self.change_selections(Default::default(), window, cx, |s| {
12368 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
12369 })
12370 }
12371
12372 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
12373 if self.take_rename(true, window, cx).is_some() {
12374 return;
12375 }
12376
12377 if self.mode.is_single_line() {
12378 cx.propagate();
12379 return;
12380 }
12381
12382 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12383
12384 let text_layout_details = &self.text_layout_details(window);
12385 let selection_count = self.selections.count();
12386 let first_selection = self.selections.first_anchor();
12387
12388 self.change_selections(Default::default(), window, cx, |s| {
12389 s.move_with(|map, selection| {
12390 if !selection.is_empty() {
12391 selection.goal = SelectionGoal::None;
12392 }
12393 let (cursor, goal) = movement::up(
12394 map,
12395 selection.start,
12396 selection.goal,
12397 false,
12398 text_layout_details,
12399 );
12400 selection.collapse_to(cursor, goal);
12401 });
12402 });
12403
12404 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12405 {
12406 cx.propagate();
12407 }
12408 }
12409
12410 pub fn move_up_by_lines(
12411 &mut self,
12412 action: &MoveUpByLines,
12413 window: &mut Window,
12414 cx: &mut Context<Self>,
12415 ) {
12416 if self.take_rename(true, window, cx).is_some() {
12417 return;
12418 }
12419
12420 if self.mode.is_single_line() {
12421 cx.propagate();
12422 return;
12423 }
12424
12425 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12426
12427 let text_layout_details = &self.text_layout_details(window);
12428
12429 self.change_selections(Default::default(), window, cx, |s| {
12430 s.move_with(|map, selection| {
12431 if !selection.is_empty() {
12432 selection.goal = SelectionGoal::None;
12433 }
12434 let (cursor, goal) = movement::up_by_rows(
12435 map,
12436 selection.start,
12437 action.lines,
12438 selection.goal,
12439 false,
12440 text_layout_details,
12441 );
12442 selection.collapse_to(cursor, goal);
12443 });
12444 })
12445 }
12446
12447 pub fn move_down_by_lines(
12448 &mut self,
12449 action: &MoveDownByLines,
12450 window: &mut Window,
12451 cx: &mut Context<Self>,
12452 ) {
12453 if self.take_rename(true, window, cx).is_some() {
12454 return;
12455 }
12456
12457 if self.mode.is_single_line() {
12458 cx.propagate();
12459 return;
12460 }
12461
12462 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12463
12464 let text_layout_details = &self.text_layout_details(window);
12465
12466 self.change_selections(Default::default(), window, cx, |s| {
12467 s.move_with(|map, selection| {
12468 if !selection.is_empty() {
12469 selection.goal = SelectionGoal::None;
12470 }
12471 let (cursor, goal) = movement::down_by_rows(
12472 map,
12473 selection.start,
12474 action.lines,
12475 selection.goal,
12476 false,
12477 text_layout_details,
12478 );
12479 selection.collapse_to(cursor, goal);
12480 });
12481 })
12482 }
12483
12484 pub fn select_down_by_lines(
12485 &mut self,
12486 action: &SelectDownByLines,
12487 window: &mut Window,
12488 cx: &mut Context<Self>,
12489 ) {
12490 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12491 let text_layout_details = &self.text_layout_details(window);
12492 self.change_selections(Default::default(), window, cx, |s| {
12493 s.move_heads_with(|map, head, goal| {
12494 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
12495 })
12496 })
12497 }
12498
12499 pub fn select_up_by_lines(
12500 &mut self,
12501 action: &SelectUpByLines,
12502 window: &mut Window,
12503 cx: &mut Context<Self>,
12504 ) {
12505 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12506 let text_layout_details = &self.text_layout_details(window);
12507 self.change_selections(Default::default(), window, cx, |s| {
12508 s.move_heads_with(|map, head, goal| {
12509 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
12510 })
12511 })
12512 }
12513
12514 pub fn select_page_up(
12515 &mut self,
12516 _: &SelectPageUp,
12517 window: &mut Window,
12518 cx: &mut Context<Self>,
12519 ) {
12520 let Some(row_count) = self.visible_row_count() else {
12521 return;
12522 };
12523
12524 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12525
12526 let text_layout_details = &self.text_layout_details(window);
12527
12528 self.change_selections(Default::default(), window, cx, |s| {
12529 s.move_heads_with(|map, head, goal| {
12530 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
12531 })
12532 })
12533 }
12534
12535 pub fn move_page_up(
12536 &mut self,
12537 action: &MovePageUp,
12538 window: &mut Window,
12539 cx: &mut Context<Self>,
12540 ) {
12541 if self.take_rename(true, window, cx).is_some() {
12542 return;
12543 }
12544
12545 if self
12546 .context_menu
12547 .borrow_mut()
12548 .as_mut()
12549 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
12550 .unwrap_or(false)
12551 {
12552 return;
12553 }
12554
12555 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12556 cx.propagate();
12557 return;
12558 }
12559
12560 let Some(row_count) = self.visible_row_count() else {
12561 return;
12562 };
12563
12564 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12565
12566 let effects = if action.center_cursor {
12567 SelectionEffects::scroll(Autoscroll::center())
12568 } else {
12569 SelectionEffects::default()
12570 };
12571
12572 let text_layout_details = &self.text_layout_details(window);
12573
12574 self.change_selections(effects, window, cx, |s| {
12575 s.move_with(|map, selection| {
12576 if !selection.is_empty() {
12577 selection.goal = SelectionGoal::None;
12578 }
12579 let (cursor, goal) = movement::up_by_rows(
12580 map,
12581 selection.end,
12582 row_count,
12583 selection.goal,
12584 false,
12585 text_layout_details,
12586 );
12587 selection.collapse_to(cursor, goal);
12588 });
12589 });
12590 }
12591
12592 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
12593 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12594 let text_layout_details = &self.text_layout_details(window);
12595 self.change_selections(Default::default(), window, cx, |s| {
12596 s.move_heads_with(|map, head, goal| {
12597 movement::up(map, head, goal, false, text_layout_details)
12598 })
12599 })
12600 }
12601
12602 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
12603 self.take_rename(true, window, cx);
12604
12605 if self.mode.is_single_line() {
12606 cx.propagate();
12607 return;
12608 }
12609
12610 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12611
12612 let text_layout_details = &self.text_layout_details(window);
12613 let selection_count = self.selections.count();
12614 let first_selection = self.selections.first_anchor();
12615
12616 self.change_selections(Default::default(), window, cx, |s| {
12617 s.move_with(|map, selection| {
12618 if !selection.is_empty() {
12619 selection.goal = SelectionGoal::None;
12620 }
12621 let (cursor, goal) = movement::down(
12622 map,
12623 selection.end,
12624 selection.goal,
12625 false,
12626 text_layout_details,
12627 );
12628 selection.collapse_to(cursor, goal);
12629 });
12630 });
12631
12632 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12633 {
12634 cx.propagate();
12635 }
12636 }
12637
12638 pub fn select_page_down(
12639 &mut self,
12640 _: &SelectPageDown,
12641 window: &mut Window,
12642 cx: &mut Context<Self>,
12643 ) {
12644 let Some(row_count) = self.visible_row_count() else {
12645 return;
12646 };
12647
12648 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12649
12650 let text_layout_details = &self.text_layout_details(window);
12651
12652 self.change_selections(Default::default(), window, cx, |s| {
12653 s.move_heads_with(|map, head, goal| {
12654 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
12655 })
12656 })
12657 }
12658
12659 pub fn move_page_down(
12660 &mut self,
12661 action: &MovePageDown,
12662 window: &mut Window,
12663 cx: &mut Context<Self>,
12664 ) {
12665 if self.take_rename(true, window, cx).is_some() {
12666 return;
12667 }
12668
12669 if self
12670 .context_menu
12671 .borrow_mut()
12672 .as_mut()
12673 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
12674 .unwrap_or(false)
12675 {
12676 return;
12677 }
12678
12679 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12680 cx.propagate();
12681 return;
12682 }
12683
12684 let Some(row_count) = self.visible_row_count() else {
12685 return;
12686 };
12687
12688 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12689
12690 let effects = if action.center_cursor {
12691 SelectionEffects::scroll(Autoscroll::center())
12692 } else {
12693 SelectionEffects::default()
12694 };
12695
12696 let text_layout_details = &self.text_layout_details(window);
12697 self.change_selections(effects, window, cx, |s| {
12698 s.move_with(|map, selection| {
12699 if !selection.is_empty() {
12700 selection.goal = SelectionGoal::None;
12701 }
12702 let (cursor, goal) = movement::down_by_rows(
12703 map,
12704 selection.end,
12705 row_count,
12706 selection.goal,
12707 false,
12708 text_layout_details,
12709 );
12710 selection.collapse_to(cursor, goal);
12711 });
12712 });
12713 }
12714
12715 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
12716 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12717 let text_layout_details = &self.text_layout_details(window);
12718 self.change_selections(Default::default(), window, cx, |s| {
12719 s.move_heads_with(|map, head, goal| {
12720 movement::down(map, head, goal, false, text_layout_details)
12721 })
12722 });
12723 }
12724
12725 pub fn context_menu_first(
12726 &mut self,
12727 _: &ContextMenuFirst,
12728 window: &mut Window,
12729 cx: &mut Context<Self>,
12730 ) {
12731 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12732 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
12733 }
12734 }
12735
12736 pub fn context_menu_prev(
12737 &mut self,
12738 _: &ContextMenuPrevious,
12739 window: &mut Window,
12740 cx: &mut Context<Self>,
12741 ) {
12742 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12743 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
12744 }
12745 }
12746
12747 pub fn context_menu_next(
12748 &mut self,
12749 _: &ContextMenuNext,
12750 window: &mut Window,
12751 cx: &mut Context<Self>,
12752 ) {
12753 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12754 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
12755 }
12756 }
12757
12758 pub fn context_menu_last(
12759 &mut self,
12760 _: &ContextMenuLast,
12761 window: &mut Window,
12762 cx: &mut Context<Self>,
12763 ) {
12764 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12765 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
12766 }
12767 }
12768
12769 pub fn signature_help_prev(
12770 &mut self,
12771 _: &SignatureHelpPrevious,
12772 _: &mut Window,
12773 cx: &mut Context<Self>,
12774 ) {
12775 if let Some(popover) = self.signature_help_state.popover_mut() {
12776 if popover.current_signature == 0 {
12777 popover.current_signature = popover.signatures.len() - 1;
12778 } else {
12779 popover.current_signature -= 1;
12780 }
12781 cx.notify();
12782 }
12783 }
12784
12785 pub fn signature_help_next(
12786 &mut self,
12787 _: &SignatureHelpNext,
12788 _: &mut Window,
12789 cx: &mut Context<Self>,
12790 ) {
12791 if let Some(popover) = self.signature_help_state.popover_mut() {
12792 if popover.current_signature + 1 == popover.signatures.len() {
12793 popover.current_signature = 0;
12794 } else {
12795 popover.current_signature += 1;
12796 }
12797 cx.notify();
12798 }
12799 }
12800
12801 pub fn move_to_previous_word_start(
12802 &mut self,
12803 _: &MoveToPreviousWordStart,
12804 window: &mut Window,
12805 cx: &mut Context<Self>,
12806 ) {
12807 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12808 self.change_selections(Default::default(), window, cx, |s| {
12809 s.move_cursors_with(|map, head, _| {
12810 (
12811 movement::previous_word_start(map, head),
12812 SelectionGoal::None,
12813 )
12814 });
12815 })
12816 }
12817
12818 pub fn move_to_previous_subword_start(
12819 &mut self,
12820 _: &MoveToPreviousSubwordStart,
12821 window: &mut Window,
12822 cx: &mut Context<Self>,
12823 ) {
12824 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12825 self.change_selections(Default::default(), window, cx, |s| {
12826 s.move_cursors_with(|map, head, _| {
12827 (
12828 movement::previous_subword_start(map, head),
12829 SelectionGoal::None,
12830 )
12831 });
12832 })
12833 }
12834
12835 pub fn select_to_previous_word_start(
12836 &mut self,
12837 _: &SelectToPreviousWordStart,
12838 window: &mut Window,
12839 cx: &mut Context<Self>,
12840 ) {
12841 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12842 self.change_selections(Default::default(), window, cx, |s| {
12843 s.move_heads_with(|map, head, _| {
12844 (
12845 movement::previous_word_start(map, head),
12846 SelectionGoal::None,
12847 )
12848 });
12849 })
12850 }
12851
12852 pub fn select_to_previous_subword_start(
12853 &mut self,
12854 _: &SelectToPreviousSubwordStart,
12855 window: &mut Window,
12856 cx: &mut Context<Self>,
12857 ) {
12858 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12859 self.change_selections(Default::default(), window, cx, |s| {
12860 s.move_heads_with(|map, head, _| {
12861 (
12862 movement::previous_subword_start(map, head),
12863 SelectionGoal::None,
12864 )
12865 });
12866 })
12867 }
12868
12869 pub fn delete_to_previous_word_start(
12870 &mut self,
12871 action: &DeleteToPreviousWordStart,
12872 window: &mut Window,
12873 cx: &mut Context<Self>,
12874 ) {
12875 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12876 self.transact(window, cx, |this, window, cx| {
12877 this.select_autoclose_pair(window, cx);
12878 this.change_selections(Default::default(), window, cx, |s| {
12879 s.move_with(|map, selection| {
12880 if selection.is_empty() {
12881 let cursor = if action.ignore_newlines {
12882 movement::previous_word_start(map, selection.head())
12883 } else {
12884 movement::previous_word_start_or_newline(map, selection.head())
12885 };
12886 selection.set_head(cursor, SelectionGoal::None);
12887 }
12888 });
12889 });
12890 this.insert("", window, cx);
12891 });
12892 }
12893
12894 pub fn delete_to_previous_subword_start(
12895 &mut self,
12896 _: &DeleteToPreviousSubwordStart,
12897 window: &mut Window,
12898 cx: &mut Context<Self>,
12899 ) {
12900 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12901 self.transact(window, cx, |this, window, cx| {
12902 this.select_autoclose_pair(window, cx);
12903 this.change_selections(Default::default(), window, cx, |s| {
12904 s.move_with(|map, selection| {
12905 if selection.is_empty() {
12906 let cursor = movement::previous_subword_start(map, selection.head());
12907 selection.set_head(cursor, SelectionGoal::None);
12908 }
12909 });
12910 });
12911 this.insert("", window, cx);
12912 });
12913 }
12914
12915 pub fn move_to_next_word_end(
12916 &mut self,
12917 _: &MoveToNextWordEnd,
12918 window: &mut Window,
12919 cx: &mut Context<Self>,
12920 ) {
12921 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12922 self.change_selections(Default::default(), window, cx, |s| {
12923 s.move_cursors_with(|map, head, _| {
12924 (movement::next_word_end(map, head), SelectionGoal::None)
12925 });
12926 })
12927 }
12928
12929 pub fn move_to_next_subword_end(
12930 &mut self,
12931 _: &MoveToNextSubwordEnd,
12932 window: &mut Window,
12933 cx: &mut Context<Self>,
12934 ) {
12935 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12936 self.change_selections(Default::default(), window, cx, |s| {
12937 s.move_cursors_with(|map, head, _| {
12938 (movement::next_subword_end(map, head), SelectionGoal::None)
12939 });
12940 })
12941 }
12942
12943 pub fn select_to_next_word_end(
12944 &mut self,
12945 _: &SelectToNextWordEnd,
12946 window: &mut Window,
12947 cx: &mut Context<Self>,
12948 ) {
12949 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12950 self.change_selections(Default::default(), window, cx, |s| {
12951 s.move_heads_with(|map, head, _| {
12952 (movement::next_word_end(map, head), SelectionGoal::None)
12953 });
12954 })
12955 }
12956
12957 pub fn select_to_next_subword_end(
12958 &mut self,
12959 _: &SelectToNextSubwordEnd,
12960 window: &mut Window,
12961 cx: &mut Context<Self>,
12962 ) {
12963 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12964 self.change_selections(Default::default(), window, cx, |s| {
12965 s.move_heads_with(|map, head, _| {
12966 (movement::next_subword_end(map, head), SelectionGoal::None)
12967 });
12968 })
12969 }
12970
12971 pub fn delete_to_next_word_end(
12972 &mut self,
12973 action: &DeleteToNextWordEnd,
12974 window: &mut Window,
12975 cx: &mut Context<Self>,
12976 ) {
12977 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12978 self.transact(window, cx, |this, window, cx| {
12979 this.change_selections(Default::default(), window, cx, |s| {
12980 s.move_with(|map, selection| {
12981 if selection.is_empty() {
12982 let cursor = if action.ignore_newlines {
12983 movement::next_word_end(map, selection.head())
12984 } else {
12985 movement::next_word_end_or_newline(map, selection.head())
12986 };
12987 selection.set_head(cursor, SelectionGoal::None);
12988 }
12989 });
12990 });
12991 this.insert("", window, cx);
12992 });
12993 }
12994
12995 pub fn delete_to_next_subword_end(
12996 &mut self,
12997 _: &DeleteToNextSubwordEnd,
12998 window: &mut Window,
12999 cx: &mut Context<Self>,
13000 ) {
13001 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13002 self.transact(window, cx, |this, window, cx| {
13003 this.change_selections(Default::default(), window, cx, |s| {
13004 s.move_with(|map, selection| {
13005 if selection.is_empty() {
13006 let cursor = movement::next_subword_end(map, selection.head());
13007 selection.set_head(cursor, SelectionGoal::None);
13008 }
13009 });
13010 });
13011 this.insert("", window, cx);
13012 });
13013 }
13014
13015 pub fn move_to_beginning_of_line(
13016 &mut self,
13017 action: &MoveToBeginningOfLine,
13018 window: &mut Window,
13019 cx: &mut Context<Self>,
13020 ) {
13021 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13022 self.change_selections(Default::default(), window, cx, |s| {
13023 s.move_cursors_with(|map, head, _| {
13024 (
13025 movement::indented_line_beginning(
13026 map,
13027 head,
13028 action.stop_at_soft_wraps,
13029 action.stop_at_indent,
13030 ),
13031 SelectionGoal::None,
13032 )
13033 });
13034 })
13035 }
13036
13037 pub fn select_to_beginning_of_line(
13038 &mut self,
13039 action: &SelectToBeginningOfLine,
13040 window: &mut Window,
13041 cx: &mut Context<Self>,
13042 ) {
13043 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13044 self.change_selections(Default::default(), window, cx, |s| {
13045 s.move_heads_with(|map, head, _| {
13046 (
13047 movement::indented_line_beginning(
13048 map,
13049 head,
13050 action.stop_at_soft_wraps,
13051 action.stop_at_indent,
13052 ),
13053 SelectionGoal::None,
13054 )
13055 });
13056 });
13057 }
13058
13059 pub fn delete_to_beginning_of_line(
13060 &mut self,
13061 action: &DeleteToBeginningOfLine,
13062 window: &mut Window,
13063 cx: &mut Context<Self>,
13064 ) {
13065 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13066 self.transact(window, cx, |this, window, cx| {
13067 this.change_selections(Default::default(), window, cx, |s| {
13068 s.move_with(|_, selection| {
13069 selection.reversed = true;
13070 });
13071 });
13072
13073 this.select_to_beginning_of_line(
13074 &SelectToBeginningOfLine {
13075 stop_at_soft_wraps: false,
13076 stop_at_indent: action.stop_at_indent,
13077 },
13078 window,
13079 cx,
13080 );
13081 this.backspace(&Backspace, window, cx);
13082 });
13083 }
13084
13085 pub fn move_to_end_of_line(
13086 &mut self,
13087 action: &MoveToEndOfLine,
13088 window: &mut Window,
13089 cx: &mut Context<Self>,
13090 ) {
13091 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13092 self.change_selections(Default::default(), window, cx, |s| {
13093 s.move_cursors_with(|map, head, _| {
13094 (
13095 movement::line_end(map, head, action.stop_at_soft_wraps),
13096 SelectionGoal::None,
13097 )
13098 });
13099 })
13100 }
13101
13102 pub fn select_to_end_of_line(
13103 &mut self,
13104 action: &SelectToEndOfLine,
13105 window: &mut Window,
13106 cx: &mut Context<Self>,
13107 ) {
13108 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13109 self.change_selections(Default::default(), window, cx, |s| {
13110 s.move_heads_with(|map, head, _| {
13111 (
13112 movement::line_end(map, head, action.stop_at_soft_wraps),
13113 SelectionGoal::None,
13114 )
13115 });
13116 })
13117 }
13118
13119 pub fn delete_to_end_of_line(
13120 &mut self,
13121 _: &DeleteToEndOfLine,
13122 window: &mut Window,
13123 cx: &mut Context<Self>,
13124 ) {
13125 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13126 self.transact(window, cx, |this, window, cx| {
13127 this.select_to_end_of_line(
13128 &SelectToEndOfLine {
13129 stop_at_soft_wraps: false,
13130 },
13131 window,
13132 cx,
13133 );
13134 this.delete(&Delete, window, cx);
13135 });
13136 }
13137
13138 pub fn cut_to_end_of_line(
13139 &mut self,
13140 _: &CutToEndOfLine,
13141 window: &mut Window,
13142 cx: &mut Context<Self>,
13143 ) {
13144 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13145 self.transact(window, cx, |this, window, cx| {
13146 this.select_to_end_of_line(
13147 &SelectToEndOfLine {
13148 stop_at_soft_wraps: false,
13149 },
13150 window,
13151 cx,
13152 );
13153 this.cut(&Cut, window, cx);
13154 });
13155 }
13156
13157 pub fn move_to_start_of_paragraph(
13158 &mut self,
13159 _: &MoveToStartOfParagraph,
13160 window: &mut Window,
13161 cx: &mut Context<Self>,
13162 ) {
13163 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13164 cx.propagate();
13165 return;
13166 }
13167 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13168 self.change_selections(Default::default(), window, cx, |s| {
13169 s.move_with(|map, selection| {
13170 selection.collapse_to(
13171 movement::start_of_paragraph(map, selection.head(), 1),
13172 SelectionGoal::None,
13173 )
13174 });
13175 })
13176 }
13177
13178 pub fn move_to_end_of_paragraph(
13179 &mut self,
13180 _: &MoveToEndOfParagraph,
13181 window: &mut Window,
13182 cx: &mut Context<Self>,
13183 ) {
13184 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13185 cx.propagate();
13186 return;
13187 }
13188 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13189 self.change_selections(Default::default(), window, cx, |s| {
13190 s.move_with(|map, selection| {
13191 selection.collapse_to(
13192 movement::end_of_paragraph(map, selection.head(), 1),
13193 SelectionGoal::None,
13194 )
13195 });
13196 })
13197 }
13198
13199 pub fn select_to_start_of_paragraph(
13200 &mut self,
13201 _: &SelectToStartOfParagraph,
13202 window: &mut Window,
13203 cx: &mut Context<Self>,
13204 ) {
13205 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13206 cx.propagate();
13207 return;
13208 }
13209 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13210 self.change_selections(Default::default(), window, cx, |s| {
13211 s.move_heads_with(|map, head, _| {
13212 (
13213 movement::start_of_paragraph(map, head, 1),
13214 SelectionGoal::None,
13215 )
13216 });
13217 })
13218 }
13219
13220 pub fn select_to_end_of_paragraph(
13221 &mut self,
13222 _: &SelectToEndOfParagraph,
13223 window: &mut Window,
13224 cx: &mut Context<Self>,
13225 ) {
13226 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13227 cx.propagate();
13228 return;
13229 }
13230 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13231 self.change_selections(Default::default(), window, cx, |s| {
13232 s.move_heads_with(|map, head, _| {
13233 (
13234 movement::end_of_paragraph(map, head, 1),
13235 SelectionGoal::None,
13236 )
13237 });
13238 })
13239 }
13240
13241 pub fn move_to_start_of_excerpt(
13242 &mut self,
13243 _: &MoveToStartOfExcerpt,
13244 window: &mut Window,
13245 cx: &mut Context<Self>,
13246 ) {
13247 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13248 cx.propagate();
13249 return;
13250 }
13251 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13252 self.change_selections(Default::default(), window, cx, |s| {
13253 s.move_with(|map, selection| {
13254 selection.collapse_to(
13255 movement::start_of_excerpt(
13256 map,
13257 selection.head(),
13258 workspace::searchable::Direction::Prev,
13259 ),
13260 SelectionGoal::None,
13261 )
13262 });
13263 })
13264 }
13265
13266 pub fn move_to_start_of_next_excerpt(
13267 &mut self,
13268 _: &MoveToStartOfNextExcerpt,
13269 window: &mut Window,
13270 cx: &mut Context<Self>,
13271 ) {
13272 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13273 cx.propagate();
13274 return;
13275 }
13276
13277 self.change_selections(Default::default(), window, cx, |s| {
13278 s.move_with(|map, selection| {
13279 selection.collapse_to(
13280 movement::start_of_excerpt(
13281 map,
13282 selection.head(),
13283 workspace::searchable::Direction::Next,
13284 ),
13285 SelectionGoal::None,
13286 )
13287 });
13288 })
13289 }
13290
13291 pub fn move_to_end_of_excerpt(
13292 &mut self,
13293 _: &MoveToEndOfExcerpt,
13294 window: &mut Window,
13295 cx: &mut Context<Self>,
13296 ) {
13297 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13298 cx.propagate();
13299 return;
13300 }
13301 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13302 self.change_selections(Default::default(), window, cx, |s| {
13303 s.move_with(|map, selection| {
13304 selection.collapse_to(
13305 movement::end_of_excerpt(
13306 map,
13307 selection.head(),
13308 workspace::searchable::Direction::Next,
13309 ),
13310 SelectionGoal::None,
13311 )
13312 });
13313 })
13314 }
13315
13316 pub fn move_to_end_of_previous_excerpt(
13317 &mut self,
13318 _: &MoveToEndOfPreviousExcerpt,
13319 window: &mut Window,
13320 cx: &mut Context<Self>,
13321 ) {
13322 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13323 cx.propagate();
13324 return;
13325 }
13326 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13327 self.change_selections(Default::default(), window, cx, |s| {
13328 s.move_with(|map, selection| {
13329 selection.collapse_to(
13330 movement::end_of_excerpt(
13331 map,
13332 selection.head(),
13333 workspace::searchable::Direction::Prev,
13334 ),
13335 SelectionGoal::None,
13336 )
13337 });
13338 })
13339 }
13340
13341 pub fn select_to_start_of_excerpt(
13342 &mut self,
13343 _: &SelectToStartOfExcerpt,
13344 window: &mut Window,
13345 cx: &mut Context<Self>,
13346 ) {
13347 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13348 cx.propagate();
13349 return;
13350 }
13351 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13352 self.change_selections(Default::default(), window, cx, |s| {
13353 s.move_heads_with(|map, head, _| {
13354 (
13355 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13356 SelectionGoal::None,
13357 )
13358 });
13359 })
13360 }
13361
13362 pub fn select_to_start_of_next_excerpt(
13363 &mut self,
13364 _: &SelectToStartOfNextExcerpt,
13365 window: &mut Window,
13366 cx: &mut Context<Self>,
13367 ) {
13368 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13369 cx.propagate();
13370 return;
13371 }
13372 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13373 self.change_selections(Default::default(), window, cx, |s| {
13374 s.move_heads_with(|map, head, _| {
13375 (
13376 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
13377 SelectionGoal::None,
13378 )
13379 });
13380 })
13381 }
13382
13383 pub fn select_to_end_of_excerpt(
13384 &mut self,
13385 _: &SelectToEndOfExcerpt,
13386 window: &mut Window,
13387 cx: &mut Context<Self>,
13388 ) {
13389 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13390 cx.propagate();
13391 return;
13392 }
13393 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13394 self.change_selections(Default::default(), window, cx, |s| {
13395 s.move_heads_with(|map, head, _| {
13396 (
13397 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
13398 SelectionGoal::None,
13399 )
13400 });
13401 })
13402 }
13403
13404 pub fn select_to_end_of_previous_excerpt(
13405 &mut self,
13406 _: &SelectToEndOfPreviousExcerpt,
13407 window: &mut Window,
13408 cx: &mut Context<Self>,
13409 ) {
13410 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13411 cx.propagate();
13412 return;
13413 }
13414 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13415 self.change_selections(Default::default(), window, cx, |s| {
13416 s.move_heads_with(|map, head, _| {
13417 (
13418 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13419 SelectionGoal::None,
13420 )
13421 });
13422 })
13423 }
13424
13425 pub fn move_to_beginning(
13426 &mut self,
13427 _: &MoveToBeginning,
13428 window: &mut Window,
13429 cx: &mut Context<Self>,
13430 ) {
13431 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13432 cx.propagate();
13433 return;
13434 }
13435 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13436 self.change_selections(Default::default(), window, cx, |s| {
13437 s.select_ranges(vec![0..0]);
13438 });
13439 }
13440
13441 pub fn select_to_beginning(
13442 &mut self,
13443 _: &SelectToBeginning,
13444 window: &mut Window,
13445 cx: &mut Context<Self>,
13446 ) {
13447 let mut selection = self.selections.last::<Point>(cx);
13448 selection.set_head(Point::zero(), SelectionGoal::None);
13449 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13450 self.change_selections(Default::default(), window, cx, |s| {
13451 s.select(vec![selection]);
13452 });
13453 }
13454
13455 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
13456 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13457 cx.propagate();
13458 return;
13459 }
13460 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13461 let cursor = self.buffer.read(cx).read(cx).len();
13462 self.change_selections(Default::default(), window, cx, |s| {
13463 s.select_ranges(vec![cursor..cursor])
13464 });
13465 }
13466
13467 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
13468 self.nav_history = nav_history;
13469 }
13470
13471 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
13472 self.nav_history.as_ref()
13473 }
13474
13475 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
13476 self.push_to_nav_history(
13477 self.selections.newest_anchor().head(),
13478 None,
13479 false,
13480 true,
13481 cx,
13482 );
13483 }
13484
13485 fn push_to_nav_history(
13486 &mut self,
13487 cursor_anchor: Anchor,
13488 new_position: Option<Point>,
13489 is_deactivate: bool,
13490 always: bool,
13491 cx: &mut Context<Self>,
13492 ) {
13493 if let Some(nav_history) = self.nav_history.as_mut() {
13494 let buffer = self.buffer.read(cx).read(cx);
13495 let cursor_position = cursor_anchor.to_point(&buffer);
13496 let scroll_state = self.scroll_manager.anchor();
13497 let scroll_top_row = scroll_state.top_row(&buffer);
13498 drop(buffer);
13499
13500 if let Some(new_position) = new_position {
13501 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
13502 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
13503 return;
13504 }
13505 }
13506
13507 nav_history.push(
13508 Some(NavigationData {
13509 cursor_anchor,
13510 cursor_position,
13511 scroll_anchor: scroll_state,
13512 scroll_top_row,
13513 }),
13514 cx,
13515 );
13516 cx.emit(EditorEvent::PushedToNavHistory {
13517 anchor: cursor_anchor,
13518 is_deactivate,
13519 })
13520 }
13521 }
13522
13523 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
13524 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13525 let buffer = self.buffer.read(cx).snapshot(cx);
13526 let mut selection = self.selections.first::<usize>(cx);
13527 selection.set_head(buffer.len(), SelectionGoal::None);
13528 self.change_selections(Default::default(), window, cx, |s| {
13529 s.select(vec![selection]);
13530 });
13531 }
13532
13533 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
13534 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13535 let end = self.buffer.read(cx).read(cx).len();
13536 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13537 s.select_ranges(vec![0..end]);
13538 });
13539 }
13540
13541 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
13542 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13543 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13544 let mut selections = self.selections.all::<Point>(cx);
13545 let max_point = display_map.buffer_snapshot.max_point();
13546 for selection in &mut selections {
13547 let rows = selection.spanned_rows(true, &display_map);
13548 selection.start = Point::new(rows.start.0, 0);
13549 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
13550 selection.reversed = false;
13551 }
13552 self.change_selections(Default::default(), window, cx, |s| {
13553 s.select(selections);
13554 });
13555 }
13556
13557 pub fn split_selection_into_lines(
13558 &mut self,
13559 _: &SplitSelectionIntoLines,
13560 window: &mut Window,
13561 cx: &mut Context<Self>,
13562 ) {
13563 let selections = self
13564 .selections
13565 .all::<Point>(cx)
13566 .into_iter()
13567 .map(|selection| selection.start..selection.end)
13568 .collect::<Vec<_>>();
13569 self.unfold_ranges(&selections, true, true, cx);
13570
13571 let mut new_selection_ranges = Vec::new();
13572 {
13573 let buffer = self.buffer.read(cx).read(cx);
13574 for selection in selections {
13575 for row in selection.start.row..selection.end.row {
13576 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
13577 new_selection_ranges.push(cursor..cursor);
13578 }
13579
13580 let is_multiline_selection = selection.start.row != selection.end.row;
13581 // Don't insert last one if it's a multi-line selection ending at the start of a line,
13582 // so this action feels more ergonomic when paired with other selection operations
13583 let should_skip_last = is_multiline_selection && selection.end.column == 0;
13584 if !should_skip_last {
13585 new_selection_ranges.push(selection.end..selection.end);
13586 }
13587 }
13588 }
13589 self.change_selections(Default::default(), window, cx, |s| {
13590 s.select_ranges(new_selection_ranges);
13591 });
13592 }
13593
13594 pub fn add_selection_above(
13595 &mut self,
13596 _: &AddSelectionAbove,
13597 window: &mut Window,
13598 cx: &mut Context<Self>,
13599 ) {
13600 self.add_selection(true, window, cx);
13601 }
13602
13603 pub fn add_selection_below(
13604 &mut self,
13605 _: &AddSelectionBelow,
13606 window: &mut Window,
13607 cx: &mut Context<Self>,
13608 ) {
13609 self.add_selection(false, window, cx);
13610 }
13611
13612 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
13613 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13614
13615 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13616 let all_selections = self.selections.all::<Point>(cx);
13617 let text_layout_details = self.text_layout_details(window);
13618
13619 let (mut columnar_selections, new_selections_to_columnarize) = {
13620 if let Some(state) = self.add_selections_state.as_ref() {
13621 let columnar_selection_ids: HashSet<_> = state
13622 .groups
13623 .iter()
13624 .flat_map(|group| group.stack.iter())
13625 .copied()
13626 .collect();
13627
13628 all_selections
13629 .into_iter()
13630 .partition(|s| columnar_selection_ids.contains(&s.id))
13631 } else {
13632 (Vec::new(), all_selections)
13633 }
13634 };
13635
13636 let mut state = self
13637 .add_selections_state
13638 .take()
13639 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
13640
13641 for selection in new_selections_to_columnarize {
13642 let range = selection.display_range(&display_map).sorted();
13643 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
13644 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
13645 let positions = start_x.min(end_x)..start_x.max(end_x);
13646 let mut stack = Vec::new();
13647 for row in range.start.row().0..=range.end.row().0 {
13648 if let Some(selection) = self.selections.build_columnar_selection(
13649 &display_map,
13650 DisplayRow(row),
13651 &positions,
13652 selection.reversed,
13653 &text_layout_details,
13654 ) {
13655 stack.push(selection.id);
13656 columnar_selections.push(selection);
13657 }
13658 }
13659 if !stack.is_empty() {
13660 if above {
13661 stack.reverse();
13662 }
13663 state.groups.push(AddSelectionsGroup { above, stack });
13664 }
13665 }
13666
13667 let mut final_selections = Vec::new();
13668 let end_row = if above {
13669 DisplayRow(0)
13670 } else {
13671 display_map.max_point().row()
13672 };
13673
13674 let mut last_added_item_per_group = HashMap::default();
13675 for group in state.groups.iter_mut() {
13676 if let Some(last_id) = group.stack.last() {
13677 last_added_item_per_group.insert(*last_id, group);
13678 }
13679 }
13680
13681 for selection in columnar_selections {
13682 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
13683 if above == group.above {
13684 let range = selection.display_range(&display_map).sorted();
13685 debug_assert_eq!(range.start.row(), range.end.row());
13686 let mut row = range.start.row();
13687 let positions =
13688 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
13689 px(start)..px(end)
13690 } else {
13691 let start_x =
13692 display_map.x_for_display_point(range.start, &text_layout_details);
13693 let end_x =
13694 display_map.x_for_display_point(range.end, &text_layout_details);
13695 start_x.min(end_x)..start_x.max(end_x)
13696 };
13697
13698 let mut maybe_new_selection = None;
13699 while row != end_row {
13700 if above {
13701 row.0 -= 1;
13702 } else {
13703 row.0 += 1;
13704 }
13705 if let Some(new_selection) = self.selections.build_columnar_selection(
13706 &display_map,
13707 row,
13708 &positions,
13709 selection.reversed,
13710 &text_layout_details,
13711 ) {
13712 maybe_new_selection = Some(new_selection);
13713 break;
13714 }
13715 }
13716
13717 if let Some(new_selection) = maybe_new_selection {
13718 group.stack.push(new_selection.id);
13719 if above {
13720 final_selections.push(new_selection);
13721 final_selections.push(selection);
13722 } else {
13723 final_selections.push(selection);
13724 final_selections.push(new_selection);
13725 }
13726 } else {
13727 final_selections.push(selection);
13728 }
13729 } else {
13730 group.stack.pop();
13731 }
13732 } else {
13733 final_selections.push(selection);
13734 }
13735 }
13736
13737 self.change_selections(Default::default(), window, cx, |s| {
13738 s.select(final_selections);
13739 });
13740
13741 let final_selection_ids: HashSet<_> = self
13742 .selections
13743 .all::<Point>(cx)
13744 .iter()
13745 .map(|s| s.id)
13746 .collect();
13747 state.groups.retain_mut(|group| {
13748 // selections might get merged above so we remove invalid items from stacks
13749 group.stack.retain(|id| final_selection_ids.contains(id));
13750
13751 // single selection in stack can be treated as initial state
13752 group.stack.len() > 1
13753 });
13754
13755 if !state.groups.is_empty() {
13756 self.add_selections_state = Some(state);
13757 }
13758 }
13759
13760 fn select_match_ranges(
13761 &mut self,
13762 range: Range<usize>,
13763 reversed: bool,
13764 replace_newest: bool,
13765 auto_scroll: Option<Autoscroll>,
13766 window: &mut Window,
13767 cx: &mut Context<Editor>,
13768 ) {
13769 self.unfold_ranges(
13770 std::slice::from_ref(&range),
13771 false,
13772 auto_scroll.is_some(),
13773 cx,
13774 );
13775 let effects = if let Some(scroll) = auto_scroll {
13776 SelectionEffects::scroll(scroll)
13777 } else {
13778 SelectionEffects::no_scroll()
13779 };
13780 self.change_selections(effects, window, cx, |s| {
13781 if replace_newest {
13782 s.delete(s.newest_anchor().id);
13783 }
13784 if reversed {
13785 s.insert_range(range.end..range.start);
13786 } else {
13787 s.insert_range(range);
13788 }
13789 });
13790 }
13791
13792 pub fn select_next_match_internal(
13793 &mut self,
13794 display_map: &DisplaySnapshot,
13795 replace_newest: bool,
13796 autoscroll: Option<Autoscroll>,
13797 window: &mut Window,
13798 cx: &mut Context<Self>,
13799 ) -> Result<()> {
13800 let buffer = &display_map.buffer_snapshot;
13801 let mut selections = self.selections.all::<usize>(cx);
13802 if let Some(mut select_next_state) = self.select_next_state.take() {
13803 let query = &select_next_state.query;
13804 if !select_next_state.done {
13805 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13806 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13807 let mut next_selected_range = None;
13808
13809 let bytes_after_last_selection =
13810 buffer.bytes_in_range(last_selection.end..buffer.len());
13811 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
13812 let query_matches = query
13813 .stream_find_iter(bytes_after_last_selection)
13814 .map(|result| (last_selection.end, result))
13815 .chain(
13816 query
13817 .stream_find_iter(bytes_before_first_selection)
13818 .map(|result| (0, result)),
13819 );
13820
13821 for (start_offset, query_match) in query_matches {
13822 let query_match = query_match.unwrap(); // can only fail due to I/O
13823 let offset_range =
13824 start_offset + query_match.start()..start_offset + query_match.end();
13825
13826 if !select_next_state.wordwise
13827 || (!buffer.is_inside_word(offset_range.start, false)
13828 && !buffer.is_inside_word(offset_range.end, false))
13829 {
13830 // TODO: This is n^2, because we might check all the selections
13831 if !selections
13832 .iter()
13833 .any(|selection| selection.range().overlaps(&offset_range))
13834 {
13835 next_selected_range = Some(offset_range);
13836 break;
13837 }
13838 }
13839 }
13840
13841 if let Some(next_selected_range) = next_selected_range {
13842 self.select_match_ranges(
13843 next_selected_range,
13844 last_selection.reversed,
13845 replace_newest,
13846 autoscroll,
13847 window,
13848 cx,
13849 );
13850 } else {
13851 select_next_state.done = true;
13852 }
13853 }
13854
13855 self.select_next_state = Some(select_next_state);
13856 } else {
13857 let mut only_carets = true;
13858 let mut same_text_selected = true;
13859 let mut selected_text = None;
13860
13861 let mut selections_iter = selections.iter().peekable();
13862 while let Some(selection) = selections_iter.next() {
13863 if selection.start != selection.end {
13864 only_carets = false;
13865 }
13866
13867 if same_text_selected {
13868 if selected_text.is_none() {
13869 selected_text =
13870 Some(buffer.text_for_range(selection.range()).collect::<String>());
13871 }
13872
13873 if let Some(next_selection) = selections_iter.peek() {
13874 if next_selection.range().len() == selection.range().len() {
13875 let next_selected_text = buffer
13876 .text_for_range(next_selection.range())
13877 .collect::<String>();
13878 if Some(next_selected_text) != selected_text {
13879 same_text_selected = false;
13880 selected_text = None;
13881 }
13882 } else {
13883 same_text_selected = false;
13884 selected_text = None;
13885 }
13886 }
13887 }
13888 }
13889
13890 if only_carets {
13891 for selection in &mut selections {
13892 let (word_range, _) = buffer.surrounding_word(selection.start, false);
13893 selection.start = word_range.start;
13894 selection.end = word_range.end;
13895 selection.goal = SelectionGoal::None;
13896 selection.reversed = false;
13897 self.select_match_ranges(
13898 selection.start..selection.end,
13899 selection.reversed,
13900 replace_newest,
13901 autoscroll,
13902 window,
13903 cx,
13904 );
13905 }
13906
13907 if selections.len() == 1 {
13908 let selection = selections
13909 .last()
13910 .expect("ensured that there's only one selection");
13911 let query = buffer
13912 .text_for_range(selection.start..selection.end)
13913 .collect::<String>();
13914 let is_empty = query.is_empty();
13915 let select_state = SelectNextState {
13916 query: AhoCorasick::new(&[query])?,
13917 wordwise: true,
13918 done: is_empty,
13919 };
13920 self.select_next_state = Some(select_state);
13921 } else {
13922 self.select_next_state = None;
13923 }
13924 } else if let Some(selected_text) = selected_text {
13925 self.select_next_state = Some(SelectNextState {
13926 query: AhoCorasick::new(&[selected_text])?,
13927 wordwise: false,
13928 done: false,
13929 });
13930 self.select_next_match_internal(
13931 display_map,
13932 replace_newest,
13933 autoscroll,
13934 window,
13935 cx,
13936 )?;
13937 }
13938 }
13939 Ok(())
13940 }
13941
13942 pub fn select_all_matches(
13943 &mut self,
13944 _action: &SelectAllMatches,
13945 window: &mut Window,
13946 cx: &mut Context<Self>,
13947 ) -> Result<()> {
13948 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13949
13950 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13951
13952 self.select_next_match_internal(&display_map, false, None, window, cx)?;
13953 let Some(select_next_state) = self.select_next_state.as_mut() else {
13954 return Ok(());
13955 };
13956 if select_next_state.done {
13957 return Ok(());
13958 }
13959
13960 let mut new_selections = Vec::new();
13961
13962 let reversed = self.selections.oldest::<usize>(cx).reversed;
13963 let buffer = &display_map.buffer_snapshot;
13964 let query_matches = select_next_state
13965 .query
13966 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
13967
13968 for query_match in query_matches.into_iter() {
13969 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
13970 let offset_range = if reversed {
13971 query_match.end()..query_match.start()
13972 } else {
13973 query_match.start()..query_match.end()
13974 };
13975
13976 if !select_next_state.wordwise
13977 || (!buffer.is_inside_word(offset_range.start, false)
13978 && !buffer.is_inside_word(offset_range.end, false))
13979 {
13980 new_selections.push(offset_range.start..offset_range.end);
13981 }
13982 }
13983
13984 select_next_state.done = true;
13985
13986 if new_selections.is_empty() {
13987 log::error!("bug: new_selections is empty in select_all_matches");
13988 return Ok(());
13989 }
13990
13991 self.unfold_ranges(&new_selections.clone(), false, false, cx);
13992 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
13993 selections.select_ranges(new_selections)
13994 });
13995
13996 Ok(())
13997 }
13998
13999 pub fn select_next(
14000 &mut self,
14001 action: &SelectNext,
14002 window: &mut Window,
14003 cx: &mut Context<Self>,
14004 ) -> Result<()> {
14005 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14006 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14007 self.select_next_match_internal(
14008 &display_map,
14009 action.replace_newest,
14010 Some(Autoscroll::newest()),
14011 window,
14012 cx,
14013 )?;
14014 Ok(())
14015 }
14016
14017 pub fn select_previous(
14018 &mut self,
14019 action: &SelectPrevious,
14020 window: &mut Window,
14021 cx: &mut Context<Self>,
14022 ) -> Result<()> {
14023 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14024 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14025 let buffer = &display_map.buffer_snapshot;
14026 let mut selections = self.selections.all::<usize>(cx);
14027 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14028 let query = &select_prev_state.query;
14029 if !select_prev_state.done {
14030 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14031 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14032 let mut next_selected_range = None;
14033 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14034 let bytes_before_last_selection =
14035 buffer.reversed_bytes_in_range(0..last_selection.start);
14036 let bytes_after_first_selection =
14037 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14038 let query_matches = query
14039 .stream_find_iter(bytes_before_last_selection)
14040 .map(|result| (last_selection.start, result))
14041 .chain(
14042 query
14043 .stream_find_iter(bytes_after_first_selection)
14044 .map(|result| (buffer.len(), result)),
14045 );
14046 for (end_offset, query_match) in query_matches {
14047 let query_match = query_match.unwrap(); // can only fail due to I/O
14048 let offset_range =
14049 end_offset - query_match.end()..end_offset - query_match.start();
14050
14051 if !select_prev_state.wordwise
14052 || (!buffer.is_inside_word(offset_range.start, false)
14053 && !buffer.is_inside_word(offset_range.end, false))
14054 {
14055 next_selected_range = Some(offset_range);
14056 break;
14057 }
14058 }
14059
14060 if let Some(next_selected_range) = next_selected_range {
14061 self.select_match_ranges(
14062 next_selected_range,
14063 last_selection.reversed,
14064 action.replace_newest,
14065 Some(Autoscroll::newest()),
14066 window,
14067 cx,
14068 );
14069 } else {
14070 select_prev_state.done = true;
14071 }
14072 }
14073
14074 self.select_prev_state = Some(select_prev_state);
14075 } else {
14076 let mut only_carets = true;
14077 let mut same_text_selected = true;
14078 let mut selected_text = None;
14079
14080 let mut selections_iter = selections.iter().peekable();
14081 while let Some(selection) = selections_iter.next() {
14082 if selection.start != selection.end {
14083 only_carets = false;
14084 }
14085
14086 if same_text_selected {
14087 if selected_text.is_none() {
14088 selected_text =
14089 Some(buffer.text_for_range(selection.range()).collect::<String>());
14090 }
14091
14092 if let Some(next_selection) = selections_iter.peek() {
14093 if next_selection.range().len() == selection.range().len() {
14094 let next_selected_text = buffer
14095 .text_for_range(next_selection.range())
14096 .collect::<String>();
14097 if Some(next_selected_text) != selected_text {
14098 same_text_selected = false;
14099 selected_text = None;
14100 }
14101 } else {
14102 same_text_selected = false;
14103 selected_text = None;
14104 }
14105 }
14106 }
14107 }
14108
14109 if only_carets {
14110 for selection in &mut selections {
14111 let (word_range, _) = buffer.surrounding_word(selection.start, false);
14112 selection.start = word_range.start;
14113 selection.end = word_range.end;
14114 selection.goal = SelectionGoal::None;
14115 selection.reversed = false;
14116 self.select_match_ranges(
14117 selection.start..selection.end,
14118 selection.reversed,
14119 action.replace_newest,
14120 Some(Autoscroll::newest()),
14121 window,
14122 cx,
14123 );
14124 }
14125 if selections.len() == 1 {
14126 let selection = selections
14127 .last()
14128 .expect("ensured that there's only one selection");
14129 let query = buffer
14130 .text_for_range(selection.start..selection.end)
14131 .collect::<String>();
14132 let is_empty = query.is_empty();
14133 let select_state = SelectNextState {
14134 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
14135 wordwise: true,
14136 done: is_empty,
14137 };
14138 self.select_prev_state = Some(select_state);
14139 } else {
14140 self.select_prev_state = None;
14141 }
14142 } else if let Some(selected_text) = selected_text {
14143 self.select_prev_state = Some(SelectNextState {
14144 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
14145 wordwise: false,
14146 done: false,
14147 });
14148 self.select_previous(action, window, cx)?;
14149 }
14150 }
14151 Ok(())
14152 }
14153
14154 pub fn find_next_match(
14155 &mut self,
14156 _: &FindNextMatch,
14157 window: &mut Window,
14158 cx: &mut Context<Self>,
14159 ) -> Result<()> {
14160 let selections = self.selections.disjoint_anchors();
14161 match selections.first() {
14162 Some(first) if selections.len() >= 2 => {
14163 self.change_selections(Default::default(), window, cx, |s| {
14164 s.select_ranges([first.range()]);
14165 });
14166 }
14167 _ => self.select_next(
14168 &SelectNext {
14169 replace_newest: true,
14170 },
14171 window,
14172 cx,
14173 )?,
14174 }
14175 Ok(())
14176 }
14177
14178 pub fn find_previous_match(
14179 &mut self,
14180 _: &FindPreviousMatch,
14181 window: &mut Window,
14182 cx: &mut Context<Self>,
14183 ) -> Result<()> {
14184 let selections = self.selections.disjoint_anchors();
14185 match selections.last() {
14186 Some(last) if selections.len() >= 2 => {
14187 self.change_selections(Default::default(), window, cx, |s| {
14188 s.select_ranges([last.range()]);
14189 });
14190 }
14191 _ => self.select_previous(
14192 &SelectPrevious {
14193 replace_newest: true,
14194 },
14195 window,
14196 cx,
14197 )?,
14198 }
14199 Ok(())
14200 }
14201
14202 pub fn toggle_comments(
14203 &mut self,
14204 action: &ToggleComments,
14205 window: &mut Window,
14206 cx: &mut Context<Self>,
14207 ) {
14208 if self.read_only(cx) {
14209 return;
14210 }
14211 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14212 let text_layout_details = &self.text_layout_details(window);
14213 self.transact(window, cx, |this, window, cx| {
14214 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
14215 let mut edits = Vec::new();
14216 let mut selection_edit_ranges = Vec::new();
14217 let mut last_toggled_row = None;
14218 let snapshot = this.buffer.read(cx).read(cx);
14219 let empty_str: Arc<str> = Arc::default();
14220 let mut suffixes_inserted = Vec::new();
14221 let ignore_indent = action.ignore_indent;
14222
14223 fn comment_prefix_range(
14224 snapshot: &MultiBufferSnapshot,
14225 row: MultiBufferRow,
14226 comment_prefix: &str,
14227 comment_prefix_whitespace: &str,
14228 ignore_indent: bool,
14229 ) -> Range<Point> {
14230 let indent_size = if ignore_indent {
14231 0
14232 } else {
14233 snapshot.indent_size_for_line(row).len
14234 };
14235
14236 let start = Point::new(row.0, indent_size);
14237
14238 let mut line_bytes = snapshot
14239 .bytes_in_range(start..snapshot.max_point())
14240 .flatten()
14241 .copied();
14242
14243 // If this line currently begins with the line comment prefix, then record
14244 // the range containing the prefix.
14245 if line_bytes
14246 .by_ref()
14247 .take(comment_prefix.len())
14248 .eq(comment_prefix.bytes())
14249 {
14250 // Include any whitespace that matches the comment prefix.
14251 let matching_whitespace_len = line_bytes
14252 .zip(comment_prefix_whitespace.bytes())
14253 .take_while(|(a, b)| a == b)
14254 .count() as u32;
14255 let end = Point::new(
14256 start.row,
14257 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14258 );
14259 start..end
14260 } else {
14261 start..start
14262 }
14263 }
14264
14265 fn comment_suffix_range(
14266 snapshot: &MultiBufferSnapshot,
14267 row: MultiBufferRow,
14268 comment_suffix: &str,
14269 comment_suffix_has_leading_space: bool,
14270 ) -> Range<Point> {
14271 let end = Point::new(row.0, snapshot.line_len(row));
14272 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14273
14274 let mut line_end_bytes = snapshot
14275 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14276 .flatten()
14277 .copied();
14278
14279 let leading_space_len = if suffix_start_column > 0
14280 && line_end_bytes.next() == Some(b' ')
14281 && comment_suffix_has_leading_space
14282 {
14283 1
14284 } else {
14285 0
14286 };
14287
14288 // If this line currently begins with the line comment prefix, then record
14289 // the range containing the prefix.
14290 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
14291 let start = Point::new(end.row, suffix_start_column - leading_space_len);
14292 start..end
14293 } else {
14294 end..end
14295 }
14296 }
14297
14298 // TODO: Handle selections that cross excerpts
14299 for selection in &mut selections {
14300 let start_column = snapshot
14301 .indent_size_for_line(MultiBufferRow(selection.start.row))
14302 .len;
14303 let language = if let Some(language) =
14304 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
14305 {
14306 language
14307 } else {
14308 continue;
14309 };
14310
14311 selection_edit_ranges.clear();
14312
14313 // If multiple selections contain a given row, avoid processing that
14314 // row more than once.
14315 let mut start_row = MultiBufferRow(selection.start.row);
14316 if last_toggled_row == Some(start_row) {
14317 start_row = start_row.next_row();
14318 }
14319 let end_row =
14320 if selection.end.row > selection.start.row && selection.end.column == 0 {
14321 MultiBufferRow(selection.end.row - 1)
14322 } else {
14323 MultiBufferRow(selection.end.row)
14324 };
14325 last_toggled_row = Some(end_row);
14326
14327 if start_row > end_row {
14328 continue;
14329 }
14330
14331 // If the language has line comments, toggle those.
14332 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
14333
14334 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
14335 if ignore_indent {
14336 full_comment_prefixes = full_comment_prefixes
14337 .into_iter()
14338 .map(|s| Arc::from(s.trim_end()))
14339 .collect();
14340 }
14341
14342 if !full_comment_prefixes.is_empty() {
14343 let first_prefix = full_comment_prefixes
14344 .first()
14345 .expect("prefixes is non-empty");
14346 let prefix_trimmed_lengths = full_comment_prefixes
14347 .iter()
14348 .map(|p| p.trim_end_matches(' ').len())
14349 .collect::<SmallVec<[usize; 4]>>();
14350
14351 let mut all_selection_lines_are_comments = true;
14352
14353 for row in start_row.0..=end_row.0 {
14354 let row = MultiBufferRow(row);
14355 if start_row < end_row && snapshot.is_line_blank(row) {
14356 continue;
14357 }
14358
14359 let prefix_range = full_comment_prefixes
14360 .iter()
14361 .zip(prefix_trimmed_lengths.iter().copied())
14362 .map(|(prefix, trimmed_prefix_len)| {
14363 comment_prefix_range(
14364 snapshot.deref(),
14365 row,
14366 &prefix[..trimmed_prefix_len],
14367 &prefix[trimmed_prefix_len..],
14368 ignore_indent,
14369 )
14370 })
14371 .max_by_key(|range| range.end.column - range.start.column)
14372 .expect("prefixes is non-empty");
14373
14374 if prefix_range.is_empty() {
14375 all_selection_lines_are_comments = false;
14376 }
14377
14378 selection_edit_ranges.push(prefix_range);
14379 }
14380
14381 if all_selection_lines_are_comments {
14382 edits.extend(
14383 selection_edit_ranges
14384 .iter()
14385 .cloned()
14386 .map(|range| (range, empty_str.clone())),
14387 );
14388 } else {
14389 let min_column = selection_edit_ranges
14390 .iter()
14391 .map(|range| range.start.column)
14392 .min()
14393 .unwrap_or(0);
14394 edits.extend(selection_edit_ranges.iter().map(|range| {
14395 let position = Point::new(range.start.row, min_column);
14396 (position..position, first_prefix.clone())
14397 }));
14398 }
14399 } else if let Some(BlockCommentConfig {
14400 start: full_comment_prefix,
14401 end: comment_suffix,
14402 ..
14403 }) = language.block_comment()
14404 {
14405 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
14406 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
14407 let prefix_range = comment_prefix_range(
14408 snapshot.deref(),
14409 start_row,
14410 comment_prefix,
14411 comment_prefix_whitespace,
14412 ignore_indent,
14413 );
14414 let suffix_range = comment_suffix_range(
14415 snapshot.deref(),
14416 end_row,
14417 comment_suffix.trim_start_matches(' '),
14418 comment_suffix.starts_with(' '),
14419 );
14420
14421 if prefix_range.is_empty() || suffix_range.is_empty() {
14422 edits.push((
14423 prefix_range.start..prefix_range.start,
14424 full_comment_prefix.clone(),
14425 ));
14426 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
14427 suffixes_inserted.push((end_row, comment_suffix.len()));
14428 } else {
14429 edits.push((prefix_range, empty_str.clone()));
14430 edits.push((suffix_range, empty_str.clone()));
14431 }
14432 } else {
14433 continue;
14434 }
14435 }
14436
14437 drop(snapshot);
14438 this.buffer.update(cx, |buffer, cx| {
14439 buffer.edit(edits, None, cx);
14440 });
14441
14442 // Adjust selections so that they end before any comment suffixes that
14443 // were inserted.
14444 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
14445 let mut selections = this.selections.all::<Point>(cx);
14446 let snapshot = this.buffer.read(cx).read(cx);
14447 for selection in &mut selections {
14448 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
14449 match row.cmp(&MultiBufferRow(selection.end.row)) {
14450 Ordering::Less => {
14451 suffixes_inserted.next();
14452 continue;
14453 }
14454 Ordering::Greater => break,
14455 Ordering::Equal => {
14456 if selection.end.column == snapshot.line_len(row) {
14457 if selection.is_empty() {
14458 selection.start.column -= suffix_len as u32;
14459 }
14460 selection.end.column -= suffix_len as u32;
14461 }
14462 break;
14463 }
14464 }
14465 }
14466 }
14467
14468 drop(snapshot);
14469 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
14470
14471 let selections = this.selections.all::<Point>(cx);
14472 let selections_on_single_row = selections.windows(2).all(|selections| {
14473 selections[0].start.row == selections[1].start.row
14474 && selections[0].end.row == selections[1].end.row
14475 && selections[0].start.row == selections[0].end.row
14476 });
14477 let selections_selecting = selections
14478 .iter()
14479 .any(|selection| selection.start != selection.end);
14480 let advance_downwards = action.advance_downwards
14481 && selections_on_single_row
14482 && !selections_selecting
14483 && !matches!(this.mode, EditorMode::SingleLine { .. });
14484
14485 if advance_downwards {
14486 let snapshot = this.buffer.read(cx).snapshot(cx);
14487
14488 this.change_selections(Default::default(), window, cx, |s| {
14489 s.move_cursors_with(|display_snapshot, display_point, _| {
14490 let mut point = display_point.to_point(display_snapshot);
14491 point.row += 1;
14492 point = snapshot.clip_point(point, Bias::Left);
14493 let display_point = point.to_display_point(display_snapshot);
14494 let goal = SelectionGoal::HorizontalPosition(
14495 display_snapshot
14496 .x_for_display_point(display_point, text_layout_details)
14497 .into(),
14498 );
14499 (display_point, goal)
14500 })
14501 });
14502 }
14503 });
14504 }
14505
14506 pub fn select_enclosing_symbol(
14507 &mut self,
14508 _: &SelectEnclosingSymbol,
14509 window: &mut Window,
14510 cx: &mut Context<Self>,
14511 ) {
14512 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14513
14514 let buffer = self.buffer.read(cx).snapshot(cx);
14515 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
14516
14517 fn update_selection(
14518 selection: &Selection<usize>,
14519 buffer_snap: &MultiBufferSnapshot,
14520 ) -> Option<Selection<usize>> {
14521 let cursor = selection.head();
14522 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
14523 for symbol in symbols.iter().rev() {
14524 let start = symbol.range.start.to_offset(buffer_snap);
14525 let end = symbol.range.end.to_offset(buffer_snap);
14526 let new_range = start..end;
14527 if start < selection.start || end > selection.end {
14528 return Some(Selection {
14529 id: selection.id,
14530 start: new_range.start,
14531 end: new_range.end,
14532 goal: SelectionGoal::None,
14533 reversed: selection.reversed,
14534 });
14535 }
14536 }
14537 None
14538 }
14539
14540 let mut selected_larger_symbol = false;
14541 let new_selections = old_selections
14542 .iter()
14543 .map(|selection| match update_selection(selection, &buffer) {
14544 Some(new_selection) => {
14545 if new_selection.range() != selection.range() {
14546 selected_larger_symbol = true;
14547 }
14548 new_selection
14549 }
14550 None => selection.clone(),
14551 })
14552 .collect::<Vec<_>>();
14553
14554 if selected_larger_symbol {
14555 self.change_selections(Default::default(), window, cx, |s| {
14556 s.select(new_selections);
14557 });
14558 }
14559 }
14560
14561 pub fn select_larger_syntax_node(
14562 &mut self,
14563 _: &SelectLargerSyntaxNode,
14564 window: &mut Window,
14565 cx: &mut Context<Self>,
14566 ) {
14567 let Some(visible_row_count) = self.visible_row_count() else {
14568 return;
14569 };
14570 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
14571 if old_selections.is_empty() {
14572 return;
14573 }
14574
14575 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14576
14577 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14578 let buffer = self.buffer.read(cx).snapshot(cx);
14579
14580 let mut selected_larger_node = false;
14581 let mut new_selections = old_selections
14582 .iter()
14583 .map(|selection| {
14584 let old_range = selection.start..selection.end;
14585
14586 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
14587 // manually select word at selection
14588 if ["string_content", "inline"].contains(&node.kind()) {
14589 let (word_range, _) = buffer.surrounding_word(old_range.start, false);
14590 // ignore if word is already selected
14591 if !word_range.is_empty() && old_range != word_range {
14592 let (last_word_range, _) =
14593 buffer.surrounding_word(old_range.end, false);
14594 // only select word if start and end point belongs to same word
14595 if word_range == last_word_range {
14596 selected_larger_node = true;
14597 return Selection {
14598 id: selection.id,
14599 start: word_range.start,
14600 end: word_range.end,
14601 goal: SelectionGoal::None,
14602 reversed: selection.reversed,
14603 };
14604 }
14605 }
14606 }
14607 }
14608
14609 let mut new_range = old_range.clone();
14610 while let Some((_node, containing_range)) =
14611 buffer.syntax_ancestor(new_range.clone())
14612 {
14613 new_range = match containing_range {
14614 MultiOrSingleBufferOffsetRange::Single(_) => break,
14615 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14616 };
14617 if !display_map.intersects_fold(new_range.start)
14618 && !display_map.intersects_fold(new_range.end)
14619 {
14620 break;
14621 }
14622 }
14623
14624 selected_larger_node |= new_range != old_range;
14625 Selection {
14626 id: selection.id,
14627 start: new_range.start,
14628 end: new_range.end,
14629 goal: SelectionGoal::None,
14630 reversed: selection.reversed,
14631 }
14632 })
14633 .collect::<Vec<_>>();
14634
14635 if !selected_larger_node {
14636 return; // don't put this call in the history
14637 }
14638
14639 // scroll based on transformation done to the last selection created by the user
14640 let (last_old, last_new) = old_selections
14641 .last()
14642 .zip(new_selections.last().cloned())
14643 .expect("old_selections isn't empty");
14644
14645 // revert selection
14646 let is_selection_reversed = {
14647 let should_newest_selection_be_reversed = last_old.start != last_new.start;
14648 new_selections.last_mut().expect("checked above").reversed =
14649 should_newest_selection_be_reversed;
14650 should_newest_selection_be_reversed
14651 };
14652
14653 if selected_larger_node {
14654 self.select_syntax_node_history.disable_clearing = true;
14655 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14656 s.select(new_selections.clone());
14657 });
14658 self.select_syntax_node_history.disable_clearing = false;
14659 }
14660
14661 let start_row = last_new.start.to_display_point(&display_map).row().0;
14662 let end_row = last_new.end.to_display_point(&display_map).row().0;
14663 let selection_height = end_row - start_row + 1;
14664 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
14665
14666 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
14667 let scroll_behavior = if fits_on_the_screen {
14668 self.request_autoscroll(Autoscroll::fit(), cx);
14669 SelectSyntaxNodeScrollBehavior::FitSelection
14670 } else if is_selection_reversed {
14671 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14672 SelectSyntaxNodeScrollBehavior::CursorTop
14673 } else {
14674 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14675 SelectSyntaxNodeScrollBehavior::CursorBottom
14676 };
14677
14678 self.select_syntax_node_history.push((
14679 old_selections,
14680 scroll_behavior,
14681 is_selection_reversed,
14682 ));
14683 }
14684
14685 pub fn select_smaller_syntax_node(
14686 &mut self,
14687 _: &SelectSmallerSyntaxNode,
14688 window: &mut Window,
14689 cx: &mut Context<Self>,
14690 ) {
14691 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14692
14693 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
14694 self.select_syntax_node_history.pop()
14695 {
14696 if let Some(selection) = selections.last_mut() {
14697 selection.reversed = is_selection_reversed;
14698 }
14699
14700 self.select_syntax_node_history.disable_clearing = true;
14701 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14702 s.select(selections.to_vec());
14703 });
14704 self.select_syntax_node_history.disable_clearing = false;
14705
14706 match scroll_behavior {
14707 SelectSyntaxNodeScrollBehavior::CursorTop => {
14708 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14709 }
14710 SelectSyntaxNodeScrollBehavior::FitSelection => {
14711 self.request_autoscroll(Autoscroll::fit(), cx);
14712 }
14713 SelectSyntaxNodeScrollBehavior::CursorBottom => {
14714 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14715 }
14716 }
14717 }
14718 }
14719
14720 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
14721 if !EditorSettings::get_global(cx).gutter.runnables {
14722 self.clear_tasks();
14723 return Task::ready(());
14724 }
14725 let project = self.project.as_ref().map(Entity::downgrade);
14726 let task_sources = self.lsp_task_sources(cx);
14727 let multi_buffer = self.buffer.downgrade();
14728 cx.spawn_in(window, async move |editor, cx| {
14729 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
14730 let Some(project) = project.and_then(|p| p.upgrade()) else {
14731 return;
14732 };
14733 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
14734 this.display_map.update(cx, |map, cx| map.snapshot(cx))
14735 }) else {
14736 return;
14737 };
14738
14739 let hide_runnables = project
14740 .update(cx, |project, cx| {
14741 // Do not display any test indicators in non-dev server remote projects.
14742 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
14743 })
14744 .unwrap_or(true);
14745 if hide_runnables {
14746 return;
14747 }
14748 let new_rows =
14749 cx.background_spawn({
14750 let snapshot = display_snapshot.clone();
14751 async move {
14752 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
14753 }
14754 })
14755 .await;
14756 let Ok(lsp_tasks) =
14757 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
14758 else {
14759 return;
14760 };
14761 let lsp_tasks = lsp_tasks.await;
14762
14763 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
14764 lsp_tasks
14765 .into_iter()
14766 .flat_map(|(kind, tasks)| {
14767 tasks.into_iter().filter_map(move |(location, task)| {
14768 Some((kind.clone(), location?, task))
14769 })
14770 })
14771 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
14772 let buffer = location.target.buffer;
14773 let buffer_snapshot = buffer.read(cx).snapshot();
14774 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
14775 |(excerpt_id, snapshot, _)| {
14776 if snapshot.remote_id() == buffer_snapshot.remote_id() {
14777 display_snapshot
14778 .buffer_snapshot
14779 .anchor_in_excerpt(excerpt_id, location.target.range.start)
14780 } else {
14781 None
14782 }
14783 },
14784 );
14785 if let Some(offset) = offset {
14786 let task_buffer_range =
14787 location.target.range.to_point(&buffer_snapshot);
14788 let context_buffer_range =
14789 task_buffer_range.to_offset(&buffer_snapshot);
14790 let context_range = BufferOffset(context_buffer_range.start)
14791 ..BufferOffset(context_buffer_range.end);
14792
14793 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
14794 .or_insert_with(|| RunnableTasks {
14795 templates: Vec::new(),
14796 offset,
14797 column: task_buffer_range.start.column,
14798 extra_variables: HashMap::default(),
14799 context_range,
14800 })
14801 .templates
14802 .push((kind, task.original_task().clone()));
14803 }
14804
14805 acc
14806 })
14807 }) else {
14808 return;
14809 };
14810
14811 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
14812 buffer.language_settings(cx).tasks.prefer_lsp
14813 }) else {
14814 return;
14815 };
14816
14817 let rows = Self::runnable_rows(
14818 project,
14819 display_snapshot,
14820 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
14821 new_rows,
14822 cx.clone(),
14823 )
14824 .await;
14825 editor
14826 .update(cx, |editor, _| {
14827 editor.clear_tasks();
14828 for (key, mut value) in rows {
14829 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
14830 value.templates.extend(lsp_tasks.templates);
14831 }
14832
14833 editor.insert_tasks(key, value);
14834 }
14835 for (key, value) in lsp_tasks_by_rows {
14836 editor.insert_tasks(key, value);
14837 }
14838 })
14839 .ok();
14840 })
14841 }
14842 fn fetch_runnable_ranges(
14843 snapshot: &DisplaySnapshot,
14844 range: Range<Anchor>,
14845 ) -> Vec<language::RunnableRange> {
14846 snapshot.buffer_snapshot.runnable_ranges(range).collect()
14847 }
14848
14849 fn runnable_rows(
14850 project: Entity<Project>,
14851 snapshot: DisplaySnapshot,
14852 prefer_lsp: bool,
14853 runnable_ranges: Vec<RunnableRange>,
14854 cx: AsyncWindowContext,
14855 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
14856 cx.spawn(async move |cx| {
14857 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
14858 for mut runnable in runnable_ranges {
14859 let Some(tasks) = cx
14860 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
14861 .ok()
14862 else {
14863 continue;
14864 };
14865 let mut tasks = tasks.await;
14866
14867 if prefer_lsp {
14868 tasks.retain(|(task_kind, _)| {
14869 !matches!(task_kind, TaskSourceKind::Language { .. })
14870 });
14871 }
14872 if tasks.is_empty() {
14873 continue;
14874 }
14875
14876 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
14877 let Some(row) = snapshot
14878 .buffer_snapshot
14879 .buffer_line_for_row(MultiBufferRow(point.row))
14880 .map(|(_, range)| range.start.row)
14881 else {
14882 continue;
14883 };
14884
14885 let context_range =
14886 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
14887 runnable_rows.push((
14888 (runnable.buffer_id, row),
14889 RunnableTasks {
14890 templates: tasks,
14891 offset: snapshot
14892 .buffer_snapshot
14893 .anchor_before(runnable.run_range.start),
14894 context_range,
14895 column: point.column,
14896 extra_variables: runnable.extra_captures,
14897 },
14898 ));
14899 }
14900 runnable_rows
14901 })
14902 }
14903
14904 fn templates_with_tags(
14905 project: &Entity<Project>,
14906 runnable: &mut Runnable,
14907 cx: &mut App,
14908 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
14909 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
14910 let (worktree_id, file) = project
14911 .buffer_for_id(runnable.buffer, cx)
14912 .and_then(|buffer| buffer.read(cx).file())
14913 .map(|file| (file.worktree_id(cx), file.clone()))
14914 .unzip();
14915
14916 (
14917 project.task_store().read(cx).task_inventory().cloned(),
14918 worktree_id,
14919 file,
14920 )
14921 });
14922
14923 let tags = mem::take(&mut runnable.tags);
14924 let language = runnable.language.clone();
14925 cx.spawn(async move |cx| {
14926 let mut templates_with_tags = Vec::new();
14927 if let Some(inventory) = inventory {
14928 for RunnableTag(tag) in tags {
14929 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
14930 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
14931 }) else {
14932 return templates_with_tags;
14933 };
14934 templates_with_tags.extend(new_tasks.await.into_iter().filter(
14935 move |(_, template)| {
14936 template.tags.iter().any(|source_tag| source_tag == &tag)
14937 },
14938 ));
14939 }
14940 }
14941 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
14942
14943 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
14944 // Strongest source wins; if we have worktree tag binding, prefer that to
14945 // global and language bindings;
14946 // if we have a global binding, prefer that to language binding.
14947 let first_mismatch = templates_with_tags
14948 .iter()
14949 .position(|(tag_source, _)| tag_source != leading_tag_source);
14950 if let Some(index) = first_mismatch {
14951 templates_with_tags.truncate(index);
14952 }
14953 }
14954
14955 templates_with_tags
14956 })
14957 }
14958
14959 pub fn move_to_enclosing_bracket(
14960 &mut self,
14961 _: &MoveToEnclosingBracket,
14962 window: &mut Window,
14963 cx: &mut Context<Self>,
14964 ) {
14965 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14966 self.change_selections(Default::default(), window, cx, |s| {
14967 s.move_offsets_with(|snapshot, selection| {
14968 let Some(enclosing_bracket_ranges) =
14969 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
14970 else {
14971 return;
14972 };
14973
14974 let mut best_length = usize::MAX;
14975 let mut best_inside = false;
14976 let mut best_in_bracket_range = false;
14977 let mut best_destination = None;
14978 for (open, close) in enclosing_bracket_ranges {
14979 let close = close.to_inclusive();
14980 let length = close.end() - open.start;
14981 let inside = selection.start >= open.end && selection.end <= *close.start();
14982 let in_bracket_range = open.to_inclusive().contains(&selection.head())
14983 || close.contains(&selection.head());
14984
14985 // If best is next to a bracket and current isn't, skip
14986 if !in_bracket_range && best_in_bracket_range {
14987 continue;
14988 }
14989
14990 // Prefer smaller lengths unless best is inside and current isn't
14991 if length > best_length && (best_inside || !inside) {
14992 continue;
14993 }
14994
14995 best_length = length;
14996 best_inside = inside;
14997 best_in_bracket_range = in_bracket_range;
14998 best_destination = Some(
14999 if close.contains(&selection.start) && close.contains(&selection.end) {
15000 if inside { open.end } else { open.start }
15001 } else if inside {
15002 *close.start()
15003 } else {
15004 *close.end()
15005 },
15006 );
15007 }
15008
15009 if let Some(destination) = best_destination {
15010 selection.collapse_to(destination, SelectionGoal::None);
15011 }
15012 })
15013 });
15014 }
15015
15016 pub fn undo_selection(
15017 &mut self,
15018 _: &UndoSelection,
15019 window: &mut Window,
15020 cx: &mut Context<Self>,
15021 ) {
15022 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15023 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
15024 self.selection_history.mode = SelectionHistoryMode::Undoing;
15025 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15026 this.end_selection(window, cx);
15027 this.change_selections(
15028 SelectionEffects::scroll(Autoscroll::newest()),
15029 window,
15030 cx,
15031 |s| s.select_anchors(entry.selections.to_vec()),
15032 );
15033 });
15034 self.selection_history.mode = SelectionHistoryMode::Normal;
15035
15036 self.select_next_state = entry.select_next_state;
15037 self.select_prev_state = entry.select_prev_state;
15038 self.add_selections_state = entry.add_selections_state;
15039 }
15040 }
15041
15042 pub fn redo_selection(
15043 &mut self,
15044 _: &RedoSelection,
15045 window: &mut Window,
15046 cx: &mut Context<Self>,
15047 ) {
15048 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15049 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
15050 self.selection_history.mode = SelectionHistoryMode::Redoing;
15051 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15052 this.end_selection(window, cx);
15053 this.change_selections(
15054 SelectionEffects::scroll(Autoscroll::newest()),
15055 window,
15056 cx,
15057 |s| s.select_anchors(entry.selections.to_vec()),
15058 );
15059 });
15060 self.selection_history.mode = SelectionHistoryMode::Normal;
15061
15062 self.select_next_state = entry.select_next_state;
15063 self.select_prev_state = entry.select_prev_state;
15064 self.add_selections_state = entry.add_selections_state;
15065 }
15066 }
15067
15068 pub fn expand_excerpts(
15069 &mut self,
15070 action: &ExpandExcerpts,
15071 _: &mut Window,
15072 cx: &mut Context<Self>,
15073 ) {
15074 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
15075 }
15076
15077 pub fn expand_excerpts_down(
15078 &mut self,
15079 action: &ExpandExcerptsDown,
15080 _: &mut Window,
15081 cx: &mut Context<Self>,
15082 ) {
15083 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
15084 }
15085
15086 pub fn expand_excerpts_up(
15087 &mut self,
15088 action: &ExpandExcerptsUp,
15089 _: &mut Window,
15090 cx: &mut Context<Self>,
15091 ) {
15092 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
15093 }
15094
15095 pub fn expand_excerpts_for_direction(
15096 &mut self,
15097 lines: u32,
15098 direction: ExpandExcerptDirection,
15099
15100 cx: &mut Context<Self>,
15101 ) {
15102 let selections = self.selections.disjoint_anchors();
15103
15104 let lines = if lines == 0 {
15105 EditorSettings::get_global(cx).expand_excerpt_lines
15106 } else {
15107 lines
15108 };
15109
15110 self.buffer.update(cx, |buffer, cx| {
15111 let snapshot = buffer.snapshot(cx);
15112 let mut excerpt_ids = selections
15113 .iter()
15114 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
15115 .collect::<Vec<_>>();
15116 excerpt_ids.sort();
15117 excerpt_ids.dedup();
15118 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
15119 })
15120 }
15121
15122 pub fn expand_excerpt(
15123 &mut self,
15124 excerpt: ExcerptId,
15125 direction: ExpandExcerptDirection,
15126 window: &mut Window,
15127 cx: &mut Context<Self>,
15128 ) {
15129 let current_scroll_position = self.scroll_position(cx);
15130 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
15131 let mut should_scroll_up = false;
15132
15133 if direction == ExpandExcerptDirection::Down {
15134 let multi_buffer = self.buffer.read(cx);
15135 let snapshot = multi_buffer.snapshot(cx);
15136 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
15137 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
15138 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
15139 let buffer_snapshot = buffer.read(cx).snapshot();
15140 let excerpt_end_row =
15141 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
15142 let last_row = buffer_snapshot.max_point().row;
15143 let lines_below = last_row.saturating_sub(excerpt_end_row);
15144 should_scroll_up = lines_below >= lines_to_expand;
15145 }
15146 }
15147 }
15148 }
15149
15150 self.buffer.update(cx, |buffer, cx| {
15151 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
15152 });
15153
15154 if should_scroll_up {
15155 let new_scroll_position =
15156 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
15157 self.set_scroll_position(new_scroll_position, window, cx);
15158 }
15159 }
15160
15161 pub fn go_to_singleton_buffer_point(
15162 &mut self,
15163 point: Point,
15164 window: &mut Window,
15165 cx: &mut Context<Self>,
15166 ) {
15167 self.go_to_singleton_buffer_range(point..point, window, cx);
15168 }
15169
15170 pub fn go_to_singleton_buffer_range(
15171 &mut self,
15172 range: Range<Point>,
15173 window: &mut Window,
15174 cx: &mut Context<Self>,
15175 ) {
15176 let multibuffer = self.buffer().read(cx);
15177 let Some(buffer) = multibuffer.as_singleton() else {
15178 return;
15179 };
15180 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
15181 return;
15182 };
15183 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
15184 return;
15185 };
15186 self.change_selections(
15187 SelectionEffects::default().nav_history(true),
15188 window,
15189 cx,
15190 |s| s.select_anchor_ranges([start..end]),
15191 );
15192 }
15193
15194 pub fn go_to_diagnostic(
15195 &mut self,
15196 action: &GoToDiagnostic,
15197 window: &mut Window,
15198 cx: &mut Context<Self>,
15199 ) {
15200 if !self.diagnostics_enabled() {
15201 return;
15202 }
15203 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15204 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
15205 }
15206
15207 pub fn go_to_prev_diagnostic(
15208 &mut self,
15209 action: &GoToPreviousDiagnostic,
15210 window: &mut Window,
15211 cx: &mut Context<Self>,
15212 ) {
15213 if !self.diagnostics_enabled() {
15214 return;
15215 }
15216 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15217 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
15218 }
15219
15220 pub fn go_to_diagnostic_impl(
15221 &mut self,
15222 direction: Direction,
15223 severity: GoToDiagnosticSeverityFilter,
15224 window: &mut Window,
15225 cx: &mut Context<Self>,
15226 ) {
15227 let buffer = self.buffer.read(cx).snapshot(cx);
15228 let selection = self.selections.newest::<usize>(cx);
15229
15230 let mut active_group_id = None;
15231 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
15232 if active_group.active_range.start.to_offset(&buffer) == selection.start {
15233 active_group_id = Some(active_group.group_id);
15234 }
15235 }
15236
15237 fn filtered(
15238 snapshot: EditorSnapshot,
15239 severity: GoToDiagnosticSeverityFilter,
15240 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
15241 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
15242 diagnostics
15243 .filter(move |entry| severity.matches(entry.diagnostic.severity))
15244 .filter(|entry| entry.range.start != entry.range.end)
15245 .filter(|entry| !entry.diagnostic.is_unnecessary)
15246 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
15247 }
15248
15249 let snapshot = self.snapshot(window, cx);
15250 let before = filtered(
15251 snapshot.clone(),
15252 severity,
15253 buffer
15254 .diagnostics_in_range(0..selection.start)
15255 .filter(|entry| entry.range.start <= selection.start),
15256 );
15257 let after = filtered(
15258 snapshot,
15259 severity,
15260 buffer
15261 .diagnostics_in_range(selection.start..buffer.len())
15262 .filter(|entry| entry.range.start >= selection.start),
15263 );
15264
15265 let mut found: Option<DiagnosticEntry<usize>> = None;
15266 if direction == Direction::Prev {
15267 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
15268 {
15269 for diagnostic in prev_diagnostics.into_iter().rev() {
15270 if diagnostic.range.start != selection.start
15271 || active_group_id
15272 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
15273 {
15274 found = Some(diagnostic);
15275 break 'outer;
15276 }
15277 }
15278 }
15279 } else {
15280 for diagnostic in after.chain(before) {
15281 if diagnostic.range.start != selection.start
15282 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
15283 {
15284 found = Some(diagnostic);
15285 break;
15286 }
15287 }
15288 }
15289 let Some(next_diagnostic) = found else {
15290 return;
15291 };
15292
15293 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
15294 return;
15295 };
15296 self.change_selections(Default::default(), window, cx, |s| {
15297 s.select_ranges(vec![
15298 next_diagnostic.range.start..next_diagnostic.range.start,
15299 ])
15300 });
15301 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
15302 self.refresh_inline_completion(false, true, window, cx);
15303 }
15304
15305 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
15306 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15307 let snapshot = self.snapshot(window, cx);
15308 let selection = self.selections.newest::<Point>(cx);
15309 self.go_to_hunk_before_or_after_position(
15310 &snapshot,
15311 selection.head(),
15312 Direction::Next,
15313 window,
15314 cx,
15315 );
15316 }
15317
15318 pub fn go_to_hunk_before_or_after_position(
15319 &mut self,
15320 snapshot: &EditorSnapshot,
15321 position: Point,
15322 direction: Direction,
15323 window: &mut Window,
15324 cx: &mut Context<Editor>,
15325 ) {
15326 let row = if direction == Direction::Next {
15327 self.hunk_after_position(snapshot, position)
15328 .map(|hunk| hunk.row_range.start)
15329 } else {
15330 self.hunk_before_position(snapshot, position)
15331 };
15332
15333 if let Some(row) = row {
15334 let destination = Point::new(row.0, 0);
15335 let autoscroll = Autoscroll::center();
15336
15337 self.unfold_ranges(&[destination..destination], false, false, cx);
15338 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
15339 s.select_ranges([destination..destination]);
15340 });
15341 }
15342 }
15343
15344 fn hunk_after_position(
15345 &mut self,
15346 snapshot: &EditorSnapshot,
15347 position: Point,
15348 ) -> Option<MultiBufferDiffHunk> {
15349 snapshot
15350 .buffer_snapshot
15351 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
15352 .find(|hunk| hunk.row_range.start.0 > position.row)
15353 .or_else(|| {
15354 snapshot
15355 .buffer_snapshot
15356 .diff_hunks_in_range(Point::zero()..position)
15357 .find(|hunk| hunk.row_range.end.0 < position.row)
15358 })
15359 }
15360
15361 fn go_to_prev_hunk(
15362 &mut self,
15363 _: &GoToPreviousHunk,
15364 window: &mut Window,
15365 cx: &mut Context<Self>,
15366 ) {
15367 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15368 let snapshot = self.snapshot(window, cx);
15369 let selection = self.selections.newest::<Point>(cx);
15370 self.go_to_hunk_before_or_after_position(
15371 &snapshot,
15372 selection.head(),
15373 Direction::Prev,
15374 window,
15375 cx,
15376 );
15377 }
15378
15379 fn hunk_before_position(
15380 &mut self,
15381 snapshot: &EditorSnapshot,
15382 position: Point,
15383 ) -> Option<MultiBufferRow> {
15384 snapshot
15385 .buffer_snapshot
15386 .diff_hunk_before(position)
15387 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
15388 }
15389
15390 fn go_to_next_change(
15391 &mut self,
15392 _: &GoToNextChange,
15393 window: &mut Window,
15394 cx: &mut Context<Self>,
15395 ) {
15396 if let Some(selections) = self
15397 .change_list
15398 .next_change(1, Direction::Next)
15399 .map(|s| s.to_vec())
15400 {
15401 self.change_selections(Default::default(), window, cx, |s| {
15402 let map = s.display_map();
15403 s.select_display_ranges(selections.iter().map(|a| {
15404 let point = a.to_display_point(&map);
15405 point..point
15406 }))
15407 })
15408 }
15409 }
15410
15411 fn go_to_previous_change(
15412 &mut self,
15413 _: &GoToPreviousChange,
15414 window: &mut Window,
15415 cx: &mut Context<Self>,
15416 ) {
15417 if let Some(selections) = self
15418 .change_list
15419 .next_change(1, Direction::Prev)
15420 .map(|s| s.to_vec())
15421 {
15422 self.change_selections(Default::default(), window, cx, |s| {
15423 let map = s.display_map();
15424 s.select_display_ranges(selections.iter().map(|a| {
15425 let point = a.to_display_point(&map);
15426 point..point
15427 }))
15428 })
15429 }
15430 }
15431
15432 fn go_to_line<T: 'static>(
15433 &mut self,
15434 position: Anchor,
15435 highlight_color: Option<Hsla>,
15436 window: &mut Window,
15437 cx: &mut Context<Self>,
15438 ) {
15439 let snapshot = self.snapshot(window, cx).display_snapshot;
15440 let position = position.to_point(&snapshot.buffer_snapshot);
15441 let start = snapshot
15442 .buffer_snapshot
15443 .clip_point(Point::new(position.row, 0), Bias::Left);
15444 let end = start + Point::new(1, 0);
15445 let start = snapshot.buffer_snapshot.anchor_before(start);
15446 let end = snapshot.buffer_snapshot.anchor_before(end);
15447
15448 self.highlight_rows::<T>(
15449 start..end,
15450 highlight_color
15451 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
15452 Default::default(),
15453 cx,
15454 );
15455
15456 if self.buffer.read(cx).is_singleton() {
15457 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
15458 }
15459 }
15460
15461 pub fn go_to_definition(
15462 &mut self,
15463 _: &GoToDefinition,
15464 window: &mut Window,
15465 cx: &mut Context<Self>,
15466 ) -> Task<Result<Navigated>> {
15467 let definition =
15468 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
15469 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
15470 cx.spawn_in(window, async move |editor, cx| {
15471 if definition.await? == Navigated::Yes {
15472 return Ok(Navigated::Yes);
15473 }
15474 match fallback_strategy {
15475 GoToDefinitionFallback::None => Ok(Navigated::No),
15476 GoToDefinitionFallback::FindAllReferences => {
15477 match editor.update_in(cx, |editor, window, cx| {
15478 editor.find_all_references(&FindAllReferences, window, cx)
15479 })? {
15480 Some(references) => references.await,
15481 None => Ok(Navigated::No),
15482 }
15483 }
15484 }
15485 })
15486 }
15487
15488 pub fn go_to_declaration(
15489 &mut self,
15490 _: &GoToDeclaration,
15491 window: &mut Window,
15492 cx: &mut Context<Self>,
15493 ) -> Task<Result<Navigated>> {
15494 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
15495 }
15496
15497 pub fn go_to_declaration_split(
15498 &mut self,
15499 _: &GoToDeclaration,
15500 window: &mut Window,
15501 cx: &mut Context<Self>,
15502 ) -> Task<Result<Navigated>> {
15503 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
15504 }
15505
15506 pub fn go_to_implementation(
15507 &mut self,
15508 _: &GoToImplementation,
15509 window: &mut Window,
15510 cx: &mut Context<Self>,
15511 ) -> Task<Result<Navigated>> {
15512 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
15513 }
15514
15515 pub fn go_to_implementation_split(
15516 &mut self,
15517 _: &GoToImplementationSplit,
15518 window: &mut Window,
15519 cx: &mut Context<Self>,
15520 ) -> Task<Result<Navigated>> {
15521 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
15522 }
15523
15524 pub fn go_to_type_definition(
15525 &mut self,
15526 _: &GoToTypeDefinition,
15527 window: &mut Window,
15528 cx: &mut Context<Self>,
15529 ) -> Task<Result<Navigated>> {
15530 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
15531 }
15532
15533 pub fn go_to_definition_split(
15534 &mut self,
15535 _: &GoToDefinitionSplit,
15536 window: &mut Window,
15537 cx: &mut Context<Self>,
15538 ) -> Task<Result<Navigated>> {
15539 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
15540 }
15541
15542 pub fn go_to_type_definition_split(
15543 &mut self,
15544 _: &GoToTypeDefinitionSplit,
15545 window: &mut Window,
15546 cx: &mut Context<Self>,
15547 ) -> Task<Result<Navigated>> {
15548 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
15549 }
15550
15551 fn go_to_definition_of_kind(
15552 &mut self,
15553 kind: GotoDefinitionKind,
15554 split: bool,
15555 window: &mut Window,
15556 cx: &mut Context<Self>,
15557 ) -> Task<Result<Navigated>> {
15558 let Some(provider) = self.semantics_provider.clone() else {
15559 return Task::ready(Ok(Navigated::No));
15560 };
15561 let head = self.selections.newest::<usize>(cx).head();
15562 let buffer = self.buffer.read(cx);
15563 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
15564 text_anchor
15565 } else {
15566 return Task::ready(Ok(Navigated::No));
15567 };
15568
15569 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
15570 return Task::ready(Ok(Navigated::No));
15571 };
15572
15573 cx.spawn_in(window, async move |editor, cx| {
15574 let definitions = definitions.await?;
15575 let navigated = editor
15576 .update_in(cx, |editor, window, cx| {
15577 editor.navigate_to_hover_links(
15578 Some(kind),
15579 definitions
15580 .into_iter()
15581 .filter(|location| {
15582 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
15583 })
15584 .map(HoverLink::Text)
15585 .collect::<Vec<_>>(),
15586 split,
15587 window,
15588 cx,
15589 )
15590 })?
15591 .await?;
15592 anyhow::Ok(navigated)
15593 })
15594 }
15595
15596 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
15597 let selection = self.selections.newest_anchor();
15598 let head = selection.head();
15599 let tail = selection.tail();
15600
15601 let Some((buffer, start_position)) =
15602 self.buffer.read(cx).text_anchor_for_position(head, cx)
15603 else {
15604 return;
15605 };
15606
15607 let end_position = if head != tail {
15608 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
15609 return;
15610 };
15611 Some(pos)
15612 } else {
15613 None
15614 };
15615
15616 let url_finder = cx.spawn_in(window, async move |editor, cx| {
15617 let url = if let Some(end_pos) = end_position {
15618 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
15619 } else {
15620 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
15621 };
15622
15623 if let Some(url) = url {
15624 editor.update(cx, |_, cx| {
15625 cx.open_url(&url);
15626 })
15627 } else {
15628 Ok(())
15629 }
15630 });
15631
15632 url_finder.detach();
15633 }
15634
15635 pub fn open_selected_filename(
15636 &mut self,
15637 _: &OpenSelectedFilename,
15638 window: &mut Window,
15639 cx: &mut Context<Self>,
15640 ) {
15641 let Some(workspace) = self.workspace() else {
15642 return;
15643 };
15644
15645 let position = self.selections.newest_anchor().head();
15646
15647 let Some((buffer, buffer_position)) =
15648 self.buffer.read(cx).text_anchor_for_position(position, cx)
15649 else {
15650 return;
15651 };
15652
15653 let project = self.project.clone();
15654
15655 cx.spawn_in(window, async move |_, cx| {
15656 let result = find_file(&buffer, project, buffer_position, cx).await;
15657
15658 if let Some((_, path)) = result {
15659 workspace
15660 .update_in(cx, |workspace, window, cx| {
15661 workspace.open_resolved_path(path, window, cx)
15662 })?
15663 .await?;
15664 }
15665 anyhow::Ok(())
15666 })
15667 .detach();
15668 }
15669
15670 pub(crate) fn navigate_to_hover_links(
15671 &mut self,
15672 kind: Option<GotoDefinitionKind>,
15673 mut definitions: Vec<HoverLink>,
15674 split: bool,
15675 window: &mut Window,
15676 cx: &mut Context<Editor>,
15677 ) -> Task<Result<Navigated>> {
15678 // If there is one definition, just open it directly
15679 if definitions.len() == 1 {
15680 let definition = definitions.pop().unwrap();
15681
15682 enum TargetTaskResult {
15683 Location(Option<Location>),
15684 AlreadyNavigated,
15685 }
15686
15687 let target_task = match definition {
15688 HoverLink::Text(link) => {
15689 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
15690 }
15691 HoverLink::InlayHint(lsp_location, server_id) => {
15692 let computation =
15693 self.compute_target_location(lsp_location, server_id, window, cx);
15694 cx.background_spawn(async move {
15695 let location = computation.await?;
15696 Ok(TargetTaskResult::Location(location))
15697 })
15698 }
15699 HoverLink::Url(url) => {
15700 cx.open_url(&url);
15701 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
15702 }
15703 HoverLink::File(path) => {
15704 if let Some(workspace) = self.workspace() {
15705 cx.spawn_in(window, async move |_, cx| {
15706 workspace
15707 .update_in(cx, |workspace, window, cx| {
15708 workspace.open_resolved_path(path, window, cx)
15709 })?
15710 .await
15711 .map(|_| TargetTaskResult::AlreadyNavigated)
15712 })
15713 } else {
15714 Task::ready(Ok(TargetTaskResult::Location(None)))
15715 }
15716 }
15717 };
15718 cx.spawn_in(window, async move |editor, cx| {
15719 let target = match target_task.await.context("target resolution task")? {
15720 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
15721 TargetTaskResult::Location(None) => return Ok(Navigated::No),
15722 TargetTaskResult::Location(Some(target)) => target,
15723 };
15724
15725 editor.update_in(cx, |editor, window, cx| {
15726 let Some(workspace) = editor.workspace() else {
15727 return Navigated::No;
15728 };
15729 let pane = workspace.read(cx).active_pane().clone();
15730
15731 let range = target.range.to_point(target.buffer.read(cx));
15732 let range = editor.range_for_match(&range);
15733 let range = collapse_multiline_range(range);
15734
15735 if !split
15736 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
15737 {
15738 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
15739 } else {
15740 window.defer(cx, move |window, cx| {
15741 let target_editor: Entity<Self> =
15742 workspace.update(cx, |workspace, cx| {
15743 let pane = if split {
15744 workspace.adjacent_pane(window, cx)
15745 } else {
15746 workspace.active_pane().clone()
15747 };
15748
15749 workspace.open_project_item(
15750 pane,
15751 target.buffer.clone(),
15752 true,
15753 true,
15754 window,
15755 cx,
15756 )
15757 });
15758 target_editor.update(cx, |target_editor, cx| {
15759 // When selecting a definition in a different buffer, disable the nav history
15760 // to avoid creating a history entry at the previous cursor location.
15761 pane.update(cx, |pane, _| pane.disable_history());
15762 target_editor.go_to_singleton_buffer_range(range, window, cx);
15763 pane.update(cx, |pane, _| pane.enable_history());
15764 });
15765 });
15766 }
15767 Navigated::Yes
15768 })
15769 })
15770 } else if !definitions.is_empty() {
15771 cx.spawn_in(window, async move |editor, cx| {
15772 let (title, location_tasks, workspace) = editor
15773 .update_in(cx, |editor, window, cx| {
15774 let tab_kind = match kind {
15775 Some(GotoDefinitionKind::Implementation) => "Implementations",
15776 _ => "Definitions",
15777 };
15778 let title = definitions
15779 .iter()
15780 .find_map(|definition| match definition {
15781 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
15782 let buffer = origin.buffer.read(cx);
15783 format!(
15784 "{} for {}",
15785 tab_kind,
15786 buffer
15787 .text_for_range(origin.range.clone())
15788 .collect::<String>()
15789 )
15790 }),
15791 HoverLink::InlayHint(_, _) => None,
15792 HoverLink::Url(_) => None,
15793 HoverLink::File(_) => None,
15794 })
15795 .unwrap_or(tab_kind.to_string());
15796 let location_tasks = definitions
15797 .into_iter()
15798 .map(|definition| match definition {
15799 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
15800 HoverLink::InlayHint(lsp_location, server_id) => editor
15801 .compute_target_location(lsp_location, server_id, window, cx),
15802 HoverLink::Url(_) => Task::ready(Ok(None)),
15803 HoverLink::File(_) => Task::ready(Ok(None)),
15804 })
15805 .collect::<Vec<_>>();
15806 (title, location_tasks, editor.workspace().clone())
15807 })
15808 .context("location tasks preparation")?;
15809
15810 let locations: Vec<Location> = future::join_all(location_tasks)
15811 .await
15812 .into_iter()
15813 .filter_map(|location| location.transpose())
15814 .collect::<Result<_>>()
15815 .context("location tasks")?;
15816
15817 if locations.is_empty() {
15818 return Ok(Navigated::No);
15819 }
15820
15821 let Some(workspace) = workspace else {
15822 return Ok(Navigated::No);
15823 };
15824
15825 let opened = workspace
15826 .update_in(cx, |workspace, window, cx| {
15827 Self::open_locations_in_multibuffer(
15828 workspace,
15829 locations,
15830 title,
15831 split,
15832 MultibufferSelectionMode::First,
15833 window,
15834 cx,
15835 )
15836 })
15837 .ok();
15838
15839 anyhow::Ok(Navigated::from_bool(opened.is_some()))
15840 })
15841 } else {
15842 Task::ready(Ok(Navigated::No))
15843 }
15844 }
15845
15846 fn compute_target_location(
15847 &self,
15848 lsp_location: lsp::Location,
15849 server_id: LanguageServerId,
15850 window: &mut Window,
15851 cx: &mut Context<Self>,
15852 ) -> Task<anyhow::Result<Option<Location>>> {
15853 let Some(project) = self.project.clone() else {
15854 return Task::ready(Ok(None));
15855 };
15856
15857 cx.spawn_in(window, async move |editor, cx| {
15858 let location_task = editor.update(cx, |_, cx| {
15859 project.update(cx, |project, cx| {
15860 let language_server_name = project
15861 .language_server_statuses(cx)
15862 .find(|(id, _)| server_id == *id)
15863 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
15864 language_server_name.map(|language_server_name| {
15865 project.open_local_buffer_via_lsp(
15866 lsp_location.uri.clone(),
15867 server_id,
15868 language_server_name,
15869 cx,
15870 )
15871 })
15872 })
15873 })?;
15874 let location = match location_task {
15875 Some(task) => Some({
15876 let target_buffer_handle = task.await.context("open local buffer")?;
15877 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
15878 let target_start = target_buffer
15879 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
15880 let target_end = target_buffer
15881 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
15882 target_buffer.anchor_after(target_start)
15883 ..target_buffer.anchor_before(target_end)
15884 })?;
15885 Location {
15886 buffer: target_buffer_handle,
15887 range,
15888 }
15889 }),
15890 None => None,
15891 };
15892 Ok(location)
15893 })
15894 }
15895
15896 pub fn find_all_references(
15897 &mut self,
15898 _: &FindAllReferences,
15899 window: &mut Window,
15900 cx: &mut Context<Self>,
15901 ) -> Option<Task<Result<Navigated>>> {
15902 let selection = self.selections.newest::<usize>(cx);
15903 let multi_buffer = self.buffer.read(cx);
15904 let head = selection.head();
15905
15906 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
15907 let head_anchor = multi_buffer_snapshot.anchor_at(
15908 head,
15909 if head < selection.tail() {
15910 Bias::Right
15911 } else {
15912 Bias::Left
15913 },
15914 );
15915
15916 match self
15917 .find_all_references_task_sources
15918 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15919 {
15920 Ok(_) => {
15921 log::info!(
15922 "Ignoring repeated FindAllReferences invocation with the position of already running task"
15923 );
15924 return None;
15925 }
15926 Err(i) => {
15927 self.find_all_references_task_sources.insert(i, head_anchor);
15928 }
15929 }
15930
15931 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
15932 let workspace = self.workspace()?;
15933 let project = workspace.read(cx).project().clone();
15934 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
15935 Some(cx.spawn_in(window, async move |editor, cx| {
15936 let _cleanup = cx.on_drop(&editor, move |editor, _| {
15937 if let Ok(i) = editor
15938 .find_all_references_task_sources
15939 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15940 {
15941 editor.find_all_references_task_sources.remove(i);
15942 }
15943 });
15944
15945 let locations = references.await?;
15946 if locations.is_empty() {
15947 return anyhow::Ok(Navigated::No);
15948 }
15949
15950 workspace.update_in(cx, |workspace, window, cx| {
15951 let title = locations
15952 .first()
15953 .as_ref()
15954 .map(|location| {
15955 let buffer = location.buffer.read(cx);
15956 format!(
15957 "References to `{}`",
15958 buffer
15959 .text_for_range(location.range.clone())
15960 .collect::<String>()
15961 )
15962 })
15963 .unwrap();
15964 Self::open_locations_in_multibuffer(
15965 workspace,
15966 locations,
15967 title,
15968 false,
15969 MultibufferSelectionMode::First,
15970 window,
15971 cx,
15972 );
15973 Navigated::Yes
15974 })
15975 }))
15976 }
15977
15978 /// Opens a multibuffer with the given project locations in it
15979 pub fn open_locations_in_multibuffer(
15980 workspace: &mut Workspace,
15981 mut locations: Vec<Location>,
15982 title: String,
15983 split: bool,
15984 multibuffer_selection_mode: MultibufferSelectionMode,
15985 window: &mut Window,
15986 cx: &mut Context<Workspace>,
15987 ) {
15988 if locations.is_empty() {
15989 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
15990 return;
15991 }
15992
15993 // If there are multiple definitions, open them in a multibuffer
15994 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
15995 let mut locations = locations.into_iter().peekable();
15996 let mut ranges: Vec<Range<Anchor>> = Vec::new();
15997 let capability = workspace.project().read(cx).capability();
15998
15999 let excerpt_buffer = cx.new(|cx| {
16000 let mut multibuffer = MultiBuffer::new(capability);
16001 while let Some(location) = locations.next() {
16002 let buffer = location.buffer.read(cx);
16003 let mut ranges_for_buffer = Vec::new();
16004 let range = location.range.to_point(buffer);
16005 ranges_for_buffer.push(range.clone());
16006
16007 while let Some(next_location) = locations.peek() {
16008 if next_location.buffer == location.buffer {
16009 ranges_for_buffer.push(next_location.range.to_point(buffer));
16010 locations.next();
16011 } else {
16012 break;
16013 }
16014 }
16015
16016 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
16017 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
16018 PathKey::for_buffer(&location.buffer, cx),
16019 location.buffer.clone(),
16020 ranges_for_buffer,
16021 DEFAULT_MULTIBUFFER_CONTEXT,
16022 cx,
16023 );
16024 ranges.extend(new_ranges)
16025 }
16026
16027 multibuffer.with_title(title)
16028 });
16029
16030 let editor = cx.new(|cx| {
16031 Editor::for_multibuffer(
16032 excerpt_buffer,
16033 Some(workspace.project().clone()),
16034 window,
16035 cx,
16036 )
16037 });
16038 editor.update(cx, |editor, cx| {
16039 match multibuffer_selection_mode {
16040 MultibufferSelectionMode::First => {
16041 if let Some(first_range) = ranges.first() {
16042 editor.change_selections(
16043 SelectionEffects::no_scroll(),
16044 window,
16045 cx,
16046 |selections| {
16047 selections.clear_disjoint();
16048 selections
16049 .select_anchor_ranges(std::iter::once(first_range.clone()));
16050 },
16051 );
16052 }
16053 editor.highlight_background::<Self>(
16054 &ranges,
16055 |theme| theme.colors().editor_highlighted_line_background,
16056 cx,
16057 );
16058 }
16059 MultibufferSelectionMode::All => {
16060 editor.change_selections(
16061 SelectionEffects::no_scroll(),
16062 window,
16063 cx,
16064 |selections| {
16065 selections.clear_disjoint();
16066 selections.select_anchor_ranges(ranges);
16067 },
16068 );
16069 }
16070 }
16071 editor.register_buffers_with_language_servers(cx);
16072 });
16073
16074 let item = Box::new(editor);
16075 let item_id = item.item_id();
16076
16077 if split {
16078 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
16079 } else {
16080 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
16081 let (preview_item_id, preview_item_idx) =
16082 workspace.active_pane().read_with(cx, |pane, _| {
16083 (pane.preview_item_id(), pane.preview_item_idx())
16084 });
16085
16086 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
16087
16088 if let Some(preview_item_id) = preview_item_id {
16089 workspace.active_pane().update(cx, |pane, cx| {
16090 pane.remove_item(preview_item_id, false, false, window, cx);
16091 });
16092 }
16093 } else {
16094 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
16095 }
16096 }
16097 workspace.active_pane().update(cx, |pane, cx| {
16098 pane.set_preview_item_id(Some(item_id), cx);
16099 });
16100 }
16101
16102 pub fn rename(
16103 &mut self,
16104 _: &Rename,
16105 window: &mut Window,
16106 cx: &mut Context<Self>,
16107 ) -> Option<Task<Result<()>>> {
16108 use language::ToOffset as _;
16109
16110 let provider = self.semantics_provider.clone()?;
16111 let selection = self.selections.newest_anchor().clone();
16112 let (cursor_buffer, cursor_buffer_position) = self
16113 .buffer
16114 .read(cx)
16115 .text_anchor_for_position(selection.head(), cx)?;
16116 let (tail_buffer, cursor_buffer_position_end) = self
16117 .buffer
16118 .read(cx)
16119 .text_anchor_for_position(selection.tail(), cx)?;
16120 if tail_buffer != cursor_buffer {
16121 return None;
16122 }
16123
16124 let snapshot = cursor_buffer.read(cx).snapshot();
16125 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
16126 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
16127 let prepare_rename = provider
16128 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
16129 .unwrap_or_else(|| Task::ready(Ok(None)));
16130 drop(snapshot);
16131
16132 Some(cx.spawn_in(window, async move |this, cx| {
16133 let rename_range = if let Some(range) = prepare_rename.await? {
16134 Some(range)
16135 } else {
16136 this.update(cx, |this, cx| {
16137 let buffer = this.buffer.read(cx).snapshot(cx);
16138 let mut buffer_highlights = this
16139 .document_highlights_for_position(selection.head(), &buffer)
16140 .filter(|highlight| {
16141 highlight.start.excerpt_id == selection.head().excerpt_id
16142 && highlight.end.excerpt_id == selection.head().excerpt_id
16143 });
16144 buffer_highlights
16145 .next()
16146 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
16147 })?
16148 };
16149 if let Some(rename_range) = rename_range {
16150 this.update_in(cx, |this, window, cx| {
16151 let snapshot = cursor_buffer.read(cx).snapshot();
16152 let rename_buffer_range = rename_range.to_offset(&snapshot);
16153 let cursor_offset_in_rename_range =
16154 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
16155 let cursor_offset_in_rename_range_end =
16156 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
16157
16158 this.take_rename(false, window, cx);
16159 let buffer = this.buffer.read(cx).read(cx);
16160 let cursor_offset = selection.head().to_offset(&buffer);
16161 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
16162 let rename_end = rename_start + rename_buffer_range.len();
16163 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
16164 let mut old_highlight_id = None;
16165 let old_name: Arc<str> = buffer
16166 .chunks(rename_start..rename_end, true)
16167 .map(|chunk| {
16168 if old_highlight_id.is_none() {
16169 old_highlight_id = chunk.syntax_highlight_id;
16170 }
16171 chunk.text
16172 })
16173 .collect::<String>()
16174 .into();
16175
16176 drop(buffer);
16177
16178 // Position the selection in the rename editor so that it matches the current selection.
16179 this.show_local_selections = false;
16180 let rename_editor = cx.new(|cx| {
16181 let mut editor = Editor::single_line(window, cx);
16182 editor.buffer.update(cx, |buffer, cx| {
16183 buffer.edit([(0..0, old_name.clone())], None, cx)
16184 });
16185 let rename_selection_range = match cursor_offset_in_rename_range
16186 .cmp(&cursor_offset_in_rename_range_end)
16187 {
16188 Ordering::Equal => {
16189 editor.select_all(&SelectAll, window, cx);
16190 return editor;
16191 }
16192 Ordering::Less => {
16193 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
16194 }
16195 Ordering::Greater => {
16196 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
16197 }
16198 };
16199 if rename_selection_range.end > old_name.len() {
16200 editor.select_all(&SelectAll, window, cx);
16201 } else {
16202 editor.change_selections(Default::default(), window, cx, |s| {
16203 s.select_ranges([rename_selection_range]);
16204 });
16205 }
16206 editor
16207 });
16208 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
16209 if e == &EditorEvent::Focused {
16210 cx.emit(EditorEvent::FocusedIn)
16211 }
16212 })
16213 .detach();
16214
16215 let write_highlights =
16216 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
16217 let read_highlights =
16218 this.clear_background_highlights::<DocumentHighlightRead>(cx);
16219 let ranges = write_highlights
16220 .iter()
16221 .flat_map(|(_, ranges)| ranges.iter())
16222 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
16223 .cloned()
16224 .collect();
16225
16226 this.highlight_text::<Rename>(
16227 ranges,
16228 HighlightStyle {
16229 fade_out: Some(0.6),
16230 ..Default::default()
16231 },
16232 cx,
16233 );
16234 let rename_focus_handle = rename_editor.focus_handle(cx);
16235 window.focus(&rename_focus_handle);
16236 let block_id = this.insert_blocks(
16237 [BlockProperties {
16238 style: BlockStyle::Flex,
16239 placement: BlockPlacement::Below(range.start),
16240 height: Some(1),
16241 render: Arc::new({
16242 let rename_editor = rename_editor.clone();
16243 move |cx: &mut BlockContext| {
16244 let mut text_style = cx.editor_style.text.clone();
16245 if let Some(highlight_style) = old_highlight_id
16246 .and_then(|h| h.style(&cx.editor_style.syntax))
16247 {
16248 text_style = text_style.highlight(highlight_style);
16249 }
16250 div()
16251 .block_mouse_except_scroll()
16252 .pl(cx.anchor_x)
16253 .child(EditorElement::new(
16254 &rename_editor,
16255 EditorStyle {
16256 background: cx.theme().system().transparent,
16257 local_player: cx.editor_style.local_player,
16258 text: text_style,
16259 scrollbar_width: cx.editor_style.scrollbar_width,
16260 syntax: cx.editor_style.syntax.clone(),
16261 status: cx.editor_style.status.clone(),
16262 inlay_hints_style: HighlightStyle {
16263 font_weight: Some(FontWeight::BOLD),
16264 ..make_inlay_hints_style(cx.app)
16265 },
16266 inline_completion_styles: make_suggestion_styles(
16267 cx.app,
16268 ),
16269 ..EditorStyle::default()
16270 },
16271 ))
16272 .into_any_element()
16273 }
16274 }),
16275 priority: 0,
16276 }],
16277 Some(Autoscroll::fit()),
16278 cx,
16279 )[0];
16280 this.pending_rename = Some(RenameState {
16281 range,
16282 old_name,
16283 editor: rename_editor,
16284 block_id,
16285 });
16286 })?;
16287 }
16288
16289 Ok(())
16290 }))
16291 }
16292
16293 pub fn confirm_rename(
16294 &mut self,
16295 _: &ConfirmRename,
16296 window: &mut Window,
16297 cx: &mut Context<Self>,
16298 ) -> Option<Task<Result<()>>> {
16299 let rename = self.take_rename(false, window, cx)?;
16300 let workspace = self.workspace()?.downgrade();
16301 let (buffer, start) = self
16302 .buffer
16303 .read(cx)
16304 .text_anchor_for_position(rename.range.start, cx)?;
16305 let (end_buffer, _) = self
16306 .buffer
16307 .read(cx)
16308 .text_anchor_for_position(rename.range.end, cx)?;
16309 if buffer != end_buffer {
16310 return None;
16311 }
16312
16313 let old_name = rename.old_name;
16314 let new_name = rename.editor.read(cx).text(cx);
16315
16316 let rename = self.semantics_provider.as_ref()?.perform_rename(
16317 &buffer,
16318 start,
16319 new_name.clone(),
16320 cx,
16321 )?;
16322
16323 Some(cx.spawn_in(window, async move |editor, cx| {
16324 let project_transaction = rename.await?;
16325 Self::open_project_transaction(
16326 &editor,
16327 workspace,
16328 project_transaction,
16329 format!("Rename: {} → {}", old_name, new_name),
16330 cx,
16331 )
16332 .await?;
16333
16334 editor.update(cx, |editor, cx| {
16335 editor.refresh_document_highlights(cx);
16336 })?;
16337 Ok(())
16338 }))
16339 }
16340
16341 fn take_rename(
16342 &mut self,
16343 moving_cursor: bool,
16344 window: &mut Window,
16345 cx: &mut Context<Self>,
16346 ) -> Option<RenameState> {
16347 let rename = self.pending_rename.take()?;
16348 if rename.editor.focus_handle(cx).is_focused(window) {
16349 window.focus(&self.focus_handle);
16350 }
16351
16352 self.remove_blocks(
16353 [rename.block_id].into_iter().collect(),
16354 Some(Autoscroll::fit()),
16355 cx,
16356 );
16357 self.clear_highlights::<Rename>(cx);
16358 self.show_local_selections = true;
16359
16360 if moving_cursor {
16361 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
16362 editor.selections.newest::<usize>(cx).head()
16363 });
16364
16365 // Update the selection to match the position of the selection inside
16366 // the rename editor.
16367 let snapshot = self.buffer.read(cx).read(cx);
16368 let rename_range = rename.range.to_offset(&snapshot);
16369 let cursor_in_editor = snapshot
16370 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
16371 .min(rename_range.end);
16372 drop(snapshot);
16373
16374 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16375 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
16376 });
16377 } else {
16378 self.refresh_document_highlights(cx);
16379 }
16380
16381 Some(rename)
16382 }
16383
16384 pub fn pending_rename(&self) -> Option<&RenameState> {
16385 self.pending_rename.as_ref()
16386 }
16387
16388 fn format(
16389 &mut self,
16390 _: &Format,
16391 window: &mut Window,
16392 cx: &mut Context<Self>,
16393 ) -> Option<Task<Result<()>>> {
16394 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16395
16396 let project = match &self.project {
16397 Some(project) => project.clone(),
16398 None => return None,
16399 };
16400
16401 Some(self.perform_format(
16402 project,
16403 FormatTrigger::Manual,
16404 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
16405 window,
16406 cx,
16407 ))
16408 }
16409
16410 fn format_selections(
16411 &mut self,
16412 _: &FormatSelections,
16413 window: &mut Window,
16414 cx: &mut Context<Self>,
16415 ) -> Option<Task<Result<()>>> {
16416 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16417
16418 let project = match &self.project {
16419 Some(project) => project.clone(),
16420 None => return None,
16421 };
16422
16423 let ranges = self
16424 .selections
16425 .all_adjusted(cx)
16426 .into_iter()
16427 .map(|selection| selection.range())
16428 .collect_vec();
16429
16430 Some(self.perform_format(
16431 project,
16432 FormatTrigger::Manual,
16433 FormatTarget::Ranges(ranges),
16434 window,
16435 cx,
16436 ))
16437 }
16438
16439 fn perform_format(
16440 &mut self,
16441 project: Entity<Project>,
16442 trigger: FormatTrigger,
16443 target: FormatTarget,
16444 window: &mut Window,
16445 cx: &mut Context<Self>,
16446 ) -> Task<Result<()>> {
16447 let buffer = self.buffer.clone();
16448 let (buffers, target) = match target {
16449 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
16450 FormatTarget::Ranges(selection_ranges) => {
16451 let multi_buffer = buffer.read(cx);
16452 let snapshot = multi_buffer.read(cx);
16453 let mut buffers = HashSet::default();
16454 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
16455 BTreeMap::new();
16456 for selection_range in selection_ranges {
16457 for (buffer, buffer_range, _) in
16458 snapshot.range_to_buffer_ranges(selection_range)
16459 {
16460 let buffer_id = buffer.remote_id();
16461 let start = buffer.anchor_before(buffer_range.start);
16462 let end = buffer.anchor_after(buffer_range.end);
16463 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
16464 buffer_id_to_ranges
16465 .entry(buffer_id)
16466 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
16467 .or_insert_with(|| vec![start..end]);
16468 }
16469 }
16470 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
16471 }
16472 };
16473
16474 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
16475 let selections_prev = transaction_id_prev
16476 .and_then(|transaction_id_prev| {
16477 // default to selections as they were after the last edit, if we have them,
16478 // instead of how they are now.
16479 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
16480 // will take you back to where you made the last edit, instead of staying where you scrolled
16481 self.selection_history
16482 .transaction(transaction_id_prev)
16483 .map(|t| t.0.clone())
16484 })
16485 .unwrap_or_else(|| {
16486 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
16487 self.selections.disjoint_anchors()
16488 });
16489
16490 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
16491 let format = project.update(cx, |project, cx| {
16492 project.format(buffers, target, true, trigger, cx)
16493 });
16494
16495 cx.spawn_in(window, async move |editor, cx| {
16496 let transaction = futures::select_biased! {
16497 transaction = format.log_err().fuse() => transaction,
16498 () = timeout => {
16499 log::warn!("timed out waiting for formatting");
16500 None
16501 }
16502 };
16503
16504 buffer
16505 .update(cx, |buffer, cx| {
16506 if let Some(transaction) = transaction {
16507 if !buffer.is_singleton() {
16508 buffer.push_transaction(&transaction.0, cx);
16509 }
16510 }
16511 cx.notify();
16512 })
16513 .ok();
16514
16515 if let Some(transaction_id_now) =
16516 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
16517 {
16518 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
16519 if has_new_transaction {
16520 _ = editor.update(cx, |editor, _| {
16521 editor
16522 .selection_history
16523 .insert_transaction(transaction_id_now, selections_prev);
16524 });
16525 }
16526 }
16527
16528 Ok(())
16529 })
16530 }
16531
16532 fn organize_imports(
16533 &mut self,
16534 _: &OrganizeImports,
16535 window: &mut Window,
16536 cx: &mut Context<Self>,
16537 ) -> Option<Task<Result<()>>> {
16538 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16539 let project = match &self.project {
16540 Some(project) => project.clone(),
16541 None => return None,
16542 };
16543 Some(self.perform_code_action_kind(
16544 project,
16545 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
16546 window,
16547 cx,
16548 ))
16549 }
16550
16551 fn perform_code_action_kind(
16552 &mut self,
16553 project: Entity<Project>,
16554 kind: CodeActionKind,
16555 window: &mut Window,
16556 cx: &mut Context<Self>,
16557 ) -> Task<Result<()>> {
16558 let buffer = self.buffer.clone();
16559 let buffers = buffer.read(cx).all_buffers();
16560 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
16561 let apply_action = project.update(cx, |project, cx| {
16562 project.apply_code_action_kind(buffers, kind, true, cx)
16563 });
16564 cx.spawn_in(window, async move |_, cx| {
16565 let transaction = futures::select_biased! {
16566 () = timeout => {
16567 log::warn!("timed out waiting for executing code action");
16568 None
16569 }
16570 transaction = apply_action.log_err().fuse() => transaction,
16571 };
16572 buffer
16573 .update(cx, |buffer, cx| {
16574 // check if we need this
16575 if let Some(transaction) = transaction {
16576 if !buffer.is_singleton() {
16577 buffer.push_transaction(&transaction.0, cx);
16578 }
16579 }
16580 cx.notify();
16581 })
16582 .ok();
16583 Ok(())
16584 })
16585 }
16586
16587 pub fn restart_language_server(
16588 &mut self,
16589 _: &RestartLanguageServer,
16590 _: &mut Window,
16591 cx: &mut Context<Self>,
16592 ) {
16593 if let Some(project) = self.project.clone() {
16594 self.buffer.update(cx, |multi_buffer, cx| {
16595 project.update(cx, |project, cx| {
16596 project.restart_language_servers_for_buffers(
16597 multi_buffer.all_buffers().into_iter().collect(),
16598 HashSet::default(),
16599 cx,
16600 );
16601 });
16602 })
16603 }
16604 }
16605
16606 pub fn stop_language_server(
16607 &mut self,
16608 _: &StopLanguageServer,
16609 _: &mut Window,
16610 cx: &mut Context<Self>,
16611 ) {
16612 if let Some(project) = self.project.clone() {
16613 self.buffer.update(cx, |multi_buffer, cx| {
16614 project.update(cx, |project, cx| {
16615 project.stop_language_servers_for_buffers(
16616 multi_buffer.all_buffers().into_iter().collect(),
16617 HashSet::default(),
16618 cx,
16619 );
16620 cx.emit(project::Event::RefreshInlayHints);
16621 });
16622 });
16623 }
16624 }
16625
16626 fn cancel_language_server_work(
16627 workspace: &mut Workspace,
16628 _: &actions::CancelLanguageServerWork,
16629 _: &mut Window,
16630 cx: &mut Context<Workspace>,
16631 ) {
16632 let project = workspace.project();
16633 let buffers = workspace
16634 .active_item(cx)
16635 .and_then(|item| item.act_as::<Editor>(cx))
16636 .map_or(HashSet::default(), |editor| {
16637 editor.read(cx).buffer.read(cx).all_buffers()
16638 });
16639 project.update(cx, |project, cx| {
16640 project.cancel_language_server_work_for_buffers(buffers, cx);
16641 });
16642 }
16643
16644 fn show_character_palette(
16645 &mut self,
16646 _: &ShowCharacterPalette,
16647 window: &mut Window,
16648 _: &mut Context<Self>,
16649 ) {
16650 window.show_character_palette();
16651 }
16652
16653 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
16654 if !self.diagnostics_enabled() {
16655 return;
16656 }
16657
16658 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
16659 let buffer = self.buffer.read(cx).snapshot(cx);
16660 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
16661 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
16662 let is_valid = buffer
16663 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
16664 .any(|entry| {
16665 entry.diagnostic.is_primary
16666 && !entry.range.is_empty()
16667 && entry.range.start == primary_range_start
16668 && entry.diagnostic.message == active_diagnostics.active_message
16669 });
16670
16671 if !is_valid {
16672 self.dismiss_diagnostics(cx);
16673 }
16674 }
16675 }
16676
16677 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
16678 match &self.active_diagnostics {
16679 ActiveDiagnostic::Group(group) => Some(group),
16680 _ => None,
16681 }
16682 }
16683
16684 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
16685 if !self.diagnostics_enabled() {
16686 return;
16687 }
16688 self.dismiss_diagnostics(cx);
16689 self.active_diagnostics = ActiveDiagnostic::All;
16690 }
16691
16692 fn activate_diagnostics(
16693 &mut self,
16694 buffer_id: BufferId,
16695 diagnostic: DiagnosticEntry<usize>,
16696 window: &mut Window,
16697 cx: &mut Context<Self>,
16698 ) {
16699 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16700 return;
16701 }
16702 self.dismiss_diagnostics(cx);
16703 let snapshot = self.snapshot(window, cx);
16704 let buffer = self.buffer.read(cx).snapshot(cx);
16705 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
16706 return;
16707 };
16708
16709 let diagnostic_group = buffer
16710 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
16711 .collect::<Vec<_>>();
16712
16713 let blocks =
16714 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
16715
16716 let blocks = self.display_map.update(cx, |display_map, cx| {
16717 display_map.insert_blocks(blocks, cx).into_iter().collect()
16718 });
16719 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
16720 active_range: buffer.anchor_before(diagnostic.range.start)
16721 ..buffer.anchor_after(diagnostic.range.end),
16722 active_message: diagnostic.diagnostic.message.clone(),
16723 group_id: diagnostic.diagnostic.group_id,
16724 blocks,
16725 });
16726 cx.notify();
16727 }
16728
16729 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
16730 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16731 return;
16732 };
16733
16734 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
16735 if let ActiveDiagnostic::Group(group) = prev {
16736 self.display_map.update(cx, |display_map, cx| {
16737 display_map.remove_blocks(group.blocks, cx);
16738 });
16739 cx.notify();
16740 }
16741 }
16742
16743 /// Disable inline diagnostics rendering for this editor.
16744 pub fn disable_inline_diagnostics(&mut self) {
16745 self.inline_diagnostics_enabled = false;
16746 self.inline_diagnostics_update = Task::ready(());
16747 self.inline_diagnostics.clear();
16748 }
16749
16750 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
16751 self.diagnostics_enabled = false;
16752 self.dismiss_diagnostics(cx);
16753 self.inline_diagnostics_update = Task::ready(());
16754 self.inline_diagnostics.clear();
16755 }
16756
16757 pub fn diagnostics_enabled(&self) -> bool {
16758 self.diagnostics_enabled && self.mode.is_full()
16759 }
16760
16761 pub fn inline_diagnostics_enabled(&self) -> bool {
16762 self.inline_diagnostics_enabled && self.diagnostics_enabled()
16763 }
16764
16765 pub fn show_inline_diagnostics(&self) -> bool {
16766 self.show_inline_diagnostics
16767 }
16768
16769 pub fn toggle_inline_diagnostics(
16770 &mut self,
16771 _: &ToggleInlineDiagnostics,
16772 window: &mut Window,
16773 cx: &mut Context<Editor>,
16774 ) {
16775 self.show_inline_diagnostics = !self.show_inline_diagnostics;
16776 self.refresh_inline_diagnostics(false, window, cx);
16777 }
16778
16779 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
16780 self.diagnostics_max_severity = severity;
16781 self.display_map.update(cx, |display_map, _| {
16782 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
16783 });
16784 }
16785
16786 pub fn toggle_diagnostics(
16787 &mut self,
16788 _: &ToggleDiagnostics,
16789 window: &mut Window,
16790 cx: &mut Context<Editor>,
16791 ) {
16792 if !self.diagnostics_enabled() {
16793 return;
16794 }
16795
16796 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16797 EditorSettings::get_global(cx)
16798 .diagnostics_max_severity
16799 .filter(|severity| severity != &DiagnosticSeverity::Off)
16800 .unwrap_or(DiagnosticSeverity::Hint)
16801 } else {
16802 DiagnosticSeverity::Off
16803 };
16804 self.set_max_diagnostics_severity(new_severity, cx);
16805 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16806 self.active_diagnostics = ActiveDiagnostic::None;
16807 self.inline_diagnostics_update = Task::ready(());
16808 self.inline_diagnostics.clear();
16809 } else {
16810 self.refresh_inline_diagnostics(false, window, cx);
16811 }
16812
16813 cx.notify();
16814 }
16815
16816 pub fn toggle_minimap(
16817 &mut self,
16818 _: &ToggleMinimap,
16819 window: &mut Window,
16820 cx: &mut Context<Editor>,
16821 ) {
16822 if self.supports_minimap(cx) {
16823 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
16824 }
16825 }
16826
16827 fn refresh_inline_diagnostics(
16828 &mut self,
16829 debounce: bool,
16830 window: &mut Window,
16831 cx: &mut Context<Self>,
16832 ) {
16833 let max_severity = ProjectSettings::get_global(cx)
16834 .diagnostics
16835 .inline
16836 .max_severity
16837 .unwrap_or(self.diagnostics_max_severity);
16838
16839 if !self.inline_diagnostics_enabled()
16840 || !self.show_inline_diagnostics
16841 || max_severity == DiagnosticSeverity::Off
16842 {
16843 self.inline_diagnostics_update = Task::ready(());
16844 self.inline_diagnostics.clear();
16845 return;
16846 }
16847
16848 let debounce_ms = ProjectSettings::get_global(cx)
16849 .diagnostics
16850 .inline
16851 .update_debounce_ms;
16852 let debounce = if debounce && debounce_ms > 0 {
16853 Some(Duration::from_millis(debounce_ms))
16854 } else {
16855 None
16856 };
16857 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
16858 if let Some(debounce) = debounce {
16859 cx.background_executor().timer(debounce).await;
16860 }
16861 let Some(snapshot) = editor.upgrade().and_then(|editor| {
16862 editor
16863 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
16864 .ok()
16865 }) else {
16866 return;
16867 };
16868
16869 let new_inline_diagnostics = cx
16870 .background_spawn(async move {
16871 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
16872 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
16873 let message = diagnostic_entry
16874 .diagnostic
16875 .message
16876 .split_once('\n')
16877 .map(|(line, _)| line)
16878 .map(SharedString::new)
16879 .unwrap_or_else(|| {
16880 SharedString::from(diagnostic_entry.diagnostic.message)
16881 });
16882 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
16883 let (Ok(i) | Err(i)) = inline_diagnostics
16884 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
16885 inline_diagnostics.insert(
16886 i,
16887 (
16888 start_anchor,
16889 InlineDiagnostic {
16890 message,
16891 group_id: diagnostic_entry.diagnostic.group_id,
16892 start: diagnostic_entry.range.start.to_point(&snapshot),
16893 is_primary: diagnostic_entry.diagnostic.is_primary,
16894 severity: diagnostic_entry.diagnostic.severity,
16895 },
16896 ),
16897 );
16898 }
16899 inline_diagnostics
16900 })
16901 .await;
16902
16903 editor
16904 .update(cx, |editor, cx| {
16905 editor.inline_diagnostics = new_inline_diagnostics;
16906 cx.notify();
16907 })
16908 .ok();
16909 });
16910 }
16911
16912 fn pull_diagnostics(
16913 &mut self,
16914 buffer_id: Option<BufferId>,
16915 window: &Window,
16916 cx: &mut Context<Self>,
16917 ) -> Option<()> {
16918 if !self.mode().is_full() {
16919 return None;
16920 }
16921 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
16922 .diagnostics
16923 .lsp_pull_diagnostics;
16924 if !pull_diagnostics_settings.enabled {
16925 return None;
16926 }
16927 let project = self.project.as_ref()?.downgrade();
16928 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
16929 let mut buffers = self.buffer.read(cx).all_buffers();
16930 if let Some(buffer_id) = buffer_id {
16931 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
16932 }
16933
16934 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
16935 cx.background_executor().timer(debounce).await;
16936
16937 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
16938 buffers
16939 .into_iter()
16940 .filter_map(|buffer| {
16941 project
16942 .update(cx, |project, cx| {
16943 project.lsp_store().update(cx, |lsp_store, cx| {
16944 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
16945 })
16946 })
16947 .ok()
16948 })
16949 .collect::<FuturesUnordered<_>>()
16950 }) else {
16951 return;
16952 };
16953
16954 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
16955 match pull_task {
16956 Ok(()) => {
16957 if editor
16958 .update_in(cx, |editor, window, cx| {
16959 editor.update_diagnostics_state(window, cx);
16960 })
16961 .is_err()
16962 {
16963 return;
16964 }
16965 }
16966 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
16967 }
16968 }
16969 });
16970
16971 Some(())
16972 }
16973
16974 pub fn set_selections_from_remote(
16975 &mut self,
16976 selections: Vec<Selection<Anchor>>,
16977 pending_selection: Option<Selection<Anchor>>,
16978 window: &mut Window,
16979 cx: &mut Context<Self>,
16980 ) {
16981 let old_cursor_position = self.selections.newest_anchor().head();
16982 self.selections.change_with(cx, |s| {
16983 s.select_anchors(selections);
16984 if let Some(pending_selection) = pending_selection {
16985 s.set_pending(pending_selection, SelectMode::Character);
16986 } else {
16987 s.clear_pending();
16988 }
16989 });
16990 self.selections_did_change(
16991 false,
16992 &old_cursor_position,
16993 SelectionEffects::default(),
16994 window,
16995 cx,
16996 );
16997 }
16998
16999 pub fn transact(
17000 &mut self,
17001 window: &mut Window,
17002 cx: &mut Context<Self>,
17003 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
17004 ) -> Option<TransactionId> {
17005 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17006 this.start_transaction_at(Instant::now(), window, cx);
17007 update(this, window, cx);
17008 this.end_transaction_at(Instant::now(), cx)
17009 })
17010 }
17011
17012 pub fn start_transaction_at(
17013 &mut self,
17014 now: Instant,
17015 window: &mut Window,
17016 cx: &mut Context<Self>,
17017 ) -> Option<TransactionId> {
17018 self.end_selection(window, cx);
17019 if let Some(tx_id) = self
17020 .buffer
17021 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
17022 {
17023 self.selection_history
17024 .insert_transaction(tx_id, self.selections.disjoint_anchors());
17025 cx.emit(EditorEvent::TransactionBegun {
17026 transaction_id: tx_id,
17027 });
17028 Some(tx_id)
17029 } else {
17030 None
17031 }
17032 }
17033
17034 pub fn end_transaction_at(
17035 &mut self,
17036 now: Instant,
17037 cx: &mut Context<Self>,
17038 ) -> Option<TransactionId> {
17039 if let Some(transaction_id) = self
17040 .buffer
17041 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
17042 {
17043 if let Some((_, end_selections)) =
17044 self.selection_history.transaction_mut(transaction_id)
17045 {
17046 *end_selections = Some(self.selections.disjoint_anchors());
17047 } else {
17048 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
17049 }
17050
17051 cx.emit(EditorEvent::Edited { transaction_id });
17052 Some(transaction_id)
17053 } else {
17054 None
17055 }
17056 }
17057
17058 pub fn modify_transaction_selection_history(
17059 &mut self,
17060 transaction_id: TransactionId,
17061 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
17062 ) -> bool {
17063 self.selection_history
17064 .transaction_mut(transaction_id)
17065 .map(modify)
17066 .is_some()
17067 }
17068
17069 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
17070 if self.selection_mark_mode {
17071 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17072 s.move_with(|_, sel| {
17073 sel.collapse_to(sel.head(), SelectionGoal::None);
17074 });
17075 })
17076 }
17077 self.selection_mark_mode = true;
17078 cx.notify();
17079 }
17080
17081 pub fn swap_selection_ends(
17082 &mut self,
17083 _: &actions::SwapSelectionEnds,
17084 window: &mut Window,
17085 cx: &mut Context<Self>,
17086 ) {
17087 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17088 s.move_with(|_, sel| {
17089 if sel.start != sel.end {
17090 sel.reversed = !sel.reversed
17091 }
17092 });
17093 });
17094 self.request_autoscroll(Autoscroll::newest(), cx);
17095 cx.notify();
17096 }
17097
17098 pub fn toggle_focus(
17099 workspace: &mut Workspace,
17100 _: &actions::ToggleFocus,
17101 window: &mut Window,
17102 cx: &mut Context<Workspace>,
17103 ) {
17104 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
17105 return;
17106 };
17107 workspace.activate_item(&item, true, true, window, cx);
17108 }
17109
17110 pub fn toggle_fold(
17111 &mut self,
17112 _: &actions::ToggleFold,
17113 window: &mut Window,
17114 cx: &mut Context<Self>,
17115 ) {
17116 if self.is_singleton(cx) {
17117 let selection = self.selections.newest::<Point>(cx);
17118
17119 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17120 let range = if selection.is_empty() {
17121 let point = selection.head().to_display_point(&display_map);
17122 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17123 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17124 .to_point(&display_map);
17125 start..end
17126 } else {
17127 selection.range()
17128 };
17129 if display_map.folds_in_range(range).next().is_some() {
17130 self.unfold_lines(&Default::default(), window, cx)
17131 } else {
17132 self.fold(&Default::default(), window, cx)
17133 }
17134 } else {
17135 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17136 let buffer_ids: HashSet<_> = self
17137 .selections
17138 .disjoint_anchor_ranges()
17139 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17140 .collect();
17141
17142 let should_unfold = buffer_ids
17143 .iter()
17144 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17145
17146 for buffer_id in buffer_ids {
17147 if should_unfold {
17148 self.unfold_buffer(buffer_id, cx);
17149 } else {
17150 self.fold_buffer(buffer_id, cx);
17151 }
17152 }
17153 }
17154 }
17155
17156 pub fn toggle_fold_recursive(
17157 &mut self,
17158 _: &actions::ToggleFoldRecursive,
17159 window: &mut Window,
17160 cx: &mut Context<Self>,
17161 ) {
17162 let selection = self.selections.newest::<Point>(cx);
17163
17164 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17165 let range = if selection.is_empty() {
17166 let point = selection.head().to_display_point(&display_map);
17167 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17168 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17169 .to_point(&display_map);
17170 start..end
17171 } else {
17172 selection.range()
17173 };
17174 if display_map.folds_in_range(range).next().is_some() {
17175 self.unfold_recursive(&Default::default(), window, cx)
17176 } else {
17177 self.fold_recursive(&Default::default(), window, cx)
17178 }
17179 }
17180
17181 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
17182 if self.is_singleton(cx) {
17183 let mut to_fold = Vec::new();
17184 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17185 let selections = self.selections.all_adjusted(cx);
17186
17187 for selection in selections {
17188 let range = selection.range().sorted();
17189 let buffer_start_row = range.start.row;
17190
17191 if range.start.row != range.end.row {
17192 let mut found = false;
17193 let mut row = range.start.row;
17194 while row <= range.end.row {
17195 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
17196 {
17197 found = true;
17198 row = crease.range().end.row + 1;
17199 to_fold.push(crease);
17200 } else {
17201 row += 1
17202 }
17203 }
17204 if found {
17205 continue;
17206 }
17207 }
17208
17209 for row in (0..=range.start.row).rev() {
17210 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17211 if crease.range().end.row >= buffer_start_row {
17212 to_fold.push(crease);
17213 if row <= range.start.row {
17214 break;
17215 }
17216 }
17217 }
17218 }
17219 }
17220
17221 self.fold_creases(to_fold, true, window, cx);
17222 } else {
17223 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17224 let buffer_ids = self
17225 .selections
17226 .disjoint_anchor_ranges()
17227 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17228 .collect::<HashSet<_>>();
17229 for buffer_id in buffer_ids {
17230 self.fold_buffer(buffer_id, cx);
17231 }
17232 }
17233 }
17234
17235 pub fn toggle_fold_all(
17236 &mut self,
17237 _: &actions::ToggleFoldAll,
17238 window: &mut Window,
17239 cx: &mut Context<Self>,
17240 ) {
17241 if self.buffer.read(cx).is_singleton() {
17242 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17243 let has_folds = display_map
17244 .folds_in_range(0..display_map.buffer_snapshot.len())
17245 .next()
17246 .is_some();
17247
17248 if has_folds {
17249 self.unfold_all(&actions::UnfoldAll, window, cx);
17250 } else {
17251 self.fold_all(&actions::FoldAll, window, cx);
17252 }
17253 } else {
17254 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
17255 let should_unfold = buffer_ids
17256 .iter()
17257 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17258
17259 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17260 editor
17261 .update_in(cx, |editor, _, cx| {
17262 for buffer_id in buffer_ids {
17263 if should_unfold {
17264 editor.unfold_buffer(buffer_id, cx);
17265 } else {
17266 editor.fold_buffer(buffer_id, cx);
17267 }
17268 }
17269 })
17270 .ok();
17271 });
17272 }
17273 }
17274
17275 fn fold_at_level(
17276 &mut self,
17277 fold_at: &FoldAtLevel,
17278 window: &mut Window,
17279 cx: &mut Context<Self>,
17280 ) {
17281 if !self.buffer.read(cx).is_singleton() {
17282 return;
17283 }
17284
17285 let fold_at_level = fold_at.0;
17286 let snapshot = self.buffer.read(cx).snapshot(cx);
17287 let mut to_fold = Vec::new();
17288 let mut stack = vec![(0, snapshot.max_row().0, 1)];
17289
17290 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
17291 while start_row < end_row {
17292 match self
17293 .snapshot(window, cx)
17294 .crease_for_buffer_row(MultiBufferRow(start_row))
17295 {
17296 Some(crease) => {
17297 let nested_start_row = crease.range().start.row + 1;
17298 let nested_end_row = crease.range().end.row;
17299
17300 if current_level < fold_at_level {
17301 stack.push((nested_start_row, nested_end_row, current_level + 1));
17302 } else if current_level == fold_at_level {
17303 to_fold.push(crease);
17304 }
17305
17306 start_row = nested_end_row + 1;
17307 }
17308 None => start_row += 1,
17309 }
17310 }
17311 }
17312
17313 self.fold_creases(to_fold, true, window, cx);
17314 }
17315
17316 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
17317 if self.buffer.read(cx).is_singleton() {
17318 let mut fold_ranges = Vec::new();
17319 let snapshot = self.buffer.read(cx).snapshot(cx);
17320
17321 for row in 0..snapshot.max_row().0 {
17322 if let Some(foldable_range) = self
17323 .snapshot(window, cx)
17324 .crease_for_buffer_row(MultiBufferRow(row))
17325 {
17326 fold_ranges.push(foldable_range);
17327 }
17328 }
17329
17330 self.fold_creases(fold_ranges, true, window, cx);
17331 } else {
17332 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17333 editor
17334 .update_in(cx, |editor, _, cx| {
17335 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17336 editor.fold_buffer(buffer_id, cx);
17337 }
17338 })
17339 .ok();
17340 });
17341 }
17342 }
17343
17344 pub fn fold_function_bodies(
17345 &mut self,
17346 _: &actions::FoldFunctionBodies,
17347 window: &mut Window,
17348 cx: &mut Context<Self>,
17349 ) {
17350 let snapshot = self.buffer.read(cx).snapshot(cx);
17351
17352 let ranges = snapshot
17353 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
17354 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
17355 .collect::<Vec<_>>();
17356
17357 let creases = ranges
17358 .into_iter()
17359 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
17360 .collect();
17361
17362 self.fold_creases(creases, true, window, cx);
17363 }
17364
17365 pub fn fold_recursive(
17366 &mut self,
17367 _: &actions::FoldRecursive,
17368 window: &mut Window,
17369 cx: &mut Context<Self>,
17370 ) {
17371 let mut to_fold = Vec::new();
17372 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17373 let selections = self.selections.all_adjusted(cx);
17374
17375 for selection in selections {
17376 let range = selection.range().sorted();
17377 let buffer_start_row = range.start.row;
17378
17379 if range.start.row != range.end.row {
17380 let mut found = false;
17381 for row in range.start.row..=range.end.row {
17382 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17383 found = true;
17384 to_fold.push(crease);
17385 }
17386 }
17387 if found {
17388 continue;
17389 }
17390 }
17391
17392 for row in (0..=range.start.row).rev() {
17393 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17394 if crease.range().end.row >= buffer_start_row {
17395 to_fold.push(crease);
17396 } else {
17397 break;
17398 }
17399 }
17400 }
17401 }
17402
17403 self.fold_creases(to_fold, true, window, cx);
17404 }
17405
17406 pub fn fold_at(
17407 &mut self,
17408 buffer_row: MultiBufferRow,
17409 window: &mut Window,
17410 cx: &mut Context<Self>,
17411 ) {
17412 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17413
17414 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
17415 let autoscroll = self
17416 .selections
17417 .all::<Point>(cx)
17418 .iter()
17419 .any(|selection| crease.range().overlaps(&selection.range()));
17420
17421 self.fold_creases(vec![crease], autoscroll, window, cx);
17422 }
17423 }
17424
17425 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
17426 if self.is_singleton(cx) {
17427 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17428 let buffer = &display_map.buffer_snapshot;
17429 let selections = self.selections.all::<Point>(cx);
17430 let ranges = selections
17431 .iter()
17432 .map(|s| {
17433 let range = s.display_range(&display_map).sorted();
17434 let mut start = range.start.to_point(&display_map);
17435 let mut end = range.end.to_point(&display_map);
17436 start.column = 0;
17437 end.column = buffer.line_len(MultiBufferRow(end.row));
17438 start..end
17439 })
17440 .collect::<Vec<_>>();
17441
17442 self.unfold_ranges(&ranges, true, true, cx);
17443 } else {
17444 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17445 let buffer_ids = self
17446 .selections
17447 .disjoint_anchor_ranges()
17448 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17449 .collect::<HashSet<_>>();
17450 for buffer_id in buffer_ids {
17451 self.unfold_buffer(buffer_id, cx);
17452 }
17453 }
17454 }
17455
17456 pub fn unfold_recursive(
17457 &mut self,
17458 _: &UnfoldRecursive,
17459 _window: &mut Window,
17460 cx: &mut Context<Self>,
17461 ) {
17462 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17463 let selections = self.selections.all::<Point>(cx);
17464 let ranges = selections
17465 .iter()
17466 .map(|s| {
17467 let mut range = s.display_range(&display_map).sorted();
17468 *range.start.column_mut() = 0;
17469 *range.end.column_mut() = display_map.line_len(range.end.row());
17470 let start = range.start.to_point(&display_map);
17471 let end = range.end.to_point(&display_map);
17472 start..end
17473 })
17474 .collect::<Vec<_>>();
17475
17476 self.unfold_ranges(&ranges, true, true, cx);
17477 }
17478
17479 pub fn unfold_at(
17480 &mut self,
17481 buffer_row: MultiBufferRow,
17482 _window: &mut Window,
17483 cx: &mut Context<Self>,
17484 ) {
17485 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17486
17487 let intersection_range = Point::new(buffer_row.0, 0)
17488 ..Point::new(
17489 buffer_row.0,
17490 display_map.buffer_snapshot.line_len(buffer_row),
17491 );
17492
17493 let autoscroll = self
17494 .selections
17495 .all::<Point>(cx)
17496 .iter()
17497 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
17498
17499 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
17500 }
17501
17502 pub fn unfold_all(
17503 &mut self,
17504 _: &actions::UnfoldAll,
17505 _window: &mut Window,
17506 cx: &mut Context<Self>,
17507 ) {
17508 if self.buffer.read(cx).is_singleton() {
17509 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17510 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
17511 } else {
17512 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
17513 editor
17514 .update(cx, |editor, cx| {
17515 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17516 editor.unfold_buffer(buffer_id, cx);
17517 }
17518 })
17519 .ok();
17520 });
17521 }
17522 }
17523
17524 pub fn fold_selected_ranges(
17525 &mut self,
17526 _: &FoldSelectedRanges,
17527 window: &mut Window,
17528 cx: &mut Context<Self>,
17529 ) {
17530 let selections = self.selections.all_adjusted(cx);
17531 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17532 let ranges = selections
17533 .into_iter()
17534 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
17535 .collect::<Vec<_>>();
17536 self.fold_creases(ranges, true, window, cx);
17537 }
17538
17539 pub fn fold_ranges<T: ToOffset + Clone>(
17540 &mut self,
17541 ranges: Vec<Range<T>>,
17542 auto_scroll: bool,
17543 window: &mut Window,
17544 cx: &mut Context<Self>,
17545 ) {
17546 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17547 let ranges = ranges
17548 .into_iter()
17549 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
17550 .collect::<Vec<_>>();
17551 self.fold_creases(ranges, auto_scroll, window, cx);
17552 }
17553
17554 pub fn fold_creases<T: ToOffset + Clone>(
17555 &mut self,
17556 creases: Vec<Crease<T>>,
17557 auto_scroll: bool,
17558 _window: &mut Window,
17559 cx: &mut Context<Self>,
17560 ) {
17561 if creases.is_empty() {
17562 return;
17563 }
17564
17565 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
17566
17567 if auto_scroll {
17568 self.request_autoscroll(Autoscroll::fit(), cx);
17569 }
17570
17571 cx.notify();
17572
17573 self.scrollbar_marker_state.dirty = true;
17574 self.folds_did_change(cx);
17575 }
17576
17577 /// Removes any folds whose ranges intersect any of the given ranges.
17578 pub fn unfold_ranges<T: ToOffset + Clone>(
17579 &mut self,
17580 ranges: &[Range<T>],
17581 inclusive: bool,
17582 auto_scroll: bool,
17583 cx: &mut Context<Self>,
17584 ) {
17585 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17586 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
17587 });
17588 self.folds_did_change(cx);
17589 }
17590
17591 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17592 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
17593 return;
17594 }
17595 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17596 self.display_map.update(cx, |display_map, cx| {
17597 display_map.fold_buffers([buffer_id], cx)
17598 });
17599 cx.emit(EditorEvent::BufferFoldToggled {
17600 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
17601 folded: true,
17602 });
17603 cx.notify();
17604 }
17605
17606 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17607 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
17608 return;
17609 }
17610 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17611 self.display_map.update(cx, |display_map, cx| {
17612 display_map.unfold_buffers([buffer_id], cx);
17613 });
17614 cx.emit(EditorEvent::BufferFoldToggled {
17615 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
17616 folded: false,
17617 });
17618 cx.notify();
17619 }
17620
17621 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
17622 self.display_map.read(cx).is_buffer_folded(buffer)
17623 }
17624
17625 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
17626 self.display_map.read(cx).folded_buffers()
17627 }
17628
17629 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17630 self.display_map.update(cx, |display_map, cx| {
17631 display_map.disable_header_for_buffer(buffer_id, cx);
17632 });
17633 cx.notify();
17634 }
17635
17636 /// Removes any folds with the given ranges.
17637 pub fn remove_folds_with_type<T: ToOffset + Clone>(
17638 &mut self,
17639 ranges: &[Range<T>],
17640 type_id: TypeId,
17641 auto_scroll: bool,
17642 cx: &mut Context<Self>,
17643 ) {
17644 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17645 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
17646 });
17647 self.folds_did_change(cx);
17648 }
17649
17650 fn remove_folds_with<T: ToOffset + Clone>(
17651 &mut self,
17652 ranges: &[Range<T>],
17653 auto_scroll: bool,
17654 cx: &mut Context<Self>,
17655 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
17656 ) {
17657 if ranges.is_empty() {
17658 return;
17659 }
17660
17661 let mut buffers_affected = HashSet::default();
17662 let multi_buffer = self.buffer().read(cx);
17663 for range in ranges {
17664 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
17665 buffers_affected.insert(buffer.read(cx).remote_id());
17666 };
17667 }
17668
17669 self.display_map.update(cx, update);
17670
17671 if auto_scroll {
17672 self.request_autoscroll(Autoscroll::fit(), cx);
17673 }
17674
17675 cx.notify();
17676 self.scrollbar_marker_state.dirty = true;
17677 self.active_indent_guides_state.dirty = true;
17678 }
17679
17680 pub fn update_renderer_widths(
17681 &mut self,
17682 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
17683 cx: &mut Context<Self>,
17684 ) -> bool {
17685 self.display_map
17686 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
17687 }
17688
17689 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
17690 self.display_map.read(cx).fold_placeholder.clone()
17691 }
17692
17693 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
17694 self.buffer.update(cx, |buffer, cx| {
17695 buffer.set_all_diff_hunks_expanded(cx);
17696 });
17697 }
17698
17699 pub fn expand_all_diff_hunks(
17700 &mut self,
17701 _: &ExpandAllDiffHunks,
17702 _window: &mut Window,
17703 cx: &mut Context<Self>,
17704 ) {
17705 self.buffer.update(cx, |buffer, cx| {
17706 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
17707 });
17708 }
17709
17710 pub fn toggle_selected_diff_hunks(
17711 &mut self,
17712 _: &ToggleSelectedDiffHunks,
17713 _window: &mut Window,
17714 cx: &mut Context<Self>,
17715 ) {
17716 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17717 self.toggle_diff_hunks_in_ranges(ranges, cx);
17718 }
17719
17720 pub fn diff_hunks_in_ranges<'a>(
17721 &'a self,
17722 ranges: &'a [Range<Anchor>],
17723 buffer: &'a MultiBufferSnapshot,
17724 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
17725 ranges.iter().flat_map(move |range| {
17726 let end_excerpt_id = range.end.excerpt_id;
17727 let range = range.to_point(buffer);
17728 let mut peek_end = range.end;
17729 if range.end.row < buffer.max_row().0 {
17730 peek_end = Point::new(range.end.row + 1, 0);
17731 }
17732 buffer
17733 .diff_hunks_in_range(range.start..peek_end)
17734 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
17735 })
17736 }
17737
17738 pub fn has_stageable_diff_hunks_in_ranges(
17739 &self,
17740 ranges: &[Range<Anchor>],
17741 snapshot: &MultiBufferSnapshot,
17742 ) -> bool {
17743 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
17744 hunks.any(|hunk| hunk.status().has_secondary_hunk())
17745 }
17746
17747 pub fn toggle_staged_selected_diff_hunks(
17748 &mut self,
17749 _: &::git::ToggleStaged,
17750 _: &mut Window,
17751 cx: &mut Context<Self>,
17752 ) {
17753 let snapshot = self.buffer.read(cx).snapshot(cx);
17754 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17755 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
17756 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17757 }
17758
17759 pub fn set_render_diff_hunk_controls(
17760 &mut self,
17761 render_diff_hunk_controls: RenderDiffHunkControlsFn,
17762 cx: &mut Context<Self>,
17763 ) {
17764 self.render_diff_hunk_controls = render_diff_hunk_controls;
17765 cx.notify();
17766 }
17767
17768 pub fn stage_and_next(
17769 &mut self,
17770 _: &::git::StageAndNext,
17771 window: &mut Window,
17772 cx: &mut Context<Self>,
17773 ) {
17774 self.do_stage_or_unstage_and_next(true, window, cx);
17775 }
17776
17777 pub fn unstage_and_next(
17778 &mut self,
17779 _: &::git::UnstageAndNext,
17780 window: &mut Window,
17781 cx: &mut Context<Self>,
17782 ) {
17783 self.do_stage_or_unstage_and_next(false, window, cx);
17784 }
17785
17786 pub fn stage_or_unstage_diff_hunks(
17787 &mut self,
17788 stage: bool,
17789 ranges: Vec<Range<Anchor>>,
17790 cx: &mut Context<Self>,
17791 ) {
17792 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
17793 cx.spawn(async move |this, cx| {
17794 task.await?;
17795 this.update(cx, |this, cx| {
17796 let snapshot = this.buffer.read(cx).snapshot(cx);
17797 let chunk_by = this
17798 .diff_hunks_in_ranges(&ranges, &snapshot)
17799 .chunk_by(|hunk| hunk.buffer_id);
17800 for (buffer_id, hunks) in &chunk_by {
17801 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
17802 }
17803 })
17804 })
17805 .detach_and_log_err(cx);
17806 }
17807
17808 fn save_buffers_for_ranges_if_needed(
17809 &mut self,
17810 ranges: &[Range<Anchor>],
17811 cx: &mut Context<Editor>,
17812 ) -> Task<Result<()>> {
17813 let multibuffer = self.buffer.read(cx);
17814 let snapshot = multibuffer.read(cx);
17815 let buffer_ids: HashSet<_> = ranges
17816 .iter()
17817 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
17818 .collect();
17819 drop(snapshot);
17820
17821 let mut buffers = HashSet::default();
17822 for buffer_id in buffer_ids {
17823 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
17824 let buffer = buffer_entity.read(cx);
17825 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
17826 {
17827 buffers.insert(buffer_entity);
17828 }
17829 }
17830 }
17831
17832 if let Some(project) = &self.project {
17833 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
17834 } else {
17835 Task::ready(Ok(()))
17836 }
17837 }
17838
17839 fn do_stage_or_unstage_and_next(
17840 &mut self,
17841 stage: bool,
17842 window: &mut Window,
17843 cx: &mut Context<Self>,
17844 ) {
17845 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
17846
17847 if ranges.iter().any(|range| range.start != range.end) {
17848 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17849 return;
17850 }
17851
17852 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17853 let snapshot = self.snapshot(window, cx);
17854 let position = self.selections.newest::<Point>(cx).head();
17855 let mut row = snapshot
17856 .buffer_snapshot
17857 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
17858 .find(|hunk| hunk.row_range.start.0 > position.row)
17859 .map(|hunk| hunk.row_range.start);
17860
17861 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
17862 // Outside of the project diff editor, wrap around to the beginning.
17863 if !all_diff_hunks_expanded {
17864 row = row.or_else(|| {
17865 snapshot
17866 .buffer_snapshot
17867 .diff_hunks_in_range(Point::zero()..position)
17868 .find(|hunk| hunk.row_range.end.0 < position.row)
17869 .map(|hunk| hunk.row_range.start)
17870 });
17871 }
17872
17873 if let Some(row) = row {
17874 let destination = Point::new(row.0, 0);
17875 let autoscroll = Autoscroll::center();
17876
17877 self.unfold_ranges(&[destination..destination], false, false, cx);
17878 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17879 s.select_ranges([destination..destination]);
17880 });
17881 }
17882 }
17883
17884 fn do_stage_or_unstage(
17885 &self,
17886 stage: bool,
17887 buffer_id: BufferId,
17888 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
17889 cx: &mut App,
17890 ) -> Option<()> {
17891 let project = self.project.as_ref()?;
17892 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
17893 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
17894 let buffer_snapshot = buffer.read(cx).snapshot();
17895 let file_exists = buffer_snapshot
17896 .file()
17897 .is_some_and(|file| file.disk_state().exists());
17898 diff.update(cx, |diff, cx| {
17899 diff.stage_or_unstage_hunks(
17900 stage,
17901 &hunks
17902 .map(|hunk| buffer_diff::DiffHunk {
17903 buffer_range: hunk.buffer_range,
17904 diff_base_byte_range: hunk.diff_base_byte_range,
17905 secondary_status: hunk.secondary_status,
17906 range: Point::zero()..Point::zero(), // unused
17907 })
17908 .collect::<Vec<_>>(),
17909 &buffer_snapshot,
17910 file_exists,
17911 cx,
17912 )
17913 });
17914 None
17915 }
17916
17917 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
17918 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17919 self.buffer
17920 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
17921 }
17922
17923 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
17924 self.buffer.update(cx, |buffer, cx| {
17925 let ranges = vec![Anchor::min()..Anchor::max()];
17926 if !buffer.all_diff_hunks_expanded()
17927 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
17928 {
17929 buffer.collapse_diff_hunks(ranges, cx);
17930 true
17931 } else {
17932 false
17933 }
17934 })
17935 }
17936
17937 fn toggle_diff_hunks_in_ranges(
17938 &mut self,
17939 ranges: Vec<Range<Anchor>>,
17940 cx: &mut Context<Editor>,
17941 ) {
17942 self.buffer.update(cx, |buffer, cx| {
17943 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
17944 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
17945 })
17946 }
17947
17948 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
17949 self.buffer.update(cx, |buffer, cx| {
17950 let snapshot = buffer.snapshot(cx);
17951 let excerpt_id = range.end.excerpt_id;
17952 let point_range = range.to_point(&snapshot);
17953 let expand = !buffer.single_hunk_is_expanded(range, cx);
17954 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
17955 })
17956 }
17957
17958 pub(crate) fn apply_all_diff_hunks(
17959 &mut self,
17960 _: &ApplyAllDiffHunks,
17961 window: &mut Window,
17962 cx: &mut Context<Self>,
17963 ) {
17964 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17965
17966 let buffers = self.buffer.read(cx).all_buffers();
17967 for branch_buffer in buffers {
17968 branch_buffer.update(cx, |branch_buffer, cx| {
17969 branch_buffer.merge_into_base(Vec::new(), cx);
17970 });
17971 }
17972
17973 if let Some(project) = self.project.clone() {
17974 self.save(
17975 SaveOptions {
17976 format: true,
17977 autosave: false,
17978 },
17979 project,
17980 window,
17981 cx,
17982 )
17983 .detach_and_log_err(cx);
17984 }
17985 }
17986
17987 pub(crate) fn apply_selected_diff_hunks(
17988 &mut self,
17989 _: &ApplyDiffHunk,
17990 window: &mut Window,
17991 cx: &mut Context<Self>,
17992 ) {
17993 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17994 let snapshot = self.snapshot(window, cx);
17995 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
17996 let mut ranges_by_buffer = HashMap::default();
17997 self.transact(window, cx, |editor, _window, cx| {
17998 for hunk in hunks {
17999 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
18000 ranges_by_buffer
18001 .entry(buffer.clone())
18002 .or_insert_with(Vec::new)
18003 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
18004 }
18005 }
18006
18007 for (buffer, ranges) in ranges_by_buffer {
18008 buffer.update(cx, |buffer, cx| {
18009 buffer.merge_into_base(ranges, cx);
18010 });
18011 }
18012 });
18013
18014 if let Some(project) = self.project.clone() {
18015 self.save(
18016 SaveOptions {
18017 format: true,
18018 autosave: false,
18019 },
18020 project,
18021 window,
18022 cx,
18023 )
18024 .detach_and_log_err(cx);
18025 }
18026 }
18027
18028 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
18029 if hovered != self.gutter_hovered {
18030 self.gutter_hovered = hovered;
18031 cx.notify();
18032 }
18033 }
18034
18035 pub fn insert_blocks(
18036 &mut self,
18037 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
18038 autoscroll: Option<Autoscroll>,
18039 cx: &mut Context<Self>,
18040 ) -> Vec<CustomBlockId> {
18041 let blocks = self
18042 .display_map
18043 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
18044 if let Some(autoscroll) = autoscroll {
18045 self.request_autoscroll(autoscroll, cx);
18046 }
18047 cx.notify();
18048 blocks
18049 }
18050
18051 pub fn resize_blocks(
18052 &mut self,
18053 heights: HashMap<CustomBlockId, u32>,
18054 autoscroll: Option<Autoscroll>,
18055 cx: &mut Context<Self>,
18056 ) {
18057 self.display_map
18058 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
18059 if let Some(autoscroll) = autoscroll {
18060 self.request_autoscroll(autoscroll, cx);
18061 }
18062 cx.notify();
18063 }
18064
18065 pub fn replace_blocks(
18066 &mut self,
18067 renderers: HashMap<CustomBlockId, RenderBlock>,
18068 autoscroll: Option<Autoscroll>,
18069 cx: &mut Context<Self>,
18070 ) {
18071 self.display_map
18072 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
18073 if let Some(autoscroll) = autoscroll {
18074 self.request_autoscroll(autoscroll, cx);
18075 }
18076 cx.notify();
18077 }
18078
18079 pub fn remove_blocks(
18080 &mut self,
18081 block_ids: HashSet<CustomBlockId>,
18082 autoscroll: Option<Autoscroll>,
18083 cx: &mut Context<Self>,
18084 ) {
18085 self.display_map.update(cx, |display_map, cx| {
18086 display_map.remove_blocks(block_ids, cx)
18087 });
18088 if let Some(autoscroll) = autoscroll {
18089 self.request_autoscroll(autoscroll, cx);
18090 }
18091 cx.notify();
18092 }
18093
18094 pub fn row_for_block(
18095 &self,
18096 block_id: CustomBlockId,
18097 cx: &mut Context<Self>,
18098 ) -> Option<DisplayRow> {
18099 self.display_map
18100 .update(cx, |map, cx| map.row_for_block(block_id, cx))
18101 }
18102
18103 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
18104 self.focused_block = Some(focused_block);
18105 }
18106
18107 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
18108 self.focused_block.take()
18109 }
18110
18111 pub fn insert_creases(
18112 &mut self,
18113 creases: impl IntoIterator<Item = Crease<Anchor>>,
18114 cx: &mut Context<Self>,
18115 ) -> Vec<CreaseId> {
18116 self.display_map
18117 .update(cx, |map, cx| map.insert_creases(creases, cx))
18118 }
18119
18120 pub fn remove_creases(
18121 &mut self,
18122 ids: impl IntoIterator<Item = CreaseId>,
18123 cx: &mut Context<Self>,
18124 ) -> Vec<(CreaseId, Range<Anchor>)> {
18125 self.display_map
18126 .update(cx, |map, cx| map.remove_creases(ids, cx))
18127 }
18128
18129 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
18130 self.display_map
18131 .update(cx, |map, cx| map.snapshot(cx))
18132 .longest_row()
18133 }
18134
18135 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
18136 self.display_map
18137 .update(cx, |map, cx| map.snapshot(cx))
18138 .max_point()
18139 }
18140
18141 pub fn text(&self, cx: &App) -> String {
18142 self.buffer.read(cx).read(cx).text()
18143 }
18144
18145 pub fn is_empty(&self, cx: &App) -> bool {
18146 self.buffer.read(cx).read(cx).is_empty()
18147 }
18148
18149 pub fn text_option(&self, cx: &App) -> Option<String> {
18150 let text = self.text(cx);
18151 let text = text.trim();
18152
18153 if text.is_empty() {
18154 return None;
18155 }
18156
18157 Some(text.to_string())
18158 }
18159
18160 pub fn set_text(
18161 &mut self,
18162 text: impl Into<Arc<str>>,
18163 window: &mut Window,
18164 cx: &mut Context<Self>,
18165 ) {
18166 self.transact(window, cx, |this, _, cx| {
18167 this.buffer
18168 .read(cx)
18169 .as_singleton()
18170 .expect("you can only call set_text on editors for singleton buffers")
18171 .update(cx, |buffer, cx| buffer.set_text(text, cx));
18172 });
18173 }
18174
18175 pub fn display_text(&self, cx: &mut App) -> String {
18176 self.display_map
18177 .update(cx, |map, cx| map.snapshot(cx))
18178 .text()
18179 }
18180
18181 fn create_minimap(
18182 &self,
18183 minimap_settings: MinimapSettings,
18184 window: &mut Window,
18185 cx: &mut Context<Self>,
18186 ) -> Option<Entity<Self>> {
18187 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
18188 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
18189 }
18190
18191 fn initialize_new_minimap(
18192 &self,
18193 minimap_settings: MinimapSettings,
18194 window: &mut Window,
18195 cx: &mut Context<Self>,
18196 ) -> Entity<Self> {
18197 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
18198
18199 let mut minimap = Editor::new_internal(
18200 EditorMode::Minimap {
18201 parent: cx.weak_entity(),
18202 },
18203 self.buffer.clone(),
18204 None,
18205 Some(self.display_map.clone()),
18206 window,
18207 cx,
18208 );
18209 minimap.scroll_manager.clone_state(&self.scroll_manager);
18210 minimap.set_text_style_refinement(TextStyleRefinement {
18211 font_size: Some(MINIMAP_FONT_SIZE),
18212 font_weight: Some(MINIMAP_FONT_WEIGHT),
18213 ..Default::default()
18214 });
18215 minimap.update_minimap_configuration(minimap_settings, cx);
18216 cx.new(|_| minimap)
18217 }
18218
18219 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
18220 let current_line_highlight = minimap_settings
18221 .current_line_highlight
18222 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
18223 self.set_current_line_highlight(Some(current_line_highlight));
18224 }
18225
18226 pub fn minimap(&self) -> Option<&Entity<Self>> {
18227 self.minimap
18228 .as_ref()
18229 .filter(|_| self.minimap_visibility.visible())
18230 }
18231
18232 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
18233 let mut wrap_guides = smallvec![];
18234
18235 if self.show_wrap_guides == Some(false) {
18236 return wrap_guides;
18237 }
18238
18239 let settings = self.buffer.read(cx).language_settings(cx);
18240 if settings.show_wrap_guides {
18241 match self.soft_wrap_mode(cx) {
18242 SoftWrap::Column(soft_wrap) => {
18243 wrap_guides.push((soft_wrap as usize, true));
18244 }
18245 SoftWrap::Bounded(soft_wrap) => {
18246 wrap_guides.push((soft_wrap as usize, true));
18247 }
18248 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
18249 }
18250 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
18251 }
18252
18253 wrap_guides
18254 }
18255
18256 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
18257 let settings = self.buffer.read(cx).language_settings(cx);
18258 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
18259 match mode {
18260 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
18261 SoftWrap::None
18262 }
18263 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
18264 language_settings::SoftWrap::PreferredLineLength => {
18265 SoftWrap::Column(settings.preferred_line_length)
18266 }
18267 language_settings::SoftWrap::Bounded => {
18268 SoftWrap::Bounded(settings.preferred_line_length)
18269 }
18270 }
18271 }
18272
18273 pub fn set_soft_wrap_mode(
18274 &mut self,
18275 mode: language_settings::SoftWrap,
18276
18277 cx: &mut Context<Self>,
18278 ) {
18279 self.soft_wrap_mode_override = Some(mode);
18280 cx.notify();
18281 }
18282
18283 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
18284 self.hard_wrap = hard_wrap;
18285 cx.notify();
18286 }
18287
18288 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
18289 self.text_style_refinement = Some(style);
18290 }
18291
18292 /// called by the Element so we know what style we were most recently rendered with.
18293 pub(crate) fn set_style(
18294 &mut self,
18295 style: EditorStyle,
18296 window: &mut Window,
18297 cx: &mut Context<Self>,
18298 ) {
18299 // We intentionally do not inform the display map about the minimap style
18300 // so that wrapping is not recalculated and stays consistent for the editor
18301 // and its linked minimap.
18302 if !self.mode.is_minimap() {
18303 let rem_size = window.rem_size();
18304 self.display_map.update(cx, |map, cx| {
18305 map.set_font(
18306 style.text.font(),
18307 style.text.font_size.to_pixels(rem_size),
18308 cx,
18309 )
18310 });
18311 }
18312 self.style = Some(style);
18313 }
18314
18315 pub fn style(&self) -> Option<&EditorStyle> {
18316 self.style.as_ref()
18317 }
18318
18319 // Called by the element. This method is not designed to be called outside of the editor
18320 // element's layout code because it does not notify when rewrapping is computed synchronously.
18321 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
18322 self.display_map
18323 .update(cx, |map, cx| map.set_wrap_width(width, cx))
18324 }
18325
18326 pub fn set_soft_wrap(&mut self) {
18327 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
18328 }
18329
18330 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
18331 if self.soft_wrap_mode_override.is_some() {
18332 self.soft_wrap_mode_override.take();
18333 } else {
18334 let soft_wrap = match self.soft_wrap_mode(cx) {
18335 SoftWrap::GitDiff => return,
18336 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
18337 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
18338 language_settings::SoftWrap::None
18339 }
18340 };
18341 self.soft_wrap_mode_override = Some(soft_wrap);
18342 }
18343 cx.notify();
18344 }
18345
18346 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
18347 let Some(workspace) = self.workspace() else {
18348 return;
18349 };
18350 let fs = workspace.read(cx).app_state().fs.clone();
18351 let current_show = TabBarSettings::get_global(cx).show;
18352 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
18353 setting.show = Some(!current_show);
18354 });
18355 }
18356
18357 pub fn toggle_indent_guides(
18358 &mut self,
18359 _: &ToggleIndentGuides,
18360 _: &mut Window,
18361 cx: &mut Context<Self>,
18362 ) {
18363 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
18364 self.buffer
18365 .read(cx)
18366 .language_settings(cx)
18367 .indent_guides
18368 .enabled
18369 });
18370 self.show_indent_guides = Some(!currently_enabled);
18371 cx.notify();
18372 }
18373
18374 fn should_show_indent_guides(&self) -> Option<bool> {
18375 self.show_indent_guides
18376 }
18377
18378 pub fn toggle_line_numbers(
18379 &mut self,
18380 _: &ToggleLineNumbers,
18381 _: &mut Window,
18382 cx: &mut Context<Self>,
18383 ) {
18384 let mut editor_settings = EditorSettings::get_global(cx).clone();
18385 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
18386 EditorSettings::override_global(editor_settings, cx);
18387 }
18388
18389 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
18390 if let Some(show_line_numbers) = self.show_line_numbers {
18391 return show_line_numbers;
18392 }
18393 EditorSettings::get_global(cx).gutter.line_numbers
18394 }
18395
18396 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
18397 self.use_relative_line_numbers
18398 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
18399 }
18400
18401 pub fn toggle_relative_line_numbers(
18402 &mut self,
18403 _: &ToggleRelativeLineNumbers,
18404 _: &mut Window,
18405 cx: &mut Context<Self>,
18406 ) {
18407 let is_relative = self.should_use_relative_line_numbers(cx);
18408 self.set_relative_line_number(Some(!is_relative), cx)
18409 }
18410
18411 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
18412 self.use_relative_line_numbers = is_relative;
18413 cx.notify();
18414 }
18415
18416 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
18417 self.show_gutter = show_gutter;
18418 cx.notify();
18419 }
18420
18421 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
18422 self.show_scrollbars = ScrollbarAxes {
18423 horizontal: show,
18424 vertical: show,
18425 };
18426 cx.notify();
18427 }
18428
18429 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18430 self.show_scrollbars.vertical = show;
18431 cx.notify();
18432 }
18433
18434 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18435 self.show_scrollbars.horizontal = show;
18436 cx.notify();
18437 }
18438
18439 pub fn set_minimap_visibility(
18440 &mut self,
18441 minimap_visibility: MinimapVisibility,
18442 window: &mut Window,
18443 cx: &mut Context<Self>,
18444 ) {
18445 if self.minimap_visibility != minimap_visibility {
18446 if minimap_visibility.visible() && self.minimap.is_none() {
18447 let minimap_settings = EditorSettings::get_global(cx).minimap;
18448 self.minimap =
18449 self.create_minimap(minimap_settings.with_show_override(), window, cx);
18450 }
18451 self.minimap_visibility = minimap_visibility;
18452 cx.notify();
18453 }
18454 }
18455
18456 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18457 self.set_show_scrollbars(false, cx);
18458 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
18459 }
18460
18461 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18462 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
18463 }
18464
18465 /// Normally the text in full mode and auto height editors is padded on the
18466 /// left side by roughly half a character width for improved hit testing.
18467 ///
18468 /// Use this method to disable this for cases where this is not wanted (e.g.
18469 /// if you want to align the editor text with some other text above or below)
18470 /// or if you want to add this padding to single-line editors.
18471 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
18472 self.offset_content = offset_content;
18473 cx.notify();
18474 }
18475
18476 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
18477 self.show_line_numbers = Some(show_line_numbers);
18478 cx.notify();
18479 }
18480
18481 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
18482 self.disable_expand_excerpt_buttons = true;
18483 cx.notify();
18484 }
18485
18486 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
18487 self.show_git_diff_gutter = Some(show_git_diff_gutter);
18488 cx.notify();
18489 }
18490
18491 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
18492 self.show_code_actions = Some(show_code_actions);
18493 cx.notify();
18494 }
18495
18496 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
18497 self.show_runnables = Some(show_runnables);
18498 cx.notify();
18499 }
18500
18501 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
18502 self.show_breakpoints = Some(show_breakpoints);
18503 cx.notify();
18504 }
18505
18506 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
18507 if self.display_map.read(cx).masked != masked {
18508 self.display_map.update(cx, |map, _| map.masked = masked);
18509 }
18510 cx.notify()
18511 }
18512
18513 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
18514 self.show_wrap_guides = Some(show_wrap_guides);
18515 cx.notify();
18516 }
18517
18518 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
18519 self.show_indent_guides = Some(show_indent_guides);
18520 cx.notify();
18521 }
18522
18523 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
18524 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
18525 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
18526 if let Some(dir) = file.abs_path(cx).parent() {
18527 return Some(dir.to_owned());
18528 }
18529 }
18530
18531 if let Some(project_path) = buffer.read(cx).project_path(cx) {
18532 return Some(project_path.path.to_path_buf());
18533 }
18534 }
18535
18536 None
18537 }
18538
18539 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
18540 self.active_excerpt(cx)?
18541 .1
18542 .read(cx)
18543 .file()
18544 .and_then(|f| f.as_local())
18545 }
18546
18547 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18548 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18549 let buffer = buffer.read(cx);
18550 if let Some(project_path) = buffer.project_path(cx) {
18551 let project = self.project.as_ref()?.read(cx);
18552 project.absolute_path(&project_path, cx)
18553 } else {
18554 buffer
18555 .file()
18556 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
18557 }
18558 })
18559 }
18560
18561 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18562 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18563 let project_path = buffer.read(cx).project_path(cx)?;
18564 let project = self.project.as_ref()?.read(cx);
18565 let entry = project.entry_for_path(&project_path, cx)?;
18566 let path = entry.path.to_path_buf();
18567 Some(path)
18568 })
18569 }
18570
18571 pub fn reveal_in_finder(
18572 &mut self,
18573 _: &RevealInFileManager,
18574 _window: &mut Window,
18575 cx: &mut Context<Self>,
18576 ) {
18577 if let Some(target) = self.target_file(cx) {
18578 cx.reveal_path(&target.abs_path(cx));
18579 }
18580 }
18581
18582 pub fn copy_path(
18583 &mut self,
18584 _: &zed_actions::workspace::CopyPath,
18585 _window: &mut Window,
18586 cx: &mut Context<Self>,
18587 ) {
18588 if let Some(path) = self.target_file_abs_path(cx) {
18589 if let Some(path) = path.to_str() {
18590 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18591 }
18592 }
18593 }
18594
18595 pub fn copy_relative_path(
18596 &mut self,
18597 _: &zed_actions::workspace::CopyRelativePath,
18598 _window: &mut Window,
18599 cx: &mut Context<Self>,
18600 ) {
18601 if let Some(path) = self.target_file_path(cx) {
18602 if let Some(path) = path.to_str() {
18603 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18604 }
18605 }
18606 }
18607
18608 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
18609 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
18610 buffer.read(cx).project_path(cx)
18611 } else {
18612 None
18613 }
18614 }
18615
18616 // Returns true if the editor handled a go-to-line request
18617 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
18618 maybe!({
18619 let breakpoint_store = self.breakpoint_store.as_ref()?;
18620
18621 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
18622 else {
18623 self.clear_row_highlights::<ActiveDebugLine>();
18624 return None;
18625 };
18626
18627 let position = active_stack_frame.position;
18628 let buffer_id = position.buffer_id?;
18629 let snapshot = self
18630 .project
18631 .as_ref()?
18632 .read(cx)
18633 .buffer_for_id(buffer_id, cx)?
18634 .read(cx)
18635 .snapshot();
18636
18637 let mut handled = false;
18638 for (id, ExcerptRange { context, .. }) in
18639 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
18640 {
18641 if context.start.cmp(&position, &snapshot).is_ge()
18642 || context.end.cmp(&position, &snapshot).is_lt()
18643 {
18644 continue;
18645 }
18646 let snapshot = self.buffer.read(cx).snapshot(cx);
18647 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
18648
18649 handled = true;
18650 self.clear_row_highlights::<ActiveDebugLine>();
18651
18652 self.go_to_line::<ActiveDebugLine>(
18653 multibuffer_anchor,
18654 Some(cx.theme().colors().editor_debugger_active_line_background),
18655 window,
18656 cx,
18657 );
18658
18659 cx.notify();
18660 }
18661
18662 handled.then_some(())
18663 })
18664 .is_some()
18665 }
18666
18667 pub fn copy_file_name_without_extension(
18668 &mut self,
18669 _: &CopyFileNameWithoutExtension,
18670 _: &mut Window,
18671 cx: &mut Context<Self>,
18672 ) {
18673 if let Some(file) = self.target_file(cx) {
18674 if let Some(file_stem) = file.path().file_stem() {
18675 if let Some(name) = file_stem.to_str() {
18676 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18677 }
18678 }
18679 }
18680 }
18681
18682 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
18683 if let Some(file) = self.target_file(cx) {
18684 if let Some(file_name) = file.path().file_name() {
18685 if let Some(name) = file_name.to_str() {
18686 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18687 }
18688 }
18689 }
18690 }
18691
18692 pub fn toggle_git_blame(
18693 &mut self,
18694 _: &::git::Blame,
18695 window: &mut Window,
18696 cx: &mut Context<Self>,
18697 ) {
18698 self.show_git_blame_gutter = !self.show_git_blame_gutter;
18699
18700 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
18701 self.start_git_blame(true, window, cx);
18702 }
18703
18704 cx.notify();
18705 }
18706
18707 pub fn toggle_git_blame_inline(
18708 &mut self,
18709 _: &ToggleGitBlameInline,
18710 window: &mut Window,
18711 cx: &mut Context<Self>,
18712 ) {
18713 self.toggle_git_blame_inline_internal(true, window, cx);
18714 cx.notify();
18715 }
18716
18717 pub fn open_git_blame_commit(
18718 &mut self,
18719 _: &OpenGitBlameCommit,
18720 window: &mut Window,
18721 cx: &mut Context<Self>,
18722 ) {
18723 self.open_git_blame_commit_internal(window, cx);
18724 }
18725
18726 fn open_git_blame_commit_internal(
18727 &mut self,
18728 window: &mut Window,
18729 cx: &mut Context<Self>,
18730 ) -> Option<()> {
18731 let blame = self.blame.as_ref()?;
18732 let snapshot = self.snapshot(window, cx);
18733 let cursor = self.selections.newest::<Point>(cx).head();
18734 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
18735 let blame_entry = blame
18736 .update(cx, |blame, cx| {
18737 blame
18738 .blame_for_rows(
18739 &[RowInfo {
18740 buffer_id: Some(buffer.remote_id()),
18741 buffer_row: Some(point.row),
18742 ..Default::default()
18743 }],
18744 cx,
18745 )
18746 .next()
18747 })
18748 .flatten()?;
18749 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
18750 let repo = blame.read(cx).repository(cx)?;
18751 let workspace = self.workspace()?.downgrade();
18752 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
18753 None
18754 }
18755
18756 pub fn git_blame_inline_enabled(&self) -> bool {
18757 self.git_blame_inline_enabled
18758 }
18759
18760 pub fn toggle_selection_menu(
18761 &mut self,
18762 _: &ToggleSelectionMenu,
18763 _: &mut Window,
18764 cx: &mut Context<Self>,
18765 ) {
18766 self.show_selection_menu = self
18767 .show_selection_menu
18768 .map(|show_selections_menu| !show_selections_menu)
18769 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
18770
18771 cx.notify();
18772 }
18773
18774 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
18775 self.show_selection_menu
18776 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
18777 }
18778
18779 fn start_git_blame(
18780 &mut self,
18781 user_triggered: bool,
18782 window: &mut Window,
18783 cx: &mut Context<Self>,
18784 ) {
18785 if let Some(project) = self.project.as_ref() {
18786 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
18787 return;
18788 };
18789
18790 if buffer.read(cx).file().is_none() {
18791 return;
18792 }
18793
18794 let focused = self.focus_handle(cx).contains_focused(window, cx);
18795
18796 let project = project.clone();
18797 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
18798 self.blame_subscription =
18799 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
18800 self.blame = Some(blame);
18801 }
18802 }
18803
18804 fn toggle_git_blame_inline_internal(
18805 &mut self,
18806 user_triggered: bool,
18807 window: &mut Window,
18808 cx: &mut Context<Self>,
18809 ) {
18810 if self.git_blame_inline_enabled {
18811 self.git_blame_inline_enabled = false;
18812 self.show_git_blame_inline = false;
18813 self.show_git_blame_inline_delay_task.take();
18814 } else {
18815 self.git_blame_inline_enabled = true;
18816 self.start_git_blame_inline(user_triggered, window, cx);
18817 }
18818
18819 cx.notify();
18820 }
18821
18822 fn start_git_blame_inline(
18823 &mut self,
18824 user_triggered: bool,
18825 window: &mut Window,
18826 cx: &mut Context<Self>,
18827 ) {
18828 self.start_git_blame(user_triggered, window, cx);
18829
18830 if ProjectSettings::get_global(cx)
18831 .git
18832 .inline_blame_delay()
18833 .is_some()
18834 {
18835 self.start_inline_blame_timer(window, cx);
18836 } else {
18837 self.show_git_blame_inline = true
18838 }
18839 }
18840
18841 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
18842 self.blame.as_ref()
18843 }
18844
18845 pub fn show_git_blame_gutter(&self) -> bool {
18846 self.show_git_blame_gutter
18847 }
18848
18849 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
18850 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
18851 }
18852
18853 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
18854 self.show_git_blame_inline
18855 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
18856 && !self.newest_selection_head_on_empty_line(cx)
18857 && self.has_blame_entries(cx)
18858 }
18859
18860 fn has_blame_entries(&self, cx: &App) -> bool {
18861 self.blame()
18862 .map_or(false, |blame| blame.read(cx).has_generated_entries())
18863 }
18864
18865 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
18866 let cursor_anchor = self.selections.newest_anchor().head();
18867
18868 let snapshot = self.buffer.read(cx).snapshot(cx);
18869 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
18870
18871 snapshot.line_len(buffer_row) == 0
18872 }
18873
18874 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
18875 let buffer_and_selection = maybe!({
18876 let selection = self.selections.newest::<Point>(cx);
18877 let selection_range = selection.range();
18878
18879 let multi_buffer = self.buffer().read(cx);
18880 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18881 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
18882
18883 let (buffer, range, _) = if selection.reversed {
18884 buffer_ranges.first()
18885 } else {
18886 buffer_ranges.last()
18887 }?;
18888
18889 let selection = text::ToPoint::to_point(&range.start, &buffer).row
18890 ..text::ToPoint::to_point(&range.end, &buffer).row;
18891 Some((
18892 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
18893 selection,
18894 ))
18895 });
18896
18897 let Some((buffer, selection)) = buffer_and_selection else {
18898 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
18899 };
18900
18901 let Some(project) = self.project.as_ref() else {
18902 return Task::ready(Err(anyhow!("editor does not have project")));
18903 };
18904
18905 project.update(cx, |project, cx| {
18906 project.get_permalink_to_line(&buffer, selection, cx)
18907 })
18908 }
18909
18910 pub fn copy_permalink_to_line(
18911 &mut self,
18912 _: &CopyPermalinkToLine,
18913 window: &mut Window,
18914 cx: &mut Context<Self>,
18915 ) {
18916 let permalink_task = self.get_permalink_to_line(cx);
18917 let workspace = self.workspace();
18918
18919 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18920 Ok(permalink) => {
18921 cx.update(|_, cx| {
18922 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
18923 })
18924 .ok();
18925 }
18926 Err(err) => {
18927 let message = format!("Failed to copy permalink: {err}");
18928
18929 anyhow::Result::<()>::Err(err).log_err();
18930
18931 if let Some(workspace) = workspace {
18932 workspace
18933 .update_in(cx, |workspace, _, cx| {
18934 struct CopyPermalinkToLine;
18935
18936 workspace.show_toast(
18937 Toast::new(
18938 NotificationId::unique::<CopyPermalinkToLine>(),
18939 message,
18940 ),
18941 cx,
18942 )
18943 })
18944 .ok();
18945 }
18946 }
18947 })
18948 .detach();
18949 }
18950
18951 pub fn copy_file_location(
18952 &mut self,
18953 _: &CopyFileLocation,
18954 _: &mut Window,
18955 cx: &mut Context<Self>,
18956 ) {
18957 let selection = self.selections.newest::<Point>(cx).start.row + 1;
18958 if let Some(file) = self.target_file(cx) {
18959 if let Some(path) = file.path().to_str() {
18960 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
18961 }
18962 }
18963 }
18964
18965 pub fn open_permalink_to_line(
18966 &mut self,
18967 _: &OpenPermalinkToLine,
18968 window: &mut Window,
18969 cx: &mut Context<Self>,
18970 ) {
18971 let permalink_task = self.get_permalink_to_line(cx);
18972 let workspace = self.workspace();
18973
18974 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18975 Ok(permalink) => {
18976 cx.update(|_, cx| {
18977 cx.open_url(permalink.as_ref());
18978 })
18979 .ok();
18980 }
18981 Err(err) => {
18982 let message = format!("Failed to open permalink: {err}");
18983
18984 anyhow::Result::<()>::Err(err).log_err();
18985
18986 if let Some(workspace) = workspace {
18987 workspace
18988 .update(cx, |workspace, cx| {
18989 struct OpenPermalinkToLine;
18990
18991 workspace.show_toast(
18992 Toast::new(
18993 NotificationId::unique::<OpenPermalinkToLine>(),
18994 message,
18995 ),
18996 cx,
18997 )
18998 })
18999 .ok();
19000 }
19001 }
19002 })
19003 .detach();
19004 }
19005
19006 pub fn insert_uuid_v4(
19007 &mut self,
19008 _: &InsertUuidV4,
19009 window: &mut Window,
19010 cx: &mut Context<Self>,
19011 ) {
19012 self.insert_uuid(UuidVersion::V4, window, cx);
19013 }
19014
19015 pub fn insert_uuid_v7(
19016 &mut self,
19017 _: &InsertUuidV7,
19018 window: &mut Window,
19019 cx: &mut Context<Self>,
19020 ) {
19021 self.insert_uuid(UuidVersion::V7, window, cx);
19022 }
19023
19024 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
19025 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19026 self.transact(window, cx, |this, window, cx| {
19027 let edits = this
19028 .selections
19029 .all::<Point>(cx)
19030 .into_iter()
19031 .map(|selection| {
19032 let uuid = match version {
19033 UuidVersion::V4 => uuid::Uuid::new_v4(),
19034 UuidVersion::V7 => uuid::Uuid::now_v7(),
19035 };
19036
19037 (selection.range(), uuid.to_string())
19038 });
19039 this.edit(edits, cx);
19040 this.refresh_inline_completion(true, false, window, cx);
19041 });
19042 }
19043
19044 pub fn open_selections_in_multibuffer(
19045 &mut self,
19046 _: &OpenSelectionsInMultibuffer,
19047 window: &mut Window,
19048 cx: &mut Context<Self>,
19049 ) {
19050 let multibuffer = self.buffer.read(cx);
19051
19052 let Some(buffer) = multibuffer.as_singleton() else {
19053 return;
19054 };
19055
19056 let Some(workspace) = self.workspace() else {
19057 return;
19058 };
19059
19060 let title = multibuffer.title(cx).to_string();
19061
19062 let locations = self
19063 .selections
19064 .all_anchors(cx)
19065 .into_iter()
19066 .map(|selection| Location {
19067 buffer: buffer.clone(),
19068 range: selection.start.text_anchor..selection.end.text_anchor,
19069 })
19070 .collect::<Vec<_>>();
19071
19072 cx.spawn_in(window, async move |_, cx| {
19073 workspace.update_in(cx, |workspace, window, cx| {
19074 Self::open_locations_in_multibuffer(
19075 workspace,
19076 locations,
19077 format!("Selections for '{title}'"),
19078 false,
19079 MultibufferSelectionMode::All,
19080 window,
19081 cx,
19082 );
19083 })
19084 })
19085 .detach();
19086 }
19087
19088 /// Adds a row highlight for the given range. If a row has multiple highlights, the
19089 /// last highlight added will be used.
19090 ///
19091 /// If the range ends at the beginning of a line, then that line will not be highlighted.
19092 pub fn highlight_rows<T: 'static>(
19093 &mut self,
19094 range: Range<Anchor>,
19095 color: Hsla,
19096 options: RowHighlightOptions,
19097 cx: &mut Context<Self>,
19098 ) {
19099 let snapshot = self.buffer().read(cx).snapshot(cx);
19100 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19101 let ix = row_highlights.binary_search_by(|highlight| {
19102 Ordering::Equal
19103 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
19104 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
19105 });
19106
19107 if let Err(mut ix) = ix {
19108 let index = post_inc(&mut self.highlight_order);
19109
19110 // If this range intersects with the preceding highlight, then merge it with
19111 // the preceding highlight. Otherwise insert a new highlight.
19112 let mut merged = false;
19113 if ix > 0 {
19114 let prev_highlight = &mut row_highlights[ix - 1];
19115 if prev_highlight
19116 .range
19117 .end
19118 .cmp(&range.start, &snapshot)
19119 .is_ge()
19120 {
19121 ix -= 1;
19122 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
19123 prev_highlight.range.end = range.end;
19124 }
19125 merged = true;
19126 prev_highlight.index = index;
19127 prev_highlight.color = color;
19128 prev_highlight.options = options;
19129 }
19130 }
19131
19132 if !merged {
19133 row_highlights.insert(
19134 ix,
19135 RowHighlight {
19136 range: range.clone(),
19137 index,
19138 color,
19139 options,
19140 type_id: TypeId::of::<T>(),
19141 },
19142 );
19143 }
19144
19145 // If any of the following highlights intersect with this one, merge them.
19146 while let Some(next_highlight) = row_highlights.get(ix + 1) {
19147 let highlight = &row_highlights[ix];
19148 if next_highlight
19149 .range
19150 .start
19151 .cmp(&highlight.range.end, &snapshot)
19152 .is_le()
19153 {
19154 if next_highlight
19155 .range
19156 .end
19157 .cmp(&highlight.range.end, &snapshot)
19158 .is_gt()
19159 {
19160 row_highlights[ix].range.end = next_highlight.range.end;
19161 }
19162 row_highlights.remove(ix + 1);
19163 } else {
19164 break;
19165 }
19166 }
19167 }
19168 }
19169
19170 /// Remove any highlighted row ranges of the given type that intersect the
19171 /// given ranges.
19172 pub fn remove_highlighted_rows<T: 'static>(
19173 &mut self,
19174 ranges_to_remove: Vec<Range<Anchor>>,
19175 cx: &mut Context<Self>,
19176 ) {
19177 let snapshot = self.buffer().read(cx).snapshot(cx);
19178 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19179 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19180 row_highlights.retain(|highlight| {
19181 while let Some(range_to_remove) = ranges_to_remove.peek() {
19182 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
19183 Ordering::Less | Ordering::Equal => {
19184 ranges_to_remove.next();
19185 }
19186 Ordering::Greater => {
19187 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
19188 Ordering::Less | Ordering::Equal => {
19189 return false;
19190 }
19191 Ordering::Greater => break,
19192 }
19193 }
19194 }
19195 }
19196
19197 true
19198 })
19199 }
19200
19201 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
19202 pub fn clear_row_highlights<T: 'static>(&mut self) {
19203 self.highlighted_rows.remove(&TypeId::of::<T>());
19204 }
19205
19206 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
19207 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
19208 self.highlighted_rows
19209 .get(&TypeId::of::<T>())
19210 .map_or(&[] as &[_], |vec| vec.as_slice())
19211 .iter()
19212 .map(|highlight| (highlight.range.clone(), highlight.color))
19213 }
19214
19215 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
19216 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
19217 /// Allows to ignore certain kinds of highlights.
19218 pub fn highlighted_display_rows(
19219 &self,
19220 window: &mut Window,
19221 cx: &mut App,
19222 ) -> BTreeMap<DisplayRow, LineHighlight> {
19223 let snapshot = self.snapshot(window, cx);
19224 let mut used_highlight_orders = HashMap::default();
19225 self.highlighted_rows
19226 .iter()
19227 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
19228 .fold(
19229 BTreeMap::<DisplayRow, LineHighlight>::new(),
19230 |mut unique_rows, highlight| {
19231 let start = highlight.range.start.to_display_point(&snapshot);
19232 let end = highlight.range.end.to_display_point(&snapshot);
19233 let start_row = start.row().0;
19234 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
19235 && end.column() == 0
19236 {
19237 end.row().0.saturating_sub(1)
19238 } else {
19239 end.row().0
19240 };
19241 for row in start_row..=end_row {
19242 let used_index =
19243 used_highlight_orders.entry(row).or_insert(highlight.index);
19244 if highlight.index >= *used_index {
19245 *used_index = highlight.index;
19246 unique_rows.insert(
19247 DisplayRow(row),
19248 LineHighlight {
19249 include_gutter: highlight.options.include_gutter,
19250 border: None,
19251 background: highlight.color.into(),
19252 type_id: Some(highlight.type_id),
19253 },
19254 );
19255 }
19256 }
19257 unique_rows
19258 },
19259 )
19260 }
19261
19262 pub fn highlighted_display_row_for_autoscroll(
19263 &self,
19264 snapshot: &DisplaySnapshot,
19265 ) -> Option<DisplayRow> {
19266 self.highlighted_rows
19267 .values()
19268 .flat_map(|highlighted_rows| highlighted_rows.iter())
19269 .filter_map(|highlight| {
19270 if highlight.options.autoscroll {
19271 Some(highlight.range.start.to_display_point(snapshot).row())
19272 } else {
19273 None
19274 }
19275 })
19276 .min()
19277 }
19278
19279 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
19280 self.highlight_background::<SearchWithinRange>(
19281 ranges,
19282 |colors| colors.colors().editor_document_highlight_read_background,
19283 cx,
19284 )
19285 }
19286
19287 pub fn set_breadcrumb_header(&mut self, new_header: String) {
19288 self.breadcrumb_header = Some(new_header);
19289 }
19290
19291 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
19292 self.clear_background_highlights::<SearchWithinRange>(cx);
19293 }
19294
19295 pub fn highlight_background<T: 'static>(
19296 &mut self,
19297 ranges: &[Range<Anchor>],
19298 color_fetcher: fn(&Theme) -> Hsla,
19299 cx: &mut Context<Self>,
19300 ) {
19301 self.background_highlights.insert(
19302 HighlightKey::Type(TypeId::of::<T>()),
19303 (color_fetcher, Arc::from(ranges)),
19304 );
19305 self.scrollbar_marker_state.dirty = true;
19306 cx.notify();
19307 }
19308
19309 pub fn highlight_background_key<T: 'static>(
19310 &mut self,
19311 key: usize,
19312 ranges: &[Range<Anchor>],
19313 color_fetcher: fn(&Theme) -> Hsla,
19314 cx: &mut Context<Self>,
19315 ) {
19316 self.background_highlights.insert(
19317 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19318 (color_fetcher, Arc::from(ranges)),
19319 );
19320 self.scrollbar_marker_state.dirty = true;
19321 cx.notify();
19322 }
19323
19324 pub fn clear_background_highlights<T: 'static>(
19325 &mut self,
19326 cx: &mut Context<Self>,
19327 ) -> Option<BackgroundHighlight> {
19328 let text_highlights = self
19329 .background_highlights
19330 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
19331 if !text_highlights.1.is_empty() {
19332 self.scrollbar_marker_state.dirty = true;
19333 cx.notify();
19334 }
19335 Some(text_highlights)
19336 }
19337
19338 pub fn highlight_gutter<T: 'static>(
19339 &mut self,
19340 ranges: impl Into<Vec<Range<Anchor>>>,
19341 color_fetcher: fn(&App) -> Hsla,
19342 cx: &mut Context<Self>,
19343 ) {
19344 self.gutter_highlights
19345 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
19346 cx.notify();
19347 }
19348
19349 pub fn clear_gutter_highlights<T: 'static>(
19350 &mut self,
19351 cx: &mut Context<Self>,
19352 ) -> Option<GutterHighlight> {
19353 cx.notify();
19354 self.gutter_highlights.remove(&TypeId::of::<T>())
19355 }
19356
19357 pub fn insert_gutter_highlight<T: 'static>(
19358 &mut self,
19359 range: Range<Anchor>,
19360 color_fetcher: fn(&App) -> Hsla,
19361 cx: &mut Context<Self>,
19362 ) {
19363 let snapshot = self.buffer().read(cx).snapshot(cx);
19364 let mut highlights = self
19365 .gutter_highlights
19366 .remove(&TypeId::of::<T>())
19367 .map(|(_, highlights)| highlights)
19368 .unwrap_or_default();
19369 let ix = highlights.binary_search_by(|highlight| {
19370 Ordering::Equal
19371 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
19372 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
19373 });
19374 if let Err(ix) = ix {
19375 highlights.insert(ix, range);
19376 }
19377 self.gutter_highlights
19378 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
19379 }
19380
19381 pub fn remove_gutter_highlights<T: 'static>(
19382 &mut self,
19383 ranges_to_remove: Vec<Range<Anchor>>,
19384 cx: &mut Context<Self>,
19385 ) {
19386 let snapshot = self.buffer().read(cx).snapshot(cx);
19387 let Some((color_fetcher, mut gutter_highlights)) =
19388 self.gutter_highlights.remove(&TypeId::of::<T>())
19389 else {
19390 return;
19391 };
19392 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19393 gutter_highlights.retain(|highlight| {
19394 while let Some(range_to_remove) = ranges_to_remove.peek() {
19395 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
19396 Ordering::Less | Ordering::Equal => {
19397 ranges_to_remove.next();
19398 }
19399 Ordering::Greater => {
19400 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
19401 Ordering::Less | Ordering::Equal => {
19402 return false;
19403 }
19404 Ordering::Greater => break,
19405 }
19406 }
19407 }
19408 }
19409
19410 true
19411 });
19412 self.gutter_highlights
19413 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
19414 }
19415
19416 #[cfg(feature = "test-support")]
19417 pub fn all_text_highlights(
19418 &self,
19419 window: &mut Window,
19420 cx: &mut Context<Self>,
19421 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
19422 let snapshot = self.snapshot(window, cx);
19423 self.display_map.update(cx, |display_map, _| {
19424 display_map
19425 .all_text_highlights()
19426 .map(|highlight| {
19427 let (style, ranges) = highlight.as_ref();
19428 (
19429 *style,
19430 ranges
19431 .iter()
19432 .map(|range| range.clone().to_display_points(&snapshot))
19433 .collect(),
19434 )
19435 })
19436 .collect()
19437 })
19438 }
19439
19440 #[cfg(feature = "test-support")]
19441 pub fn all_text_background_highlights(
19442 &self,
19443 window: &mut Window,
19444 cx: &mut Context<Self>,
19445 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19446 let snapshot = self.snapshot(window, cx);
19447 let buffer = &snapshot.buffer_snapshot;
19448 let start = buffer.anchor_before(0);
19449 let end = buffer.anchor_after(buffer.len());
19450 self.background_highlights_in_range(start..end, &snapshot, cx.theme())
19451 }
19452
19453 #[cfg(feature = "test-support")]
19454 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
19455 let snapshot = self.buffer().read(cx).snapshot(cx);
19456
19457 let highlights = self
19458 .background_highlights
19459 .get(&HighlightKey::Type(TypeId::of::<
19460 items::BufferSearchHighlights,
19461 >()));
19462
19463 if let Some((_color, ranges)) = highlights {
19464 ranges
19465 .iter()
19466 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
19467 .collect_vec()
19468 } else {
19469 vec![]
19470 }
19471 }
19472
19473 fn document_highlights_for_position<'a>(
19474 &'a self,
19475 position: Anchor,
19476 buffer: &'a MultiBufferSnapshot,
19477 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
19478 let read_highlights = self
19479 .background_highlights
19480 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
19481 .map(|h| &h.1);
19482 let write_highlights = self
19483 .background_highlights
19484 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
19485 .map(|h| &h.1);
19486 let left_position = position.bias_left(buffer);
19487 let right_position = position.bias_right(buffer);
19488 read_highlights
19489 .into_iter()
19490 .chain(write_highlights)
19491 .flat_map(move |ranges| {
19492 let start_ix = match ranges.binary_search_by(|probe| {
19493 let cmp = probe.end.cmp(&left_position, buffer);
19494 if cmp.is_ge() {
19495 Ordering::Greater
19496 } else {
19497 Ordering::Less
19498 }
19499 }) {
19500 Ok(i) | Err(i) => i,
19501 };
19502
19503 ranges[start_ix..]
19504 .iter()
19505 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
19506 })
19507 }
19508
19509 pub fn has_background_highlights<T: 'static>(&self) -> bool {
19510 self.background_highlights
19511 .get(&HighlightKey::Type(TypeId::of::<T>()))
19512 .map_or(false, |(_, highlights)| !highlights.is_empty())
19513 }
19514
19515 pub fn background_highlights_in_range(
19516 &self,
19517 search_range: Range<Anchor>,
19518 display_snapshot: &DisplaySnapshot,
19519 theme: &Theme,
19520 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19521 let mut results = Vec::new();
19522 for (color_fetcher, ranges) in self.background_highlights.values() {
19523 let color = color_fetcher(theme);
19524 let start_ix = match ranges.binary_search_by(|probe| {
19525 let cmp = probe
19526 .end
19527 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19528 if cmp.is_gt() {
19529 Ordering::Greater
19530 } else {
19531 Ordering::Less
19532 }
19533 }) {
19534 Ok(i) | Err(i) => i,
19535 };
19536 for range in &ranges[start_ix..] {
19537 if range
19538 .start
19539 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19540 .is_ge()
19541 {
19542 break;
19543 }
19544
19545 let start = range.start.to_display_point(display_snapshot);
19546 let end = range.end.to_display_point(display_snapshot);
19547 results.push((start..end, color))
19548 }
19549 }
19550 results
19551 }
19552
19553 pub fn background_highlight_row_ranges<T: 'static>(
19554 &self,
19555 search_range: Range<Anchor>,
19556 display_snapshot: &DisplaySnapshot,
19557 count: usize,
19558 ) -> Vec<RangeInclusive<DisplayPoint>> {
19559 let mut results = Vec::new();
19560 let Some((_, ranges)) = self
19561 .background_highlights
19562 .get(&HighlightKey::Type(TypeId::of::<T>()))
19563 else {
19564 return vec![];
19565 };
19566
19567 let start_ix = match ranges.binary_search_by(|probe| {
19568 let cmp = probe
19569 .end
19570 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19571 if cmp.is_gt() {
19572 Ordering::Greater
19573 } else {
19574 Ordering::Less
19575 }
19576 }) {
19577 Ok(i) | Err(i) => i,
19578 };
19579 let mut push_region = |start: Option<Point>, end: Option<Point>| {
19580 if let (Some(start_display), Some(end_display)) = (start, end) {
19581 results.push(
19582 start_display.to_display_point(display_snapshot)
19583 ..=end_display.to_display_point(display_snapshot),
19584 );
19585 }
19586 };
19587 let mut start_row: Option<Point> = None;
19588 let mut end_row: Option<Point> = None;
19589 if ranges.len() > count {
19590 return Vec::new();
19591 }
19592 for range in &ranges[start_ix..] {
19593 if range
19594 .start
19595 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19596 .is_ge()
19597 {
19598 break;
19599 }
19600 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
19601 if let Some(current_row) = &end_row {
19602 if end.row == current_row.row {
19603 continue;
19604 }
19605 }
19606 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
19607 if start_row.is_none() {
19608 assert_eq!(end_row, None);
19609 start_row = Some(start);
19610 end_row = Some(end);
19611 continue;
19612 }
19613 if let Some(current_end) = end_row.as_mut() {
19614 if start.row > current_end.row + 1 {
19615 push_region(start_row, end_row);
19616 start_row = Some(start);
19617 end_row = Some(end);
19618 } else {
19619 // Merge two hunks.
19620 *current_end = end;
19621 }
19622 } else {
19623 unreachable!();
19624 }
19625 }
19626 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
19627 push_region(start_row, end_row);
19628 results
19629 }
19630
19631 pub fn gutter_highlights_in_range(
19632 &self,
19633 search_range: Range<Anchor>,
19634 display_snapshot: &DisplaySnapshot,
19635 cx: &App,
19636 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19637 let mut results = Vec::new();
19638 for (color_fetcher, ranges) in self.gutter_highlights.values() {
19639 let color = color_fetcher(cx);
19640 let start_ix = match ranges.binary_search_by(|probe| {
19641 let cmp = probe
19642 .end
19643 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19644 if cmp.is_gt() {
19645 Ordering::Greater
19646 } else {
19647 Ordering::Less
19648 }
19649 }) {
19650 Ok(i) | Err(i) => i,
19651 };
19652 for range in &ranges[start_ix..] {
19653 if range
19654 .start
19655 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19656 .is_ge()
19657 {
19658 break;
19659 }
19660
19661 let start = range.start.to_display_point(display_snapshot);
19662 let end = range.end.to_display_point(display_snapshot);
19663 results.push((start..end, color))
19664 }
19665 }
19666 results
19667 }
19668
19669 /// Get the text ranges corresponding to the redaction query
19670 pub fn redacted_ranges(
19671 &self,
19672 search_range: Range<Anchor>,
19673 display_snapshot: &DisplaySnapshot,
19674 cx: &App,
19675 ) -> Vec<Range<DisplayPoint>> {
19676 display_snapshot
19677 .buffer_snapshot
19678 .redacted_ranges(search_range, |file| {
19679 if let Some(file) = file {
19680 file.is_private()
19681 && EditorSettings::get(
19682 Some(SettingsLocation {
19683 worktree_id: file.worktree_id(cx),
19684 path: file.path().as_ref(),
19685 }),
19686 cx,
19687 )
19688 .redact_private_values
19689 } else {
19690 false
19691 }
19692 })
19693 .map(|range| {
19694 range.start.to_display_point(display_snapshot)
19695 ..range.end.to_display_point(display_snapshot)
19696 })
19697 .collect()
19698 }
19699
19700 pub fn highlight_text_key<T: 'static>(
19701 &mut self,
19702 key: usize,
19703 ranges: Vec<Range<Anchor>>,
19704 style: HighlightStyle,
19705 cx: &mut Context<Self>,
19706 ) {
19707 self.display_map.update(cx, |map, _| {
19708 map.highlight_text(
19709 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19710 ranges,
19711 style,
19712 );
19713 });
19714 cx.notify();
19715 }
19716
19717 pub fn highlight_text<T: 'static>(
19718 &mut self,
19719 ranges: Vec<Range<Anchor>>,
19720 style: HighlightStyle,
19721 cx: &mut Context<Self>,
19722 ) {
19723 self.display_map.update(cx, |map, _| {
19724 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
19725 });
19726 cx.notify();
19727 }
19728
19729 pub(crate) fn highlight_inlays<T: 'static>(
19730 &mut self,
19731 highlights: Vec<InlayHighlight>,
19732 style: HighlightStyle,
19733 cx: &mut Context<Self>,
19734 ) {
19735 self.display_map.update(cx, |map, _| {
19736 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
19737 });
19738 cx.notify();
19739 }
19740
19741 pub fn text_highlights<'a, T: 'static>(
19742 &'a self,
19743 cx: &'a App,
19744 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
19745 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
19746 }
19747
19748 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
19749 let cleared = self
19750 .display_map
19751 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
19752 if cleared {
19753 cx.notify();
19754 }
19755 }
19756
19757 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
19758 (self.read_only(cx) || self.blink_manager.read(cx).visible())
19759 && self.focus_handle.is_focused(window)
19760 }
19761
19762 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
19763 self.show_cursor_when_unfocused = is_enabled;
19764 cx.notify();
19765 }
19766
19767 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
19768 cx.notify();
19769 }
19770
19771 fn on_debug_session_event(
19772 &mut self,
19773 _session: Entity<Session>,
19774 event: &SessionEvent,
19775 cx: &mut Context<Self>,
19776 ) {
19777 match event {
19778 SessionEvent::InvalidateInlineValue => {
19779 self.refresh_inline_values(cx);
19780 }
19781 _ => {}
19782 }
19783 }
19784
19785 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
19786 let Some(project) = self.project.clone() else {
19787 return;
19788 };
19789
19790 if !self.inline_value_cache.enabled {
19791 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
19792 self.splice_inlays(&inlays, Vec::new(), cx);
19793 return;
19794 }
19795
19796 let current_execution_position = self
19797 .highlighted_rows
19798 .get(&TypeId::of::<ActiveDebugLine>())
19799 .and_then(|lines| lines.last().map(|line| line.range.end));
19800
19801 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
19802 let inline_values = editor
19803 .update(cx, |editor, cx| {
19804 let Some(current_execution_position) = current_execution_position else {
19805 return Some(Task::ready(Ok(Vec::new())));
19806 };
19807
19808 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
19809 let snapshot = buffer.snapshot(cx);
19810
19811 let excerpt = snapshot.excerpt_containing(
19812 current_execution_position..current_execution_position,
19813 )?;
19814
19815 editor.buffer.read(cx).buffer(excerpt.buffer_id())
19816 })?;
19817
19818 let range =
19819 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
19820
19821 project.inline_values(buffer, range, cx)
19822 })
19823 .ok()
19824 .flatten()?
19825 .await
19826 .context("refreshing debugger inlays")
19827 .log_err()?;
19828
19829 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
19830
19831 for (buffer_id, inline_value) in inline_values
19832 .into_iter()
19833 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
19834 {
19835 buffer_inline_values
19836 .entry(buffer_id)
19837 .or_default()
19838 .push(inline_value);
19839 }
19840
19841 editor
19842 .update(cx, |editor, cx| {
19843 let snapshot = editor.buffer.read(cx).snapshot(cx);
19844 let mut new_inlays = Vec::default();
19845
19846 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
19847 let buffer_id = buffer_snapshot.remote_id();
19848 buffer_inline_values
19849 .get(&buffer_id)
19850 .into_iter()
19851 .flatten()
19852 .for_each(|hint| {
19853 let inlay = Inlay::debugger(
19854 post_inc(&mut editor.next_inlay_id),
19855 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
19856 hint.text(),
19857 );
19858 if !inlay.text.chars().contains(&'\n') {
19859 new_inlays.push(inlay);
19860 }
19861 });
19862 }
19863
19864 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
19865 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
19866
19867 editor.splice_inlays(&inlay_ids, new_inlays, cx);
19868 })
19869 .ok()?;
19870 Some(())
19871 });
19872 }
19873
19874 fn on_buffer_event(
19875 &mut self,
19876 multibuffer: &Entity<MultiBuffer>,
19877 event: &multi_buffer::Event,
19878 window: &mut Window,
19879 cx: &mut Context<Self>,
19880 ) {
19881 match event {
19882 multi_buffer::Event::Edited {
19883 singleton_buffer_edited,
19884 edited_buffer,
19885 } => {
19886 self.scrollbar_marker_state.dirty = true;
19887 self.active_indent_guides_state.dirty = true;
19888 self.refresh_active_diagnostics(cx);
19889 self.refresh_code_actions(window, cx);
19890 self.refresh_selected_text_highlights(true, window, cx);
19891 self.refresh_single_line_folds(window, cx);
19892 refresh_matching_bracket_highlights(self, window, cx);
19893 if self.has_active_inline_completion() {
19894 self.update_visible_inline_completion(window, cx);
19895 }
19896 if let Some(project) = self.project.as_ref() {
19897 if let Some(edited_buffer) = edited_buffer {
19898 project.update(cx, |project, cx| {
19899 self.registered_buffers
19900 .entry(edited_buffer.read(cx).remote_id())
19901 .or_insert_with(|| {
19902 project
19903 .register_buffer_with_language_servers(&edited_buffer, cx)
19904 });
19905 });
19906 }
19907 }
19908 cx.emit(EditorEvent::BufferEdited);
19909 cx.emit(SearchEvent::MatchesInvalidated);
19910
19911 if let Some(buffer) = edited_buffer {
19912 self.update_lsp_data(false, Some(buffer.read(cx).remote_id()), window, cx);
19913 }
19914
19915 if *singleton_buffer_edited {
19916 if let Some(buffer) = edited_buffer {
19917 if buffer.read(cx).file().is_none() {
19918 cx.emit(EditorEvent::TitleChanged);
19919 }
19920 }
19921 if let Some(project) = &self.project {
19922 #[allow(clippy::mutable_key_type)]
19923 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
19924 multibuffer
19925 .all_buffers()
19926 .into_iter()
19927 .filter_map(|buffer| {
19928 buffer.update(cx, |buffer, cx| {
19929 let language = buffer.language()?;
19930 let should_discard = project.update(cx, |project, cx| {
19931 project.is_local()
19932 && !project.has_language_servers_for(buffer, cx)
19933 });
19934 should_discard.not().then_some(language.clone())
19935 })
19936 })
19937 .collect::<HashSet<_>>()
19938 });
19939 if !languages_affected.is_empty() {
19940 self.refresh_inlay_hints(
19941 InlayHintRefreshReason::BufferEdited(languages_affected),
19942 cx,
19943 );
19944 }
19945 }
19946 }
19947
19948 let Some(project) = &self.project else { return };
19949 let (telemetry, is_via_ssh) = {
19950 let project = project.read(cx);
19951 let telemetry = project.client().telemetry().clone();
19952 let is_via_ssh = project.is_via_ssh();
19953 (telemetry, is_via_ssh)
19954 };
19955 refresh_linked_ranges(self, window, cx);
19956 telemetry.log_edit_event("editor", is_via_ssh);
19957 }
19958 multi_buffer::Event::ExcerptsAdded {
19959 buffer,
19960 predecessor,
19961 excerpts,
19962 } => {
19963 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19964 let buffer_id = buffer.read(cx).remote_id();
19965 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
19966 if let Some(project) = &self.project {
19967 update_uncommitted_diff_for_buffer(
19968 cx.entity(),
19969 project,
19970 [buffer.clone()],
19971 self.buffer.clone(),
19972 cx,
19973 )
19974 .detach();
19975 }
19976 }
19977 self.update_lsp_data(false, Some(buffer_id), window, cx);
19978 cx.emit(EditorEvent::ExcerptsAdded {
19979 buffer: buffer.clone(),
19980 predecessor: *predecessor,
19981 excerpts: excerpts.clone(),
19982 });
19983 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
19984 }
19985 multi_buffer::Event::ExcerptsRemoved {
19986 ids,
19987 removed_buffer_ids,
19988 } => {
19989 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
19990 let buffer = self.buffer.read(cx);
19991 self.registered_buffers
19992 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
19993 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19994 cx.emit(EditorEvent::ExcerptsRemoved {
19995 ids: ids.clone(),
19996 removed_buffer_ids: removed_buffer_ids.clone(),
19997 });
19998 }
19999 multi_buffer::Event::ExcerptsEdited {
20000 excerpt_ids,
20001 buffer_ids,
20002 } => {
20003 self.display_map.update(cx, |map, cx| {
20004 map.unfold_buffers(buffer_ids.iter().copied(), cx)
20005 });
20006 cx.emit(EditorEvent::ExcerptsEdited {
20007 ids: excerpt_ids.clone(),
20008 });
20009 }
20010 multi_buffer::Event::ExcerptsExpanded { ids } => {
20011 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20012 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
20013 }
20014 multi_buffer::Event::Reparsed(buffer_id) => {
20015 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20016 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20017
20018 cx.emit(EditorEvent::Reparsed(*buffer_id));
20019 }
20020 multi_buffer::Event::DiffHunksToggled => {
20021 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20022 }
20023 multi_buffer::Event::LanguageChanged(buffer_id) => {
20024 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
20025 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20026 cx.emit(EditorEvent::Reparsed(*buffer_id));
20027 cx.notify();
20028 }
20029 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
20030 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
20031 multi_buffer::Event::FileHandleChanged
20032 | multi_buffer::Event::Reloaded
20033 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
20034 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
20035 multi_buffer::Event::DiagnosticsUpdated => {
20036 self.update_diagnostics_state(window, cx);
20037 }
20038 _ => {}
20039 };
20040 }
20041
20042 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
20043 if !self.diagnostics_enabled() {
20044 return;
20045 }
20046 self.refresh_active_diagnostics(cx);
20047 self.refresh_inline_diagnostics(true, window, cx);
20048 self.scrollbar_marker_state.dirty = true;
20049 cx.notify();
20050 }
20051
20052 pub fn start_temporary_diff_override(&mut self) {
20053 self.load_diff_task.take();
20054 self.temporary_diff_override = true;
20055 }
20056
20057 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
20058 self.temporary_diff_override = false;
20059 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
20060 self.buffer.update(cx, |buffer, cx| {
20061 buffer.set_all_diff_hunks_collapsed(cx);
20062 });
20063
20064 if let Some(project) = self.project.clone() {
20065 self.load_diff_task = Some(
20066 update_uncommitted_diff_for_buffer(
20067 cx.entity(),
20068 &project,
20069 self.buffer.read(cx).all_buffers(),
20070 self.buffer.clone(),
20071 cx,
20072 )
20073 .shared(),
20074 );
20075 }
20076 }
20077
20078 fn on_display_map_changed(
20079 &mut self,
20080 _: Entity<DisplayMap>,
20081 _: &mut Window,
20082 cx: &mut Context<Self>,
20083 ) {
20084 cx.notify();
20085 }
20086
20087 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20088 if self.diagnostics_enabled() {
20089 let new_severity = EditorSettings::get_global(cx)
20090 .diagnostics_max_severity
20091 .unwrap_or(DiagnosticSeverity::Hint);
20092 self.set_max_diagnostics_severity(new_severity, cx);
20093 }
20094 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20095 self.update_edit_prediction_settings(cx);
20096 self.refresh_inline_completion(true, false, window, cx);
20097 self.refresh_inline_values(cx);
20098 self.refresh_inlay_hints(
20099 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
20100 self.selections.newest_anchor().head(),
20101 &self.buffer.read(cx).snapshot(cx),
20102 cx,
20103 )),
20104 cx,
20105 );
20106
20107 let old_cursor_shape = self.cursor_shape;
20108
20109 {
20110 let editor_settings = EditorSettings::get_global(cx);
20111 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
20112 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
20113 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
20114 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
20115 }
20116
20117 if old_cursor_shape != self.cursor_shape {
20118 cx.emit(EditorEvent::CursorShapeChanged);
20119 }
20120
20121 let project_settings = ProjectSettings::get_global(cx);
20122 self.serialize_dirty_buffers =
20123 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
20124
20125 if self.mode.is_full() {
20126 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
20127 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
20128 if self.show_inline_diagnostics != show_inline_diagnostics {
20129 self.show_inline_diagnostics = show_inline_diagnostics;
20130 self.refresh_inline_diagnostics(false, window, cx);
20131 }
20132
20133 if self.git_blame_inline_enabled != inline_blame_enabled {
20134 self.toggle_git_blame_inline_internal(false, window, cx);
20135 }
20136
20137 let minimap_settings = EditorSettings::get_global(cx).minimap;
20138 if self.minimap_visibility != MinimapVisibility::Disabled {
20139 if self.minimap_visibility.settings_visibility()
20140 != minimap_settings.minimap_enabled()
20141 {
20142 self.set_minimap_visibility(
20143 MinimapVisibility::for_mode(self.mode(), cx),
20144 window,
20145 cx,
20146 );
20147 } else if let Some(minimap_entity) = self.minimap.as_ref() {
20148 minimap_entity.update(cx, |minimap_editor, cx| {
20149 minimap_editor.update_minimap_configuration(minimap_settings, cx)
20150 })
20151 }
20152 }
20153 }
20154
20155 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
20156 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
20157 }) {
20158 if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
20159 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
20160 }
20161 self.refresh_colors(false, None, window, cx);
20162 }
20163
20164 cx.notify();
20165 }
20166
20167 pub fn set_searchable(&mut self, searchable: bool) {
20168 self.searchable = searchable;
20169 }
20170
20171 pub fn searchable(&self) -> bool {
20172 self.searchable
20173 }
20174
20175 fn open_proposed_changes_editor(
20176 &mut self,
20177 _: &OpenProposedChangesEditor,
20178 window: &mut Window,
20179 cx: &mut Context<Self>,
20180 ) {
20181 let Some(workspace) = self.workspace() else {
20182 cx.propagate();
20183 return;
20184 };
20185
20186 let selections = self.selections.all::<usize>(cx);
20187 let multi_buffer = self.buffer.read(cx);
20188 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20189 let mut new_selections_by_buffer = HashMap::default();
20190 for selection in selections {
20191 for (buffer, range, _) in
20192 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
20193 {
20194 let mut range = range.to_point(buffer);
20195 range.start.column = 0;
20196 range.end.column = buffer.line_len(range.end.row);
20197 new_selections_by_buffer
20198 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
20199 .or_insert(Vec::new())
20200 .push(range)
20201 }
20202 }
20203
20204 let proposed_changes_buffers = new_selections_by_buffer
20205 .into_iter()
20206 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
20207 .collect::<Vec<_>>();
20208 let proposed_changes_editor = cx.new(|cx| {
20209 ProposedChangesEditor::new(
20210 "Proposed changes",
20211 proposed_changes_buffers,
20212 self.project.clone(),
20213 window,
20214 cx,
20215 )
20216 });
20217
20218 window.defer(cx, move |window, cx| {
20219 workspace.update(cx, |workspace, cx| {
20220 workspace.active_pane().update(cx, |pane, cx| {
20221 pane.add_item(
20222 Box::new(proposed_changes_editor),
20223 true,
20224 true,
20225 None,
20226 window,
20227 cx,
20228 );
20229 });
20230 });
20231 });
20232 }
20233
20234 pub fn open_excerpts_in_split(
20235 &mut self,
20236 _: &OpenExcerptsSplit,
20237 window: &mut Window,
20238 cx: &mut Context<Self>,
20239 ) {
20240 self.open_excerpts_common(None, true, window, cx)
20241 }
20242
20243 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
20244 self.open_excerpts_common(None, false, window, cx)
20245 }
20246
20247 fn open_excerpts_common(
20248 &mut self,
20249 jump_data: Option<JumpData>,
20250 split: bool,
20251 window: &mut Window,
20252 cx: &mut Context<Self>,
20253 ) {
20254 let Some(workspace) = self.workspace() else {
20255 cx.propagate();
20256 return;
20257 };
20258
20259 if self.buffer.read(cx).is_singleton() {
20260 cx.propagate();
20261 return;
20262 }
20263
20264 let mut new_selections_by_buffer = HashMap::default();
20265 match &jump_data {
20266 Some(JumpData::MultiBufferPoint {
20267 excerpt_id,
20268 position,
20269 anchor,
20270 line_offset_from_top,
20271 }) => {
20272 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20273 if let Some(buffer) = multi_buffer_snapshot
20274 .buffer_id_for_excerpt(*excerpt_id)
20275 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
20276 {
20277 let buffer_snapshot = buffer.read(cx).snapshot();
20278 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
20279 language::ToPoint::to_point(anchor, &buffer_snapshot)
20280 } else {
20281 buffer_snapshot.clip_point(*position, Bias::Left)
20282 };
20283 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
20284 new_selections_by_buffer.insert(
20285 buffer,
20286 (
20287 vec![jump_to_offset..jump_to_offset],
20288 Some(*line_offset_from_top),
20289 ),
20290 );
20291 }
20292 }
20293 Some(JumpData::MultiBufferRow {
20294 row,
20295 line_offset_from_top,
20296 }) => {
20297 let point = MultiBufferPoint::new(row.0, 0);
20298 if let Some((buffer, buffer_point, _)) =
20299 self.buffer.read(cx).point_to_buffer_point(point, cx)
20300 {
20301 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
20302 new_selections_by_buffer
20303 .entry(buffer)
20304 .or_insert((Vec::new(), Some(*line_offset_from_top)))
20305 .0
20306 .push(buffer_offset..buffer_offset)
20307 }
20308 }
20309 None => {
20310 let selections = self.selections.all::<usize>(cx);
20311 let multi_buffer = self.buffer.read(cx);
20312 for selection in selections {
20313 for (snapshot, range, _, anchor) in multi_buffer
20314 .snapshot(cx)
20315 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
20316 {
20317 if let Some(anchor) = anchor {
20318 // selection is in a deleted hunk
20319 let Some(buffer_id) = anchor.buffer_id else {
20320 continue;
20321 };
20322 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
20323 continue;
20324 };
20325 let offset = text::ToOffset::to_offset(
20326 &anchor.text_anchor,
20327 &buffer_handle.read(cx).snapshot(),
20328 );
20329 let range = offset..offset;
20330 new_selections_by_buffer
20331 .entry(buffer_handle)
20332 .or_insert((Vec::new(), None))
20333 .0
20334 .push(range)
20335 } else {
20336 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
20337 else {
20338 continue;
20339 };
20340 new_selections_by_buffer
20341 .entry(buffer_handle)
20342 .or_insert((Vec::new(), None))
20343 .0
20344 .push(range)
20345 }
20346 }
20347 }
20348 }
20349 }
20350
20351 new_selections_by_buffer
20352 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
20353
20354 if new_selections_by_buffer.is_empty() {
20355 return;
20356 }
20357
20358 // We defer the pane interaction because we ourselves are a workspace item
20359 // and activating a new item causes the pane to call a method on us reentrantly,
20360 // which panics if we're on the stack.
20361 window.defer(cx, move |window, cx| {
20362 workspace.update(cx, |workspace, cx| {
20363 let pane = if split {
20364 workspace.adjacent_pane(window, cx)
20365 } else {
20366 workspace.active_pane().clone()
20367 };
20368
20369 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
20370 let editor = buffer
20371 .read(cx)
20372 .file()
20373 .is_none()
20374 .then(|| {
20375 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
20376 // so `workspace.open_project_item` will never find them, always opening a new editor.
20377 // Instead, we try to activate the existing editor in the pane first.
20378 let (editor, pane_item_index) =
20379 pane.read(cx).items().enumerate().find_map(|(i, item)| {
20380 let editor = item.downcast::<Editor>()?;
20381 let singleton_buffer =
20382 editor.read(cx).buffer().read(cx).as_singleton()?;
20383 if singleton_buffer == buffer {
20384 Some((editor, i))
20385 } else {
20386 None
20387 }
20388 })?;
20389 pane.update(cx, |pane, cx| {
20390 pane.activate_item(pane_item_index, true, true, window, cx)
20391 });
20392 Some(editor)
20393 })
20394 .flatten()
20395 .unwrap_or_else(|| {
20396 workspace.open_project_item::<Self>(
20397 pane.clone(),
20398 buffer,
20399 true,
20400 true,
20401 window,
20402 cx,
20403 )
20404 });
20405
20406 editor.update(cx, |editor, cx| {
20407 let autoscroll = match scroll_offset {
20408 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
20409 None => Autoscroll::newest(),
20410 };
20411 let nav_history = editor.nav_history.take();
20412 editor.change_selections(
20413 SelectionEffects::scroll(autoscroll),
20414 window,
20415 cx,
20416 |s| {
20417 s.select_ranges(ranges);
20418 },
20419 );
20420 editor.nav_history = nav_history;
20421 });
20422 }
20423 })
20424 });
20425 }
20426
20427 // For now, don't allow opening excerpts in buffers that aren't backed by
20428 // regular project files.
20429 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
20430 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
20431 }
20432
20433 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
20434 let snapshot = self.buffer.read(cx).read(cx);
20435 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
20436 Some(
20437 ranges
20438 .iter()
20439 .map(move |range| {
20440 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
20441 })
20442 .collect(),
20443 )
20444 }
20445
20446 fn selection_replacement_ranges(
20447 &self,
20448 range: Range<OffsetUtf16>,
20449 cx: &mut App,
20450 ) -> Vec<Range<OffsetUtf16>> {
20451 let selections = self.selections.all::<OffsetUtf16>(cx);
20452 let newest_selection = selections
20453 .iter()
20454 .max_by_key(|selection| selection.id)
20455 .unwrap();
20456 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
20457 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
20458 let snapshot = self.buffer.read(cx).read(cx);
20459 selections
20460 .into_iter()
20461 .map(|mut selection| {
20462 selection.start.0 =
20463 (selection.start.0 as isize).saturating_add(start_delta) as usize;
20464 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
20465 snapshot.clip_offset_utf16(selection.start, Bias::Left)
20466 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
20467 })
20468 .collect()
20469 }
20470
20471 fn report_editor_event(
20472 &self,
20473 event_type: &'static str,
20474 file_extension: Option<String>,
20475 cx: &App,
20476 ) {
20477 if cfg!(any(test, feature = "test-support")) {
20478 return;
20479 }
20480
20481 let Some(project) = &self.project else { return };
20482
20483 // If None, we are in a file without an extension
20484 let file = self
20485 .buffer
20486 .read(cx)
20487 .as_singleton()
20488 .and_then(|b| b.read(cx).file());
20489 let file_extension = file_extension.or(file
20490 .as_ref()
20491 .and_then(|file| Path::new(file.file_name(cx)).extension())
20492 .and_then(|e| e.to_str())
20493 .map(|a| a.to_string()));
20494
20495 let vim_mode = vim_enabled(cx);
20496
20497 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
20498 let copilot_enabled = edit_predictions_provider
20499 == language::language_settings::EditPredictionProvider::Copilot;
20500 let copilot_enabled_for_language = self
20501 .buffer
20502 .read(cx)
20503 .language_settings(cx)
20504 .show_edit_predictions;
20505
20506 let project = project.read(cx);
20507 telemetry::event!(
20508 event_type,
20509 file_extension,
20510 vim_mode,
20511 copilot_enabled,
20512 copilot_enabled_for_language,
20513 edit_predictions_provider,
20514 is_via_ssh = project.is_via_ssh(),
20515 );
20516 }
20517
20518 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
20519 /// with each line being an array of {text, highlight} objects.
20520 fn copy_highlight_json(
20521 &mut self,
20522 _: &CopyHighlightJson,
20523 window: &mut Window,
20524 cx: &mut Context<Self>,
20525 ) {
20526 #[derive(Serialize)]
20527 struct Chunk<'a> {
20528 text: String,
20529 highlight: Option<&'a str>,
20530 }
20531
20532 let snapshot = self.buffer.read(cx).snapshot(cx);
20533 let range = self
20534 .selected_text_range(false, window, cx)
20535 .and_then(|selection| {
20536 if selection.range.is_empty() {
20537 None
20538 } else {
20539 Some(selection.range)
20540 }
20541 })
20542 .unwrap_or_else(|| 0..snapshot.len());
20543
20544 let chunks = snapshot.chunks(range, true);
20545 let mut lines = Vec::new();
20546 let mut line: VecDeque<Chunk> = VecDeque::new();
20547
20548 let Some(style) = self.style.as_ref() else {
20549 return;
20550 };
20551
20552 for chunk in chunks {
20553 let highlight = chunk
20554 .syntax_highlight_id
20555 .and_then(|id| id.name(&style.syntax));
20556 let mut chunk_lines = chunk.text.split('\n').peekable();
20557 while let Some(text) = chunk_lines.next() {
20558 let mut merged_with_last_token = false;
20559 if let Some(last_token) = line.back_mut() {
20560 if last_token.highlight == highlight {
20561 last_token.text.push_str(text);
20562 merged_with_last_token = true;
20563 }
20564 }
20565
20566 if !merged_with_last_token {
20567 line.push_back(Chunk {
20568 text: text.into(),
20569 highlight,
20570 });
20571 }
20572
20573 if chunk_lines.peek().is_some() {
20574 if line.len() > 1 && line.front().unwrap().text.is_empty() {
20575 line.pop_front();
20576 }
20577 if line.len() > 1 && line.back().unwrap().text.is_empty() {
20578 line.pop_back();
20579 }
20580
20581 lines.push(mem::take(&mut line));
20582 }
20583 }
20584 }
20585
20586 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
20587 return;
20588 };
20589 cx.write_to_clipboard(ClipboardItem::new_string(lines));
20590 }
20591
20592 pub fn open_context_menu(
20593 &mut self,
20594 _: &OpenContextMenu,
20595 window: &mut Window,
20596 cx: &mut Context<Self>,
20597 ) {
20598 self.request_autoscroll(Autoscroll::newest(), cx);
20599 let position = self.selections.newest_display(cx).start;
20600 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
20601 }
20602
20603 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
20604 &self.inlay_hint_cache
20605 }
20606
20607 pub fn replay_insert_event(
20608 &mut self,
20609 text: &str,
20610 relative_utf16_range: Option<Range<isize>>,
20611 window: &mut Window,
20612 cx: &mut Context<Self>,
20613 ) {
20614 if !self.input_enabled {
20615 cx.emit(EditorEvent::InputIgnored { text: text.into() });
20616 return;
20617 }
20618 if let Some(relative_utf16_range) = relative_utf16_range {
20619 let selections = self.selections.all::<OffsetUtf16>(cx);
20620 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20621 let new_ranges = selections.into_iter().map(|range| {
20622 let start = OffsetUtf16(
20623 range
20624 .head()
20625 .0
20626 .saturating_add_signed(relative_utf16_range.start),
20627 );
20628 let end = OffsetUtf16(
20629 range
20630 .head()
20631 .0
20632 .saturating_add_signed(relative_utf16_range.end),
20633 );
20634 start..end
20635 });
20636 s.select_ranges(new_ranges);
20637 });
20638 }
20639
20640 self.handle_input(text, window, cx);
20641 }
20642
20643 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
20644 let Some(provider) = self.semantics_provider.as_ref() else {
20645 return false;
20646 };
20647
20648 let mut supports = false;
20649 self.buffer().update(cx, |this, cx| {
20650 this.for_each_buffer(|buffer| {
20651 supports |= provider.supports_inlay_hints(buffer, cx);
20652 });
20653 });
20654
20655 supports
20656 }
20657
20658 pub fn is_focused(&self, window: &Window) -> bool {
20659 self.focus_handle.is_focused(window)
20660 }
20661
20662 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20663 cx.emit(EditorEvent::Focused);
20664
20665 if let Some(descendant) = self
20666 .last_focused_descendant
20667 .take()
20668 .and_then(|descendant| descendant.upgrade())
20669 {
20670 window.focus(&descendant);
20671 } else {
20672 if let Some(blame) = self.blame.as_ref() {
20673 blame.update(cx, GitBlame::focus)
20674 }
20675
20676 self.blink_manager.update(cx, BlinkManager::enable);
20677 self.show_cursor_names(window, cx);
20678 self.buffer.update(cx, |buffer, cx| {
20679 buffer.finalize_last_transaction(cx);
20680 if self.leader_id.is_none() {
20681 buffer.set_active_selections(
20682 &self.selections.disjoint_anchors(),
20683 self.selections.line_mode,
20684 self.cursor_shape,
20685 cx,
20686 );
20687 }
20688 });
20689 }
20690 }
20691
20692 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
20693 cx.emit(EditorEvent::FocusedIn)
20694 }
20695
20696 fn handle_focus_out(
20697 &mut self,
20698 event: FocusOutEvent,
20699 _window: &mut Window,
20700 cx: &mut Context<Self>,
20701 ) {
20702 if event.blurred != self.focus_handle {
20703 self.last_focused_descendant = Some(event.blurred);
20704 }
20705 self.selection_drag_state = SelectionDragState::None;
20706 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
20707 }
20708
20709 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20710 self.blink_manager.update(cx, BlinkManager::disable);
20711 self.buffer
20712 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
20713
20714 if let Some(blame) = self.blame.as_ref() {
20715 blame.update(cx, GitBlame::blur)
20716 }
20717 if !self.hover_state.focused(window, cx) {
20718 hide_hover(self, cx);
20719 }
20720 if !self
20721 .context_menu
20722 .borrow()
20723 .as_ref()
20724 .is_some_and(|context_menu| context_menu.focused(window, cx))
20725 {
20726 self.hide_context_menu(window, cx);
20727 }
20728 self.discard_inline_completion(false, cx);
20729 cx.emit(EditorEvent::Blurred);
20730 cx.notify();
20731 }
20732
20733 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20734 let mut pending: String = window
20735 .pending_input_keystrokes()
20736 .into_iter()
20737 .flatten()
20738 .filter_map(|keystroke| {
20739 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
20740 keystroke.key_char.clone()
20741 } else {
20742 None
20743 }
20744 })
20745 .collect();
20746
20747 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
20748 pending = "".to_string();
20749 }
20750
20751 let existing_pending = self
20752 .text_highlights::<PendingInput>(cx)
20753 .map(|(_, ranges)| ranges.iter().cloned().collect::<Vec<_>>());
20754 if existing_pending.is_none() && pending.is_empty() {
20755 return;
20756 }
20757 let transaction =
20758 self.transact(window, cx, |this, window, cx| {
20759 let selections = this.selections.all::<usize>(cx);
20760 let edits = selections
20761 .iter()
20762 .map(|selection| (selection.end..selection.end, pending.clone()));
20763 this.edit(edits, cx);
20764 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20765 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
20766 sel.start + ix * pending.len()..sel.end + ix * pending.len()
20767 }));
20768 });
20769 if let Some(existing_ranges) = existing_pending {
20770 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
20771 this.edit(edits, cx);
20772 }
20773 });
20774
20775 let snapshot = self.snapshot(window, cx);
20776 let ranges = self
20777 .selections
20778 .all::<usize>(cx)
20779 .into_iter()
20780 .map(|selection| {
20781 snapshot.buffer_snapshot.anchor_after(selection.end)
20782 ..snapshot
20783 .buffer_snapshot
20784 .anchor_before(selection.end + pending.len())
20785 })
20786 .collect();
20787
20788 if pending.is_empty() {
20789 self.clear_highlights::<PendingInput>(cx);
20790 } else {
20791 self.highlight_text::<PendingInput>(
20792 ranges,
20793 HighlightStyle {
20794 underline: Some(UnderlineStyle {
20795 thickness: px(1.),
20796 color: None,
20797 wavy: false,
20798 }),
20799 ..Default::default()
20800 },
20801 cx,
20802 );
20803 }
20804
20805 self.ime_transaction = self.ime_transaction.or(transaction);
20806 if let Some(transaction) = self.ime_transaction {
20807 self.buffer.update(cx, |buffer, cx| {
20808 buffer.group_until_transaction(transaction, cx);
20809 });
20810 }
20811
20812 if self.text_highlights::<PendingInput>(cx).is_none() {
20813 self.ime_transaction.take();
20814 }
20815 }
20816
20817 pub fn register_action_renderer(
20818 &mut self,
20819 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
20820 ) -> Subscription {
20821 let id = self.next_editor_action_id.post_inc();
20822 self.editor_actions
20823 .borrow_mut()
20824 .insert(id, Box::new(listener));
20825
20826 let editor_actions = self.editor_actions.clone();
20827 Subscription::new(move || {
20828 editor_actions.borrow_mut().remove(&id);
20829 })
20830 }
20831
20832 pub fn register_action<A: Action>(
20833 &mut self,
20834 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
20835 ) -> Subscription {
20836 let id = self.next_editor_action_id.post_inc();
20837 let listener = Arc::new(listener);
20838 self.editor_actions.borrow_mut().insert(
20839 id,
20840 Box::new(move |_, window, _| {
20841 let listener = listener.clone();
20842 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
20843 let action = action.downcast_ref().unwrap();
20844 if phase == DispatchPhase::Bubble {
20845 listener(action, window, cx)
20846 }
20847 })
20848 }),
20849 );
20850
20851 let editor_actions = self.editor_actions.clone();
20852 Subscription::new(move || {
20853 editor_actions.borrow_mut().remove(&id);
20854 })
20855 }
20856
20857 pub fn file_header_size(&self) -> u32 {
20858 FILE_HEADER_HEIGHT
20859 }
20860
20861 pub fn restore(
20862 &mut self,
20863 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
20864 window: &mut Window,
20865 cx: &mut Context<Self>,
20866 ) {
20867 let workspace = self.workspace();
20868 let project = self.project.as_ref();
20869 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
20870 let mut tasks = Vec::new();
20871 for (buffer_id, changes) in revert_changes {
20872 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
20873 buffer.update(cx, |buffer, cx| {
20874 buffer.edit(
20875 changes
20876 .into_iter()
20877 .map(|(range, text)| (range, text.to_string())),
20878 None,
20879 cx,
20880 );
20881 });
20882
20883 if let Some(project) =
20884 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
20885 {
20886 project.update(cx, |project, cx| {
20887 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
20888 })
20889 }
20890 }
20891 }
20892 tasks
20893 });
20894 cx.spawn_in(window, async move |_, cx| {
20895 for (buffer, task) in save_tasks {
20896 let result = task.await;
20897 if result.is_err() {
20898 let Some(path) = buffer
20899 .read_with(cx, |buffer, cx| buffer.project_path(cx))
20900 .ok()
20901 else {
20902 continue;
20903 };
20904 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
20905 let Some(task) = cx
20906 .update_window_entity(&workspace, |workspace, window, cx| {
20907 workspace
20908 .open_path_preview(path, None, false, false, false, window, cx)
20909 })
20910 .ok()
20911 else {
20912 continue;
20913 };
20914 task.await.log_err();
20915 }
20916 }
20917 }
20918 })
20919 .detach();
20920 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
20921 selections.refresh()
20922 });
20923 }
20924
20925 pub fn to_pixel_point(
20926 &self,
20927 source: multi_buffer::Anchor,
20928 editor_snapshot: &EditorSnapshot,
20929 window: &mut Window,
20930 ) -> Option<gpui::Point<Pixels>> {
20931 let source_point = source.to_display_point(editor_snapshot);
20932 self.display_to_pixel_point(source_point, editor_snapshot, window)
20933 }
20934
20935 pub fn display_to_pixel_point(
20936 &self,
20937 source: DisplayPoint,
20938 editor_snapshot: &EditorSnapshot,
20939 window: &mut Window,
20940 ) -> Option<gpui::Point<Pixels>> {
20941 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
20942 let text_layout_details = self.text_layout_details(window);
20943 let scroll_top = text_layout_details
20944 .scroll_anchor
20945 .scroll_position(editor_snapshot)
20946 .y;
20947
20948 if source.row().as_f32() < scroll_top.floor() {
20949 return None;
20950 }
20951 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
20952 let source_y = line_height * (source.row().as_f32() - scroll_top);
20953 Some(gpui::Point::new(source_x, source_y))
20954 }
20955
20956 pub fn has_visible_completions_menu(&self) -> bool {
20957 !self.edit_prediction_preview_is_active()
20958 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
20959 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
20960 })
20961 }
20962
20963 pub fn register_addon<T: Addon>(&mut self, instance: T) {
20964 if self.mode.is_minimap() {
20965 return;
20966 }
20967 self.addons
20968 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
20969 }
20970
20971 pub fn unregister_addon<T: Addon>(&mut self) {
20972 self.addons.remove(&std::any::TypeId::of::<T>());
20973 }
20974
20975 pub fn addon<T: Addon>(&self) -> Option<&T> {
20976 let type_id = std::any::TypeId::of::<T>();
20977 self.addons
20978 .get(&type_id)
20979 .and_then(|item| item.to_any().downcast_ref::<T>())
20980 }
20981
20982 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
20983 let type_id = std::any::TypeId::of::<T>();
20984 self.addons
20985 .get_mut(&type_id)
20986 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
20987 }
20988
20989 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
20990 let text_layout_details = self.text_layout_details(window);
20991 let style = &text_layout_details.editor_style;
20992 let font_id = window.text_system().resolve_font(&style.text.font());
20993 let font_size = style.text.font_size.to_pixels(window.rem_size());
20994 let line_height = style.text.line_height_in_pixels(window.rem_size());
20995 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
20996 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
20997
20998 CharacterDimensions {
20999 em_width,
21000 em_advance,
21001 line_height,
21002 }
21003 }
21004
21005 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
21006 self.load_diff_task.clone()
21007 }
21008
21009 fn read_metadata_from_db(
21010 &mut self,
21011 item_id: u64,
21012 workspace_id: WorkspaceId,
21013 window: &mut Window,
21014 cx: &mut Context<Editor>,
21015 ) {
21016 if self.is_singleton(cx)
21017 && !self.mode.is_minimap()
21018 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
21019 {
21020 let buffer_snapshot = OnceCell::new();
21021
21022 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
21023 if !folds.is_empty() {
21024 let snapshot =
21025 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21026 self.fold_ranges(
21027 folds
21028 .into_iter()
21029 .map(|(start, end)| {
21030 snapshot.clip_offset(start, Bias::Left)
21031 ..snapshot.clip_offset(end, Bias::Right)
21032 })
21033 .collect(),
21034 false,
21035 window,
21036 cx,
21037 );
21038 }
21039 }
21040
21041 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
21042 if !selections.is_empty() {
21043 let snapshot =
21044 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21045 // skip adding the initial selection to selection history
21046 self.selection_history.mode = SelectionHistoryMode::Skipping;
21047 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21048 s.select_ranges(selections.into_iter().map(|(start, end)| {
21049 snapshot.clip_offset(start, Bias::Left)
21050 ..snapshot.clip_offset(end, Bias::Right)
21051 }));
21052 });
21053 self.selection_history.mode = SelectionHistoryMode::Normal;
21054 }
21055 };
21056 }
21057
21058 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
21059 }
21060
21061 fn update_lsp_data(
21062 &mut self,
21063 ignore_cache: bool,
21064 for_buffer: Option<BufferId>,
21065 window: &mut Window,
21066 cx: &mut Context<'_, Self>,
21067 ) {
21068 self.pull_diagnostics(for_buffer, window, cx);
21069 self.refresh_colors(ignore_cache, for_buffer, window, cx);
21070 }
21071}
21072
21073fn vim_enabled(cx: &App) -> bool {
21074 cx.global::<SettingsStore>()
21075 .raw_user_settings()
21076 .get("vim_mode")
21077 == Some(&serde_json::Value::Bool(true))
21078}
21079
21080fn process_completion_for_edit(
21081 completion: &Completion,
21082 intent: CompletionIntent,
21083 buffer: &Entity<Buffer>,
21084 cursor_position: &text::Anchor,
21085 cx: &mut Context<Editor>,
21086) -> CompletionEdit {
21087 let buffer = buffer.read(cx);
21088 let buffer_snapshot = buffer.snapshot();
21089 let (snippet, new_text) = if completion.is_snippet() {
21090 // Workaround for typescript language server issues so that methods don't expand within
21091 // strings and functions with type expressions. The previous point is used because the query
21092 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
21093 let mut snippet_source = completion.new_text.clone();
21094 let mut previous_point = text::ToPoint::to_point(cursor_position, buffer);
21095 previous_point.column = previous_point.column.saturating_sub(1);
21096 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point) {
21097 if scope.prefers_label_for_snippet_in_completion() {
21098 if let Some(label) = completion.label() {
21099 if matches!(
21100 completion.kind(),
21101 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
21102 ) {
21103 snippet_source = label;
21104 }
21105 }
21106 }
21107 }
21108 match Snippet::parse(&snippet_source).log_err() {
21109 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
21110 None => (None, completion.new_text.clone()),
21111 }
21112 } else {
21113 (None, completion.new_text.clone())
21114 };
21115
21116 let mut range_to_replace = {
21117 let replace_range = &completion.replace_range;
21118 if let CompletionSource::Lsp {
21119 insert_range: Some(insert_range),
21120 ..
21121 } = &completion.source
21122 {
21123 debug_assert_eq!(
21124 insert_range.start, replace_range.start,
21125 "insert_range and replace_range should start at the same position"
21126 );
21127 debug_assert!(
21128 insert_range
21129 .start
21130 .cmp(&cursor_position, &buffer_snapshot)
21131 .is_le(),
21132 "insert_range should start before or at cursor position"
21133 );
21134 debug_assert!(
21135 replace_range
21136 .start
21137 .cmp(&cursor_position, &buffer_snapshot)
21138 .is_le(),
21139 "replace_range should start before or at cursor position"
21140 );
21141 debug_assert!(
21142 insert_range
21143 .end
21144 .cmp(&cursor_position, &buffer_snapshot)
21145 .is_le(),
21146 "insert_range should end before or at cursor position"
21147 );
21148
21149 let should_replace = match intent {
21150 CompletionIntent::CompleteWithInsert => false,
21151 CompletionIntent::CompleteWithReplace => true,
21152 CompletionIntent::Complete | CompletionIntent::Compose => {
21153 let insert_mode =
21154 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
21155 .completions
21156 .lsp_insert_mode;
21157 match insert_mode {
21158 LspInsertMode::Insert => false,
21159 LspInsertMode::Replace => true,
21160 LspInsertMode::ReplaceSubsequence => {
21161 let mut text_to_replace = buffer.chars_for_range(
21162 buffer.anchor_before(replace_range.start)
21163 ..buffer.anchor_after(replace_range.end),
21164 );
21165 let mut current_needle = text_to_replace.next();
21166 for haystack_ch in completion.label.text.chars() {
21167 if let Some(needle_ch) = current_needle {
21168 if haystack_ch.eq_ignore_ascii_case(&needle_ch) {
21169 current_needle = text_to_replace.next();
21170 }
21171 }
21172 }
21173 current_needle.is_none()
21174 }
21175 LspInsertMode::ReplaceSuffix => {
21176 if replace_range
21177 .end
21178 .cmp(&cursor_position, &buffer_snapshot)
21179 .is_gt()
21180 {
21181 let range_after_cursor = *cursor_position..replace_range.end;
21182 let text_after_cursor = buffer
21183 .text_for_range(
21184 buffer.anchor_before(range_after_cursor.start)
21185 ..buffer.anchor_after(range_after_cursor.end),
21186 )
21187 .collect::<String>()
21188 .to_ascii_lowercase();
21189 completion
21190 .label
21191 .text
21192 .to_ascii_lowercase()
21193 .ends_with(&text_after_cursor)
21194 } else {
21195 true
21196 }
21197 }
21198 }
21199 }
21200 };
21201
21202 if should_replace {
21203 replace_range.clone()
21204 } else {
21205 insert_range.clone()
21206 }
21207 } else {
21208 replace_range.clone()
21209 }
21210 };
21211
21212 if range_to_replace
21213 .end
21214 .cmp(&cursor_position, &buffer_snapshot)
21215 .is_lt()
21216 {
21217 range_to_replace.end = *cursor_position;
21218 }
21219
21220 CompletionEdit {
21221 new_text,
21222 replace_range: range_to_replace.to_offset(&buffer),
21223 snippet,
21224 }
21225}
21226
21227struct CompletionEdit {
21228 new_text: String,
21229 replace_range: Range<usize>,
21230 snippet: Option<Snippet>,
21231}
21232
21233fn insert_extra_newline_brackets(
21234 buffer: &MultiBufferSnapshot,
21235 range: Range<usize>,
21236 language: &language::LanguageScope,
21237) -> bool {
21238 let leading_whitespace_len = buffer
21239 .reversed_chars_at(range.start)
21240 .take_while(|c| c.is_whitespace() && *c != '\n')
21241 .map(|c| c.len_utf8())
21242 .sum::<usize>();
21243 let trailing_whitespace_len = buffer
21244 .chars_at(range.end)
21245 .take_while(|c| c.is_whitespace() && *c != '\n')
21246 .map(|c| c.len_utf8())
21247 .sum::<usize>();
21248 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
21249
21250 language.brackets().any(|(pair, enabled)| {
21251 let pair_start = pair.start.trim_end();
21252 let pair_end = pair.end.trim_start();
21253
21254 enabled
21255 && pair.newline
21256 && buffer.contains_str_at(range.end, pair_end)
21257 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
21258 })
21259}
21260
21261fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
21262 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
21263 [(buffer, range, _)] => (*buffer, range.clone()),
21264 _ => return false,
21265 };
21266 let pair = {
21267 let mut result: Option<BracketMatch> = None;
21268
21269 for pair in buffer
21270 .all_bracket_ranges(range.clone())
21271 .filter(move |pair| {
21272 pair.open_range.start <= range.start && pair.close_range.end >= range.end
21273 })
21274 {
21275 let len = pair.close_range.end - pair.open_range.start;
21276
21277 if let Some(existing) = &result {
21278 let existing_len = existing.close_range.end - existing.open_range.start;
21279 if len > existing_len {
21280 continue;
21281 }
21282 }
21283
21284 result = Some(pair);
21285 }
21286
21287 result
21288 };
21289 let Some(pair) = pair else {
21290 return false;
21291 };
21292 pair.newline_only
21293 && buffer
21294 .chars_for_range(pair.open_range.end..range.start)
21295 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
21296 .all(|c| c.is_whitespace() && c != '\n')
21297}
21298
21299fn update_uncommitted_diff_for_buffer(
21300 editor: Entity<Editor>,
21301 project: &Entity<Project>,
21302 buffers: impl IntoIterator<Item = Entity<Buffer>>,
21303 buffer: Entity<MultiBuffer>,
21304 cx: &mut App,
21305) -> Task<()> {
21306 let mut tasks = Vec::new();
21307 project.update(cx, |project, cx| {
21308 for buffer in buffers {
21309 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
21310 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
21311 }
21312 }
21313 });
21314 cx.spawn(async move |cx| {
21315 let diffs = future::join_all(tasks).await;
21316 if editor
21317 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
21318 .unwrap_or(false)
21319 {
21320 return;
21321 }
21322
21323 buffer
21324 .update(cx, |buffer, cx| {
21325 for diff in diffs.into_iter().flatten() {
21326 buffer.add_diff(diff, cx);
21327 }
21328 })
21329 .ok();
21330 })
21331}
21332
21333fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
21334 let tab_size = tab_size.get() as usize;
21335 let mut width = offset;
21336
21337 for ch in text.chars() {
21338 width += if ch == '\t' {
21339 tab_size - (width % tab_size)
21340 } else {
21341 1
21342 };
21343 }
21344
21345 width - offset
21346}
21347
21348#[cfg(test)]
21349mod tests {
21350 use super::*;
21351
21352 #[test]
21353 fn test_string_size_with_expanded_tabs() {
21354 let nz = |val| NonZeroU32::new(val).unwrap();
21355 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
21356 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
21357 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
21358 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
21359 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
21360 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
21361 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
21362 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
21363 }
21364}
21365
21366/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
21367struct WordBreakingTokenizer<'a> {
21368 input: &'a str,
21369}
21370
21371impl<'a> WordBreakingTokenizer<'a> {
21372 fn new(input: &'a str) -> Self {
21373 Self { input }
21374 }
21375}
21376
21377fn is_char_ideographic(ch: char) -> bool {
21378 use unicode_script::Script::*;
21379 use unicode_script::UnicodeScript;
21380 matches!(ch.script(), Han | Tangut | Yi)
21381}
21382
21383fn is_grapheme_ideographic(text: &str) -> bool {
21384 text.chars().any(is_char_ideographic)
21385}
21386
21387fn is_grapheme_whitespace(text: &str) -> bool {
21388 text.chars().any(|x| x.is_whitespace())
21389}
21390
21391fn should_stay_with_preceding_ideograph(text: &str) -> bool {
21392 text.chars().next().map_or(false, |ch| {
21393 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
21394 })
21395}
21396
21397#[derive(PartialEq, Eq, Debug, Clone, Copy)]
21398enum WordBreakToken<'a> {
21399 Word { token: &'a str, grapheme_len: usize },
21400 InlineWhitespace { token: &'a str, grapheme_len: usize },
21401 Newline,
21402}
21403
21404impl<'a> Iterator for WordBreakingTokenizer<'a> {
21405 /// Yields a span, the count of graphemes in the token, and whether it was
21406 /// whitespace. Note that it also breaks at word boundaries.
21407 type Item = WordBreakToken<'a>;
21408
21409 fn next(&mut self) -> Option<Self::Item> {
21410 use unicode_segmentation::UnicodeSegmentation;
21411 if self.input.is_empty() {
21412 return None;
21413 }
21414
21415 let mut iter = self.input.graphemes(true).peekable();
21416 let mut offset = 0;
21417 let mut grapheme_len = 0;
21418 if let Some(first_grapheme) = iter.next() {
21419 let is_newline = first_grapheme == "\n";
21420 let is_whitespace = is_grapheme_whitespace(first_grapheme);
21421 offset += first_grapheme.len();
21422 grapheme_len += 1;
21423 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
21424 if let Some(grapheme) = iter.peek().copied() {
21425 if should_stay_with_preceding_ideograph(grapheme) {
21426 offset += grapheme.len();
21427 grapheme_len += 1;
21428 }
21429 }
21430 } else {
21431 let mut words = self.input[offset..].split_word_bound_indices().peekable();
21432 let mut next_word_bound = words.peek().copied();
21433 if next_word_bound.map_or(false, |(i, _)| i == 0) {
21434 next_word_bound = words.next();
21435 }
21436 while let Some(grapheme) = iter.peek().copied() {
21437 if next_word_bound.map_or(false, |(i, _)| i == offset) {
21438 break;
21439 };
21440 if is_grapheme_whitespace(grapheme) != is_whitespace
21441 || (grapheme == "\n") != is_newline
21442 {
21443 break;
21444 };
21445 offset += grapheme.len();
21446 grapheme_len += 1;
21447 iter.next();
21448 }
21449 }
21450 let token = &self.input[..offset];
21451 self.input = &self.input[offset..];
21452 if token == "\n" {
21453 Some(WordBreakToken::Newline)
21454 } else if is_whitespace {
21455 Some(WordBreakToken::InlineWhitespace {
21456 token,
21457 grapheme_len,
21458 })
21459 } else {
21460 Some(WordBreakToken::Word {
21461 token,
21462 grapheme_len,
21463 })
21464 }
21465 } else {
21466 None
21467 }
21468 }
21469}
21470
21471#[test]
21472fn test_word_breaking_tokenizer() {
21473 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
21474 ("", &[]),
21475 (" ", &[whitespace(" ", 2)]),
21476 ("Ʒ", &[word("Ʒ", 1)]),
21477 ("Ǽ", &[word("Ǽ", 1)]),
21478 ("⋑", &[word("⋑", 1)]),
21479 ("⋑⋑", &[word("⋑⋑", 2)]),
21480 (
21481 "原理,进而",
21482 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
21483 ),
21484 (
21485 "hello world",
21486 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
21487 ),
21488 (
21489 "hello, world",
21490 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
21491 ),
21492 (
21493 " hello world",
21494 &[
21495 whitespace(" ", 2),
21496 word("hello", 5),
21497 whitespace(" ", 1),
21498 word("world", 5),
21499 ],
21500 ),
21501 (
21502 "这是什么 \n 钢笔",
21503 &[
21504 word("这", 1),
21505 word("是", 1),
21506 word("什", 1),
21507 word("么", 1),
21508 whitespace(" ", 1),
21509 newline(),
21510 whitespace(" ", 1),
21511 word("钢", 1),
21512 word("笔", 1),
21513 ],
21514 ),
21515 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
21516 ];
21517
21518 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21519 WordBreakToken::Word {
21520 token,
21521 grapheme_len,
21522 }
21523 }
21524
21525 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21526 WordBreakToken::InlineWhitespace {
21527 token,
21528 grapheme_len,
21529 }
21530 }
21531
21532 fn newline() -> WordBreakToken<'static> {
21533 WordBreakToken::Newline
21534 }
21535
21536 for (input, result) in tests {
21537 assert_eq!(
21538 WordBreakingTokenizer::new(input)
21539 .collect::<Vec<_>>()
21540 .as_slice(),
21541 *result,
21542 );
21543 }
21544}
21545
21546fn wrap_with_prefix(
21547 first_line_prefix: String,
21548 subsequent_lines_prefix: String,
21549 unwrapped_text: String,
21550 wrap_column: usize,
21551 tab_size: NonZeroU32,
21552 preserve_existing_whitespace: bool,
21553) -> String {
21554 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
21555 let subsequent_lines_prefix_len =
21556 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
21557 let mut wrapped_text = String::new();
21558 let mut current_line = first_line_prefix.clone();
21559 let mut is_first_line = true;
21560
21561 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
21562 let mut current_line_len = first_line_prefix_len;
21563 let mut in_whitespace = false;
21564 for token in tokenizer {
21565 let have_preceding_whitespace = in_whitespace;
21566 match token {
21567 WordBreakToken::Word {
21568 token,
21569 grapheme_len,
21570 } => {
21571 in_whitespace = false;
21572 let current_prefix_len = if is_first_line {
21573 first_line_prefix_len
21574 } else {
21575 subsequent_lines_prefix_len
21576 };
21577 if current_line_len + grapheme_len > wrap_column
21578 && current_line_len != current_prefix_len
21579 {
21580 wrapped_text.push_str(current_line.trim_end());
21581 wrapped_text.push('\n');
21582 is_first_line = false;
21583 current_line = subsequent_lines_prefix.clone();
21584 current_line_len = subsequent_lines_prefix_len;
21585 }
21586 current_line.push_str(token);
21587 current_line_len += grapheme_len;
21588 }
21589 WordBreakToken::InlineWhitespace {
21590 mut token,
21591 mut grapheme_len,
21592 } => {
21593 in_whitespace = true;
21594 if have_preceding_whitespace && !preserve_existing_whitespace {
21595 continue;
21596 }
21597 if !preserve_existing_whitespace {
21598 token = " ";
21599 grapheme_len = 1;
21600 }
21601 let current_prefix_len = if is_first_line {
21602 first_line_prefix_len
21603 } else {
21604 subsequent_lines_prefix_len
21605 };
21606 if current_line_len + grapheme_len > wrap_column {
21607 wrapped_text.push_str(current_line.trim_end());
21608 wrapped_text.push('\n');
21609 is_first_line = false;
21610 current_line = subsequent_lines_prefix.clone();
21611 current_line_len = subsequent_lines_prefix_len;
21612 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
21613 current_line.push_str(token);
21614 current_line_len += grapheme_len;
21615 }
21616 }
21617 WordBreakToken::Newline => {
21618 in_whitespace = true;
21619 let current_prefix_len = if is_first_line {
21620 first_line_prefix_len
21621 } else {
21622 subsequent_lines_prefix_len
21623 };
21624 if preserve_existing_whitespace {
21625 wrapped_text.push_str(current_line.trim_end());
21626 wrapped_text.push('\n');
21627 is_first_line = false;
21628 current_line = subsequent_lines_prefix.clone();
21629 current_line_len = subsequent_lines_prefix_len;
21630 } else if have_preceding_whitespace {
21631 continue;
21632 } else if current_line_len + 1 > wrap_column
21633 && current_line_len != current_prefix_len
21634 {
21635 wrapped_text.push_str(current_line.trim_end());
21636 wrapped_text.push('\n');
21637 is_first_line = false;
21638 current_line = subsequent_lines_prefix.clone();
21639 current_line_len = subsequent_lines_prefix_len;
21640 } else if current_line_len != current_prefix_len {
21641 current_line.push(' ');
21642 current_line_len += 1;
21643 }
21644 }
21645 }
21646 }
21647
21648 if !current_line.is_empty() {
21649 wrapped_text.push_str(¤t_line);
21650 }
21651 wrapped_text
21652}
21653
21654#[test]
21655fn test_wrap_with_prefix() {
21656 assert_eq!(
21657 wrap_with_prefix(
21658 "# ".to_string(),
21659 "# ".to_string(),
21660 "abcdefg".to_string(),
21661 4,
21662 NonZeroU32::new(4).unwrap(),
21663 false,
21664 ),
21665 "# abcdefg"
21666 );
21667 assert_eq!(
21668 wrap_with_prefix(
21669 "".to_string(),
21670 "".to_string(),
21671 "\thello world".to_string(),
21672 8,
21673 NonZeroU32::new(4).unwrap(),
21674 false,
21675 ),
21676 "hello\nworld"
21677 );
21678 assert_eq!(
21679 wrap_with_prefix(
21680 "// ".to_string(),
21681 "// ".to_string(),
21682 "xx \nyy zz aa bb cc".to_string(),
21683 12,
21684 NonZeroU32::new(4).unwrap(),
21685 false,
21686 ),
21687 "// xx yy zz\n// aa bb cc"
21688 );
21689 assert_eq!(
21690 wrap_with_prefix(
21691 String::new(),
21692 String::new(),
21693 "这是什么 \n 钢笔".to_string(),
21694 3,
21695 NonZeroU32::new(4).unwrap(),
21696 false,
21697 ),
21698 "这是什\n么 钢\n笔"
21699 );
21700}
21701
21702pub trait CollaborationHub {
21703 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
21704 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
21705 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
21706}
21707
21708impl CollaborationHub for Entity<Project> {
21709 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
21710 self.read(cx).collaborators()
21711 }
21712
21713 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
21714 self.read(cx).user_store().read(cx).participant_indices()
21715 }
21716
21717 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
21718 let this = self.read(cx);
21719 let user_ids = this.collaborators().values().map(|c| c.user_id);
21720 this.user_store().read(cx).participant_names(user_ids, cx)
21721 }
21722}
21723
21724pub trait SemanticsProvider {
21725 fn hover(
21726 &self,
21727 buffer: &Entity<Buffer>,
21728 position: text::Anchor,
21729 cx: &mut App,
21730 ) -> Option<Task<Vec<project::Hover>>>;
21731
21732 fn inline_values(
21733 &self,
21734 buffer_handle: Entity<Buffer>,
21735 range: Range<text::Anchor>,
21736 cx: &mut App,
21737 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21738
21739 fn inlay_hints(
21740 &self,
21741 buffer_handle: Entity<Buffer>,
21742 range: Range<text::Anchor>,
21743 cx: &mut App,
21744 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21745
21746 fn resolve_inlay_hint(
21747 &self,
21748 hint: InlayHint,
21749 buffer_handle: Entity<Buffer>,
21750 server_id: LanguageServerId,
21751 cx: &mut App,
21752 ) -> Option<Task<anyhow::Result<InlayHint>>>;
21753
21754 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
21755
21756 fn document_highlights(
21757 &self,
21758 buffer: &Entity<Buffer>,
21759 position: text::Anchor,
21760 cx: &mut App,
21761 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
21762
21763 fn definitions(
21764 &self,
21765 buffer: &Entity<Buffer>,
21766 position: text::Anchor,
21767 kind: GotoDefinitionKind,
21768 cx: &mut App,
21769 ) -> Option<Task<Result<Vec<LocationLink>>>>;
21770
21771 fn range_for_rename(
21772 &self,
21773 buffer: &Entity<Buffer>,
21774 position: text::Anchor,
21775 cx: &mut App,
21776 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
21777
21778 fn perform_rename(
21779 &self,
21780 buffer: &Entity<Buffer>,
21781 position: text::Anchor,
21782 new_name: String,
21783 cx: &mut App,
21784 ) -> Option<Task<Result<ProjectTransaction>>>;
21785}
21786
21787pub trait CompletionProvider {
21788 fn completions(
21789 &self,
21790 excerpt_id: ExcerptId,
21791 buffer: &Entity<Buffer>,
21792 buffer_position: text::Anchor,
21793 trigger: CompletionContext,
21794 window: &mut Window,
21795 cx: &mut Context<Editor>,
21796 ) -> Task<Result<Vec<CompletionResponse>>>;
21797
21798 fn resolve_completions(
21799 &self,
21800 _buffer: Entity<Buffer>,
21801 _completion_indices: Vec<usize>,
21802 _completions: Rc<RefCell<Box<[Completion]>>>,
21803 _cx: &mut Context<Editor>,
21804 ) -> Task<Result<bool>> {
21805 Task::ready(Ok(false))
21806 }
21807
21808 fn apply_additional_edits_for_completion(
21809 &self,
21810 _buffer: Entity<Buffer>,
21811 _completions: Rc<RefCell<Box<[Completion]>>>,
21812 _completion_index: usize,
21813 _push_to_history: bool,
21814 _cx: &mut Context<Editor>,
21815 ) -> Task<Result<Option<language::Transaction>>> {
21816 Task::ready(Ok(None))
21817 }
21818
21819 fn is_completion_trigger(
21820 &self,
21821 buffer: &Entity<Buffer>,
21822 position: language::Anchor,
21823 text: &str,
21824 trigger_in_words: bool,
21825 menu_is_open: bool,
21826 cx: &mut Context<Editor>,
21827 ) -> bool;
21828
21829 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
21830
21831 fn sort_completions(&self) -> bool {
21832 true
21833 }
21834
21835 fn filter_completions(&self) -> bool {
21836 true
21837 }
21838}
21839
21840pub trait CodeActionProvider {
21841 fn id(&self) -> Arc<str>;
21842
21843 fn code_actions(
21844 &self,
21845 buffer: &Entity<Buffer>,
21846 range: Range<text::Anchor>,
21847 window: &mut Window,
21848 cx: &mut App,
21849 ) -> Task<Result<Vec<CodeAction>>>;
21850
21851 fn apply_code_action(
21852 &self,
21853 buffer_handle: Entity<Buffer>,
21854 action: CodeAction,
21855 excerpt_id: ExcerptId,
21856 push_to_history: bool,
21857 window: &mut Window,
21858 cx: &mut App,
21859 ) -> Task<Result<ProjectTransaction>>;
21860}
21861
21862impl CodeActionProvider for Entity<Project> {
21863 fn id(&self) -> Arc<str> {
21864 "project".into()
21865 }
21866
21867 fn code_actions(
21868 &self,
21869 buffer: &Entity<Buffer>,
21870 range: Range<text::Anchor>,
21871 _window: &mut Window,
21872 cx: &mut App,
21873 ) -> Task<Result<Vec<CodeAction>>> {
21874 self.update(cx, |project, cx| {
21875 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
21876 let code_actions = project.code_actions(buffer, range, None, cx);
21877 cx.background_spawn(async move {
21878 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
21879 Ok(code_lens_actions
21880 .context("code lens fetch")?
21881 .into_iter()
21882 .chain(code_actions.context("code action fetch")?)
21883 .collect())
21884 })
21885 })
21886 }
21887
21888 fn apply_code_action(
21889 &self,
21890 buffer_handle: Entity<Buffer>,
21891 action: CodeAction,
21892 _excerpt_id: ExcerptId,
21893 push_to_history: bool,
21894 _window: &mut Window,
21895 cx: &mut App,
21896 ) -> Task<Result<ProjectTransaction>> {
21897 self.update(cx, |project, cx| {
21898 project.apply_code_action(buffer_handle, action, push_to_history, cx)
21899 })
21900 }
21901}
21902
21903fn snippet_completions(
21904 project: &Project,
21905 buffer: &Entity<Buffer>,
21906 buffer_position: text::Anchor,
21907 cx: &mut App,
21908) -> Task<Result<CompletionResponse>> {
21909 let languages = buffer.read(cx).languages_at(buffer_position);
21910 let snippet_store = project.snippets().read(cx);
21911
21912 let scopes: Vec<_> = languages
21913 .iter()
21914 .filter_map(|language| {
21915 let language_name = language.lsp_id();
21916 let snippets = snippet_store.snippets_for(Some(language_name), cx);
21917
21918 if snippets.is_empty() {
21919 None
21920 } else {
21921 Some((language.default_scope(), snippets))
21922 }
21923 })
21924 .collect();
21925
21926 if scopes.is_empty() {
21927 return Task::ready(Ok(CompletionResponse {
21928 completions: vec![],
21929 is_incomplete: false,
21930 }));
21931 }
21932
21933 let snapshot = buffer.read(cx).text_snapshot();
21934 let chars: String = snapshot
21935 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
21936 .collect();
21937 let executor = cx.background_executor().clone();
21938
21939 cx.background_spawn(async move {
21940 let mut is_incomplete = false;
21941 let mut completions: Vec<Completion> = Vec::new();
21942 for (scope, snippets) in scopes.into_iter() {
21943 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
21944 let mut last_word = chars
21945 .chars()
21946 .take_while(|c| classifier.is_word(*c))
21947 .collect::<String>();
21948 last_word = last_word.chars().rev().collect();
21949
21950 if last_word.is_empty() {
21951 return Ok(CompletionResponse {
21952 completions: vec![],
21953 is_incomplete: true,
21954 });
21955 }
21956
21957 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
21958 let to_lsp = |point: &text::Anchor| {
21959 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
21960 point_to_lsp(end)
21961 };
21962 let lsp_end = to_lsp(&buffer_position);
21963
21964 let candidates = snippets
21965 .iter()
21966 .enumerate()
21967 .flat_map(|(ix, snippet)| {
21968 snippet
21969 .prefix
21970 .iter()
21971 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
21972 })
21973 .collect::<Vec<StringMatchCandidate>>();
21974
21975 const MAX_RESULTS: usize = 100;
21976 let mut matches = fuzzy::match_strings(
21977 &candidates,
21978 &last_word,
21979 last_word.chars().any(|c| c.is_uppercase()),
21980 true,
21981 MAX_RESULTS,
21982 &Default::default(),
21983 executor.clone(),
21984 )
21985 .await;
21986
21987 if matches.len() >= MAX_RESULTS {
21988 is_incomplete = true;
21989 }
21990
21991 // Remove all candidates where the query's start does not match the start of any word in the candidate
21992 if let Some(query_start) = last_word.chars().next() {
21993 matches.retain(|string_match| {
21994 split_words(&string_match.string).any(|word| {
21995 // Check that the first codepoint of the word as lowercase matches the first
21996 // codepoint of the query as lowercase
21997 word.chars()
21998 .flat_map(|codepoint| codepoint.to_lowercase())
21999 .zip(query_start.to_lowercase())
22000 .all(|(word_cp, query_cp)| word_cp == query_cp)
22001 })
22002 });
22003 }
22004
22005 let matched_strings = matches
22006 .into_iter()
22007 .map(|m| m.string)
22008 .collect::<HashSet<_>>();
22009
22010 completions.extend(snippets.iter().filter_map(|snippet| {
22011 let matching_prefix = snippet
22012 .prefix
22013 .iter()
22014 .find(|prefix| matched_strings.contains(*prefix))?;
22015 let start = as_offset - last_word.len();
22016 let start = snapshot.anchor_before(start);
22017 let range = start..buffer_position;
22018 let lsp_start = to_lsp(&start);
22019 let lsp_range = lsp::Range {
22020 start: lsp_start,
22021 end: lsp_end,
22022 };
22023 Some(Completion {
22024 replace_range: range,
22025 new_text: snippet.body.clone(),
22026 source: CompletionSource::Lsp {
22027 insert_range: None,
22028 server_id: LanguageServerId(usize::MAX),
22029 resolved: true,
22030 lsp_completion: Box::new(lsp::CompletionItem {
22031 label: snippet.prefix.first().unwrap().clone(),
22032 kind: Some(CompletionItemKind::SNIPPET),
22033 label_details: snippet.description.as_ref().map(|description| {
22034 lsp::CompletionItemLabelDetails {
22035 detail: Some(description.clone()),
22036 description: None,
22037 }
22038 }),
22039 insert_text_format: Some(InsertTextFormat::SNIPPET),
22040 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22041 lsp::InsertReplaceEdit {
22042 new_text: snippet.body.clone(),
22043 insert: lsp_range,
22044 replace: lsp_range,
22045 },
22046 )),
22047 filter_text: Some(snippet.body.clone()),
22048 sort_text: Some(char::MAX.to_string()),
22049 ..lsp::CompletionItem::default()
22050 }),
22051 lsp_defaults: None,
22052 },
22053 label: CodeLabel {
22054 text: matching_prefix.clone(),
22055 runs: Vec::new(),
22056 filter_range: 0..matching_prefix.len(),
22057 },
22058 icon_path: None,
22059 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
22060 single_line: snippet.name.clone().into(),
22061 plain_text: snippet
22062 .description
22063 .clone()
22064 .map(|description| description.into()),
22065 }),
22066 insert_text_mode: None,
22067 confirm: None,
22068 })
22069 }))
22070 }
22071
22072 Ok(CompletionResponse {
22073 completions,
22074 is_incomplete,
22075 })
22076 })
22077}
22078
22079impl CompletionProvider for Entity<Project> {
22080 fn completions(
22081 &self,
22082 _excerpt_id: ExcerptId,
22083 buffer: &Entity<Buffer>,
22084 buffer_position: text::Anchor,
22085 options: CompletionContext,
22086 _window: &mut Window,
22087 cx: &mut Context<Editor>,
22088 ) -> Task<Result<Vec<CompletionResponse>>> {
22089 self.update(cx, |project, cx| {
22090 let snippets = snippet_completions(project, buffer, buffer_position, cx);
22091 let project_completions = project.completions(buffer, buffer_position, options, cx);
22092 cx.background_spawn(async move {
22093 let mut responses = project_completions.await?;
22094 let snippets = snippets.await?;
22095 if !snippets.completions.is_empty() {
22096 responses.push(snippets);
22097 }
22098 Ok(responses)
22099 })
22100 })
22101 }
22102
22103 fn resolve_completions(
22104 &self,
22105 buffer: Entity<Buffer>,
22106 completion_indices: Vec<usize>,
22107 completions: Rc<RefCell<Box<[Completion]>>>,
22108 cx: &mut Context<Editor>,
22109 ) -> Task<Result<bool>> {
22110 self.update(cx, |project, cx| {
22111 project.lsp_store().update(cx, |lsp_store, cx| {
22112 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
22113 })
22114 })
22115 }
22116
22117 fn apply_additional_edits_for_completion(
22118 &self,
22119 buffer: Entity<Buffer>,
22120 completions: Rc<RefCell<Box<[Completion]>>>,
22121 completion_index: usize,
22122 push_to_history: bool,
22123 cx: &mut Context<Editor>,
22124 ) -> Task<Result<Option<language::Transaction>>> {
22125 self.update(cx, |project, cx| {
22126 project.lsp_store().update(cx, |lsp_store, cx| {
22127 lsp_store.apply_additional_edits_for_completion(
22128 buffer,
22129 completions,
22130 completion_index,
22131 push_to_history,
22132 cx,
22133 )
22134 })
22135 })
22136 }
22137
22138 fn is_completion_trigger(
22139 &self,
22140 buffer: &Entity<Buffer>,
22141 position: language::Anchor,
22142 text: &str,
22143 trigger_in_words: bool,
22144 menu_is_open: bool,
22145 cx: &mut Context<Editor>,
22146 ) -> bool {
22147 let mut chars = text.chars();
22148 let char = if let Some(char) = chars.next() {
22149 char
22150 } else {
22151 return false;
22152 };
22153 if chars.next().is_some() {
22154 return false;
22155 }
22156
22157 let buffer = buffer.read(cx);
22158 let snapshot = buffer.snapshot();
22159 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
22160 return false;
22161 }
22162 let classifier = snapshot.char_classifier_at(position).for_completion(true);
22163 if trigger_in_words && classifier.is_word(char) {
22164 return true;
22165 }
22166
22167 buffer.completion_triggers().contains(text)
22168 }
22169}
22170
22171impl SemanticsProvider for Entity<Project> {
22172 fn hover(
22173 &self,
22174 buffer: &Entity<Buffer>,
22175 position: text::Anchor,
22176 cx: &mut App,
22177 ) -> Option<Task<Vec<project::Hover>>> {
22178 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
22179 }
22180
22181 fn document_highlights(
22182 &self,
22183 buffer: &Entity<Buffer>,
22184 position: text::Anchor,
22185 cx: &mut App,
22186 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
22187 Some(self.update(cx, |project, cx| {
22188 project.document_highlights(buffer, position, cx)
22189 }))
22190 }
22191
22192 fn definitions(
22193 &self,
22194 buffer: &Entity<Buffer>,
22195 position: text::Anchor,
22196 kind: GotoDefinitionKind,
22197 cx: &mut App,
22198 ) -> Option<Task<Result<Vec<LocationLink>>>> {
22199 Some(self.update(cx, |project, cx| match kind {
22200 GotoDefinitionKind::Symbol => project.definitions(&buffer, position, cx),
22201 GotoDefinitionKind::Declaration => project.declarations(&buffer, position, cx),
22202 GotoDefinitionKind::Type => project.type_definitions(&buffer, position, cx),
22203 GotoDefinitionKind::Implementation => project.implementations(&buffer, position, cx),
22204 }))
22205 }
22206
22207 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
22208 // TODO: make this work for remote projects
22209 self.update(cx, |project, cx| {
22210 if project
22211 .active_debug_session(cx)
22212 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
22213 {
22214 return true;
22215 }
22216
22217 buffer.update(cx, |buffer, cx| {
22218 project.any_language_server_supports_inlay_hints(buffer, cx)
22219 })
22220 })
22221 }
22222
22223 fn inline_values(
22224 &self,
22225 buffer_handle: Entity<Buffer>,
22226 range: Range<text::Anchor>,
22227 cx: &mut App,
22228 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22229 self.update(cx, |project, cx| {
22230 let (session, active_stack_frame) = project.active_debug_session(cx)?;
22231
22232 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
22233 })
22234 }
22235
22236 fn inlay_hints(
22237 &self,
22238 buffer_handle: Entity<Buffer>,
22239 range: Range<text::Anchor>,
22240 cx: &mut App,
22241 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22242 Some(self.update(cx, |project, cx| {
22243 project.inlay_hints(buffer_handle, range, cx)
22244 }))
22245 }
22246
22247 fn resolve_inlay_hint(
22248 &self,
22249 hint: InlayHint,
22250 buffer_handle: Entity<Buffer>,
22251 server_id: LanguageServerId,
22252 cx: &mut App,
22253 ) -> Option<Task<anyhow::Result<InlayHint>>> {
22254 Some(self.update(cx, |project, cx| {
22255 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
22256 }))
22257 }
22258
22259 fn range_for_rename(
22260 &self,
22261 buffer: &Entity<Buffer>,
22262 position: text::Anchor,
22263 cx: &mut App,
22264 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
22265 Some(self.update(cx, |project, cx| {
22266 let buffer = buffer.clone();
22267 let task = project.prepare_rename(buffer.clone(), position, cx);
22268 cx.spawn(async move |_, cx| {
22269 Ok(match task.await? {
22270 PrepareRenameResponse::Success(range) => Some(range),
22271 PrepareRenameResponse::InvalidPosition => None,
22272 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
22273 // Fallback on using TreeSitter info to determine identifier range
22274 buffer.read_with(cx, |buffer, _| {
22275 let snapshot = buffer.snapshot();
22276 let (range, kind) = snapshot.surrounding_word(position, false);
22277 if kind != Some(CharKind::Word) {
22278 return None;
22279 }
22280 Some(
22281 snapshot.anchor_before(range.start)
22282 ..snapshot.anchor_after(range.end),
22283 )
22284 })?
22285 }
22286 })
22287 })
22288 }))
22289 }
22290
22291 fn perform_rename(
22292 &self,
22293 buffer: &Entity<Buffer>,
22294 position: text::Anchor,
22295 new_name: String,
22296 cx: &mut App,
22297 ) -> Option<Task<Result<ProjectTransaction>>> {
22298 Some(self.update(cx, |project, cx| {
22299 project.perform_rename(buffer.clone(), position, new_name, cx)
22300 }))
22301 }
22302}
22303
22304fn inlay_hint_settings(
22305 location: Anchor,
22306 snapshot: &MultiBufferSnapshot,
22307 cx: &mut Context<Editor>,
22308) -> InlayHintSettings {
22309 let file = snapshot.file_at(location);
22310 let language = snapshot.language_at(location).map(|l| l.name());
22311 language_settings(language, file, cx).inlay_hints
22312}
22313
22314fn consume_contiguous_rows(
22315 contiguous_row_selections: &mut Vec<Selection<Point>>,
22316 selection: &Selection<Point>,
22317 display_map: &DisplaySnapshot,
22318 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
22319) -> (MultiBufferRow, MultiBufferRow) {
22320 contiguous_row_selections.push(selection.clone());
22321 let start_row = starting_row(selection, display_map);
22322 let mut end_row = ending_row(selection, display_map);
22323
22324 while let Some(next_selection) = selections.peek() {
22325 if next_selection.start.row <= end_row.0 {
22326 end_row = ending_row(next_selection, display_map);
22327 contiguous_row_selections.push(selections.next().unwrap().clone());
22328 } else {
22329 break;
22330 }
22331 }
22332 (start_row, end_row)
22333}
22334
22335fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22336 if selection.start.column > 0 {
22337 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
22338 } else {
22339 MultiBufferRow(selection.start.row)
22340 }
22341}
22342
22343fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22344 if next_selection.end.column > 0 || next_selection.is_empty() {
22345 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
22346 } else {
22347 MultiBufferRow(next_selection.end.row)
22348 }
22349}
22350
22351impl EditorSnapshot {
22352 pub fn remote_selections_in_range<'a>(
22353 &'a self,
22354 range: &'a Range<Anchor>,
22355 collaboration_hub: &dyn CollaborationHub,
22356 cx: &'a App,
22357 ) -> impl 'a + Iterator<Item = RemoteSelection> {
22358 let participant_names = collaboration_hub.user_names(cx);
22359 let participant_indices = collaboration_hub.user_participant_indices(cx);
22360 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
22361 let collaborators_by_replica_id = collaborators_by_peer_id
22362 .values()
22363 .map(|collaborator| (collaborator.replica_id, collaborator))
22364 .collect::<HashMap<_, _>>();
22365 self.buffer_snapshot
22366 .selections_in_range(range, false)
22367 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
22368 if replica_id == AGENT_REPLICA_ID {
22369 Some(RemoteSelection {
22370 replica_id,
22371 selection,
22372 cursor_shape,
22373 line_mode,
22374 collaborator_id: CollaboratorId::Agent,
22375 user_name: Some("Agent".into()),
22376 color: cx.theme().players().agent(),
22377 })
22378 } else {
22379 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
22380 let participant_index = participant_indices.get(&collaborator.user_id).copied();
22381 let user_name = participant_names.get(&collaborator.user_id).cloned();
22382 Some(RemoteSelection {
22383 replica_id,
22384 selection,
22385 cursor_shape,
22386 line_mode,
22387 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
22388 user_name,
22389 color: if let Some(index) = participant_index {
22390 cx.theme().players().color_for_participant(index.0)
22391 } else {
22392 cx.theme().players().absent()
22393 },
22394 })
22395 }
22396 })
22397 }
22398
22399 pub fn hunks_for_ranges(
22400 &self,
22401 ranges: impl IntoIterator<Item = Range<Point>>,
22402 ) -> Vec<MultiBufferDiffHunk> {
22403 let mut hunks = Vec::new();
22404 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
22405 HashMap::default();
22406 for query_range in ranges {
22407 let query_rows =
22408 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
22409 for hunk in self.buffer_snapshot.diff_hunks_in_range(
22410 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
22411 ) {
22412 // Include deleted hunks that are adjacent to the query range, because
22413 // otherwise they would be missed.
22414 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
22415 if hunk.status().is_deleted() {
22416 intersects_range |= hunk.row_range.start == query_rows.end;
22417 intersects_range |= hunk.row_range.end == query_rows.start;
22418 }
22419 if intersects_range {
22420 if !processed_buffer_rows
22421 .entry(hunk.buffer_id)
22422 .or_default()
22423 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
22424 {
22425 continue;
22426 }
22427 hunks.push(hunk);
22428 }
22429 }
22430 }
22431
22432 hunks
22433 }
22434
22435 fn display_diff_hunks_for_rows<'a>(
22436 &'a self,
22437 display_rows: Range<DisplayRow>,
22438 folded_buffers: &'a HashSet<BufferId>,
22439 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
22440 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
22441 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
22442
22443 self.buffer_snapshot
22444 .diff_hunks_in_range(buffer_start..buffer_end)
22445 .filter_map(|hunk| {
22446 if folded_buffers.contains(&hunk.buffer_id) {
22447 return None;
22448 }
22449
22450 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
22451 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
22452
22453 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
22454 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
22455
22456 let display_hunk = if hunk_display_start.column() != 0 {
22457 DisplayDiffHunk::Folded {
22458 display_row: hunk_display_start.row(),
22459 }
22460 } else {
22461 let mut end_row = hunk_display_end.row();
22462 if hunk_display_end.column() > 0 {
22463 end_row.0 += 1;
22464 }
22465 let is_created_file = hunk.is_created_file();
22466 DisplayDiffHunk::Unfolded {
22467 status: hunk.status(),
22468 diff_base_byte_range: hunk.diff_base_byte_range,
22469 display_row_range: hunk_display_start.row()..end_row,
22470 multi_buffer_range: Anchor::range_in_buffer(
22471 hunk.excerpt_id,
22472 hunk.buffer_id,
22473 hunk.buffer_range,
22474 ),
22475 is_created_file,
22476 }
22477 };
22478
22479 Some(display_hunk)
22480 })
22481 }
22482
22483 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
22484 self.display_snapshot.buffer_snapshot.language_at(position)
22485 }
22486
22487 pub fn is_focused(&self) -> bool {
22488 self.is_focused
22489 }
22490
22491 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
22492 self.placeholder_text.as_ref()
22493 }
22494
22495 pub fn scroll_position(&self) -> gpui::Point<f32> {
22496 self.scroll_anchor.scroll_position(&self.display_snapshot)
22497 }
22498
22499 fn gutter_dimensions(
22500 &self,
22501 font_id: FontId,
22502 font_size: Pixels,
22503 max_line_number_width: Pixels,
22504 cx: &App,
22505 ) -> Option<GutterDimensions> {
22506 if !self.show_gutter {
22507 return None;
22508 }
22509
22510 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
22511 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
22512
22513 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
22514 matches!(
22515 ProjectSettings::get_global(cx).git.git_gutter,
22516 Some(GitGutterSetting::TrackedFiles)
22517 )
22518 });
22519 let gutter_settings = EditorSettings::get_global(cx).gutter;
22520 let show_line_numbers = self
22521 .show_line_numbers
22522 .unwrap_or(gutter_settings.line_numbers);
22523 let line_gutter_width = if show_line_numbers {
22524 // Avoid flicker-like gutter resizes when the line number gains another digit by
22525 // only resizing the gutter on files with > 10**min_line_number_digits lines.
22526 let min_width_for_number_on_gutter =
22527 ch_advance * gutter_settings.min_line_number_digits as f32;
22528 max_line_number_width.max(min_width_for_number_on_gutter)
22529 } else {
22530 0.0.into()
22531 };
22532
22533 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
22534 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
22535
22536 let git_blame_entries_width =
22537 self.git_blame_gutter_max_author_length
22538 .map(|max_author_length| {
22539 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
22540 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
22541
22542 /// The number of characters to dedicate to gaps and margins.
22543 const SPACING_WIDTH: usize = 4;
22544
22545 let max_char_count = max_author_length.min(renderer.max_author_length())
22546 + ::git::SHORT_SHA_LENGTH
22547 + MAX_RELATIVE_TIMESTAMP.len()
22548 + SPACING_WIDTH;
22549
22550 ch_advance * max_char_count
22551 });
22552
22553 let is_singleton = self.buffer_snapshot.is_singleton();
22554
22555 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
22556 left_padding += if !is_singleton {
22557 ch_width * 4.0
22558 } else if show_runnables || show_breakpoints {
22559 ch_width * 3.0
22560 } else if show_git_gutter && show_line_numbers {
22561 ch_width * 2.0
22562 } else if show_git_gutter || show_line_numbers {
22563 ch_width
22564 } else {
22565 px(0.)
22566 };
22567
22568 let shows_folds = is_singleton && gutter_settings.folds;
22569
22570 let right_padding = if shows_folds && show_line_numbers {
22571 ch_width * 4.0
22572 } else if shows_folds || (!is_singleton && show_line_numbers) {
22573 ch_width * 3.0
22574 } else if show_line_numbers {
22575 ch_width
22576 } else {
22577 px(0.)
22578 };
22579
22580 Some(GutterDimensions {
22581 left_padding,
22582 right_padding,
22583 width: line_gutter_width + left_padding + right_padding,
22584 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
22585 git_blame_entries_width,
22586 })
22587 }
22588
22589 pub fn render_crease_toggle(
22590 &self,
22591 buffer_row: MultiBufferRow,
22592 row_contains_cursor: bool,
22593 editor: Entity<Editor>,
22594 window: &mut Window,
22595 cx: &mut App,
22596 ) -> Option<AnyElement> {
22597 let folded = self.is_line_folded(buffer_row);
22598 let mut is_foldable = false;
22599
22600 if let Some(crease) = self
22601 .crease_snapshot
22602 .query_row(buffer_row, &self.buffer_snapshot)
22603 {
22604 is_foldable = true;
22605 match crease {
22606 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
22607 if let Some(render_toggle) = render_toggle {
22608 let toggle_callback =
22609 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
22610 if folded {
22611 editor.update(cx, |editor, cx| {
22612 editor.fold_at(buffer_row, window, cx)
22613 });
22614 } else {
22615 editor.update(cx, |editor, cx| {
22616 editor.unfold_at(buffer_row, window, cx)
22617 });
22618 }
22619 });
22620 return Some((render_toggle)(
22621 buffer_row,
22622 folded,
22623 toggle_callback,
22624 window,
22625 cx,
22626 ));
22627 }
22628 }
22629 }
22630 }
22631
22632 is_foldable |= self.starts_indent(buffer_row);
22633
22634 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
22635 Some(
22636 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
22637 .toggle_state(folded)
22638 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
22639 if folded {
22640 this.unfold_at(buffer_row, window, cx);
22641 } else {
22642 this.fold_at(buffer_row, window, cx);
22643 }
22644 }))
22645 .into_any_element(),
22646 )
22647 } else {
22648 None
22649 }
22650 }
22651
22652 pub fn render_crease_trailer(
22653 &self,
22654 buffer_row: MultiBufferRow,
22655 window: &mut Window,
22656 cx: &mut App,
22657 ) -> Option<AnyElement> {
22658 let folded = self.is_line_folded(buffer_row);
22659 if let Crease::Inline { render_trailer, .. } = self
22660 .crease_snapshot
22661 .query_row(buffer_row, &self.buffer_snapshot)?
22662 {
22663 let render_trailer = render_trailer.as_ref()?;
22664 Some(render_trailer(buffer_row, folded, window, cx))
22665 } else {
22666 None
22667 }
22668 }
22669}
22670
22671impl Deref for EditorSnapshot {
22672 type Target = DisplaySnapshot;
22673
22674 fn deref(&self) -> &Self::Target {
22675 &self.display_snapshot
22676 }
22677}
22678
22679#[derive(Clone, Debug, PartialEq, Eq)]
22680pub enum EditorEvent {
22681 InputIgnored {
22682 text: Arc<str>,
22683 },
22684 InputHandled {
22685 utf16_range_to_replace: Option<Range<isize>>,
22686 text: Arc<str>,
22687 },
22688 ExcerptsAdded {
22689 buffer: Entity<Buffer>,
22690 predecessor: ExcerptId,
22691 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
22692 },
22693 ExcerptsRemoved {
22694 ids: Vec<ExcerptId>,
22695 removed_buffer_ids: Vec<BufferId>,
22696 },
22697 BufferFoldToggled {
22698 ids: Vec<ExcerptId>,
22699 folded: bool,
22700 },
22701 ExcerptsEdited {
22702 ids: Vec<ExcerptId>,
22703 },
22704 ExcerptsExpanded {
22705 ids: Vec<ExcerptId>,
22706 },
22707 BufferEdited,
22708 Edited {
22709 transaction_id: clock::Lamport,
22710 },
22711 Reparsed(BufferId),
22712 Focused,
22713 FocusedIn,
22714 Blurred,
22715 DirtyChanged,
22716 Saved,
22717 TitleChanged,
22718 DiffBaseChanged,
22719 SelectionsChanged {
22720 local: bool,
22721 },
22722 ScrollPositionChanged {
22723 local: bool,
22724 autoscroll: bool,
22725 },
22726 Closed,
22727 TransactionUndone {
22728 transaction_id: clock::Lamport,
22729 },
22730 TransactionBegun {
22731 transaction_id: clock::Lamport,
22732 },
22733 Reloaded,
22734 CursorShapeChanged,
22735 PushedToNavHistory {
22736 anchor: Anchor,
22737 is_deactivate: bool,
22738 },
22739}
22740
22741impl EventEmitter<EditorEvent> for Editor {}
22742
22743impl Focusable for Editor {
22744 fn focus_handle(&self, _cx: &App) -> FocusHandle {
22745 self.focus_handle.clone()
22746 }
22747}
22748
22749impl Render for Editor {
22750 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22751 let settings = ThemeSettings::get_global(cx);
22752
22753 let mut text_style = match self.mode {
22754 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
22755 color: cx.theme().colors().editor_foreground,
22756 font_family: settings.ui_font.family.clone(),
22757 font_features: settings.ui_font.features.clone(),
22758 font_fallbacks: settings.ui_font.fallbacks.clone(),
22759 font_size: rems(0.875).into(),
22760 font_weight: settings.ui_font.weight,
22761 line_height: relative(settings.buffer_line_height.value()),
22762 ..Default::default()
22763 },
22764 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
22765 color: cx.theme().colors().editor_foreground,
22766 font_family: settings.buffer_font.family.clone(),
22767 font_features: settings.buffer_font.features.clone(),
22768 font_fallbacks: settings.buffer_font.fallbacks.clone(),
22769 font_size: settings.buffer_font_size(cx).into(),
22770 font_weight: settings.buffer_font.weight,
22771 line_height: relative(settings.buffer_line_height.value()),
22772 ..Default::default()
22773 },
22774 };
22775 if let Some(text_style_refinement) = &self.text_style_refinement {
22776 text_style.refine(text_style_refinement)
22777 }
22778
22779 let background = match self.mode {
22780 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
22781 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
22782 EditorMode::Full { .. } => cx.theme().colors().editor_background,
22783 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
22784 };
22785
22786 EditorElement::new(
22787 &cx.entity(),
22788 EditorStyle {
22789 background,
22790 border: cx.theme().colors().border,
22791 local_player: cx.theme().players().local(),
22792 text: text_style,
22793 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
22794 syntax: cx.theme().syntax().clone(),
22795 status: cx.theme().status().clone(),
22796 inlay_hints_style: make_inlay_hints_style(cx),
22797 inline_completion_styles: make_suggestion_styles(cx),
22798 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
22799 show_underlines: self.diagnostics_enabled(),
22800 },
22801 )
22802 }
22803}
22804
22805impl EntityInputHandler for Editor {
22806 fn text_for_range(
22807 &mut self,
22808 range_utf16: Range<usize>,
22809 adjusted_range: &mut Option<Range<usize>>,
22810 _: &mut Window,
22811 cx: &mut Context<Self>,
22812 ) -> Option<String> {
22813 let snapshot = self.buffer.read(cx).read(cx);
22814 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
22815 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
22816 if (start.0..end.0) != range_utf16 {
22817 adjusted_range.replace(start.0..end.0);
22818 }
22819 Some(snapshot.text_for_range(start..end).collect())
22820 }
22821
22822 fn selected_text_range(
22823 &mut self,
22824 ignore_disabled_input: bool,
22825 _: &mut Window,
22826 cx: &mut Context<Self>,
22827 ) -> Option<UTF16Selection> {
22828 // Prevent the IME menu from appearing when holding down an alphabetic key
22829 // while input is disabled.
22830 if !ignore_disabled_input && !self.input_enabled {
22831 return None;
22832 }
22833
22834 let selection = self.selections.newest::<OffsetUtf16>(cx);
22835 let range = selection.range();
22836
22837 Some(UTF16Selection {
22838 range: range.start.0..range.end.0,
22839 reversed: selection.reversed,
22840 })
22841 }
22842
22843 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
22844 let snapshot = self.buffer.read(cx).read(cx);
22845 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
22846 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
22847 }
22848
22849 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
22850 self.clear_highlights::<InputComposition>(cx);
22851 self.ime_transaction.take();
22852 }
22853
22854 fn replace_text_in_range(
22855 &mut self,
22856 range_utf16: Option<Range<usize>>,
22857 text: &str,
22858 window: &mut Window,
22859 cx: &mut Context<Self>,
22860 ) {
22861 if !self.input_enabled {
22862 cx.emit(EditorEvent::InputIgnored { text: text.into() });
22863 return;
22864 }
22865
22866 self.transact(window, cx, |this, window, cx| {
22867 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
22868 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
22869 Some(this.selection_replacement_ranges(range_utf16, cx))
22870 } else {
22871 this.marked_text_ranges(cx)
22872 };
22873
22874 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
22875 let newest_selection_id = this.selections.newest_anchor().id;
22876 this.selections
22877 .all::<OffsetUtf16>(cx)
22878 .iter()
22879 .zip(ranges_to_replace.iter())
22880 .find_map(|(selection, range)| {
22881 if selection.id == newest_selection_id {
22882 Some(
22883 (range.start.0 as isize - selection.head().0 as isize)
22884 ..(range.end.0 as isize - selection.head().0 as isize),
22885 )
22886 } else {
22887 None
22888 }
22889 })
22890 });
22891
22892 cx.emit(EditorEvent::InputHandled {
22893 utf16_range_to_replace: range_to_replace,
22894 text: text.into(),
22895 });
22896
22897 if let Some(new_selected_ranges) = new_selected_ranges {
22898 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22899 selections.select_ranges(new_selected_ranges)
22900 });
22901 this.backspace(&Default::default(), window, cx);
22902 }
22903
22904 this.handle_input(text, window, cx);
22905 });
22906
22907 if let Some(transaction) = self.ime_transaction {
22908 self.buffer.update(cx, |buffer, cx| {
22909 buffer.group_until_transaction(transaction, cx);
22910 });
22911 }
22912
22913 self.unmark_text(window, cx);
22914 }
22915
22916 fn replace_and_mark_text_in_range(
22917 &mut self,
22918 range_utf16: Option<Range<usize>>,
22919 text: &str,
22920 new_selected_range_utf16: Option<Range<usize>>,
22921 window: &mut Window,
22922 cx: &mut Context<Self>,
22923 ) {
22924 if !self.input_enabled {
22925 return;
22926 }
22927
22928 let transaction = self.transact(window, cx, |this, window, cx| {
22929 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
22930 let snapshot = this.buffer.read(cx).read(cx);
22931 if let Some(relative_range_utf16) = range_utf16.as_ref() {
22932 for marked_range in &mut marked_ranges {
22933 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
22934 marked_range.start.0 += relative_range_utf16.start;
22935 marked_range.start =
22936 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
22937 marked_range.end =
22938 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
22939 }
22940 }
22941 Some(marked_ranges)
22942 } else if let Some(range_utf16) = range_utf16 {
22943 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
22944 Some(this.selection_replacement_ranges(range_utf16, cx))
22945 } else {
22946 None
22947 };
22948
22949 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
22950 let newest_selection_id = this.selections.newest_anchor().id;
22951 this.selections
22952 .all::<OffsetUtf16>(cx)
22953 .iter()
22954 .zip(ranges_to_replace.iter())
22955 .find_map(|(selection, range)| {
22956 if selection.id == newest_selection_id {
22957 Some(
22958 (range.start.0 as isize - selection.head().0 as isize)
22959 ..(range.end.0 as isize - selection.head().0 as isize),
22960 )
22961 } else {
22962 None
22963 }
22964 })
22965 });
22966
22967 cx.emit(EditorEvent::InputHandled {
22968 utf16_range_to_replace: range_to_replace,
22969 text: text.into(),
22970 });
22971
22972 if let Some(ranges) = ranges_to_replace {
22973 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22974 s.select_ranges(ranges)
22975 });
22976 }
22977
22978 let marked_ranges = {
22979 let snapshot = this.buffer.read(cx).read(cx);
22980 this.selections
22981 .disjoint_anchors()
22982 .iter()
22983 .map(|selection| {
22984 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
22985 })
22986 .collect::<Vec<_>>()
22987 };
22988
22989 if text.is_empty() {
22990 this.unmark_text(window, cx);
22991 } else {
22992 this.highlight_text::<InputComposition>(
22993 marked_ranges.clone(),
22994 HighlightStyle {
22995 underline: Some(UnderlineStyle {
22996 thickness: px(1.),
22997 color: None,
22998 wavy: false,
22999 }),
23000 ..Default::default()
23001 },
23002 cx,
23003 );
23004 }
23005
23006 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
23007 let use_autoclose = this.use_autoclose;
23008 let use_auto_surround = this.use_auto_surround;
23009 this.set_use_autoclose(false);
23010 this.set_use_auto_surround(false);
23011 this.handle_input(text, window, cx);
23012 this.set_use_autoclose(use_autoclose);
23013 this.set_use_auto_surround(use_auto_surround);
23014
23015 if let Some(new_selected_range) = new_selected_range_utf16 {
23016 let snapshot = this.buffer.read(cx).read(cx);
23017 let new_selected_ranges = marked_ranges
23018 .into_iter()
23019 .map(|marked_range| {
23020 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
23021 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
23022 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
23023 snapshot.clip_offset_utf16(new_start, Bias::Left)
23024 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
23025 })
23026 .collect::<Vec<_>>();
23027
23028 drop(snapshot);
23029 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23030 selections.select_ranges(new_selected_ranges)
23031 });
23032 }
23033 });
23034
23035 self.ime_transaction = self.ime_transaction.or(transaction);
23036 if let Some(transaction) = self.ime_transaction {
23037 self.buffer.update(cx, |buffer, cx| {
23038 buffer.group_until_transaction(transaction, cx);
23039 });
23040 }
23041
23042 if self.text_highlights::<InputComposition>(cx).is_none() {
23043 self.ime_transaction.take();
23044 }
23045 }
23046
23047 fn bounds_for_range(
23048 &mut self,
23049 range_utf16: Range<usize>,
23050 element_bounds: gpui::Bounds<Pixels>,
23051 window: &mut Window,
23052 cx: &mut Context<Self>,
23053 ) -> Option<gpui::Bounds<Pixels>> {
23054 let text_layout_details = self.text_layout_details(window);
23055 let CharacterDimensions {
23056 em_width,
23057 em_advance,
23058 line_height,
23059 } = self.character_dimensions(window);
23060
23061 let snapshot = self.snapshot(window, cx);
23062 let scroll_position = snapshot.scroll_position();
23063 let scroll_left = scroll_position.x * em_advance;
23064
23065 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
23066 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
23067 + self.gutter_dimensions.full_width();
23068 let y = line_height * (start.row().as_f32() - scroll_position.y);
23069
23070 Some(Bounds {
23071 origin: element_bounds.origin + point(x, y),
23072 size: size(em_width, line_height),
23073 })
23074 }
23075
23076 fn character_index_for_point(
23077 &mut self,
23078 point: gpui::Point<Pixels>,
23079 _window: &mut Window,
23080 _cx: &mut Context<Self>,
23081 ) -> Option<usize> {
23082 let position_map = self.last_position_map.as_ref()?;
23083 if !position_map.text_hitbox.contains(&point) {
23084 return None;
23085 }
23086 let display_point = position_map.point_for_position(point).previous_valid;
23087 let anchor = position_map
23088 .snapshot
23089 .display_point_to_anchor(display_point, Bias::Left);
23090 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
23091 Some(utf16_offset.0)
23092 }
23093}
23094
23095trait SelectionExt {
23096 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
23097 fn spanned_rows(
23098 &self,
23099 include_end_if_at_line_start: bool,
23100 map: &DisplaySnapshot,
23101 ) -> Range<MultiBufferRow>;
23102}
23103
23104impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
23105 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
23106 let start = self
23107 .start
23108 .to_point(&map.buffer_snapshot)
23109 .to_display_point(map);
23110 let end = self
23111 .end
23112 .to_point(&map.buffer_snapshot)
23113 .to_display_point(map);
23114 if self.reversed {
23115 end..start
23116 } else {
23117 start..end
23118 }
23119 }
23120
23121 fn spanned_rows(
23122 &self,
23123 include_end_if_at_line_start: bool,
23124 map: &DisplaySnapshot,
23125 ) -> Range<MultiBufferRow> {
23126 let start = self.start.to_point(&map.buffer_snapshot);
23127 let mut end = self.end.to_point(&map.buffer_snapshot);
23128 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
23129 end.row -= 1;
23130 }
23131
23132 let buffer_start = map.prev_line_boundary(start).0;
23133 let buffer_end = map.next_line_boundary(end).0;
23134 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
23135 }
23136}
23137
23138impl<T: InvalidationRegion> InvalidationStack<T> {
23139 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
23140 where
23141 S: Clone + ToOffset,
23142 {
23143 while let Some(region) = self.last() {
23144 let all_selections_inside_invalidation_ranges =
23145 if selections.len() == region.ranges().len() {
23146 selections
23147 .iter()
23148 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
23149 .all(|(selection, invalidation_range)| {
23150 let head = selection.head().to_offset(buffer);
23151 invalidation_range.start <= head && invalidation_range.end >= head
23152 })
23153 } else {
23154 false
23155 };
23156
23157 if all_selections_inside_invalidation_ranges {
23158 break;
23159 } else {
23160 self.pop();
23161 }
23162 }
23163 }
23164}
23165
23166impl<T> Default for InvalidationStack<T> {
23167 fn default() -> Self {
23168 Self(Default::default())
23169 }
23170}
23171
23172impl<T> Deref for InvalidationStack<T> {
23173 type Target = Vec<T>;
23174
23175 fn deref(&self) -> &Self::Target {
23176 &self.0
23177 }
23178}
23179
23180impl<T> DerefMut for InvalidationStack<T> {
23181 fn deref_mut(&mut self) -> &mut Self::Target {
23182 &mut self.0
23183 }
23184}
23185
23186impl InvalidationRegion for SnippetState {
23187 fn ranges(&self) -> &[Range<Anchor>] {
23188 &self.ranges[self.active_index]
23189 }
23190}
23191
23192fn inline_completion_edit_text(
23193 current_snapshot: &BufferSnapshot,
23194 edits: &[(Range<Anchor>, String)],
23195 edit_preview: &EditPreview,
23196 include_deletions: bool,
23197 cx: &App,
23198) -> HighlightedText {
23199 let edits = edits
23200 .iter()
23201 .map(|(anchor, text)| {
23202 (
23203 anchor.start.text_anchor..anchor.end.text_anchor,
23204 text.clone(),
23205 )
23206 })
23207 .collect::<Vec<_>>();
23208
23209 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
23210}
23211
23212pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
23213 match severity {
23214 lsp::DiagnosticSeverity::ERROR => colors.error,
23215 lsp::DiagnosticSeverity::WARNING => colors.warning,
23216 lsp::DiagnosticSeverity::INFORMATION => colors.info,
23217 lsp::DiagnosticSeverity::HINT => colors.info,
23218 _ => colors.ignored,
23219 }
23220}
23221
23222pub fn styled_runs_for_code_label<'a>(
23223 label: &'a CodeLabel,
23224 syntax_theme: &'a theme::SyntaxTheme,
23225) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
23226 let fade_out = HighlightStyle {
23227 fade_out: Some(0.35),
23228 ..Default::default()
23229 };
23230
23231 let mut prev_end = label.filter_range.end;
23232 label
23233 .runs
23234 .iter()
23235 .enumerate()
23236 .flat_map(move |(ix, (range, highlight_id))| {
23237 let style = if let Some(style) = highlight_id.style(syntax_theme) {
23238 style
23239 } else {
23240 return Default::default();
23241 };
23242 let mut muted_style = style;
23243 muted_style.highlight(fade_out);
23244
23245 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
23246 if range.start >= label.filter_range.end {
23247 if range.start > prev_end {
23248 runs.push((prev_end..range.start, fade_out));
23249 }
23250 runs.push((range.clone(), muted_style));
23251 } else if range.end <= label.filter_range.end {
23252 runs.push((range.clone(), style));
23253 } else {
23254 runs.push((range.start..label.filter_range.end, style));
23255 runs.push((label.filter_range.end..range.end, muted_style));
23256 }
23257 prev_end = cmp::max(prev_end, range.end);
23258
23259 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
23260 runs.push((prev_end..label.text.len(), fade_out));
23261 }
23262
23263 runs
23264 })
23265}
23266
23267pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
23268 let mut prev_index = 0;
23269 let mut prev_codepoint: Option<char> = None;
23270 text.char_indices()
23271 .chain([(text.len(), '\0')])
23272 .filter_map(move |(index, codepoint)| {
23273 let prev_codepoint = prev_codepoint.replace(codepoint)?;
23274 let is_boundary = index == text.len()
23275 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
23276 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
23277 if is_boundary {
23278 let chunk = &text[prev_index..index];
23279 prev_index = index;
23280 Some(chunk)
23281 } else {
23282 None
23283 }
23284 })
23285}
23286
23287pub trait RangeToAnchorExt: Sized {
23288 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
23289
23290 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
23291 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
23292 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
23293 }
23294}
23295
23296impl<T: ToOffset> RangeToAnchorExt for Range<T> {
23297 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
23298 let start_offset = self.start.to_offset(snapshot);
23299 let end_offset = self.end.to_offset(snapshot);
23300 if start_offset == end_offset {
23301 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
23302 } else {
23303 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
23304 }
23305 }
23306}
23307
23308pub trait RowExt {
23309 fn as_f32(&self) -> f32;
23310
23311 fn next_row(&self) -> Self;
23312
23313 fn previous_row(&self) -> Self;
23314
23315 fn minus(&self, other: Self) -> u32;
23316}
23317
23318impl RowExt for DisplayRow {
23319 fn as_f32(&self) -> f32 {
23320 self.0 as f32
23321 }
23322
23323 fn next_row(&self) -> Self {
23324 Self(self.0 + 1)
23325 }
23326
23327 fn previous_row(&self) -> Self {
23328 Self(self.0.saturating_sub(1))
23329 }
23330
23331 fn minus(&self, other: Self) -> u32 {
23332 self.0 - other.0
23333 }
23334}
23335
23336impl RowExt for MultiBufferRow {
23337 fn as_f32(&self) -> f32 {
23338 self.0 as f32
23339 }
23340
23341 fn next_row(&self) -> Self {
23342 Self(self.0 + 1)
23343 }
23344
23345 fn previous_row(&self) -> Self {
23346 Self(self.0.saturating_sub(1))
23347 }
23348
23349 fn minus(&self, other: Self) -> u32 {
23350 self.0 - other.0
23351 }
23352}
23353
23354trait RowRangeExt {
23355 type Row;
23356
23357 fn len(&self) -> usize;
23358
23359 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
23360}
23361
23362impl RowRangeExt for Range<MultiBufferRow> {
23363 type Row = MultiBufferRow;
23364
23365 fn len(&self) -> usize {
23366 (self.end.0 - self.start.0) as usize
23367 }
23368
23369 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
23370 (self.start.0..self.end.0).map(MultiBufferRow)
23371 }
23372}
23373
23374impl RowRangeExt for Range<DisplayRow> {
23375 type Row = DisplayRow;
23376
23377 fn len(&self) -> usize {
23378 (self.end.0 - self.start.0) as usize
23379 }
23380
23381 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
23382 (self.start.0..self.end.0).map(DisplayRow)
23383 }
23384}
23385
23386/// If select range has more than one line, we
23387/// just point the cursor to range.start.
23388fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
23389 if range.start.row == range.end.row {
23390 range
23391 } else {
23392 range.start..range.start
23393 }
23394}
23395pub struct KillRing(ClipboardItem);
23396impl Global for KillRing {}
23397
23398const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
23399
23400enum BreakpointPromptEditAction {
23401 Log,
23402 Condition,
23403 HitCondition,
23404}
23405
23406struct BreakpointPromptEditor {
23407 pub(crate) prompt: Entity<Editor>,
23408 editor: WeakEntity<Editor>,
23409 breakpoint_anchor: Anchor,
23410 breakpoint: Breakpoint,
23411 edit_action: BreakpointPromptEditAction,
23412 block_ids: HashSet<CustomBlockId>,
23413 editor_margins: Arc<Mutex<EditorMargins>>,
23414 _subscriptions: Vec<Subscription>,
23415}
23416
23417impl BreakpointPromptEditor {
23418 const MAX_LINES: u8 = 4;
23419
23420 fn new(
23421 editor: WeakEntity<Editor>,
23422 breakpoint_anchor: Anchor,
23423 breakpoint: Breakpoint,
23424 edit_action: BreakpointPromptEditAction,
23425 window: &mut Window,
23426 cx: &mut Context<Self>,
23427 ) -> Self {
23428 let base_text = match edit_action {
23429 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
23430 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
23431 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
23432 }
23433 .map(|msg| msg.to_string())
23434 .unwrap_or_default();
23435
23436 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
23437 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
23438
23439 let prompt = cx.new(|cx| {
23440 let mut prompt = Editor::new(
23441 EditorMode::AutoHeight {
23442 min_lines: 1,
23443 max_lines: Some(Self::MAX_LINES as usize),
23444 },
23445 buffer,
23446 None,
23447 window,
23448 cx,
23449 );
23450 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
23451 prompt.set_show_cursor_when_unfocused(false, cx);
23452 prompt.set_placeholder_text(
23453 match edit_action {
23454 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
23455 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
23456 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
23457 },
23458 cx,
23459 );
23460
23461 prompt
23462 });
23463
23464 Self {
23465 prompt,
23466 editor,
23467 breakpoint_anchor,
23468 breakpoint,
23469 edit_action,
23470 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
23471 block_ids: Default::default(),
23472 _subscriptions: vec![],
23473 }
23474 }
23475
23476 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
23477 self.block_ids.extend(block_ids)
23478 }
23479
23480 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
23481 if let Some(editor) = self.editor.upgrade() {
23482 let message = self
23483 .prompt
23484 .read(cx)
23485 .buffer
23486 .read(cx)
23487 .as_singleton()
23488 .expect("A multi buffer in breakpoint prompt isn't possible")
23489 .read(cx)
23490 .as_rope()
23491 .to_string();
23492
23493 editor.update(cx, |editor, cx| {
23494 editor.edit_breakpoint_at_anchor(
23495 self.breakpoint_anchor,
23496 self.breakpoint.clone(),
23497 match self.edit_action {
23498 BreakpointPromptEditAction::Log => {
23499 BreakpointEditAction::EditLogMessage(message.into())
23500 }
23501 BreakpointPromptEditAction::Condition => {
23502 BreakpointEditAction::EditCondition(message.into())
23503 }
23504 BreakpointPromptEditAction::HitCondition => {
23505 BreakpointEditAction::EditHitCondition(message.into())
23506 }
23507 },
23508 cx,
23509 );
23510
23511 editor.remove_blocks(self.block_ids.clone(), None, cx);
23512 cx.focus_self(window);
23513 });
23514 }
23515 }
23516
23517 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
23518 self.editor
23519 .update(cx, |editor, cx| {
23520 editor.remove_blocks(self.block_ids.clone(), None, cx);
23521 window.focus(&editor.focus_handle);
23522 })
23523 .log_err();
23524 }
23525
23526 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
23527 let settings = ThemeSettings::get_global(cx);
23528 let text_style = TextStyle {
23529 color: if self.prompt.read(cx).read_only(cx) {
23530 cx.theme().colors().text_disabled
23531 } else {
23532 cx.theme().colors().text
23533 },
23534 font_family: settings.buffer_font.family.clone(),
23535 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23536 font_size: settings.buffer_font_size(cx).into(),
23537 font_weight: settings.buffer_font.weight,
23538 line_height: relative(settings.buffer_line_height.value()),
23539 ..Default::default()
23540 };
23541 EditorElement::new(
23542 &self.prompt,
23543 EditorStyle {
23544 background: cx.theme().colors().editor_background,
23545 local_player: cx.theme().players().local(),
23546 text: text_style,
23547 ..Default::default()
23548 },
23549 )
23550 }
23551}
23552
23553impl Render for BreakpointPromptEditor {
23554 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23555 let editor_margins = *self.editor_margins.lock();
23556 let gutter_dimensions = editor_margins.gutter;
23557 h_flex()
23558 .key_context("Editor")
23559 .bg(cx.theme().colors().editor_background)
23560 .border_y_1()
23561 .border_color(cx.theme().status().info_border)
23562 .size_full()
23563 .py(window.line_height() / 2.5)
23564 .on_action(cx.listener(Self::confirm))
23565 .on_action(cx.listener(Self::cancel))
23566 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
23567 .child(div().flex_1().child(self.render_prompt_editor(cx)))
23568 }
23569}
23570
23571impl Focusable for BreakpointPromptEditor {
23572 fn focus_handle(&self, cx: &App) -> FocusHandle {
23573 self.prompt.focus_handle(cx)
23574 }
23575}
23576
23577fn all_edits_insertions_or_deletions(
23578 edits: &Vec<(Range<Anchor>, String)>,
23579 snapshot: &MultiBufferSnapshot,
23580) -> bool {
23581 let mut all_insertions = true;
23582 let mut all_deletions = true;
23583
23584 for (range, new_text) in edits.iter() {
23585 let range_is_empty = range.to_offset(&snapshot).is_empty();
23586 let text_is_empty = new_text.is_empty();
23587
23588 if range_is_empty != text_is_empty {
23589 if range_is_empty {
23590 all_deletions = false;
23591 } else {
23592 all_insertions = false;
23593 }
23594 } else {
23595 return false;
23596 }
23597
23598 if !all_insertions && !all_deletions {
23599 return false;
23600 }
23601 }
23602 all_insertions || all_deletions
23603}
23604
23605struct MissingEditPredictionKeybindingTooltip;
23606
23607impl Render for MissingEditPredictionKeybindingTooltip {
23608 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23609 ui::tooltip_container(window, cx, |container, _, cx| {
23610 container
23611 .flex_shrink_0()
23612 .max_w_80()
23613 .min_h(rems_from_px(124.))
23614 .justify_between()
23615 .child(
23616 v_flex()
23617 .flex_1()
23618 .text_ui_sm(cx)
23619 .child(Label::new("Conflict with Accept Keybinding"))
23620 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
23621 )
23622 .child(
23623 h_flex()
23624 .pb_1()
23625 .gap_1()
23626 .items_end()
23627 .w_full()
23628 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
23629 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
23630 }))
23631 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
23632 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
23633 })),
23634 )
23635 })
23636 }
23637}
23638
23639#[derive(Debug, Clone, Copy, PartialEq)]
23640pub struct LineHighlight {
23641 pub background: Background,
23642 pub border: Option<gpui::Hsla>,
23643 pub include_gutter: bool,
23644 pub type_id: Option<TypeId>,
23645}
23646
23647struct LineManipulationResult {
23648 pub new_text: String,
23649 pub line_count_before: usize,
23650 pub line_count_after: usize,
23651}
23652
23653fn render_diff_hunk_controls(
23654 row: u32,
23655 status: &DiffHunkStatus,
23656 hunk_range: Range<Anchor>,
23657 is_created_file: bool,
23658 line_height: Pixels,
23659 editor: &Entity<Editor>,
23660 _window: &mut Window,
23661 cx: &mut App,
23662) -> AnyElement {
23663 h_flex()
23664 .h(line_height)
23665 .mr_1()
23666 .gap_1()
23667 .px_0p5()
23668 .pb_1()
23669 .border_x_1()
23670 .border_b_1()
23671 .border_color(cx.theme().colors().border_variant)
23672 .rounded_b_lg()
23673 .bg(cx.theme().colors().editor_background)
23674 .gap_1()
23675 .block_mouse_except_scroll()
23676 .shadow_md()
23677 .child(if status.has_secondary_hunk() {
23678 Button::new(("stage", row as u64), "Stage")
23679 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
23680 .tooltip({
23681 let focus_handle = editor.focus_handle(cx);
23682 move |window, cx| {
23683 Tooltip::for_action_in(
23684 "Stage Hunk",
23685 &::git::ToggleStaged,
23686 &focus_handle,
23687 window,
23688 cx,
23689 )
23690 }
23691 })
23692 .on_click({
23693 let editor = editor.clone();
23694 move |_event, _window, cx| {
23695 editor.update(cx, |editor, cx| {
23696 editor.stage_or_unstage_diff_hunks(
23697 true,
23698 vec![hunk_range.start..hunk_range.start],
23699 cx,
23700 );
23701 });
23702 }
23703 })
23704 } else {
23705 Button::new(("unstage", row as u64), "Unstage")
23706 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
23707 .tooltip({
23708 let focus_handle = editor.focus_handle(cx);
23709 move |window, cx| {
23710 Tooltip::for_action_in(
23711 "Unstage Hunk",
23712 &::git::ToggleStaged,
23713 &focus_handle,
23714 window,
23715 cx,
23716 )
23717 }
23718 })
23719 .on_click({
23720 let editor = editor.clone();
23721 move |_event, _window, cx| {
23722 editor.update(cx, |editor, cx| {
23723 editor.stage_or_unstage_diff_hunks(
23724 false,
23725 vec![hunk_range.start..hunk_range.start],
23726 cx,
23727 );
23728 });
23729 }
23730 })
23731 })
23732 .child(
23733 Button::new(("restore", row as u64), "Restore")
23734 .tooltip({
23735 let focus_handle = editor.focus_handle(cx);
23736 move |window, cx| {
23737 Tooltip::for_action_in(
23738 "Restore Hunk",
23739 &::git::Restore,
23740 &focus_handle,
23741 window,
23742 cx,
23743 )
23744 }
23745 })
23746 .on_click({
23747 let editor = editor.clone();
23748 move |_event, window, cx| {
23749 editor.update(cx, |editor, cx| {
23750 let snapshot = editor.snapshot(window, cx);
23751 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
23752 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
23753 });
23754 }
23755 })
23756 .disabled(is_created_file),
23757 )
23758 .when(
23759 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
23760 |el| {
23761 el.child(
23762 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
23763 .shape(IconButtonShape::Square)
23764 .icon_size(IconSize::Small)
23765 // .disabled(!has_multiple_hunks)
23766 .tooltip({
23767 let focus_handle = editor.focus_handle(cx);
23768 move |window, cx| {
23769 Tooltip::for_action_in(
23770 "Next Hunk",
23771 &GoToHunk,
23772 &focus_handle,
23773 window,
23774 cx,
23775 )
23776 }
23777 })
23778 .on_click({
23779 let editor = editor.clone();
23780 move |_event, window, cx| {
23781 editor.update(cx, |editor, cx| {
23782 let snapshot = editor.snapshot(window, cx);
23783 let position =
23784 hunk_range.end.to_point(&snapshot.buffer_snapshot);
23785 editor.go_to_hunk_before_or_after_position(
23786 &snapshot,
23787 position,
23788 Direction::Next,
23789 window,
23790 cx,
23791 );
23792 editor.expand_selected_diff_hunks(cx);
23793 });
23794 }
23795 }),
23796 )
23797 .child(
23798 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
23799 .shape(IconButtonShape::Square)
23800 .icon_size(IconSize::Small)
23801 // .disabled(!has_multiple_hunks)
23802 .tooltip({
23803 let focus_handle = editor.focus_handle(cx);
23804 move |window, cx| {
23805 Tooltip::for_action_in(
23806 "Previous Hunk",
23807 &GoToPreviousHunk,
23808 &focus_handle,
23809 window,
23810 cx,
23811 )
23812 }
23813 })
23814 .on_click({
23815 let editor = editor.clone();
23816 move |_event, window, cx| {
23817 editor.update(cx, |editor, cx| {
23818 let snapshot = editor.snapshot(window, cx);
23819 let point =
23820 hunk_range.start.to_point(&snapshot.buffer_snapshot);
23821 editor.go_to_hunk_before_or_after_position(
23822 &snapshot,
23823 point,
23824 Direction::Prev,
23825 window,
23826 cx,
23827 );
23828 editor.expand_selected_diff_hunks(cx);
23829 });
23830 }
23831 }),
23832 )
23833 },
23834 )
23835 .into_any_element()
23836}