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 edit_prediction_tests;
47#[cfg(test)]
48mod editor_tests;
49mod signature_help;
50#[cfg(any(test, feature = "test-support"))]
51pub mod test;
52
53pub(crate) use actions::*;
54pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
55pub use edit_prediction::Direction;
56pub use editor_settings::{
57 CurrentLineHighlight, DocumentColorsRenderMode, EditorSettings, HideMouseMode,
58 ScrollBeyondLastLine, ScrollbarAxes, SearchSettings, ShowMinimap, ShowScrollbar,
59};
60pub use editor_settings_controls::*;
61pub use element::{
62 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
63};
64pub use git::blame::BlameRenderer;
65pub use hover_popover::hover_markdown_style;
66pub use items::MAX_TAB_TITLE_LEN;
67pub use lsp::CompletionContext;
68pub use lsp_ext::lsp_tasks;
69pub use multi_buffer::{
70 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, PathKey,
71 RowInfo, ToOffset, ToPoint,
72};
73pub use proposed_changes_editor::{
74 ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
75};
76pub use text::Bias;
77
78use ::git::{
79 Restore,
80 blame::{BlameEntry, ParsedCommitMessage},
81};
82use aho_corasick::AhoCorasick;
83use anyhow::{Context as _, Result, anyhow};
84use blink_manager::BlinkManager;
85use buffer_diff::DiffHunkStatus;
86use client::{Collaborator, ParticipantIndex};
87use clock::{AGENT_REPLICA_ID, ReplicaId};
88use code_context_menus::{
89 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
90 CompletionsMenu, ContextMenuOrigin,
91};
92use collections::{BTreeMap, HashMap, HashSet, VecDeque};
93use convert_case::{Case, Casing};
94use dap::TelemetrySpawnLocation;
95use display_map::*;
96use edit_prediction::{EditPredictionProvider, EditPredictionProviderHandle};
97use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
98use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
99use futures::{
100 FutureExt, StreamExt as _,
101 future::{self, Shared, join},
102 stream::FuturesUnordered,
103};
104use fuzzy::{StringMatch, StringMatchCandidate};
105use git::blame::{GitBlame, GlobalBlameRenderer};
106use gpui::{
107 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
108 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
109 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
110 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
111 MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render, ScrollHandle,
112 SharedString, Size, Stateful, Styled, Subscription, Task, TextStyle, TextStyleRefinement,
113 UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
114 div, point, prelude::*, pulsating_between, px, relative, size,
115};
116use highlight_matching_bracket::refresh_matching_bracket_highlights;
117use hover_links::{HoverLink, HoveredLinkState, InlayHighlight, find_file};
118use hover_popover::{HoverState, hide_hover};
119use indent_guides::ActiveIndentGuidesState;
120use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
121use itertools::Itertools;
122use language::{
123 AutoindentMode, BlockCommentConfig, BracketMatch, BracketPair, Buffer, BufferRow,
124 BufferSnapshot, Capability, CharClassifier, CharKind, CodeLabel, CursorShape, DiagnosticEntry,
125 DiffOptions, EditPredictionsMode, EditPreview, HighlightedText, IndentKind, IndentSize,
126 Language, OffsetRangeExt, Point, Runnable, RunnableRange, Selection, SelectionGoal, TextObject,
127 TransactionId, TreeSitterOptions, WordsQuery,
128 language_settings::{
129 self, InlayHintSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
130 all_language_settings, language_settings,
131 },
132 point_from_lsp, point_to_lsp, text_diff_with_options,
133};
134use linked_editing_ranges::refresh_linked_ranges;
135use lsp::{
136 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
137 LanguageServerId,
138};
139use lsp_colors::LspColorData;
140use markdown::Markdown;
141use mouse_context_menu::MouseContextMenu;
142use movement::TextLayoutDetails;
143use multi_buffer::{
144 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
145 MultiOrSingleBufferOffsetRange, ToOffsetUtf16,
146};
147use parking_lot::Mutex;
148use persistence::DB;
149use project::{
150 BreakpointWithPosition, CodeAction, Completion, CompletionIntent, CompletionResponse,
151 CompletionSource, DisableAiSettings, DocumentHighlight, InlayHint, Location, LocationLink,
152 PrepareRenameResponse, Project, ProjectItem, ProjectPath, ProjectTransaction, TaskSourceKind,
153 debugger::breakpoint_store::Breakpoint,
154 debugger::{
155 breakpoint_store::{
156 BreakpointEditAction, BreakpointSessionState, BreakpointState, BreakpointStore,
157 BreakpointStoreEvent,
158 },
159 session::{Session, SessionEvent},
160 },
161 git_store::{GitStoreEvent, RepositoryEvent},
162 lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
163 project_settings::{DiagnosticSeverity, GoToDiagnosticSeverityFilter},
164 project_settings::{GitGutterSetting, ProjectSettings},
165};
166use rand::{seq::SliceRandom, thread_rng};
167use rpc::{ErrorCode, ErrorExt, proto::PeerId};
168use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide};
169use selections_collection::{
170 MutableSelectionsCollection, SelectionsCollection, resolve_selections,
171};
172use serde::{Deserialize, Serialize};
173use settings::{Settings, SettingsLocation, SettingsStore, update_settings_file};
174use smallvec::{SmallVec, smallvec};
175use snippet::Snippet;
176use std::{
177 any::TypeId,
178 borrow::Cow,
179 cell::OnceCell,
180 cell::RefCell,
181 cmp::{self, Ordering, Reverse},
182 iter::Peekable,
183 mem,
184 num::NonZeroU32,
185 ops::Not,
186 ops::{ControlFlow, Deref, DerefMut, Range, RangeInclusive},
187 path::{Path, PathBuf},
188 rc::Rc,
189 sync::Arc,
190 time::{Duration, Instant},
191};
192use sum_tree::TreeMap;
193use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
194use text::{BufferId, FromAnchor, OffsetUtf16, Rope};
195use theme::{
196 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, Theme, ThemeSettings,
197 observe_buffer_font_size_adjustment,
198};
199use ui::{
200 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
201 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*,
202};
203use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
204use workspace::{
205 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
206 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
207 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
208 item::{ItemHandle, PreviewTabsSettings, SaveOptions},
209 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
210 searchable::SearchEvent,
211};
212
213use crate::{
214 code_context_menus::CompletionsMenuSource,
215 editor_settings::MultiCursorModifier,
216 hover_links::{find_url, find_url_from_range},
217 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
218};
219
220pub const FILE_HEADER_HEIGHT: u32 = 2;
221pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
222pub const DEFAULT_MULTIBUFFER_CONTEXT: u32 = 2;
223const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
224const MAX_LINE_LEN: usize = 1024;
225const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
226const MAX_SELECTION_HISTORY_LEN: usize = 1024;
227pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
228#[doc(hidden)]
229pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
230const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
231
232pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
233pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
234pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
235
236pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
237pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
238pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
239
240pub type RenderDiffHunkControlsFn = Arc<
241 dyn Fn(
242 u32,
243 &DiffHunkStatus,
244 Range<Anchor>,
245 bool,
246 Pixels,
247 &Entity<Editor>,
248 &mut Window,
249 &mut App,
250 ) -> AnyElement,
251>;
252
253struct InlineValueCache {
254 enabled: bool,
255 inlays: Vec<InlayId>,
256 refresh_task: Task<Option<()>>,
257}
258
259impl InlineValueCache {
260 fn new(enabled: bool) -> Self {
261 Self {
262 enabled,
263 inlays: Vec::new(),
264 refresh_task: Task::ready(None),
265 }
266 }
267}
268
269#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
270pub enum InlayId {
271 EditPrediction(usize),
272 DebuggerValue(usize),
273 // LSP
274 Hint(usize),
275 Color(usize),
276}
277
278impl InlayId {
279 fn id(&self) -> usize {
280 match self {
281 Self::EditPrediction(id) => *id,
282 Self::DebuggerValue(id) => *id,
283 Self::Hint(id) => *id,
284 Self::Color(id) => *id,
285 }
286 }
287}
288
289pub enum ActiveDebugLine {}
290pub enum DebugStackFrameLine {}
291enum DocumentHighlightRead {}
292enum DocumentHighlightWrite {}
293enum InputComposition {}
294pub enum PendingInput {}
295enum SelectedTextHighlight {}
296
297pub enum ConflictsOuter {}
298pub enum ConflictsOurs {}
299pub enum ConflictsTheirs {}
300pub enum ConflictsOursMarker {}
301pub enum ConflictsTheirsMarker {}
302
303#[derive(Debug, Copy, Clone, PartialEq, Eq)]
304pub enum Navigated {
305 Yes,
306 No,
307}
308
309impl Navigated {
310 pub fn from_bool(yes: bool) -> Navigated {
311 if yes { Navigated::Yes } else { Navigated::No }
312 }
313}
314
315#[derive(Debug, Clone, PartialEq, Eq)]
316enum DisplayDiffHunk {
317 Folded {
318 display_row: DisplayRow,
319 },
320 Unfolded {
321 is_created_file: bool,
322 diff_base_byte_range: Range<usize>,
323 display_row_range: Range<DisplayRow>,
324 multi_buffer_range: Range<Anchor>,
325 status: DiffHunkStatus,
326 },
327}
328
329pub enum HideMouseCursorOrigin {
330 TypingAction,
331 MovementAction,
332}
333
334pub fn init_settings(cx: &mut App) {
335 EditorSettings::register(cx);
336}
337
338pub fn init(cx: &mut App) {
339 init_settings(cx);
340
341 cx.set_global(GlobalBlameRenderer(Arc::new(())));
342
343 workspace::register_project_item::<Editor>(cx);
344 workspace::FollowableViewRegistry::register::<Editor>(cx);
345 workspace::register_serializable_item::<Editor>(cx);
346
347 cx.observe_new(
348 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
349 workspace.register_action(Editor::new_file);
350 workspace.register_action(Editor::new_file_vertical);
351 workspace.register_action(Editor::new_file_horizontal);
352 workspace.register_action(Editor::cancel_language_server_work);
353 workspace.register_action(Editor::toggle_focus);
354 },
355 )
356 .detach();
357
358 cx.on_action(move |_: &workspace::NewFile, cx| {
359 let app_state = workspace::AppState::global(cx);
360 if let Some(app_state) = app_state.upgrade() {
361 workspace::open_new(
362 Default::default(),
363 app_state,
364 cx,
365 |workspace, window, cx| {
366 Editor::new_file(workspace, &Default::default(), window, cx)
367 },
368 )
369 .detach();
370 }
371 });
372 cx.on_action(move |_: &workspace::NewWindow, cx| {
373 let app_state = workspace::AppState::global(cx);
374 if let Some(app_state) = app_state.upgrade() {
375 workspace::open_new(
376 Default::default(),
377 app_state,
378 cx,
379 |workspace, window, cx| {
380 cx.activate(true);
381 Editor::new_file(workspace, &Default::default(), window, cx)
382 },
383 )
384 .detach();
385 }
386 });
387}
388
389pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
390 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
391}
392
393pub trait DiagnosticRenderer {
394 fn render_group(
395 &self,
396 diagnostic_group: Vec<DiagnosticEntry<Point>>,
397 buffer_id: BufferId,
398 snapshot: EditorSnapshot,
399 editor: WeakEntity<Editor>,
400 cx: &mut App,
401 ) -> Vec<BlockProperties<Anchor>>;
402
403 fn render_hover(
404 &self,
405 diagnostic_group: Vec<DiagnosticEntry<Point>>,
406 range: Range<Point>,
407 buffer_id: BufferId,
408 cx: &mut App,
409 ) -> Option<Entity<markdown::Markdown>>;
410
411 fn open_link(
412 &self,
413 editor: &mut Editor,
414 link: SharedString,
415 window: &mut Window,
416 cx: &mut Context<Editor>,
417 );
418}
419
420pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
421
422impl GlobalDiagnosticRenderer {
423 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
424 cx.try_global::<Self>().map(|g| g.0.clone())
425 }
426}
427
428impl gpui::Global for GlobalDiagnosticRenderer {}
429pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
430 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
431}
432
433pub struct SearchWithinRange;
434
435trait InvalidationRegion {
436 fn ranges(&self) -> &[Range<Anchor>];
437}
438
439#[derive(Clone, Debug, PartialEq)]
440pub enum SelectPhase {
441 Begin {
442 position: DisplayPoint,
443 add: bool,
444 click_count: usize,
445 },
446 BeginColumnar {
447 position: DisplayPoint,
448 reset: bool,
449 mode: ColumnarMode,
450 goal_column: u32,
451 },
452 Extend {
453 position: DisplayPoint,
454 click_count: usize,
455 },
456 Update {
457 position: DisplayPoint,
458 goal_column: u32,
459 scroll_delta: gpui::Point<f32>,
460 },
461 End,
462}
463
464#[derive(Clone, Debug, PartialEq)]
465pub enum ColumnarMode {
466 FromMouse,
467 FromSelection,
468}
469
470#[derive(Clone, Debug)]
471pub enum SelectMode {
472 Character,
473 Word(Range<Anchor>),
474 Line(Range<Anchor>),
475 All,
476}
477
478#[derive(Clone, PartialEq, Eq, Debug)]
479pub enum EditorMode {
480 SingleLine,
481 AutoHeight {
482 min_lines: usize,
483 max_lines: Option<usize>,
484 },
485 Full {
486 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
487 scale_ui_elements_with_buffer_font_size: bool,
488 /// When set to `true`, the editor will render a background for the active line.
489 show_active_line_background: bool,
490 /// When set to `true`, the editor's height will be determined by its content.
491 sized_by_content: bool,
492 },
493 Minimap {
494 parent: WeakEntity<Editor>,
495 },
496}
497
498impl EditorMode {
499 pub fn full() -> Self {
500 Self::Full {
501 scale_ui_elements_with_buffer_font_size: true,
502 show_active_line_background: true,
503 sized_by_content: false,
504 }
505 }
506
507 #[inline]
508 pub fn is_full(&self) -> bool {
509 matches!(self, Self::Full { .. })
510 }
511
512 #[inline]
513 pub fn is_single_line(&self) -> bool {
514 matches!(self, Self::SingleLine { .. })
515 }
516
517 #[inline]
518 fn is_minimap(&self) -> bool {
519 matches!(self, Self::Minimap { .. })
520 }
521}
522
523#[derive(Copy, Clone, Debug)]
524pub enum SoftWrap {
525 /// Prefer not to wrap at all.
526 ///
527 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
528 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
529 GitDiff,
530 /// Prefer a single line generally, unless an overly long line is encountered.
531 None,
532 /// Soft wrap lines that exceed the editor width.
533 EditorWidth,
534 /// Soft wrap lines at the preferred line length.
535 Column(u32),
536 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
537 Bounded(u32),
538}
539
540#[derive(Clone)]
541pub struct EditorStyle {
542 pub background: Hsla,
543 pub border: Hsla,
544 pub local_player: PlayerColor,
545 pub text: TextStyle,
546 pub scrollbar_width: Pixels,
547 pub syntax: Arc<SyntaxTheme>,
548 pub status: StatusColors,
549 pub inlay_hints_style: HighlightStyle,
550 pub edit_prediction_styles: EditPredictionStyles,
551 pub unnecessary_code_fade: f32,
552 pub show_underlines: bool,
553}
554
555impl Default for EditorStyle {
556 fn default() -> Self {
557 Self {
558 background: Hsla::default(),
559 border: Hsla::default(),
560 local_player: PlayerColor::default(),
561 text: TextStyle::default(),
562 scrollbar_width: Pixels::default(),
563 syntax: Default::default(),
564 // HACK: Status colors don't have a real default.
565 // We should look into removing the status colors from the editor
566 // style and retrieve them directly from the theme.
567 status: StatusColors::dark(),
568 inlay_hints_style: HighlightStyle::default(),
569 edit_prediction_styles: EditPredictionStyles {
570 insertion: HighlightStyle::default(),
571 whitespace: HighlightStyle::default(),
572 },
573 unnecessary_code_fade: Default::default(),
574 show_underlines: true,
575 }
576 }
577}
578
579pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
580 let show_background = language_settings::language_settings(None, None, cx)
581 .inlay_hints
582 .show_background;
583
584 HighlightStyle {
585 color: Some(cx.theme().status().hint),
586 background_color: show_background.then(|| cx.theme().status().hint_background),
587 ..HighlightStyle::default()
588 }
589}
590
591pub fn make_suggestion_styles(cx: &mut App) -> EditPredictionStyles {
592 EditPredictionStyles {
593 insertion: HighlightStyle {
594 color: Some(cx.theme().status().predictive),
595 ..HighlightStyle::default()
596 },
597 whitespace: HighlightStyle {
598 background_color: Some(cx.theme().status().created_background),
599 ..HighlightStyle::default()
600 },
601 }
602}
603
604type CompletionId = usize;
605
606pub(crate) enum EditDisplayMode {
607 TabAccept,
608 DiffPopover,
609 Inline,
610}
611
612enum EditPrediction {
613 Edit {
614 edits: Vec<(Range<Anchor>, String)>,
615 edit_preview: Option<EditPreview>,
616 display_mode: EditDisplayMode,
617 snapshot: BufferSnapshot,
618 },
619 Move {
620 target: Anchor,
621 snapshot: BufferSnapshot,
622 },
623}
624
625struct EditPredictionState {
626 inlay_ids: Vec<InlayId>,
627 completion: EditPrediction,
628 completion_id: Option<SharedString>,
629 invalidation_range: Range<Anchor>,
630}
631
632enum EditPredictionSettings {
633 Disabled,
634 Enabled {
635 show_in_menu: bool,
636 preview_requires_modifier: bool,
637 },
638}
639
640enum EditPredictionHighlight {}
641
642#[derive(Debug, Clone)]
643struct InlineDiagnostic {
644 message: SharedString,
645 group_id: usize,
646 is_primary: bool,
647 start: Point,
648 severity: lsp::DiagnosticSeverity,
649}
650
651pub enum MenuEditPredictionsPolicy {
652 Never,
653 ByProvider,
654}
655
656pub enum EditPredictionPreview {
657 /// Modifier is not pressed
658 Inactive { released_too_fast: bool },
659 /// Modifier pressed
660 Active {
661 since: Instant,
662 previous_scroll_position: Option<ScrollAnchor>,
663 },
664}
665
666impl EditPredictionPreview {
667 pub fn released_too_fast(&self) -> bool {
668 match self {
669 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
670 EditPredictionPreview::Active { .. } => false,
671 }
672 }
673
674 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
675 if let EditPredictionPreview::Active {
676 previous_scroll_position,
677 ..
678 } = self
679 {
680 *previous_scroll_position = scroll_position;
681 }
682 }
683}
684
685pub struct ContextMenuOptions {
686 pub min_entries_visible: usize,
687 pub max_entries_visible: usize,
688 pub placement: Option<ContextMenuPlacement>,
689}
690
691#[derive(Debug, Clone, PartialEq, Eq)]
692pub enum ContextMenuPlacement {
693 Above,
694 Below,
695}
696
697#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
698struct EditorActionId(usize);
699
700impl EditorActionId {
701 pub fn post_inc(&mut self) -> Self {
702 let answer = self.0;
703
704 *self = Self(answer + 1);
705
706 Self(answer)
707 }
708}
709
710// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
711// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
712
713type BackgroundHighlight = (fn(&Theme) -> Hsla, Arc<[Range<Anchor>]>);
714type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
715
716#[derive(Default)]
717struct ScrollbarMarkerState {
718 scrollbar_size: Size<Pixels>,
719 dirty: bool,
720 markers: Arc<[PaintQuad]>,
721 pending_refresh: Option<Task<Result<()>>>,
722}
723
724impl ScrollbarMarkerState {
725 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
726 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
727 }
728}
729
730#[derive(Clone, Copy, PartialEq, Eq)]
731pub enum MinimapVisibility {
732 Disabled,
733 Enabled {
734 /// The configuration currently present in the users settings.
735 setting_configuration: bool,
736 /// Whether to override the currently set visibility from the users setting.
737 toggle_override: bool,
738 },
739}
740
741impl MinimapVisibility {
742 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
743 if mode.is_full() {
744 Self::Enabled {
745 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
746 toggle_override: false,
747 }
748 } else {
749 Self::Disabled
750 }
751 }
752
753 fn hidden(&self) -> Self {
754 match *self {
755 Self::Enabled {
756 setting_configuration,
757 ..
758 } => Self::Enabled {
759 setting_configuration,
760 toggle_override: setting_configuration,
761 },
762 Self::Disabled => Self::Disabled,
763 }
764 }
765
766 fn disabled(&self) -> bool {
767 match *self {
768 Self::Disabled => true,
769 _ => false,
770 }
771 }
772
773 fn settings_visibility(&self) -> bool {
774 match *self {
775 Self::Enabled {
776 setting_configuration,
777 ..
778 } => setting_configuration,
779 _ => false,
780 }
781 }
782
783 fn visible(&self) -> bool {
784 match *self {
785 Self::Enabled {
786 setting_configuration,
787 toggle_override,
788 } => setting_configuration ^ toggle_override,
789 _ => false,
790 }
791 }
792
793 fn toggle_visibility(&self) -> Self {
794 match *self {
795 Self::Enabled {
796 toggle_override,
797 setting_configuration,
798 } => Self::Enabled {
799 setting_configuration,
800 toggle_override: !toggle_override,
801 },
802 Self::Disabled => Self::Disabled,
803 }
804 }
805}
806
807#[derive(Clone, Debug)]
808struct RunnableTasks {
809 templates: Vec<(TaskSourceKind, TaskTemplate)>,
810 offset: multi_buffer::Anchor,
811 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
812 column: u32,
813 // Values of all named captures, including those starting with '_'
814 extra_variables: HashMap<String, String>,
815 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
816 context_range: Range<BufferOffset>,
817}
818
819impl RunnableTasks {
820 fn resolve<'a>(
821 &'a self,
822 cx: &'a task::TaskContext,
823 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
824 self.templates.iter().filter_map(|(kind, template)| {
825 template
826 .resolve_task(&kind.to_id_base(), cx)
827 .map(|task| (kind.clone(), task))
828 })
829 }
830}
831
832#[derive(Clone)]
833pub struct ResolvedTasks {
834 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
835 position: Anchor,
836}
837
838#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
839struct BufferOffset(usize);
840
841// Addons allow storing per-editor state in other crates (e.g. Vim)
842pub trait Addon: 'static {
843 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
844
845 fn render_buffer_header_controls(
846 &self,
847 _: &ExcerptInfo,
848 _: &Window,
849 _: &App,
850 ) -> Option<AnyElement> {
851 None
852 }
853
854 fn to_any(&self) -> &dyn std::any::Any;
855
856 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
857 None
858 }
859}
860
861struct ChangeLocation {
862 current: Option<Vec<Anchor>>,
863 original: Vec<Anchor>,
864}
865impl ChangeLocation {
866 fn locations(&self) -> &[Anchor] {
867 self.current.as_ref().unwrap_or(&self.original)
868 }
869}
870
871/// A set of caret positions, registered when the editor was edited.
872pub struct ChangeList {
873 changes: Vec<ChangeLocation>,
874 /// Currently "selected" change.
875 position: Option<usize>,
876}
877
878impl ChangeList {
879 pub fn new() -> Self {
880 Self {
881 changes: Vec::new(),
882 position: None,
883 }
884 }
885
886 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
887 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
888 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
889 if self.changes.is_empty() {
890 return None;
891 }
892
893 let prev = self.position.unwrap_or(self.changes.len());
894 let next = if direction == Direction::Prev {
895 prev.saturating_sub(count)
896 } else {
897 (prev + count).min(self.changes.len() - 1)
898 };
899 self.position = Some(next);
900 self.changes.get(next).map(|change| change.locations())
901 }
902
903 /// Adds a new change to the list, resetting the change list position.
904 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
905 self.position.take();
906 if let Some(last) = self.changes.last_mut()
907 && group
908 {
909 last.current = Some(new_positions)
910 } else {
911 self.changes.push(ChangeLocation {
912 original: new_positions,
913 current: None,
914 });
915 }
916 }
917
918 pub fn last(&self) -> Option<&[Anchor]> {
919 self.changes.last().map(|change| change.locations())
920 }
921
922 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
923 self.changes.last().map(|change| change.original.as_slice())
924 }
925
926 pub fn invert_last_group(&mut self) {
927 if let Some(last) = self.changes.last_mut() {
928 if let Some(current) = last.current.as_mut() {
929 mem::swap(&mut last.original, current);
930 }
931 }
932 }
933}
934
935#[derive(Clone)]
936struct InlineBlamePopoverState {
937 scroll_handle: ScrollHandle,
938 commit_message: Option<ParsedCommitMessage>,
939 markdown: Entity<Markdown>,
940}
941
942struct InlineBlamePopover {
943 position: gpui::Point<Pixels>,
944 hide_task: Option<Task<()>>,
945 popover_bounds: Option<Bounds<Pixels>>,
946 popover_state: InlineBlamePopoverState,
947 keyboard_grace: bool,
948}
949
950enum SelectionDragState {
951 /// State when no drag related activity is detected.
952 None,
953 /// State when the mouse is down on a selection that is about to be dragged.
954 ReadyToDrag {
955 selection: Selection<Anchor>,
956 click_position: gpui::Point<Pixels>,
957 mouse_down_time: Instant,
958 },
959 /// State when the mouse is dragging the selection in the editor.
960 Dragging {
961 selection: Selection<Anchor>,
962 drop_cursor: Selection<Anchor>,
963 hide_drop_cursor: bool,
964 },
965}
966
967enum ColumnarSelectionState {
968 FromMouse {
969 selection_tail: Anchor,
970 display_point: Option<DisplayPoint>,
971 },
972 FromSelection {
973 selection_tail: Anchor,
974 },
975}
976
977/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
978/// a breakpoint on them.
979#[derive(Clone, Copy, Debug, PartialEq, Eq)]
980struct PhantomBreakpointIndicator {
981 display_row: DisplayRow,
982 /// There's a small debounce between hovering over the line and showing the indicator.
983 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
984 is_active: bool,
985 collides_with_existing_breakpoint: bool,
986}
987
988/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
989///
990/// See the [module level documentation](self) for more information.
991pub struct Editor {
992 focus_handle: FocusHandle,
993 last_focused_descendant: Option<WeakFocusHandle>,
994 /// The text buffer being edited
995 buffer: Entity<MultiBuffer>,
996 /// Map of how text in the buffer should be displayed.
997 /// Handles soft wraps, folds, fake inlay text insertions, etc.
998 pub display_map: Entity<DisplayMap>,
999 pub selections: SelectionsCollection,
1000 pub scroll_manager: ScrollManager,
1001 /// When inline assist editors are linked, they all render cursors because
1002 /// typing enters text into each of them, even the ones that aren't focused.
1003 pub(crate) show_cursor_when_unfocused: bool,
1004 columnar_selection_state: Option<ColumnarSelectionState>,
1005 add_selections_state: Option<AddSelectionsState>,
1006 select_next_state: Option<SelectNextState>,
1007 select_prev_state: Option<SelectNextState>,
1008 selection_history: SelectionHistory,
1009 defer_selection_effects: bool,
1010 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1011 autoclose_regions: Vec<AutocloseRegion>,
1012 snippet_stack: InvalidationStack<SnippetState>,
1013 select_syntax_node_history: SelectSyntaxNodeHistory,
1014 ime_transaction: Option<TransactionId>,
1015 pub diagnostics_max_severity: DiagnosticSeverity,
1016 active_diagnostics: ActiveDiagnostic,
1017 show_inline_diagnostics: bool,
1018 inline_diagnostics_update: Task<()>,
1019 inline_diagnostics_enabled: bool,
1020 diagnostics_enabled: bool,
1021 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1022 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1023 hard_wrap: Option<usize>,
1024
1025 // TODO: make this a access method
1026 pub project: Option<Entity<Project>>,
1027 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1028 completion_provider: Option<Rc<dyn CompletionProvider>>,
1029 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1030 blink_manager: Entity<BlinkManager>,
1031 show_cursor_names: bool,
1032 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1033 pub show_local_selections: bool,
1034 mode: EditorMode,
1035 show_breadcrumbs: bool,
1036 show_gutter: bool,
1037 show_scrollbars: ScrollbarAxes,
1038 minimap_visibility: MinimapVisibility,
1039 offset_content: bool,
1040 disable_expand_excerpt_buttons: bool,
1041 show_line_numbers: Option<bool>,
1042 use_relative_line_numbers: Option<bool>,
1043 show_git_diff_gutter: Option<bool>,
1044 show_code_actions: Option<bool>,
1045 show_runnables: Option<bool>,
1046 show_breakpoints: Option<bool>,
1047 show_wrap_guides: Option<bool>,
1048 show_indent_guides: Option<bool>,
1049 placeholder_text: Option<Arc<str>>,
1050 highlight_order: usize,
1051 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1052 background_highlights: TreeMap<HighlightKey, BackgroundHighlight>,
1053 gutter_highlights: TreeMap<TypeId, GutterHighlight>,
1054 scrollbar_marker_state: ScrollbarMarkerState,
1055 active_indent_guides_state: ActiveIndentGuidesState,
1056 nav_history: Option<ItemNavHistory>,
1057 context_menu: RefCell<Option<CodeContextMenu>>,
1058 context_menu_options: Option<ContextMenuOptions>,
1059 mouse_context_menu: Option<MouseContextMenu>,
1060 completion_tasks: Vec<(CompletionId, Task<()>)>,
1061 inline_blame_popover: Option<InlineBlamePopover>,
1062 inline_blame_popover_show_task: Option<Task<()>>,
1063 signature_help_state: SignatureHelpState,
1064 auto_signature_help: Option<bool>,
1065 find_all_references_task_sources: Vec<Anchor>,
1066 next_completion_id: CompletionId,
1067 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1068 code_actions_task: Option<Task<Result<()>>>,
1069 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1070 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1071 document_highlights_task: Option<Task<()>>,
1072 linked_editing_range_task: Option<Task<Option<()>>>,
1073 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1074 pending_rename: Option<RenameState>,
1075 searchable: bool,
1076 cursor_shape: CursorShape,
1077 current_line_highlight: Option<CurrentLineHighlight>,
1078 collapse_matches: bool,
1079 autoindent_mode: Option<AutoindentMode>,
1080 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1081 input_enabled: bool,
1082 use_modal_editing: bool,
1083 read_only: bool,
1084 leader_id: Option<CollaboratorId>,
1085 remote_id: Option<ViewId>,
1086 pub hover_state: HoverState,
1087 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1088 gutter_hovered: bool,
1089 hovered_link_state: Option<HoveredLinkState>,
1090 edit_prediction_provider: Option<RegisteredEditPredictionProvider>,
1091 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1092 active_edit_prediction: Option<EditPredictionState>,
1093 /// Used to prevent flickering as the user types while the menu is open
1094 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1095 edit_prediction_settings: EditPredictionSettings,
1096 edit_predictions_hidden_for_vim_mode: bool,
1097 show_edit_predictions_override: Option<bool>,
1098 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1099 edit_prediction_preview: EditPredictionPreview,
1100 edit_prediction_indent_conflict: bool,
1101 edit_prediction_requires_modifier_in_indent_conflict: bool,
1102 inlay_hint_cache: InlayHintCache,
1103 next_inlay_id: usize,
1104 _subscriptions: Vec<Subscription>,
1105 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1106 gutter_dimensions: GutterDimensions,
1107 style: Option<EditorStyle>,
1108 text_style_refinement: Option<TextStyleRefinement>,
1109 next_editor_action_id: EditorActionId,
1110 editor_actions: Rc<
1111 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1112 >,
1113 use_autoclose: bool,
1114 use_auto_surround: bool,
1115 auto_replace_emoji_shortcode: bool,
1116 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1117 show_git_blame_gutter: bool,
1118 show_git_blame_inline: bool,
1119 show_git_blame_inline_delay_task: Option<Task<()>>,
1120 git_blame_inline_enabled: bool,
1121 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1122 serialize_dirty_buffers: bool,
1123 show_selection_menu: Option<bool>,
1124 blame: Option<Entity<GitBlame>>,
1125 blame_subscription: Option<Subscription>,
1126 custom_context_menu: Option<
1127 Box<
1128 dyn 'static
1129 + Fn(
1130 &mut Self,
1131 DisplayPoint,
1132 &mut Window,
1133 &mut Context<Self>,
1134 ) -> Option<Entity<ui::ContextMenu>>,
1135 >,
1136 >,
1137 last_bounds: Option<Bounds<Pixels>>,
1138 last_position_map: Option<Rc<PositionMap>>,
1139 expect_bounds_change: Option<Bounds<Pixels>>,
1140 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1141 tasks_update_task: Option<Task<()>>,
1142 breakpoint_store: Option<Entity<BreakpointStore>>,
1143 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1144 hovered_diff_hunk_row: Option<DisplayRow>,
1145 pull_diagnostics_task: Task<()>,
1146 in_project_search: bool,
1147 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1148 breadcrumb_header: Option<String>,
1149 focused_block: Option<FocusedBlock>,
1150 next_scroll_position: NextScrollCursorCenterTopBottom,
1151 addons: HashMap<TypeId, Box<dyn Addon>>,
1152 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1153 load_diff_task: Option<Shared<Task<()>>>,
1154 /// Whether we are temporarily displaying a diff other than git's
1155 temporary_diff_override: bool,
1156 selection_mark_mode: bool,
1157 toggle_fold_multiple_buffers: Task<()>,
1158 _scroll_cursor_center_top_bottom_task: Task<()>,
1159 serialize_selections: Task<()>,
1160 serialize_folds: Task<()>,
1161 mouse_cursor_hidden: bool,
1162 minimap: Option<Entity<Self>>,
1163 hide_mouse_mode: HideMouseMode,
1164 pub change_list: ChangeList,
1165 inline_value_cache: InlineValueCache,
1166 selection_drag_state: SelectionDragState,
1167 next_color_inlay_id: usize,
1168 colors: Option<LspColorData>,
1169 folding_newlines: Task<()>,
1170}
1171
1172#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1173enum NextScrollCursorCenterTopBottom {
1174 #[default]
1175 Center,
1176 Top,
1177 Bottom,
1178}
1179
1180impl NextScrollCursorCenterTopBottom {
1181 fn next(&self) -> Self {
1182 match self {
1183 Self::Center => Self::Top,
1184 Self::Top => Self::Bottom,
1185 Self::Bottom => Self::Center,
1186 }
1187 }
1188}
1189
1190#[derive(Clone)]
1191pub struct EditorSnapshot {
1192 pub mode: EditorMode,
1193 show_gutter: bool,
1194 show_line_numbers: Option<bool>,
1195 show_git_diff_gutter: Option<bool>,
1196 show_code_actions: Option<bool>,
1197 show_runnables: Option<bool>,
1198 show_breakpoints: Option<bool>,
1199 git_blame_gutter_max_author_length: Option<usize>,
1200 pub display_snapshot: DisplaySnapshot,
1201 pub placeholder_text: Option<Arc<str>>,
1202 is_focused: bool,
1203 scroll_anchor: ScrollAnchor,
1204 ongoing_scroll: OngoingScroll,
1205 current_line_highlight: CurrentLineHighlight,
1206 gutter_hovered: bool,
1207}
1208
1209#[derive(Default, Debug, Clone, Copy)]
1210pub struct GutterDimensions {
1211 pub left_padding: Pixels,
1212 pub right_padding: Pixels,
1213 pub width: Pixels,
1214 pub margin: Pixels,
1215 pub git_blame_entries_width: Option<Pixels>,
1216}
1217
1218impl GutterDimensions {
1219 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1220 Self {
1221 margin: Self::default_gutter_margin(font_id, font_size, cx),
1222 ..Default::default()
1223 }
1224 }
1225
1226 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1227 -cx.text_system().descent(font_id, font_size)
1228 }
1229 /// The full width of the space taken up by the gutter.
1230 pub fn full_width(&self) -> Pixels {
1231 self.margin + self.width
1232 }
1233
1234 /// The width of the space reserved for the fold indicators,
1235 /// use alongside 'justify_end' and `gutter_width` to
1236 /// right align content with the line numbers
1237 pub fn fold_area_width(&self) -> Pixels {
1238 self.margin + self.right_padding
1239 }
1240}
1241
1242struct CharacterDimensions {
1243 em_width: Pixels,
1244 em_advance: Pixels,
1245 line_height: Pixels,
1246}
1247
1248#[derive(Debug)]
1249pub struct RemoteSelection {
1250 pub replica_id: ReplicaId,
1251 pub selection: Selection<Anchor>,
1252 pub cursor_shape: CursorShape,
1253 pub collaborator_id: CollaboratorId,
1254 pub line_mode: bool,
1255 pub user_name: Option<SharedString>,
1256 pub color: PlayerColor,
1257}
1258
1259#[derive(Clone, Debug)]
1260struct SelectionHistoryEntry {
1261 selections: Arc<[Selection<Anchor>]>,
1262 select_next_state: Option<SelectNextState>,
1263 select_prev_state: Option<SelectNextState>,
1264 add_selections_state: Option<AddSelectionsState>,
1265}
1266
1267#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1268enum SelectionHistoryMode {
1269 Normal,
1270 Undoing,
1271 Redoing,
1272 Skipping,
1273}
1274
1275#[derive(Clone, PartialEq, Eq, Hash)]
1276struct HoveredCursor {
1277 replica_id: u16,
1278 selection_id: usize,
1279}
1280
1281impl Default for SelectionHistoryMode {
1282 fn default() -> Self {
1283 Self::Normal
1284 }
1285}
1286
1287#[derive(Debug)]
1288/// SelectionEffects controls the side-effects of updating the selection.
1289///
1290/// The default behaviour does "what you mostly want":
1291/// - it pushes to the nav history if the cursor moved by >10 lines
1292/// - it re-triggers completion requests
1293/// - it scrolls to fit
1294///
1295/// You might want to modify these behaviours. For example when doing a "jump"
1296/// like go to definition, we always want to add to nav history; but when scrolling
1297/// in vim mode we never do.
1298///
1299/// Similarly, you might want to disable scrolling if you don't want the viewport to
1300/// move.
1301#[derive(Clone)]
1302pub struct SelectionEffects {
1303 nav_history: Option<bool>,
1304 completions: bool,
1305 scroll: Option<Autoscroll>,
1306}
1307
1308impl Default for SelectionEffects {
1309 fn default() -> Self {
1310 Self {
1311 nav_history: None,
1312 completions: true,
1313 scroll: Some(Autoscroll::fit()),
1314 }
1315 }
1316}
1317impl SelectionEffects {
1318 pub fn scroll(scroll: Autoscroll) -> Self {
1319 Self {
1320 scroll: Some(scroll),
1321 ..Default::default()
1322 }
1323 }
1324
1325 pub fn no_scroll() -> Self {
1326 Self {
1327 scroll: None,
1328 ..Default::default()
1329 }
1330 }
1331
1332 pub fn completions(self, completions: bool) -> Self {
1333 Self {
1334 completions,
1335 ..self
1336 }
1337 }
1338
1339 pub fn nav_history(self, nav_history: bool) -> Self {
1340 Self {
1341 nav_history: Some(nav_history),
1342 ..self
1343 }
1344 }
1345}
1346
1347struct DeferredSelectionEffectsState {
1348 changed: bool,
1349 effects: SelectionEffects,
1350 old_cursor_position: Anchor,
1351 history_entry: SelectionHistoryEntry,
1352}
1353
1354#[derive(Default)]
1355struct SelectionHistory {
1356 #[allow(clippy::type_complexity)]
1357 selections_by_transaction:
1358 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1359 mode: SelectionHistoryMode,
1360 undo_stack: VecDeque<SelectionHistoryEntry>,
1361 redo_stack: VecDeque<SelectionHistoryEntry>,
1362}
1363
1364impl SelectionHistory {
1365 #[track_caller]
1366 fn insert_transaction(
1367 &mut self,
1368 transaction_id: TransactionId,
1369 selections: Arc<[Selection<Anchor>]>,
1370 ) {
1371 if selections.is_empty() {
1372 log::error!(
1373 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1374 std::panic::Location::caller()
1375 );
1376 return;
1377 }
1378 self.selections_by_transaction
1379 .insert(transaction_id, (selections, None));
1380 }
1381
1382 #[allow(clippy::type_complexity)]
1383 fn transaction(
1384 &self,
1385 transaction_id: TransactionId,
1386 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1387 self.selections_by_transaction.get(&transaction_id)
1388 }
1389
1390 #[allow(clippy::type_complexity)]
1391 fn transaction_mut(
1392 &mut self,
1393 transaction_id: TransactionId,
1394 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1395 self.selections_by_transaction.get_mut(&transaction_id)
1396 }
1397
1398 fn push(&mut self, entry: SelectionHistoryEntry) {
1399 if !entry.selections.is_empty() {
1400 match self.mode {
1401 SelectionHistoryMode::Normal => {
1402 self.push_undo(entry);
1403 self.redo_stack.clear();
1404 }
1405 SelectionHistoryMode::Undoing => self.push_redo(entry),
1406 SelectionHistoryMode::Redoing => self.push_undo(entry),
1407 SelectionHistoryMode::Skipping => {}
1408 }
1409 }
1410 }
1411
1412 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1413 if self
1414 .undo_stack
1415 .back()
1416 .map_or(true, |e| e.selections != entry.selections)
1417 {
1418 self.undo_stack.push_back(entry);
1419 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1420 self.undo_stack.pop_front();
1421 }
1422 }
1423 }
1424
1425 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1426 if self
1427 .redo_stack
1428 .back()
1429 .map_or(true, |e| e.selections != entry.selections)
1430 {
1431 self.redo_stack.push_back(entry);
1432 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1433 self.redo_stack.pop_front();
1434 }
1435 }
1436 }
1437}
1438
1439#[derive(Clone, Copy)]
1440pub struct RowHighlightOptions {
1441 pub autoscroll: bool,
1442 pub include_gutter: bool,
1443}
1444
1445impl Default for RowHighlightOptions {
1446 fn default() -> Self {
1447 Self {
1448 autoscroll: Default::default(),
1449 include_gutter: true,
1450 }
1451 }
1452}
1453
1454struct RowHighlight {
1455 index: usize,
1456 range: Range<Anchor>,
1457 color: Hsla,
1458 options: RowHighlightOptions,
1459 type_id: TypeId,
1460}
1461
1462#[derive(Clone, Debug)]
1463struct AddSelectionsState {
1464 groups: Vec<AddSelectionsGroup>,
1465}
1466
1467#[derive(Clone, Debug)]
1468struct AddSelectionsGroup {
1469 above: bool,
1470 stack: Vec<usize>,
1471}
1472
1473#[derive(Clone)]
1474struct SelectNextState {
1475 query: AhoCorasick,
1476 wordwise: bool,
1477 done: bool,
1478}
1479
1480impl std::fmt::Debug for SelectNextState {
1481 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1482 f.debug_struct(std::any::type_name::<Self>())
1483 .field("wordwise", &self.wordwise)
1484 .field("done", &self.done)
1485 .finish()
1486 }
1487}
1488
1489#[derive(Debug)]
1490struct AutocloseRegion {
1491 selection_id: usize,
1492 range: Range<Anchor>,
1493 pair: BracketPair,
1494}
1495
1496#[derive(Debug)]
1497struct SnippetState {
1498 ranges: Vec<Vec<Range<Anchor>>>,
1499 active_index: usize,
1500 choices: Vec<Option<Vec<String>>>,
1501}
1502
1503#[doc(hidden)]
1504pub struct RenameState {
1505 pub range: Range<Anchor>,
1506 pub old_name: Arc<str>,
1507 pub editor: Entity<Editor>,
1508 block_id: CustomBlockId,
1509}
1510
1511struct InvalidationStack<T>(Vec<T>);
1512
1513struct RegisteredEditPredictionProvider {
1514 provider: Arc<dyn EditPredictionProviderHandle>,
1515 _subscription: Subscription,
1516}
1517
1518#[derive(Debug, PartialEq, Eq)]
1519pub struct ActiveDiagnosticGroup {
1520 pub active_range: Range<Anchor>,
1521 pub active_message: String,
1522 pub group_id: usize,
1523 pub blocks: HashSet<CustomBlockId>,
1524}
1525
1526#[derive(Debug, PartialEq, Eq)]
1527
1528pub(crate) enum ActiveDiagnostic {
1529 None,
1530 All,
1531 Group(ActiveDiagnosticGroup),
1532}
1533
1534#[derive(Serialize, Deserialize, Clone, Debug)]
1535pub struct ClipboardSelection {
1536 /// The number of bytes in this selection.
1537 pub len: usize,
1538 /// Whether this was a full-line selection.
1539 pub is_entire_line: bool,
1540 /// The indentation of the first line when this content was originally copied.
1541 pub first_line_indent: u32,
1542}
1543
1544// selections, scroll behavior, was newest selection reversed
1545type SelectSyntaxNodeHistoryState = (
1546 Box<[Selection<usize>]>,
1547 SelectSyntaxNodeScrollBehavior,
1548 bool,
1549);
1550
1551#[derive(Default)]
1552struct SelectSyntaxNodeHistory {
1553 stack: Vec<SelectSyntaxNodeHistoryState>,
1554 // disable temporarily to allow changing selections without losing the stack
1555 pub disable_clearing: bool,
1556}
1557
1558impl SelectSyntaxNodeHistory {
1559 pub fn try_clear(&mut self) {
1560 if !self.disable_clearing {
1561 self.stack.clear();
1562 }
1563 }
1564
1565 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1566 self.stack.push(selection);
1567 }
1568
1569 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1570 self.stack.pop()
1571 }
1572}
1573
1574enum SelectSyntaxNodeScrollBehavior {
1575 CursorTop,
1576 FitSelection,
1577 CursorBottom,
1578}
1579
1580#[derive(Debug)]
1581pub(crate) struct NavigationData {
1582 cursor_anchor: Anchor,
1583 cursor_position: Point,
1584 scroll_anchor: ScrollAnchor,
1585 scroll_top_row: u32,
1586}
1587
1588#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1589pub enum GotoDefinitionKind {
1590 Symbol,
1591 Declaration,
1592 Type,
1593 Implementation,
1594}
1595
1596#[derive(Debug, Clone)]
1597enum InlayHintRefreshReason {
1598 ModifiersChanged(bool),
1599 Toggle(bool),
1600 SettingsChange(InlayHintSettings),
1601 NewLinesShown,
1602 BufferEdited(HashSet<Arc<Language>>),
1603 RefreshRequested,
1604 ExcerptsRemoved(Vec<ExcerptId>),
1605}
1606
1607impl InlayHintRefreshReason {
1608 fn description(&self) -> &'static str {
1609 match self {
1610 Self::ModifiersChanged(_) => "modifiers changed",
1611 Self::Toggle(_) => "toggle",
1612 Self::SettingsChange(_) => "settings change",
1613 Self::NewLinesShown => "new lines shown",
1614 Self::BufferEdited(_) => "buffer edited",
1615 Self::RefreshRequested => "refresh requested",
1616 Self::ExcerptsRemoved(_) => "excerpts removed",
1617 }
1618 }
1619}
1620
1621pub enum FormatTarget {
1622 Buffers(HashSet<Entity<Buffer>>),
1623 Ranges(Vec<Range<MultiBufferPoint>>),
1624}
1625
1626pub(crate) struct FocusedBlock {
1627 id: BlockId,
1628 focus_handle: WeakFocusHandle,
1629}
1630
1631#[derive(Clone)]
1632enum JumpData {
1633 MultiBufferRow {
1634 row: MultiBufferRow,
1635 line_offset_from_top: u32,
1636 },
1637 MultiBufferPoint {
1638 excerpt_id: ExcerptId,
1639 position: Point,
1640 anchor: text::Anchor,
1641 line_offset_from_top: u32,
1642 },
1643}
1644
1645pub enum MultibufferSelectionMode {
1646 First,
1647 All,
1648}
1649
1650#[derive(Clone, Copy, Debug, Default)]
1651pub struct RewrapOptions {
1652 pub override_language_settings: bool,
1653 pub preserve_existing_whitespace: bool,
1654}
1655
1656impl Editor {
1657 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1658 let buffer = cx.new(|cx| Buffer::local("", cx));
1659 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1660 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1661 }
1662
1663 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1664 let buffer = cx.new(|cx| Buffer::local("", cx));
1665 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1666 Self::new(EditorMode::full(), buffer, None, window, cx)
1667 }
1668
1669 pub fn auto_height(
1670 min_lines: usize,
1671 max_lines: usize,
1672 window: &mut Window,
1673 cx: &mut Context<Self>,
1674 ) -> Self {
1675 let buffer = cx.new(|cx| Buffer::local("", cx));
1676 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1677 Self::new(
1678 EditorMode::AutoHeight {
1679 min_lines,
1680 max_lines: Some(max_lines),
1681 },
1682 buffer,
1683 None,
1684 window,
1685 cx,
1686 )
1687 }
1688
1689 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1690 /// The editor grows as tall as needed to fit its content.
1691 pub fn auto_height_unbounded(
1692 min_lines: usize,
1693 window: &mut Window,
1694 cx: &mut Context<Self>,
1695 ) -> Self {
1696 let buffer = cx.new(|cx| Buffer::local("", cx));
1697 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1698 Self::new(
1699 EditorMode::AutoHeight {
1700 min_lines,
1701 max_lines: None,
1702 },
1703 buffer,
1704 None,
1705 window,
1706 cx,
1707 )
1708 }
1709
1710 pub fn for_buffer(
1711 buffer: Entity<Buffer>,
1712 project: Option<Entity<Project>>,
1713 window: &mut Window,
1714 cx: &mut Context<Self>,
1715 ) -> Self {
1716 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1717 Self::new(EditorMode::full(), buffer, project, window, cx)
1718 }
1719
1720 pub fn for_multibuffer(
1721 buffer: Entity<MultiBuffer>,
1722 project: Option<Entity<Project>>,
1723 window: &mut Window,
1724 cx: &mut Context<Self>,
1725 ) -> Self {
1726 Self::new(EditorMode::full(), buffer, project, window, cx)
1727 }
1728
1729 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1730 let mut clone = Self::new(
1731 self.mode.clone(),
1732 self.buffer.clone(),
1733 self.project.clone(),
1734 window,
1735 cx,
1736 );
1737 self.display_map.update(cx, |display_map, cx| {
1738 let snapshot = display_map.snapshot(cx);
1739 clone.display_map.update(cx, |display_map, cx| {
1740 display_map.set_state(&snapshot, cx);
1741 });
1742 });
1743 clone.folds_did_change(cx);
1744 clone.selections.clone_state(&self.selections);
1745 clone.scroll_manager.clone_state(&self.scroll_manager);
1746 clone.searchable = self.searchable;
1747 clone.read_only = self.read_only;
1748 clone
1749 }
1750
1751 pub fn new(
1752 mode: EditorMode,
1753 buffer: Entity<MultiBuffer>,
1754 project: Option<Entity<Project>>,
1755 window: &mut Window,
1756 cx: &mut Context<Self>,
1757 ) -> Self {
1758 Editor::new_internal(mode, buffer, project, None, window, cx)
1759 }
1760
1761 fn new_internal(
1762 mode: EditorMode,
1763 buffer: Entity<MultiBuffer>,
1764 project: Option<Entity<Project>>,
1765 display_map: Option<Entity<DisplayMap>>,
1766 window: &mut Window,
1767 cx: &mut Context<Self>,
1768 ) -> Self {
1769 debug_assert!(
1770 display_map.is_none() || mode.is_minimap(),
1771 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1772 );
1773
1774 let full_mode = mode.is_full();
1775 let is_minimap = mode.is_minimap();
1776 let diagnostics_max_severity = if full_mode {
1777 EditorSettings::get_global(cx)
1778 .diagnostics_max_severity
1779 .unwrap_or(DiagnosticSeverity::Hint)
1780 } else {
1781 DiagnosticSeverity::Off
1782 };
1783 let style = window.text_style();
1784 let font_size = style.font_size.to_pixels(window.rem_size());
1785 let editor = cx.entity().downgrade();
1786 let fold_placeholder = FoldPlaceholder {
1787 constrain_width: true,
1788 render: Arc::new(move |fold_id, fold_range, cx| {
1789 let editor = editor.clone();
1790 div()
1791 .id(fold_id)
1792 .bg(cx.theme().colors().ghost_element_background)
1793 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1794 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1795 .rounded_xs()
1796 .size_full()
1797 .cursor_pointer()
1798 .child("⋯")
1799 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1800 .on_click(move |_, _window, cx| {
1801 editor
1802 .update(cx, |editor, cx| {
1803 editor.unfold_ranges(
1804 &[fold_range.start..fold_range.end],
1805 true,
1806 false,
1807 cx,
1808 );
1809 cx.stop_propagation();
1810 })
1811 .ok();
1812 })
1813 .into_any()
1814 }),
1815 merge_adjacent: true,
1816 ..FoldPlaceholder::default()
1817 };
1818 let display_map = display_map.unwrap_or_else(|| {
1819 cx.new(|cx| {
1820 DisplayMap::new(
1821 buffer.clone(),
1822 style.font(),
1823 font_size,
1824 None,
1825 FILE_HEADER_HEIGHT,
1826 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1827 fold_placeholder,
1828 diagnostics_max_severity,
1829 cx,
1830 )
1831 })
1832 });
1833
1834 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1835
1836 let blink_manager = cx.new(|cx| {
1837 let mut blink_manager = BlinkManager::new(CURSOR_BLINK_INTERVAL, cx);
1838 if is_minimap {
1839 blink_manager.disable(cx);
1840 }
1841 blink_manager
1842 });
1843
1844 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1845 .then(|| language_settings::SoftWrap::None);
1846
1847 let mut project_subscriptions = Vec::new();
1848 if full_mode {
1849 if let Some(project) = project.as_ref() {
1850 project_subscriptions.push(cx.subscribe_in(
1851 project,
1852 window,
1853 |editor, _, event, window, cx| match event {
1854 project::Event::RefreshCodeLens => {
1855 // we always query lens with actions, without storing them, always refreshing them
1856 }
1857 project::Event::RefreshInlayHints => {
1858 editor
1859 .refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1860 }
1861 project::Event::LanguageServerAdded(..)
1862 | project::Event::LanguageServerRemoved(..) => {
1863 if editor.tasks_update_task.is_none() {
1864 editor.tasks_update_task =
1865 Some(editor.refresh_runnables(window, cx));
1866 }
1867 }
1868 project::Event::SnippetEdit(id, snippet_edits) => {
1869 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1870 let focus_handle = editor.focus_handle(cx);
1871 if focus_handle.is_focused(window) {
1872 let snapshot = buffer.read(cx).snapshot();
1873 for (range, snippet) in snippet_edits {
1874 let editor_range =
1875 language::range_from_lsp(*range).to_offset(&snapshot);
1876 editor
1877 .insert_snippet(
1878 &[editor_range],
1879 snippet.clone(),
1880 window,
1881 cx,
1882 )
1883 .ok();
1884 }
1885 }
1886 }
1887 }
1888 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
1889 if editor.buffer().read(cx).buffer(*buffer_id).is_some() {
1890 editor.update_lsp_data(false, Some(*buffer_id), window, cx);
1891 }
1892 }
1893 _ => {}
1894 },
1895 ));
1896 if let Some(task_inventory) = project
1897 .read(cx)
1898 .task_store()
1899 .read(cx)
1900 .task_inventory()
1901 .cloned()
1902 {
1903 project_subscriptions.push(cx.observe_in(
1904 &task_inventory,
1905 window,
1906 |editor, _, window, cx| {
1907 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1908 },
1909 ));
1910 };
1911
1912 project_subscriptions.push(cx.subscribe_in(
1913 &project.read(cx).breakpoint_store(),
1914 window,
1915 |editor, _, event, window, cx| match event {
1916 BreakpointStoreEvent::ClearDebugLines => {
1917 editor.clear_row_highlights::<ActiveDebugLine>();
1918 editor.refresh_inline_values(cx);
1919 }
1920 BreakpointStoreEvent::SetDebugLine => {
1921 if editor.go_to_active_debug_line(window, cx) {
1922 cx.stop_propagation();
1923 }
1924
1925 editor.refresh_inline_values(cx);
1926 }
1927 _ => {}
1928 },
1929 ));
1930 let git_store = project.read(cx).git_store().clone();
1931 let project = project.clone();
1932 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
1933 match event {
1934 GitStoreEvent::RepositoryUpdated(
1935 _,
1936 RepositoryEvent::Updated {
1937 new_instance: true, ..
1938 },
1939 _,
1940 ) => {
1941 this.load_diff_task = Some(
1942 update_uncommitted_diff_for_buffer(
1943 cx.entity(),
1944 &project,
1945 this.buffer.read(cx).all_buffers(),
1946 this.buffer.clone(),
1947 cx,
1948 )
1949 .shared(),
1950 );
1951 }
1952 _ => {}
1953 }
1954 }));
1955 }
1956 }
1957
1958 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1959
1960 let inlay_hint_settings =
1961 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1962 let focus_handle = cx.focus_handle();
1963 if !is_minimap {
1964 cx.on_focus(&focus_handle, window, Self::handle_focus)
1965 .detach();
1966 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1967 .detach();
1968 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1969 .detach();
1970 cx.on_blur(&focus_handle, window, Self::handle_blur)
1971 .detach();
1972 cx.observe_pending_input(window, Self::observe_pending_input)
1973 .detach();
1974 }
1975
1976 let show_indent_guides = if matches!(
1977 mode,
1978 EditorMode::SingleLine { .. } | EditorMode::Minimap { .. }
1979 ) {
1980 Some(false)
1981 } else {
1982 None
1983 };
1984
1985 let breakpoint_store = match (&mode, project.as_ref()) {
1986 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
1987 _ => None,
1988 };
1989
1990 let mut code_action_providers = Vec::new();
1991 let mut load_uncommitted_diff = None;
1992 if let Some(project) = project.clone() {
1993 load_uncommitted_diff = Some(
1994 update_uncommitted_diff_for_buffer(
1995 cx.entity(),
1996 &project,
1997 buffer.read(cx).all_buffers(),
1998 buffer.clone(),
1999 cx,
2000 )
2001 .shared(),
2002 );
2003 code_action_providers.push(Rc::new(project) as Rc<_>);
2004 }
2005
2006 let mut editor = Self {
2007 focus_handle,
2008 show_cursor_when_unfocused: false,
2009 last_focused_descendant: None,
2010 buffer: buffer.clone(),
2011 display_map: display_map.clone(),
2012 selections,
2013 scroll_manager: ScrollManager::new(cx),
2014 columnar_selection_state: None,
2015 add_selections_state: None,
2016 select_next_state: None,
2017 select_prev_state: None,
2018 selection_history: SelectionHistory::default(),
2019 defer_selection_effects: false,
2020 deferred_selection_effects_state: None,
2021 autoclose_regions: Vec::new(),
2022 snippet_stack: InvalidationStack::default(),
2023 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2024 ime_transaction: None,
2025 active_diagnostics: ActiveDiagnostic::None,
2026 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2027 inline_diagnostics_update: Task::ready(()),
2028 inline_diagnostics: Vec::new(),
2029 soft_wrap_mode_override,
2030 diagnostics_max_severity,
2031 hard_wrap: None,
2032 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2033 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2034 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2035 project,
2036 blink_manager: blink_manager.clone(),
2037 show_local_selections: true,
2038 show_scrollbars: ScrollbarAxes {
2039 horizontal: full_mode,
2040 vertical: full_mode,
2041 },
2042 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2043 offset_content: !matches!(mode, EditorMode::SingleLine { .. }),
2044 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2045 show_gutter: full_mode,
2046 show_line_numbers: (!full_mode).then_some(false),
2047 use_relative_line_numbers: None,
2048 disable_expand_excerpt_buttons: !full_mode,
2049 show_git_diff_gutter: None,
2050 show_code_actions: None,
2051 show_runnables: None,
2052 show_breakpoints: None,
2053 show_wrap_guides: None,
2054 show_indent_guides,
2055 placeholder_text: None,
2056 highlight_order: 0,
2057 highlighted_rows: HashMap::default(),
2058 background_highlights: TreeMap::default(),
2059 gutter_highlights: TreeMap::default(),
2060 scrollbar_marker_state: ScrollbarMarkerState::default(),
2061 active_indent_guides_state: ActiveIndentGuidesState::default(),
2062 nav_history: None,
2063 context_menu: RefCell::new(None),
2064 context_menu_options: None,
2065 mouse_context_menu: None,
2066 completion_tasks: Vec::new(),
2067 inline_blame_popover: None,
2068 inline_blame_popover_show_task: None,
2069 signature_help_state: SignatureHelpState::default(),
2070 auto_signature_help: None,
2071 find_all_references_task_sources: Vec::new(),
2072 next_completion_id: 0,
2073 next_inlay_id: 0,
2074 code_action_providers,
2075 available_code_actions: None,
2076 code_actions_task: None,
2077 quick_selection_highlight_task: None,
2078 debounced_selection_highlight_task: None,
2079 document_highlights_task: None,
2080 linked_editing_range_task: None,
2081 pending_rename: None,
2082 searchable: !is_minimap,
2083 cursor_shape: EditorSettings::get_global(cx)
2084 .cursor_shape
2085 .unwrap_or_default(),
2086 current_line_highlight: None,
2087 autoindent_mode: Some(AutoindentMode::EachLine),
2088 collapse_matches: false,
2089 workspace: None,
2090 input_enabled: !is_minimap,
2091 use_modal_editing: full_mode,
2092 read_only: is_minimap,
2093 use_autoclose: true,
2094 use_auto_surround: true,
2095 auto_replace_emoji_shortcode: false,
2096 jsx_tag_auto_close_enabled_in_any_buffer: false,
2097 leader_id: None,
2098 remote_id: None,
2099 hover_state: HoverState::default(),
2100 pending_mouse_down: None,
2101 hovered_link_state: None,
2102 edit_prediction_provider: None,
2103 active_edit_prediction: None,
2104 stale_edit_prediction_in_menu: None,
2105 edit_prediction_preview: EditPredictionPreview::Inactive {
2106 released_too_fast: false,
2107 },
2108 inline_diagnostics_enabled: full_mode,
2109 diagnostics_enabled: full_mode,
2110 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2111 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
2112 gutter_hovered: false,
2113 pixel_position_of_newest_cursor: None,
2114 last_bounds: None,
2115 last_position_map: None,
2116 expect_bounds_change: None,
2117 gutter_dimensions: GutterDimensions::default(),
2118 style: None,
2119 show_cursor_names: false,
2120 hovered_cursors: HashMap::default(),
2121 next_editor_action_id: EditorActionId::default(),
2122 editor_actions: Rc::default(),
2123 edit_predictions_hidden_for_vim_mode: false,
2124 show_edit_predictions_override: None,
2125 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2126 edit_prediction_settings: EditPredictionSettings::Disabled,
2127 edit_prediction_indent_conflict: false,
2128 edit_prediction_requires_modifier_in_indent_conflict: true,
2129 custom_context_menu: None,
2130 show_git_blame_gutter: false,
2131 show_git_blame_inline: false,
2132 show_selection_menu: None,
2133 show_git_blame_inline_delay_task: None,
2134 git_blame_inline_enabled: full_mode
2135 && ProjectSettings::get_global(cx).git.inline_blame_enabled(),
2136 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2137 serialize_dirty_buffers: !is_minimap
2138 && ProjectSettings::get_global(cx)
2139 .session
2140 .restore_unsaved_buffers,
2141 blame: None,
2142 blame_subscription: None,
2143 tasks: BTreeMap::default(),
2144
2145 breakpoint_store,
2146 gutter_breakpoint_indicator: (None, None),
2147 hovered_diff_hunk_row: None,
2148 _subscriptions: (!is_minimap)
2149 .then(|| {
2150 vec![
2151 cx.observe(&buffer, Self::on_buffer_changed),
2152 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
2153 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2154 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2155 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2156 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2157 cx.observe_window_activation(window, |editor, window, cx| {
2158 let active = window.is_window_active();
2159 editor.blink_manager.update(cx, |blink_manager, cx| {
2160 if active {
2161 blink_manager.enable(cx);
2162 } else {
2163 blink_manager.disable(cx);
2164 }
2165 });
2166 if active {
2167 editor.show_mouse_cursor(cx);
2168 }
2169 }),
2170 ]
2171 })
2172 .unwrap_or_default(),
2173 tasks_update_task: None,
2174 pull_diagnostics_task: Task::ready(()),
2175 colors: None,
2176 next_color_inlay_id: 0,
2177 linked_edit_ranges: Default::default(),
2178 in_project_search: false,
2179 previous_search_ranges: None,
2180 breadcrumb_header: None,
2181 focused_block: None,
2182 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2183 addons: HashMap::default(),
2184 registered_buffers: HashMap::default(),
2185 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2186 selection_mark_mode: false,
2187 toggle_fold_multiple_buffers: Task::ready(()),
2188 serialize_selections: Task::ready(()),
2189 serialize_folds: Task::ready(()),
2190 text_style_refinement: None,
2191 load_diff_task: load_uncommitted_diff,
2192 temporary_diff_override: false,
2193 mouse_cursor_hidden: false,
2194 minimap: None,
2195 hide_mouse_mode: EditorSettings::get_global(cx)
2196 .hide_mouse
2197 .unwrap_or_default(),
2198 change_list: ChangeList::new(),
2199 mode,
2200 selection_drag_state: SelectionDragState::None,
2201 folding_newlines: Task::ready(()),
2202 };
2203
2204 if is_minimap {
2205 return editor;
2206 }
2207
2208 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2209 editor
2210 ._subscriptions
2211 .push(cx.observe(breakpoints, |_, _, cx| {
2212 cx.notify();
2213 }));
2214 }
2215 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2216 editor._subscriptions.extend(project_subscriptions);
2217
2218 editor._subscriptions.push(cx.subscribe_in(
2219 &cx.entity(),
2220 window,
2221 |editor, _, e: &EditorEvent, window, cx| match e {
2222 EditorEvent::ScrollPositionChanged { local, .. } => {
2223 if *local {
2224 let new_anchor = editor.scroll_manager.anchor();
2225 let snapshot = editor.snapshot(window, cx);
2226 editor.update_restoration_data(cx, move |data| {
2227 data.scroll_position = (
2228 new_anchor.top_row(&snapshot.buffer_snapshot),
2229 new_anchor.offset,
2230 );
2231 });
2232 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2233 editor.inline_blame_popover.take();
2234 }
2235 }
2236 EditorEvent::Edited { .. } => {
2237 if !vim_enabled(cx) {
2238 let (map, selections) = editor.selections.all_adjusted_display(cx);
2239 let pop_state = editor
2240 .change_list
2241 .last()
2242 .map(|previous| {
2243 previous.len() == selections.len()
2244 && previous.iter().enumerate().all(|(ix, p)| {
2245 p.to_display_point(&map).row()
2246 == selections[ix].head().row()
2247 })
2248 })
2249 .unwrap_or(false);
2250 let new_positions = selections
2251 .into_iter()
2252 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
2253 .collect();
2254 editor
2255 .change_list
2256 .push_to_change_list(pop_state, new_positions);
2257 }
2258 }
2259 _ => (),
2260 },
2261 ));
2262
2263 if let Some(dap_store) = editor
2264 .project
2265 .as_ref()
2266 .map(|project| project.read(cx).dap_store())
2267 {
2268 let weak_editor = cx.weak_entity();
2269
2270 editor
2271 ._subscriptions
2272 .push(
2273 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2274 let session_entity = cx.entity();
2275 weak_editor
2276 .update(cx, |editor, cx| {
2277 editor._subscriptions.push(
2278 cx.subscribe(&session_entity, Self::on_debug_session_event),
2279 );
2280 })
2281 .ok();
2282 }),
2283 );
2284
2285 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2286 editor
2287 ._subscriptions
2288 .push(cx.subscribe(&session, Self::on_debug_session_event));
2289 }
2290 }
2291
2292 // skip adding the initial selection to selection history
2293 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2294 editor.end_selection(window, cx);
2295 editor.selection_history.mode = SelectionHistoryMode::Normal;
2296
2297 editor.scroll_manager.show_scrollbars(window, cx);
2298 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &buffer, cx);
2299
2300 if full_mode {
2301 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2302 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2303
2304 if editor.git_blame_inline_enabled {
2305 editor.start_git_blame_inline(false, window, cx);
2306 }
2307
2308 editor.go_to_active_debug_line(window, cx);
2309
2310 if let Some(buffer) = buffer.read(cx).as_singleton() {
2311 if let Some(project) = editor.project.as_ref() {
2312 let handle = project.update(cx, |project, cx| {
2313 project.register_buffer_with_language_servers(&buffer, cx)
2314 });
2315 editor
2316 .registered_buffers
2317 .insert(buffer.read(cx).remote_id(), handle);
2318 }
2319 }
2320
2321 editor.minimap =
2322 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2323 editor.colors = Some(LspColorData::new(cx));
2324 editor.update_lsp_data(false, None, window, cx);
2325 }
2326
2327 if editor.mode.is_full() {
2328 editor.report_editor_event("Editor Opened", None, cx);
2329 }
2330
2331 editor
2332 }
2333
2334 pub fn deploy_mouse_context_menu(
2335 &mut self,
2336 position: gpui::Point<Pixels>,
2337 context_menu: Entity<ContextMenu>,
2338 window: &mut Window,
2339 cx: &mut Context<Self>,
2340 ) {
2341 self.mouse_context_menu = Some(MouseContextMenu::new(
2342 self,
2343 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2344 context_menu,
2345 window,
2346 cx,
2347 ));
2348 }
2349
2350 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2351 self.mouse_context_menu
2352 .as_ref()
2353 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2354 }
2355
2356 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2357 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2358 }
2359
2360 fn key_context_internal(
2361 &self,
2362 has_active_edit_prediction: bool,
2363 window: &Window,
2364 cx: &App,
2365 ) -> KeyContext {
2366 let mut key_context = KeyContext::new_with_defaults();
2367 key_context.add("Editor");
2368 let mode = match self.mode {
2369 EditorMode::SingleLine { .. } => "single_line",
2370 EditorMode::AutoHeight { .. } => "auto_height",
2371 EditorMode::Minimap { .. } => "minimap",
2372 EditorMode::Full { .. } => "full",
2373 };
2374
2375 if EditorSettings::jupyter_enabled(cx) {
2376 key_context.add("jupyter");
2377 }
2378
2379 key_context.set("mode", mode);
2380 if self.pending_rename.is_some() {
2381 key_context.add("renaming");
2382 }
2383
2384 match self.context_menu.borrow().as_ref() {
2385 Some(CodeContextMenu::Completions(menu)) => {
2386 if menu.visible() {
2387 key_context.add("menu");
2388 key_context.add("showing_completions");
2389 }
2390 }
2391 Some(CodeContextMenu::CodeActions(menu)) => {
2392 if menu.visible() {
2393 key_context.add("menu");
2394 key_context.add("showing_code_actions")
2395 }
2396 }
2397 None => {}
2398 }
2399
2400 if self.signature_help_state.has_multiple_signatures() {
2401 key_context.add("showing_signature_help");
2402 }
2403
2404 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2405 if !self.focus_handle(cx).contains_focused(window, cx)
2406 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2407 {
2408 for addon in self.addons.values() {
2409 addon.extend_key_context(&mut key_context, cx)
2410 }
2411 }
2412
2413 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2414 if let Some(extension) = singleton_buffer
2415 .read(cx)
2416 .file()
2417 .and_then(|file| file.path().extension()?.to_str())
2418 {
2419 key_context.set("extension", extension.to_string());
2420 }
2421 } else {
2422 key_context.add("multibuffer");
2423 }
2424
2425 if has_active_edit_prediction {
2426 if self.edit_prediction_in_conflict() {
2427 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2428 } else {
2429 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2430 key_context.add("copilot_suggestion");
2431 }
2432 }
2433
2434 if self.selection_mark_mode {
2435 key_context.add("selection_mode");
2436 }
2437
2438 key_context
2439 }
2440
2441 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2442 if self.mouse_cursor_hidden {
2443 self.mouse_cursor_hidden = false;
2444 cx.notify();
2445 }
2446 }
2447
2448 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2449 let hide_mouse_cursor = match origin {
2450 HideMouseCursorOrigin::TypingAction => {
2451 matches!(
2452 self.hide_mouse_mode,
2453 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2454 )
2455 }
2456 HideMouseCursorOrigin::MovementAction => {
2457 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2458 }
2459 };
2460 if self.mouse_cursor_hidden != hide_mouse_cursor {
2461 self.mouse_cursor_hidden = hide_mouse_cursor;
2462 cx.notify();
2463 }
2464 }
2465
2466 pub fn edit_prediction_in_conflict(&self) -> bool {
2467 if !self.show_edit_predictions_in_menu() {
2468 return false;
2469 }
2470
2471 let showing_completions = self
2472 .context_menu
2473 .borrow()
2474 .as_ref()
2475 .map_or(false, |context| {
2476 matches!(context, CodeContextMenu::Completions(_))
2477 });
2478
2479 showing_completions
2480 || self.edit_prediction_requires_modifier()
2481 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2482 // bindings to insert tab characters.
2483 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2484 }
2485
2486 pub fn accept_edit_prediction_keybind(
2487 &self,
2488 accept_partial: bool,
2489 window: &Window,
2490 cx: &App,
2491 ) -> AcceptEditPredictionBinding {
2492 let key_context = self.key_context_internal(true, window, cx);
2493 let in_conflict = self.edit_prediction_in_conflict();
2494
2495 let bindings = if accept_partial {
2496 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2497 } else {
2498 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2499 };
2500
2501 // TODO: if the binding contains multiple keystrokes, display all of them, not
2502 // just the first one.
2503 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2504 !in_conflict
2505 || binding
2506 .keystrokes()
2507 .first()
2508 .map_or(false, |keystroke| keystroke.modifiers.modified())
2509 }))
2510 }
2511
2512 pub fn new_file(
2513 workspace: &mut Workspace,
2514 _: &workspace::NewFile,
2515 window: &mut Window,
2516 cx: &mut Context<Workspace>,
2517 ) {
2518 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2519 "Failed to create buffer",
2520 window,
2521 cx,
2522 |e, _, _| match e.error_code() {
2523 ErrorCode::RemoteUpgradeRequired => Some(format!(
2524 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2525 e.error_tag("required").unwrap_or("the latest version")
2526 )),
2527 _ => None,
2528 },
2529 );
2530 }
2531
2532 pub fn new_in_workspace(
2533 workspace: &mut Workspace,
2534 window: &mut Window,
2535 cx: &mut Context<Workspace>,
2536 ) -> Task<Result<Entity<Editor>>> {
2537 let project = workspace.project().clone();
2538 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2539
2540 cx.spawn_in(window, async move |workspace, cx| {
2541 let buffer = create.await?;
2542 workspace.update_in(cx, |workspace, window, cx| {
2543 let editor =
2544 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2545 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2546 editor
2547 })
2548 })
2549 }
2550
2551 fn new_file_vertical(
2552 workspace: &mut Workspace,
2553 _: &workspace::NewFileSplitVertical,
2554 window: &mut Window,
2555 cx: &mut Context<Workspace>,
2556 ) {
2557 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2558 }
2559
2560 fn new_file_horizontal(
2561 workspace: &mut Workspace,
2562 _: &workspace::NewFileSplitHorizontal,
2563 window: &mut Window,
2564 cx: &mut Context<Workspace>,
2565 ) {
2566 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2567 }
2568
2569 fn new_file_in_direction(
2570 workspace: &mut Workspace,
2571 direction: SplitDirection,
2572 window: &mut Window,
2573 cx: &mut Context<Workspace>,
2574 ) {
2575 let project = workspace.project().clone();
2576 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2577
2578 cx.spawn_in(window, async move |workspace, cx| {
2579 let buffer = create.await?;
2580 workspace.update_in(cx, move |workspace, window, cx| {
2581 workspace.split_item(
2582 direction,
2583 Box::new(
2584 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2585 ),
2586 window,
2587 cx,
2588 )
2589 })?;
2590 anyhow::Ok(())
2591 })
2592 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2593 match e.error_code() {
2594 ErrorCode::RemoteUpgradeRequired => Some(format!(
2595 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2596 e.error_tag("required").unwrap_or("the latest version")
2597 )),
2598 _ => None,
2599 }
2600 });
2601 }
2602
2603 pub fn leader_id(&self) -> Option<CollaboratorId> {
2604 self.leader_id
2605 }
2606
2607 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2608 &self.buffer
2609 }
2610
2611 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2612 self.workspace.as_ref()?.0.upgrade()
2613 }
2614
2615 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2616 self.buffer().read(cx).title(cx)
2617 }
2618
2619 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2620 let git_blame_gutter_max_author_length = self
2621 .render_git_blame_gutter(cx)
2622 .then(|| {
2623 if let Some(blame) = self.blame.as_ref() {
2624 let max_author_length =
2625 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2626 Some(max_author_length)
2627 } else {
2628 None
2629 }
2630 })
2631 .flatten();
2632
2633 EditorSnapshot {
2634 mode: self.mode.clone(),
2635 show_gutter: self.show_gutter,
2636 show_line_numbers: self.show_line_numbers,
2637 show_git_diff_gutter: self.show_git_diff_gutter,
2638 show_code_actions: self.show_code_actions,
2639 show_runnables: self.show_runnables,
2640 show_breakpoints: self.show_breakpoints,
2641 git_blame_gutter_max_author_length,
2642 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2643 scroll_anchor: self.scroll_manager.anchor(),
2644 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2645 placeholder_text: self.placeholder_text.clone(),
2646 is_focused: self.focus_handle.is_focused(window),
2647 current_line_highlight: self
2648 .current_line_highlight
2649 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2650 gutter_hovered: self.gutter_hovered,
2651 }
2652 }
2653
2654 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2655 self.buffer.read(cx).language_at(point, cx)
2656 }
2657
2658 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2659 self.buffer.read(cx).read(cx).file_at(point).cloned()
2660 }
2661
2662 pub fn active_excerpt(
2663 &self,
2664 cx: &App,
2665 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2666 self.buffer
2667 .read(cx)
2668 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2669 }
2670
2671 pub fn mode(&self) -> &EditorMode {
2672 &self.mode
2673 }
2674
2675 pub fn set_mode(&mut self, mode: EditorMode) {
2676 self.mode = mode;
2677 }
2678
2679 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2680 self.collaboration_hub.as_deref()
2681 }
2682
2683 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2684 self.collaboration_hub = Some(hub);
2685 }
2686
2687 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2688 self.in_project_search = in_project_search;
2689 }
2690
2691 pub fn set_custom_context_menu(
2692 &mut self,
2693 f: impl 'static
2694 + Fn(
2695 &mut Self,
2696 DisplayPoint,
2697 &mut Window,
2698 &mut Context<Self>,
2699 ) -> Option<Entity<ui::ContextMenu>>,
2700 ) {
2701 self.custom_context_menu = Some(Box::new(f))
2702 }
2703
2704 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2705 self.completion_provider = provider;
2706 }
2707
2708 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2709 self.semantics_provider.clone()
2710 }
2711
2712 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2713 self.semantics_provider = provider;
2714 }
2715
2716 pub fn set_edit_prediction_provider<T>(
2717 &mut self,
2718 provider: Option<Entity<T>>,
2719 window: &mut Window,
2720 cx: &mut Context<Self>,
2721 ) where
2722 T: EditPredictionProvider,
2723 {
2724 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionProvider {
2725 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2726 if this.focus_handle.is_focused(window) {
2727 this.update_visible_edit_prediction(window, cx);
2728 }
2729 }),
2730 provider: Arc::new(provider),
2731 });
2732 self.update_edit_prediction_settings(cx);
2733 self.refresh_edit_prediction(false, false, window, cx);
2734 }
2735
2736 pub fn placeholder_text(&self) -> Option<&str> {
2737 self.placeholder_text.as_deref()
2738 }
2739
2740 pub fn set_placeholder_text(
2741 &mut self,
2742 placeholder_text: impl Into<Arc<str>>,
2743 cx: &mut Context<Self>,
2744 ) {
2745 let placeholder_text = Some(placeholder_text.into());
2746 if self.placeholder_text != placeholder_text {
2747 self.placeholder_text = placeholder_text;
2748 cx.notify();
2749 }
2750 }
2751
2752 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2753 self.cursor_shape = cursor_shape;
2754
2755 // Disrupt blink for immediate user feedback that the cursor shape has changed
2756 self.blink_manager.update(cx, BlinkManager::show_cursor);
2757
2758 cx.notify();
2759 }
2760
2761 pub fn set_current_line_highlight(
2762 &mut self,
2763 current_line_highlight: Option<CurrentLineHighlight>,
2764 ) {
2765 self.current_line_highlight = current_line_highlight;
2766 }
2767
2768 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2769 self.collapse_matches = collapse_matches;
2770 }
2771
2772 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2773 let buffers = self.buffer.read(cx).all_buffers();
2774 let Some(project) = self.project.as_ref() else {
2775 return;
2776 };
2777 project.update(cx, |project, cx| {
2778 for buffer in buffers {
2779 self.registered_buffers
2780 .entry(buffer.read(cx).remote_id())
2781 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2782 }
2783 })
2784 }
2785
2786 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2787 if self.collapse_matches {
2788 return range.start..range.start;
2789 }
2790 range.clone()
2791 }
2792
2793 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2794 if self.display_map.read(cx).clip_at_line_ends != clip {
2795 self.display_map
2796 .update(cx, |map, _| map.clip_at_line_ends = clip);
2797 }
2798 }
2799
2800 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2801 self.input_enabled = input_enabled;
2802 }
2803
2804 pub fn set_edit_predictions_hidden_for_vim_mode(
2805 &mut self,
2806 hidden: bool,
2807 window: &mut Window,
2808 cx: &mut Context<Self>,
2809 ) {
2810 if hidden != self.edit_predictions_hidden_for_vim_mode {
2811 self.edit_predictions_hidden_for_vim_mode = hidden;
2812 if hidden {
2813 self.update_visible_edit_prediction(window, cx);
2814 } else {
2815 self.refresh_edit_prediction(true, false, window, cx);
2816 }
2817 }
2818 }
2819
2820 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
2821 self.menu_edit_predictions_policy = value;
2822 }
2823
2824 pub fn set_autoindent(&mut self, autoindent: bool) {
2825 if autoindent {
2826 self.autoindent_mode = Some(AutoindentMode::EachLine);
2827 } else {
2828 self.autoindent_mode = None;
2829 }
2830 }
2831
2832 pub fn read_only(&self, cx: &App) -> bool {
2833 self.read_only || self.buffer.read(cx).read_only()
2834 }
2835
2836 pub fn set_read_only(&mut self, read_only: bool) {
2837 self.read_only = read_only;
2838 }
2839
2840 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2841 self.use_autoclose = autoclose;
2842 }
2843
2844 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2845 self.use_auto_surround = auto_surround;
2846 }
2847
2848 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2849 self.auto_replace_emoji_shortcode = auto_replace;
2850 }
2851
2852 pub fn toggle_edit_predictions(
2853 &mut self,
2854 _: &ToggleEditPrediction,
2855 window: &mut Window,
2856 cx: &mut Context<Self>,
2857 ) {
2858 if self.show_edit_predictions_override.is_some() {
2859 self.set_show_edit_predictions(None, window, cx);
2860 } else {
2861 let show_edit_predictions = !self.edit_predictions_enabled();
2862 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2863 }
2864 }
2865
2866 pub fn set_show_edit_predictions(
2867 &mut self,
2868 show_edit_predictions: Option<bool>,
2869 window: &mut Window,
2870 cx: &mut Context<Self>,
2871 ) {
2872 self.show_edit_predictions_override = show_edit_predictions;
2873 self.update_edit_prediction_settings(cx);
2874
2875 if let Some(false) = show_edit_predictions {
2876 self.discard_edit_prediction(false, cx);
2877 } else {
2878 self.refresh_edit_prediction(false, true, window, cx);
2879 }
2880 }
2881
2882 fn edit_predictions_disabled_in_scope(
2883 &self,
2884 buffer: &Entity<Buffer>,
2885 buffer_position: language::Anchor,
2886 cx: &App,
2887 ) -> bool {
2888 let snapshot = buffer.read(cx).snapshot();
2889 let settings = snapshot.settings_at(buffer_position, cx);
2890
2891 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2892 return false;
2893 };
2894
2895 scope.override_name().map_or(false, |scope_name| {
2896 settings
2897 .edit_predictions_disabled_in
2898 .iter()
2899 .any(|s| s == scope_name)
2900 })
2901 }
2902
2903 pub fn set_use_modal_editing(&mut self, to: bool) {
2904 self.use_modal_editing = to;
2905 }
2906
2907 pub fn use_modal_editing(&self) -> bool {
2908 self.use_modal_editing
2909 }
2910
2911 fn selections_did_change(
2912 &mut self,
2913 local: bool,
2914 old_cursor_position: &Anchor,
2915 effects: SelectionEffects,
2916 window: &mut Window,
2917 cx: &mut Context<Self>,
2918 ) {
2919 window.invalidate_character_coordinates();
2920
2921 // Copy selections to primary selection buffer
2922 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2923 if local {
2924 let selections = self.selections.all::<usize>(cx);
2925 let buffer_handle = self.buffer.read(cx).read(cx);
2926
2927 let mut text = String::new();
2928 for (index, selection) in selections.iter().enumerate() {
2929 let text_for_selection = buffer_handle
2930 .text_for_range(selection.start..selection.end)
2931 .collect::<String>();
2932
2933 text.push_str(&text_for_selection);
2934 if index != selections.len() - 1 {
2935 text.push('\n');
2936 }
2937 }
2938
2939 if !text.is_empty() {
2940 cx.write_to_primary(ClipboardItem::new_string(text));
2941 }
2942 }
2943
2944 let selection_anchors = self.selections.disjoint_anchors();
2945
2946 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
2947 self.buffer.update(cx, |buffer, cx| {
2948 buffer.set_active_selections(
2949 &selection_anchors,
2950 self.selections.line_mode,
2951 self.cursor_shape,
2952 cx,
2953 )
2954 });
2955 }
2956 let display_map = self
2957 .display_map
2958 .update(cx, |display_map, cx| display_map.snapshot(cx));
2959 let buffer = &display_map.buffer_snapshot;
2960 if self.selections.count() == 1 {
2961 self.add_selections_state = None;
2962 }
2963 self.select_next_state = None;
2964 self.select_prev_state = None;
2965 self.select_syntax_node_history.try_clear();
2966 self.invalidate_autoclose_regions(&selection_anchors, buffer);
2967 self.snippet_stack.invalidate(&selection_anchors, buffer);
2968 self.take_rename(false, window, cx);
2969
2970 let newest_selection = self.selections.newest_anchor();
2971 let new_cursor_position = newest_selection.head();
2972 let selection_start = newest_selection.start;
2973
2974 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
2975 self.push_to_nav_history(
2976 *old_cursor_position,
2977 Some(new_cursor_position.to_point(buffer)),
2978 false,
2979 effects.nav_history == Some(true),
2980 cx,
2981 );
2982 }
2983
2984 if local {
2985 if let Some(buffer_id) = new_cursor_position.buffer_id {
2986 if !self.registered_buffers.contains_key(&buffer_id) {
2987 if let Some(project) = self.project.as_ref() {
2988 project.update(cx, |project, cx| {
2989 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
2990 return;
2991 };
2992 self.registered_buffers.insert(
2993 buffer_id,
2994 project.register_buffer_with_language_servers(&buffer, cx),
2995 );
2996 })
2997 }
2998 }
2999 }
3000
3001 let mut context_menu = self.context_menu.borrow_mut();
3002 let completion_menu = match context_menu.as_ref() {
3003 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3004 Some(CodeContextMenu::CodeActions(_)) => {
3005 *context_menu = None;
3006 None
3007 }
3008 None => None,
3009 };
3010 let completion_position = completion_menu.map(|menu| menu.initial_position);
3011 drop(context_menu);
3012
3013 if effects.completions {
3014 if let Some(completion_position) = completion_position {
3015 let start_offset = selection_start.to_offset(buffer);
3016 let position_matches = start_offset == completion_position.to_offset(buffer);
3017 let continue_showing = if position_matches {
3018 if self.snippet_stack.is_empty() {
3019 buffer.char_kind_before(start_offset, true) == Some(CharKind::Word)
3020 } else {
3021 // Snippet choices can be shown even when the cursor is in whitespace.
3022 // Dismissing the menu with actions like backspace is handled by
3023 // invalidation regions.
3024 true
3025 }
3026 } else {
3027 false
3028 };
3029
3030 if continue_showing {
3031 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
3032 } else {
3033 self.hide_context_menu(window, cx);
3034 }
3035 }
3036 }
3037
3038 hide_hover(self, cx);
3039
3040 if old_cursor_position.to_display_point(&display_map).row()
3041 != new_cursor_position.to_display_point(&display_map).row()
3042 {
3043 self.available_code_actions.take();
3044 }
3045 self.refresh_code_actions(window, cx);
3046 self.refresh_document_highlights(cx);
3047 self.refresh_selected_text_highlights(false, window, cx);
3048 refresh_matching_bracket_highlights(self, window, cx);
3049 self.update_visible_edit_prediction(window, cx);
3050 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3051 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
3052 self.inline_blame_popover.take();
3053 if self.git_blame_inline_enabled {
3054 self.start_inline_blame_timer(window, cx);
3055 }
3056 }
3057
3058 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3059 cx.emit(EditorEvent::SelectionsChanged { local });
3060
3061 let selections = &self.selections.disjoint;
3062 if selections.len() == 1 {
3063 cx.emit(SearchEvent::ActiveMatchChanged)
3064 }
3065 if local {
3066 if let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3067 let inmemory_selections = selections
3068 .iter()
3069 .map(|s| {
3070 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3071 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3072 })
3073 .collect();
3074 self.update_restoration_data(cx, |data| {
3075 data.selections = inmemory_selections;
3076 });
3077
3078 if WorkspaceSettings::get(None, cx).restore_on_startup
3079 != RestoreOnStartupBehavior::None
3080 {
3081 if let Some(workspace_id) =
3082 self.workspace.as_ref().and_then(|workspace| workspace.1)
3083 {
3084 let snapshot = self.buffer().read(cx).snapshot(cx);
3085 let selections = selections.clone();
3086 let background_executor = cx.background_executor().clone();
3087 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3088 self.serialize_selections = cx.background_spawn(async move {
3089 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3090 let db_selections = selections
3091 .iter()
3092 .map(|selection| {
3093 (
3094 selection.start.to_offset(&snapshot),
3095 selection.end.to_offset(&snapshot),
3096 )
3097 })
3098 .collect();
3099
3100 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3101 .await
3102 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
3103 .log_err();
3104 });
3105 }
3106 }
3107 }
3108 }
3109
3110 cx.notify();
3111 }
3112
3113 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3114 use text::ToOffset as _;
3115 use text::ToPoint as _;
3116
3117 if self.mode.is_minimap()
3118 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3119 {
3120 return;
3121 }
3122
3123 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
3124 return;
3125 };
3126
3127 let snapshot = singleton.read(cx).snapshot();
3128 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
3129 let display_snapshot = display_map.snapshot(cx);
3130
3131 display_snapshot
3132 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
3133 .map(|fold| {
3134 fold.range.start.text_anchor.to_point(&snapshot)
3135 ..fold.range.end.text_anchor.to_point(&snapshot)
3136 })
3137 .collect()
3138 });
3139 self.update_restoration_data(cx, |data| {
3140 data.folds = inmemory_folds;
3141 });
3142
3143 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
3144 return;
3145 };
3146 let background_executor = cx.background_executor().clone();
3147 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3148 let db_folds = self.display_map.update(cx, |display_map, cx| {
3149 display_map
3150 .snapshot(cx)
3151 .folds_in_range(0..snapshot.len())
3152 .map(|fold| {
3153 (
3154 fold.range.start.text_anchor.to_offset(&snapshot),
3155 fold.range.end.text_anchor.to_offset(&snapshot),
3156 )
3157 })
3158 .collect()
3159 });
3160 self.serialize_folds = cx.background_spawn(async move {
3161 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3162 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3163 .await
3164 .with_context(|| {
3165 format!(
3166 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3167 )
3168 })
3169 .log_err();
3170 });
3171 }
3172
3173 pub fn sync_selections(
3174 &mut self,
3175 other: Entity<Editor>,
3176 cx: &mut Context<Self>,
3177 ) -> gpui::Subscription {
3178 let other_selections = other.read(cx).selections.disjoint.to_vec();
3179 self.selections.change_with(cx, |selections| {
3180 selections.select_anchors(other_selections);
3181 });
3182
3183 let other_subscription =
3184 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
3185 EditorEvent::SelectionsChanged { local: true } => {
3186 let other_selections = other.read(cx).selections.disjoint.to_vec();
3187 if other_selections.is_empty() {
3188 return;
3189 }
3190 this.selections.change_with(cx, |selections| {
3191 selections.select_anchors(other_selections);
3192 });
3193 }
3194 _ => {}
3195 });
3196
3197 let this_subscription =
3198 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
3199 EditorEvent::SelectionsChanged { local: true } => {
3200 let these_selections = this.selections.disjoint.to_vec();
3201 if these_selections.is_empty() {
3202 return;
3203 }
3204 other.update(cx, |other_editor, cx| {
3205 other_editor.selections.change_with(cx, |selections| {
3206 selections.select_anchors(these_selections);
3207 })
3208 });
3209 }
3210 _ => {}
3211 });
3212
3213 Subscription::join(other_subscription, this_subscription)
3214 }
3215
3216 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3217 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3218 /// effects of selection change occur at the end of the transaction.
3219 pub fn change_selections<R>(
3220 &mut self,
3221 effects: SelectionEffects,
3222 window: &mut Window,
3223 cx: &mut Context<Self>,
3224 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3225 ) -> R {
3226 if let Some(state) = &mut self.deferred_selection_effects_state {
3227 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3228 state.effects.completions = effects.completions;
3229 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3230 let (changed, result) = self.selections.change_with(cx, change);
3231 state.changed |= changed;
3232 return result;
3233 }
3234 let mut state = DeferredSelectionEffectsState {
3235 changed: false,
3236 effects,
3237 old_cursor_position: self.selections.newest_anchor().head(),
3238 history_entry: SelectionHistoryEntry {
3239 selections: self.selections.disjoint_anchors(),
3240 select_next_state: self.select_next_state.clone(),
3241 select_prev_state: self.select_prev_state.clone(),
3242 add_selections_state: self.add_selections_state.clone(),
3243 },
3244 };
3245 let (changed, result) = self.selections.change_with(cx, change);
3246 state.changed = state.changed || changed;
3247 if self.defer_selection_effects {
3248 self.deferred_selection_effects_state = Some(state);
3249 } else {
3250 self.apply_selection_effects(state, window, cx);
3251 }
3252 result
3253 }
3254
3255 /// Defers the effects of selection change, so that the effects of multiple calls to
3256 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3257 /// to selection history and the state of popovers based on selection position aren't
3258 /// erroneously updated.
3259 pub fn with_selection_effects_deferred<R>(
3260 &mut self,
3261 window: &mut Window,
3262 cx: &mut Context<Self>,
3263 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3264 ) -> R {
3265 let already_deferred = self.defer_selection_effects;
3266 self.defer_selection_effects = true;
3267 let result = update(self, window, cx);
3268 if !already_deferred {
3269 self.defer_selection_effects = false;
3270 if let Some(state) = self.deferred_selection_effects_state.take() {
3271 self.apply_selection_effects(state, window, cx);
3272 }
3273 }
3274 result
3275 }
3276
3277 fn apply_selection_effects(
3278 &mut self,
3279 state: DeferredSelectionEffectsState,
3280 window: &mut Window,
3281 cx: &mut Context<Self>,
3282 ) {
3283 if state.changed {
3284 self.selection_history.push(state.history_entry);
3285
3286 if let Some(autoscroll) = state.effects.scroll {
3287 self.request_autoscroll(autoscroll, cx);
3288 }
3289
3290 let old_cursor_position = &state.old_cursor_position;
3291
3292 self.selections_did_change(true, &old_cursor_position, state.effects, window, cx);
3293
3294 if self.should_open_signature_help_automatically(&old_cursor_position, cx) {
3295 self.show_signature_help(&ShowSignatureHelp, window, cx);
3296 }
3297 }
3298 }
3299
3300 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3301 where
3302 I: IntoIterator<Item = (Range<S>, T)>,
3303 S: ToOffset,
3304 T: Into<Arc<str>>,
3305 {
3306 if self.read_only(cx) {
3307 return;
3308 }
3309
3310 self.buffer
3311 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3312 }
3313
3314 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3315 where
3316 I: IntoIterator<Item = (Range<S>, T)>,
3317 S: ToOffset,
3318 T: Into<Arc<str>>,
3319 {
3320 if self.read_only(cx) {
3321 return;
3322 }
3323
3324 self.buffer.update(cx, |buffer, cx| {
3325 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3326 });
3327 }
3328
3329 pub fn edit_with_block_indent<I, S, T>(
3330 &mut self,
3331 edits: I,
3332 original_indent_columns: Vec<Option<u32>>,
3333 cx: &mut Context<Self>,
3334 ) where
3335 I: IntoIterator<Item = (Range<S>, T)>,
3336 S: ToOffset,
3337 T: Into<Arc<str>>,
3338 {
3339 if self.read_only(cx) {
3340 return;
3341 }
3342
3343 self.buffer.update(cx, |buffer, cx| {
3344 buffer.edit(
3345 edits,
3346 Some(AutoindentMode::Block {
3347 original_indent_columns,
3348 }),
3349 cx,
3350 )
3351 });
3352 }
3353
3354 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3355 self.hide_context_menu(window, cx);
3356
3357 match phase {
3358 SelectPhase::Begin {
3359 position,
3360 add,
3361 click_count,
3362 } => self.begin_selection(position, add, click_count, window, cx),
3363 SelectPhase::BeginColumnar {
3364 position,
3365 goal_column,
3366 reset,
3367 mode,
3368 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3369 SelectPhase::Extend {
3370 position,
3371 click_count,
3372 } => self.extend_selection(position, click_count, window, cx),
3373 SelectPhase::Update {
3374 position,
3375 goal_column,
3376 scroll_delta,
3377 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3378 SelectPhase::End => self.end_selection(window, cx),
3379 }
3380 }
3381
3382 fn extend_selection(
3383 &mut self,
3384 position: DisplayPoint,
3385 click_count: usize,
3386 window: &mut Window,
3387 cx: &mut Context<Self>,
3388 ) {
3389 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3390 let tail = self.selections.newest::<usize>(cx).tail();
3391 self.begin_selection(position, false, click_count, window, cx);
3392
3393 let position = position.to_offset(&display_map, Bias::Left);
3394 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3395
3396 let mut pending_selection = self
3397 .selections
3398 .pending_anchor()
3399 .expect("extend_selection not called with pending selection");
3400 if position >= tail {
3401 pending_selection.start = tail_anchor;
3402 } else {
3403 pending_selection.end = tail_anchor;
3404 pending_selection.reversed = true;
3405 }
3406
3407 let mut pending_mode = self.selections.pending_mode().unwrap();
3408 match &mut pending_mode {
3409 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3410 _ => {}
3411 }
3412
3413 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3414 SelectionEffects::scroll(Autoscroll::fit())
3415 } else {
3416 SelectionEffects::no_scroll()
3417 };
3418
3419 self.change_selections(effects, window, cx, |s| {
3420 s.set_pending(pending_selection, pending_mode)
3421 });
3422 }
3423
3424 fn begin_selection(
3425 &mut self,
3426 position: DisplayPoint,
3427 add: bool,
3428 click_count: usize,
3429 window: &mut Window,
3430 cx: &mut Context<Self>,
3431 ) {
3432 if !self.focus_handle.is_focused(window) {
3433 self.last_focused_descendant = None;
3434 window.focus(&self.focus_handle);
3435 }
3436
3437 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3438 let buffer = &display_map.buffer_snapshot;
3439 let position = display_map.clip_point(position, Bias::Left);
3440
3441 let start;
3442 let end;
3443 let mode;
3444 let mut auto_scroll;
3445 match click_count {
3446 1 => {
3447 start = buffer.anchor_before(position.to_point(&display_map));
3448 end = start;
3449 mode = SelectMode::Character;
3450 auto_scroll = true;
3451 }
3452 2 => {
3453 let position = display_map
3454 .clip_point(position, Bias::Left)
3455 .to_offset(&display_map, Bias::Left);
3456 let (range, _) = buffer.surrounding_word(position, false);
3457 start = buffer.anchor_before(range.start);
3458 end = buffer.anchor_before(range.end);
3459 mode = SelectMode::Word(start..end);
3460 auto_scroll = true;
3461 }
3462 3 => {
3463 let position = display_map
3464 .clip_point(position, Bias::Left)
3465 .to_point(&display_map);
3466 let line_start = display_map.prev_line_boundary(position).0;
3467 let next_line_start = buffer.clip_point(
3468 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3469 Bias::Left,
3470 );
3471 start = buffer.anchor_before(line_start);
3472 end = buffer.anchor_before(next_line_start);
3473 mode = SelectMode::Line(start..end);
3474 auto_scroll = true;
3475 }
3476 _ => {
3477 start = buffer.anchor_before(0);
3478 end = buffer.anchor_before(buffer.len());
3479 mode = SelectMode::All;
3480 auto_scroll = false;
3481 }
3482 }
3483 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3484
3485 let point_to_delete: Option<usize> = {
3486 let selected_points: Vec<Selection<Point>> =
3487 self.selections.disjoint_in_range(start..end, cx);
3488
3489 if !add || click_count > 1 {
3490 None
3491 } else if !selected_points.is_empty() {
3492 Some(selected_points[0].id)
3493 } else {
3494 let clicked_point_already_selected =
3495 self.selections.disjoint.iter().find(|selection| {
3496 selection.start.to_point(buffer) == start.to_point(buffer)
3497 || selection.end.to_point(buffer) == end.to_point(buffer)
3498 });
3499
3500 clicked_point_already_selected.map(|selection| selection.id)
3501 }
3502 };
3503
3504 let selections_count = self.selections.count();
3505 let effects = if auto_scroll {
3506 SelectionEffects::default()
3507 } else {
3508 SelectionEffects::no_scroll()
3509 };
3510
3511 self.change_selections(effects, window, cx, |s| {
3512 if let Some(point_to_delete) = point_to_delete {
3513 s.delete(point_to_delete);
3514
3515 if selections_count == 1 {
3516 s.set_pending_anchor_range(start..end, mode);
3517 }
3518 } else {
3519 if !add {
3520 s.clear_disjoint();
3521 }
3522
3523 s.set_pending_anchor_range(start..end, mode);
3524 }
3525 });
3526 }
3527
3528 fn begin_columnar_selection(
3529 &mut self,
3530 position: DisplayPoint,
3531 goal_column: u32,
3532 reset: bool,
3533 mode: ColumnarMode,
3534 window: &mut Window,
3535 cx: &mut Context<Self>,
3536 ) {
3537 if !self.focus_handle.is_focused(window) {
3538 self.last_focused_descendant = None;
3539 window.focus(&self.focus_handle);
3540 }
3541
3542 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3543
3544 if reset {
3545 let pointer_position = display_map
3546 .buffer_snapshot
3547 .anchor_before(position.to_point(&display_map));
3548
3549 self.change_selections(
3550 SelectionEffects::scroll(Autoscroll::newest()),
3551 window,
3552 cx,
3553 |s| {
3554 s.clear_disjoint();
3555 s.set_pending_anchor_range(
3556 pointer_position..pointer_position,
3557 SelectMode::Character,
3558 );
3559 },
3560 );
3561 };
3562
3563 let tail = self.selections.newest::<Point>(cx).tail();
3564 let selection_anchor = display_map.buffer_snapshot.anchor_before(tail);
3565 self.columnar_selection_state = match mode {
3566 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3567 selection_tail: selection_anchor,
3568 display_point: if reset {
3569 if position.column() != goal_column {
3570 Some(DisplayPoint::new(position.row(), goal_column))
3571 } else {
3572 None
3573 }
3574 } else {
3575 None
3576 },
3577 }),
3578 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3579 selection_tail: selection_anchor,
3580 }),
3581 };
3582
3583 if !reset {
3584 self.select_columns(position, goal_column, &display_map, window, cx);
3585 }
3586 }
3587
3588 fn update_selection(
3589 &mut self,
3590 position: DisplayPoint,
3591 goal_column: u32,
3592 scroll_delta: gpui::Point<f32>,
3593 window: &mut Window,
3594 cx: &mut Context<Self>,
3595 ) {
3596 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3597
3598 if self.columnar_selection_state.is_some() {
3599 self.select_columns(position, goal_column, &display_map, window, cx);
3600 } else if let Some(mut pending) = self.selections.pending_anchor() {
3601 let buffer = &display_map.buffer_snapshot;
3602 let head;
3603 let tail;
3604 let mode = self.selections.pending_mode().unwrap();
3605 match &mode {
3606 SelectMode::Character => {
3607 head = position.to_point(&display_map);
3608 tail = pending.tail().to_point(buffer);
3609 }
3610 SelectMode::Word(original_range) => {
3611 let offset = display_map
3612 .clip_point(position, Bias::Left)
3613 .to_offset(&display_map, Bias::Left);
3614 let original_range = original_range.to_offset(buffer);
3615
3616 let head_offset = if buffer.is_inside_word(offset, false)
3617 || original_range.contains(&offset)
3618 {
3619 let (word_range, _) = buffer.surrounding_word(offset, false);
3620 if word_range.start < original_range.start {
3621 word_range.start
3622 } else {
3623 word_range.end
3624 }
3625 } else {
3626 offset
3627 };
3628
3629 head = head_offset.to_point(buffer);
3630 if head_offset <= original_range.start {
3631 tail = original_range.end.to_point(buffer);
3632 } else {
3633 tail = original_range.start.to_point(buffer);
3634 }
3635 }
3636 SelectMode::Line(original_range) => {
3637 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3638
3639 let position = display_map
3640 .clip_point(position, Bias::Left)
3641 .to_point(&display_map);
3642 let line_start = display_map.prev_line_boundary(position).0;
3643 let next_line_start = buffer.clip_point(
3644 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3645 Bias::Left,
3646 );
3647
3648 if line_start < original_range.start {
3649 head = line_start
3650 } else {
3651 head = next_line_start
3652 }
3653
3654 if head <= original_range.start {
3655 tail = original_range.end;
3656 } else {
3657 tail = original_range.start;
3658 }
3659 }
3660 SelectMode::All => {
3661 return;
3662 }
3663 };
3664
3665 if head < tail {
3666 pending.start = buffer.anchor_before(head);
3667 pending.end = buffer.anchor_before(tail);
3668 pending.reversed = true;
3669 } else {
3670 pending.start = buffer.anchor_before(tail);
3671 pending.end = buffer.anchor_before(head);
3672 pending.reversed = false;
3673 }
3674
3675 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3676 s.set_pending(pending, mode);
3677 });
3678 } else {
3679 log::error!("update_selection dispatched with no pending selection");
3680 return;
3681 }
3682
3683 self.apply_scroll_delta(scroll_delta, window, cx);
3684 cx.notify();
3685 }
3686
3687 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3688 self.columnar_selection_state.take();
3689 if self.selections.pending_anchor().is_some() {
3690 let selections = self.selections.all::<usize>(cx);
3691 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3692 s.select(selections);
3693 s.clear_pending();
3694 });
3695 }
3696 }
3697
3698 fn select_columns(
3699 &mut self,
3700 head: DisplayPoint,
3701 goal_column: u32,
3702 display_map: &DisplaySnapshot,
3703 window: &mut Window,
3704 cx: &mut Context<Self>,
3705 ) {
3706 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3707 return;
3708 };
3709
3710 let tail = match columnar_state {
3711 ColumnarSelectionState::FromMouse {
3712 selection_tail,
3713 display_point,
3714 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(&display_map)),
3715 ColumnarSelectionState::FromSelection { selection_tail } => {
3716 selection_tail.to_display_point(&display_map)
3717 }
3718 };
3719
3720 let start_row = cmp::min(tail.row(), head.row());
3721 let end_row = cmp::max(tail.row(), head.row());
3722 let start_column = cmp::min(tail.column(), goal_column);
3723 let end_column = cmp::max(tail.column(), goal_column);
3724 let reversed = start_column < tail.column();
3725
3726 let selection_ranges = (start_row.0..=end_row.0)
3727 .map(DisplayRow)
3728 .filter_map(|row| {
3729 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3730 || start_column <= display_map.line_len(row))
3731 && !display_map.is_block_line(row)
3732 {
3733 let start = display_map
3734 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3735 .to_point(display_map);
3736 let end = display_map
3737 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3738 .to_point(display_map);
3739 if reversed {
3740 Some(end..start)
3741 } else {
3742 Some(start..end)
3743 }
3744 } else {
3745 None
3746 }
3747 })
3748 .collect::<Vec<_>>();
3749
3750 let ranges = match columnar_state {
3751 ColumnarSelectionState::FromMouse { .. } => {
3752 let mut non_empty_ranges = selection_ranges
3753 .iter()
3754 .filter(|selection_range| selection_range.start != selection_range.end)
3755 .peekable();
3756 if non_empty_ranges.peek().is_some() {
3757 non_empty_ranges.cloned().collect()
3758 } else {
3759 selection_ranges
3760 }
3761 }
3762 _ => selection_ranges,
3763 };
3764
3765 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3766 s.select_ranges(ranges);
3767 });
3768 cx.notify();
3769 }
3770
3771 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3772 self.selections
3773 .all_adjusted(cx)
3774 .iter()
3775 .any(|selection| !selection.is_empty())
3776 }
3777
3778 pub fn has_pending_nonempty_selection(&self) -> bool {
3779 let pending_nonempty_selection = match self.selections.pending_anchor() {
3780 Some(Selection { start, end, .. }) => start != end,
3781 None => false,
3782 };
3783
3784 pending_nonempty_selection
3785 || (self.columnar_selection_state.is_some() && self.selections.disjoint.len() > 1)
3786 }
3787
3788 pub fn has_pending_selection(&self) -> bool {
3789 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
3790 }
3791
3792 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3793 self.selection_mark_mode = false;
3794 self.selection_drag_state = SelectionDragState::None;
3795
3796 if self.clear_expanded_diff_hunks(cx) {
3797 cx.notify();
3798 return;
3799 }
3800 if self.dismiss_menus_and_popups(true, window, cx) {
3801 return;
3802 }
3803
3804 if self.mode.is_full()
3805 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
3806 {
3807 return;
3808 }
3809
3810 cx.propagate();
3811 }
3812
3813 pub fn dismiss_menus_and_popups(
3814 &mut self,
3815 is_user_requested: bool,
3816 window: &mut Window,
3817 cx: &mut Context<Self>,
3818 ) -> bool {
3819 if self.take_rename(false, window, cx).is_some() {
3820 return true;
3821 }
3822
3823 if hide_hover(self, cx) {
3824 return true;
3825 }
3826
3827 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3828 return true;
3829 }
3830
3831 if self.hide_context_menu(window, cx).is_some() {
3832 return true;
3833 }
3834
3835 if self.mouse_context_menu.take().is_some() {
3836 return true;
3837 }
3838
3839 if is_user_requested && self.discard_edit_prediction(true, cx) {
3840 return true;
3841 }
3842
3843 if self.snippet_stack.pop().is_some() {
3844 return true;
3845 }
3846
3847 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3848 self.dismiss_diagnostics(cx);
3849 return true;
3850 }
3851
3852 false
3853 }
3854
3855 fn linked_editing_ranges_for(
3856 &self,
3857 selection: Range<text::Anchor>,
3858 cx: &App,
3859 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3860 if self.linked_edit_ranges.is_empty() {
3861 return None;
3862 }
3863 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3864 selection.end.buffer_id.and_then(|end_buffer_id| {
3865 if selection.start.buffer_id != Some(end_buffer_id) {
3866 return None;
3867 }
3868 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3869 let snapshot = buffer.read(cx).snapshot();
3870 self.linked_edit_ranges
3871 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3872 .map(|ranges| (ranges, snapshot, buffer))
3873 })?;
3874 use text::ToOffset as TO;
3875 // find offset from the start of current range to current cursor position
3876 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3877
3878 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3879 let start_difference = start_offset - start_byte_offset;
3880 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3881 let end_difference = end_offset - start_byte_offset;
3882 // Current range has associated linked ranges.
3883 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3884 for range in linked_ranges.iter() {
3885 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3886 let end_offset = start_offset + end_difference;
3887 let start_offset = start_offset + start_difference;
3888 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3889 continue;
3890 }
3891 if self.selections.disjoint_anchor_ranges().any(|s| {
3892 if s.start.buffer_id != selection.start.buffer_id
3893 || s.end.buffer_id != selection.end.buffer_id
3894 {
3895 return false;
3896 }
3897 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3898 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3899 }) {
3900 continue;
3901 }
3902 let start = buffer_snapshot.anchor_after(start_offset);
3903 let end = buffer_snapshot.anchor_after(end_offset);
3904 linked_edits
3905 .entry(buffer.clone())
3906 .or_default()
3907 .push(start..end);
3908 }
3909 Some(linked_edits)
3910 }
3911
3912 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3913 let text: Arc<str> = text.into();
3914
3915 if self.read_only(cx) {
3916 return;
3917 }
3918
3919 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
3920
3921 let selections = self.selections.all_adjusted(cx);
3922 let mut bracket_inserted = false;
3923 let mut edits = Vec::new();
3924 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3925 let mut new_selections = Vec::with_capacity(selections.len());
3926 let mut new_autoclose_regions = Vec::new();
3927 let snapshot = self.buffer.read(cx).read(cx);
3928 let mut clear_linked_edit_ranges = false;
3929
3930 for (selection, autoclose_region) in
3931 self.selections_with_autoclose_regions(selections, &snapshot)
3932 {
3933 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3934 // Determine if the inserted text matches the opening or closing
3935 // bracket of any of this language's bracket pairs.
3936 let mut bracket_pair = None;
3937 let mut is_bracket_pair_start = false;
3938 let mut is_bracket_pair_end = false;
3939 if !text.is_empty() {
3940 let mut bracket_pair_matching_end = None;
3941 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3942 // and they are removing the character that triggered IME popup.
3943 for (pair, enabled) in scope.brackets() {
3944 if !pair.close && !pair.surround {
3945 continue;
3946 }
3947
3948 if enabled && pair.start.ends_with(text.as_ref()) {
3949 let prefix_len = pair.start.len() - text.len();
3950 let preceding_text_matches_prefix = prefix_len == 0
3951 || (selection.start.column >= (prefix_len as u32)
3952 && snapshot.contains_str_at(
3953 Point::new(
3954 selection.start.row,
3955 selection.start.column - (prefix_len as u32),
3956 ),
3957 &pair.start[..prefix_len],
3958 ));
3959 if preceding_text_matches_prefix {
3960 bracket_pair = Some(pair.clone());
3961 is_bracket_pair_start = true;
3962 break;
3963 }
3964 }
3965 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
3966 {
3967 // take first bracket pair matching end, but don't break in case a later bracket
3968 // pair matches start
3969 bracket_pair_matching_end = Some(pair.clone());
3970 }
3971 }
3972 if let Some(end) = bracket_pair_matching_end
3973 && bracket_pair.is_none()
3974 {
3975 bracket_pair = Some(end);
3976 is_bracket_pair_end = true;
3977 }
3978 }
3979
3980 if let Some(bracket_pair) = bracket_pair {
3981 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
3982 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
3983 let auto_surround =
3984 self.use_auto_surround && snapshot_settings.use_auto_surround;
3985 if selection.is_empty() {
3986 if is_bracket_pair_start {
3987 // If the inserted text is a suffix of an opening bracket and the
3988 // selection is preceded by the rest of the opening bracket, then
3989 // insert the closing bracket.
3990 let following_text_allows_autoclose = snapshot
3991 .chars_at(selection.start)
3992 .next()
3993 .map_or(true, |c| scope.should_autoclose_before(c));
3994
3995 let preceding_text_allows_autoclose = selection.start.column == 0
3996 || snapshot.reversed_chars_at(selection.start).next().map_or(
3997 true,
3998 |c| {
3999 bracket_pair.start != bracket_pair.end
4000 || !snapshot
4001 .char_classifier_at(selection.start)
4002 .is_word(c)
4003 },
4004 );
4005
4006 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4007 && bracket_pair.start.len() == 1
4008 {
4009 let target = bracket_pair.start.chars().next().unwrap();
4010 let current_line_count = snapshot
4011 .reversed_chars_at(selection.start)
4012 .take_while(|&c| c != '\n')
4013 .filter(|&c| c == target)
4014 .count();
4015 current_line_count % 2 == 1
4016 } else {
4017 false
4018 };
4019
4020 if autoclose
4021 && bracket_pair.close
4022 && following_text_allows_autoclose
4023 && preceding_text_allows_autoclose
4024 && !is_closing_quote
4025 {
4026 let anchor = snapshot.anchor_before(selection.end);
4027 new_selections.push((selection.map(|_| anchor), text.len()));
4028 new_autoclose_regions.push((
4029 anchor,
4030 text.len(),
4031 selection.id,
4032 bracket_pair.clone(),
4033 ));
4034 edits.push((
4035 selection.range(),
4036 format!("{}{}", text, bracket_pair.end).into(),
4037 ));
4038 bracket_inserted = true;
4039 continue;
4040 }
4041 }
4042
4043 if let Some(region) = autoclose_region {
4044 // If the selection is followed by an auto-inserted closing bracket,
4045 // then don't insert that closing bracket again; just move the selection
4046 // past the closing bracket.
4047 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4048 && text.as_ref() == region.pair.end.as_str()
4049 && snapshot.contains_str_at(region.range.end, text.as_ref());
4050 if should_skip {
4051 let anchor = snapshot.anchor_after(selection.end);
4052 new_selections
4053 .push((selection.map(|_| anchor), region.pair.end.len()));
4054 continue;
4055 }
4056 }
4057
4058 let always_treat_brackets_as_autoclosed = snapshot
4059 .language_settings_at(selection.start, cx)
4060 .always_treat_brackets_as_autoclosed;
4061 if always_treat_brackets_as_autoclosed
4062 && is_bracket_pair_end
4063 && snapshot.contains_str_at(selection.end, text.as_ref())
4064 {
4065 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4066 // and the inserted text is a closing bracket and the selection is followed
4067 // by the closing bracket then move the selection past the closing bracket.
4068 let anchor = snapshot.anchor_after(selection.end);
4069 new_selections.push((selection.map(|_| anchor), text.len()));
4070 continue;
4071 }
4072 }
4073 // If an opening bracket is 1 character long and is typed while
4074 // text is selected, then surround that text with the bracket pair.
4075 else if auto_surround
4076 && bracket_pair.surround
4077 && is_bracket_pair_start
4078 && bracket_pair.start.chars().count() == 1
4079 {
4080 edits.push((selection.start..selection.start, text.clone()));
4081 edits.push((
4082 selection.end..selection.end,
4083 bracket_pair.end.as_str().into(),
4084 ));
4085 bracket_inserted = true;
4086 new_selections.push((
4087 Selection {
4088 id: selection.id,
4089 start: snapshot.anchor_after(selection.start),
4090 end: snapshot.anchor_before(selection.end),
4091 reversed: selection.reversed,
4092 goal: selection.goal,
4093 },
4094 0,
4095 ));
4096 continue;
4097 }
4098 }
4099 }
4100
4101 if self.auto_replace_emoji_shortcode
4102 && selection.is_empty()
4103 && text.as_ref().ends_with(':')
4104 {
4105 if let Some(possible_emoji_short_code) =
4106 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4107 {
4108 if !possible_emoji_short_code.is_empty() {
4109 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
4110 let emoji_shortcode_start = Point::new(
4111 selection.start.row,
4112 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4113 );
4114
4115 // Remove shortcode from buffer
4116 edits.push((
4117 emoji_shortcode_start..selection.start,
4118 "".to_string().into(),
4119 ));
4120 new_selections.push((
4121 Selection {
4122 id: selection.id,
4123 start: snapshot.anchor_after(emoji_shortcode_start),
4124 end: snapshot.anchor_before(selection.start),
4125 reversed: selection.reversed,
4126 goal: selection.goal,
4127 },
4128 0,
4129 ));
4130
4131 // Insert emoji
4132 let selection_start_anchor = snapshot.anchor_after(selection.start);
4133 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4134 edits.push((selection.start..selection.end, emoji.to_string().into()));
4135
4136 continue;
4137 }
4138 }
4139 }
4140 }
4141
4142 // If not handling any auto-close operation, then just replace the selected
4143 // text with the given input and move the selection to the end of the
4144 // newly inserted text.
4145 let anchor = snapshot.anchor_after(selection.end);
4146 if !self.linked_edit_ranges.is_empty() {
4147 let start_anchor = snapshot.anchor_before(selection.start);
4148
4149 let is_word_char = text.chars().next().map_or(true, |char| {
4150 let classifier = snapshot
4151 .char_classifier_at(start_anchor.to_offset(&snapshot))
4152 .ignore_punctuation(true);
4153 classifier.is_word(char)
4154 });
4155
4156 if is_word_char {
4157 if let Some(ranges) = self
4158 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4159 {
4160 for (buffer, edits) in ranges {
4161 linked_edits
4162 .entry(buffer.clone())
4163 .or_default()
4164 .extend(edits.into_iter().map(|range| (range, text.clone())));
4165 }
4166 }
4167 } else {
4168 clear_linked_edit_ranges = true;
4169 }
4170 }
4171
4172 new_selections.push((selection.map(|_| anchor), 0));
4173 edits.push((selection.start..selection.end, text.clone()));
4174 }
4175
4176 drop(snapshot);
4177
4178 self.transact(window, cx, |this, window, cx| {
4179 if clear_linked_edit_ranges {
4180 this.linked_edit_ranges.clear();
4181 }
4182 let initial_buffer_versions =
4183 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4184
4185 this.buffer.update(cx, |buffer, cx| {
4186 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4187 });
4188 for (buffer, edits) in linked_edits {
4189 buffer.update(cx, |buffer, cx| {
4190 let snapshot = buffer.snapshot();
4191 let edits = edits
4192 .into_iter()
4193 .map(|(range, text)| {
4194 use text::ToPoint as TP;
4195 let end_point = TP::to_point(&range.end, &snapshot);
4196 let start_point = TP::to_point(&range.start, &snapshot);
4197 (start_point..end_point, text)
4198 })
4199 .sorted_by_key(|(range, _)| range.start);
4200 buffer.edit(edits, None, cx);
4201 })
4202 }
4203 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4204 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4205 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4206 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
4207 .zip(new_selection_deltas)
4208 .map(|(selection, delta)| Selection {
4209 id: selection.id,
4210 start: selection.start + delta,
4211 end: selection.end + delta,
4212 reversed: selection.reversed,
4213 goal: SelectionGoal::None,
4214 })
4215 .collect::<Vec<_>>();
4216
4217 let mut i = 0;
4218 for (position, delta, selection_id, pair) in new_autoclose_regions {
4219 let position = position.to_offset(&map.buffer_snapshot) + delta;
4220 let start = map.buffer_snapshot.anchor_before(position);
4221 let end = map.buffer_snapshot.anchor_after(position);
4222 while let Some(existing_state) = this.autoclose_regions.get(i) {
4223 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
4224 Ordering::Less => i += 1,
4225 Ordering::Greater => break,
4226 Ordering::Equal => {
4227 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
4228 Ordering::Less => i += 1,
4229 Ordering::Equal => break,
4230 Ordering::Greater => break,
4231 }
4232 }
4233 }
4234 }
4235 this.autoclose_regions.insert(
4236 i,
4237 AutocloseRegion {
4238 selection_id,
4239 range: start..end,
4240 pair,
4241 },
4242 );
4243 }
4244
4245 let had_active_edit_prediction = this.has_active_edit_prediction();
4246 this.change_selections(
4247 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4248 window,
4249 cx,
4250 |s| s.select(new_selections),
4251 );
4252
4253 if !bracket_inserted {
4254 if let Some(on_type_format_task) =
4255 this.trigger_on_type_formatting(text.to_string(), window, cx)
4256 {
4257 on_type_format_task.detach_and_log_err(cx);
4258 }
4259 }
4260
4261 let editor_settings = EditorSettings::get_global(cx);
4262 if bracket_inserted
4263 && (editor_settings.auto_signature_help
4264 || editor_settings.show_signature_help_after_edits)
4265 {
4266 this.show_signature_help(&ShowSignatureHelp, window, cx);
4267 }
4268
4269 let trigger_in_words =
4270 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4271 if this.hard_wrap.is_some() {
4272 let latest: Range<Point> = this.selections.newest(cx).range();
4273 if latest.is_empty()
4274 && this
4275 .buffer()
4276 .read(cx)
4277 .snapshot(cx)
4278 .line_len(MultiBufferRow(latest.start.row))
4279 == latest.start.column
4280 {
4281 this.rewrap_impl(
4282 RewrapOptions {
4283 override_language_settings: true,
4284 preserve_existing_whitespace: true,
4285 },
4286 cx,
4287 )
4288 }
4289 }
4290 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4291 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
4292 this.refresh_edit_prediction(true, false, window, cx);
4293 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4294 });
4295 }
4296
4297 fn find_possible_emoji_shortcode_at_position(
4298 snapshot: &MultiBufferSnapshot,
4299 position: Point,
4300 ) -> Option<String> {
4301 let mut chars = Vec::new();
4302 let mut found_colon = false;
4303 for char in snapshot.reversed_chars_at(position).take(100) {
4304 // Found a possible emoji shortcode in the middle of the buffer
4305 if found_colon {
4306 if char.is_whitespace() {
4307 chars.reverse();
4308 return Some(chars.iter().collect());
4309 }
4310 // If the previous character is not a whitespace, we are in the middle of a word
4311 // and we only want to complete the shortcode if the word is made up of other emojis
4312 let mut containing_word = String::new();
4313 for ch in snapshot
4314 .reversed_chars_at(position)
4315 .skip(chars.len() + 1)
4316 .take(100)
4317 {
4318 if ch.is_whitespace() {
4319 break;
4320 }
4321 containing_word.push(ch);
4322 }
4323 let containing_word = containing_word.chars().rev().collect::<String>();
4324 if util::word_consists_of_emojis(containing_word.as_str()) {
4325 chars.reverse();
4326 return Some(chars.iter().collect());
4327 }
4328 }
4329
4330 if char.is_whitespace() || !char.is_ascii() {
4331 return None;
4332 }
4333 if char == ':' {
4334 found_colon = true;
4335 } else {
4336 chars.push(char);
4337 }
4338 }
4339 // Found a possible emoji shortcode at the beginning of the buffer
4340 chars.reverse();
4341 Some(chars.iter().collect())
4342 }
4343
4344 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4345 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4346 self.transact(window, cx, |this, window, cx| {
4347 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4348 let selections = this.selections.all::<usize>(cx);
4349 let multi_buffer = this.buffer.read(cx);
4350 let buffer = multi_buffer.snapshot(cx);
4351 selections
4352 .iter()
4353 .map(|selection| {
4354 let start_point = selection.start.to_point(&buffer);
4355 let mut existing_indent =
4356 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4357 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4358 let start = selection.start;
4359 let end = selection.end;
4360 let selection_is_empty = start == end;
4361 let language_scope = buffer.language_scope_at(start);
4362 let (
4363 comment_delimiter,
4364 doc_delimiter,
4365 insert_extra_newline,
4366 indent_on_newline,
4367 indent_on_extra_newline,
4368 ) = if let Some(language) = &language_scope {
4369 let mut insert_extra_newline =
4370 insert_extra_newline_brackets(&buffer, start..end, language)
4371 || insert_extra_newline_tree_sitter(&buffer, start..end);
4372
4373 // Comment extension on newline is allowed only for cursor selections
4374 let comment_delimiter = maybe!({
4375 if !selection_is_empty {
4376 return None;
4377 }
4378
4379 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4380 return None;
4381 }
4382
4383 let delimiters = language.line_comment_prefixes();
4384 let max_len_of_delimiter =
4385 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4386 let (snapshot, range) =
4387 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4388
4389 let num_of_whitespaces = snapshot
4390 .chars_for_range(range.clone())
4391 .take_while(|c| c.is_whitespace())
4392 .count();
4393 let comment_candidate = snapshot
4394 .chars_for_range(range.clone())
4395 .skip(num_of_whitespaces)
4396 .take(max_len_of_delimiter)
4397 .collect::<String>();
4398 let (delimiter, trimmed_len) = delimiters
4399 .iter()
4400 .filter_map(|delimiter| {
4401 let prefix = delimiter.trim_end();
4402 if comment_candidate.starts_with(prefix) {
4403 Some((delimiter, prefix.len()))
4404 } else {
4405 None
4406 }
4407 })
4408 .max_by_key(|(_, len)| *len)?;
4409
4410 if let Some(BlockCommentConfig {
4411 start: block_start, ..
4412 }) = language.block_comment()
4413 {
4414 let block_start_trimmed = block_start.trim_end();
4415 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4416 let line_content = snapshot
4417 .chars_for_range(range)
4418 .skip(num_of_whitespaces)
4419 .take(block_start_trimmed.len())
4420 .collect::<String>();
4421
4422 if line_content.starts_with(block_start_trimmed) {
4423 return None;
4424 }
4425 }
4426 }
4427
4428 let cursor_is_placed_after_comment_marker =
4429 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4430 if cursor_is_placed_after_comment_marker {
4431 Some(delimiter.clone())
4432 } else {
4433 None
4434 }
4435 });
4436
4437 let mut indent_on_newline = IndentSize::spaces(0);
4438 let mut indent_on_extra_newline = IndentSize::spaces(0);
4439
4440 let doc_delimiter = maybe!({
4441 if !selection_is_empty {
4442 return None;
4443 }
4444
4445 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4446 return None;
4447 }
4448
4449 let BlockCommentConfig {
4450 start: start_tag,
4451 end: end_tag,
4452 prefix: delimiter,
4453 tab_size: len,
4454 } = language.documentation_comment()?;
4455 let is_within_block_comment = buffer
4456 .language_scope_at(start_point)
4457 .is_some_and(|scope| scope.override_name() == Some("comment"));
4458 if !is_within_block_comment {
4459 return None;
4460 }
4461
4462 let (snapshot, range) =
4463 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4464
4465 let num_of_whitespaces = snapshot
4466 .chars_for_range(range.clone())
4467 .take_while(|c| c.is_whitespace())
4468 .count();
4469
4470 // 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.
4471 let column = start_point.column;
4472 let cursor_is_after_start_tag = {
4473 let start_tag_len = start_tag.len();
4474 let start_tag_line = snapshot
4475 .chars_for_range(range.clone())
4476 .skip(num_of_whitespaces)
4477 .take(start_tag_len)
4478 .collect::<String>();
4479 if start_tag_line.starts_with(start_tag.as_ref()) {
4480 num_of_whitespaces + start_tag_len <= column as usize
4481 } else {
4482 false
4483 }
4484 };
4485
4486 let cursor_is_after_delimiter = {
4487 let delimiter_trim = delimiter.trim_end();
4488 let delimiter_line = snapshot
4489 .chars_for_range(range.clone())
4490 .skip(num_of_whitespaces)
4491 .take(delimiter_trim.len())
4492 .collect::<String>();
4493 if delimiter_line.starts_with(delimiter_trim) {
4494 num_of_whitespaces + delimiter_trim.len() <= column as usize
4495 } else {
4496 false
4497 }
4498 };
4499
4500 let cursor_is_before_end_tag_if_exists = {
4501 let mut char_position = 0u32;
4502 let mut end_tag_offset = None;
4503
4504 'outer: for chunk in snapshot.text_for_range(range.clone()) {
4505 if let Some(byte_pos) = chunk.find(&**end_tag) {
4506 let chars_before_match =
4507 chunk[..byte_pos].chars().count() as u32;
4508 end_tag_offset =
4509 Some(char_position + chars_before_match);
4510 break 'outer;
4511 }
4512 char_position += chunk.chars().count() as u32;
4513 }
4514
4515 if let Some(end_tag_offset) = end_tag_offset {
4516 let cursor_is_before_end_tag = column <= end_tag_offset;
4517 if cursor_is_after_start_tag {
4518 if cursor_is_before_end_tag {
4519 insert_extra_newline = true;
4520 }
4521 let cursor_is_at_start_of_end_tag =
4522 column == end_tag_offset;
4523 if cursor_is_at_start_of_end_tag {
4524 indent_on_extra_newline.len = *len;
4525 }
4526 }
4527 cursor_is_before_end_tag
4528 } else {
4529 true
4530 }
4531 };
4532
4533 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4534 && cursor_is_before_end_tag_if_exists
4535 {
4536 if cursor_is_after_start_tag {
4537 indent_on_newline.len = *len;
4538 }
4539 Some(delimiter.clone())
4540 } else {
4541 None
4542 }
4543 });
4544
4545 (
4546 comment_delimiter,
4547 doc_delimiter,
4548 insert_extra_newline,
4549 indent_on_newline,
4550 indent_on_extra_newline,
4551 )
4552 } else {
4553 (
4554 None,
4555 None,
4556 false,
4557 IndentSize::default(),
4558 IndentSize::default(),
4559 )
4560 };
4561
4562 let prevent_auto_indent = doc_delimiter.is_some();
4563 let delimiter = comment_delimiter.or(doc_delimiter);
4564
4565 let capacity_for_delimiter =
4566 delimiter.as_deref().map(str::len).unwrap_or_default();
4567 let mut new_text = String::with_capacity(
4568 1 + capacity_for_delimiter
4569 + existing_indent.len as usize
4570 + indent_on_newline.len as usize
4571 + indent_on_extra_newline.len as usize,
4572 );
4573 new_text.push('\n');
4574 new_text.extend(existing_indent.chars());
4575 new_text.extend(indent_on_newline.chars());
4576
4577 if let Some(delimiter) = &delimiter {
4578 new_text.push_str(delimiter);
4579 }
4580
4581 if insert_extra_newline {
4582 new_text.push('\n');
4583 new_text.extend(existing_indent.chars());
4584 new_text.extend(indent_on_extra_newline.chars());
4585 }
4586
4587 let anchor = buffer.anchor_after(end);
4588 let new_selection = selection.map(|_| anchor);
4589 (
4590 ((start..end, new_text), prevent_auto_indent),
4591 (insert_extra_newline, new_selection),
4592 )
4593 })
4594 .unzip()
4595 };
4596
4597 let mut auto_indent_edits = Vec::new();
4598 let mut edits = Vec::new();
4599 for (edit, prevent_auto_indent) in edits_with_flags {
4600 if prevent_auto_indent {
4601 edits.push(edit);
4602 } else {
4603 auto_indent_edits.push(edit);
4604 }
4605 }
4606 if !edits.is_empty() {
4607 this.edit(edits, cx);
4608 }
4609 if !auto_indent_edits.is_empty() {
4610 this.edit_with_autoindent(auto_indent_edits, cx);
4611 }
4612
4613 let buffer = this.buffer.read(cx).snapshot(cx);
4614 let new_selections = selection_info
4615 .into_iter()
4616 .map(|(extra_newline_inserted, new_selection)| {
4617 let mut cursor = new_selection.end.to_point(&buffer);
4618 if extra_newline_inserted {
4619 cursor.row -= 1;
4620 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4621 }
4622 new_selection.map(|_| cursor)
4623 })
4624 .collect();
4625
4626 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4627 this.refresh_edit_prediction(true, false, window, cx);
4628 });
4629 }
4630
4631 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4632 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4633
4634 let buffer = self.buffer.read(cx);
4635 let snapshot = buffer.snapshot(cx);
4636
4637 let mut edits = Vec::new();
4638 let mut rows = Vec::new();
4639
4640 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4641 let cursor = selection.head();
4642 let row = cursor.row;
4643
4644 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4645
4646 let newline = "\n".to_string();
4647 edits.push((start_of_line..start_of_line, newline));
4648
4649 rows.push(row + rows_inserted as u32);
4650 }
4651
4652 self.transact(window, cx, |editor, window, cx| {
4653 editor.edit(edits, cx);
4654
4655 editor.change_selections(Default::default(), window, cx, |s| {
4656 let mut index = 0;
4657 s.move_cursors_with(|map, _, _| {
4658 let row = rows[index];
4659 index += 1;
4660
4661 let point = Point::new(row, 0);
4662 let boundary = map.next_line_boundary(point).1;
4663 let clipped = map.clip_point(boundary, Bias::Left);
4664
4665 (clipped, SelectionGoal::None)
4666 });
4667 });
4668
4669 let mut indent_edits = Vec::new();
4670 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4671 for row in rows {
4672 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4673 for (row, indent) in indents {
4674 if indent.len == 0 {
4675 continue;
4676 }
4677
4678 let text = match indent.kind {
4679 IndentKind::Space => " ".repeat(indent.len as usize),
4680 IndentKind::Tab => "\t".repeat(indent.len as usize),
4681 };
4682 let point = Point::new(row.0, 0);
4683 indent_edits.push((point..point, text));
4684 }
4685 }
4686 editor.edit(indent_edits, cx);
4687 });
4688 }
4689
4690 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4691 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4692
4693 let buffer = self.buffer.read(cx);
4694 let snapshot = buffer.snapshot(cx);
4695
4696 let mut edits = Vec::new();
4697 let mut rows = Vec::new();
4698 let mut rows_inserted = 0;
4699
4700 for selection in self.selections.all_adjusted(cx) {
4701 let cursor = selection.head();
4702 let row = cursor.row;
4703
4704 let point = Point::new(row + 1, 0);
4705 let start_of_line = snapshot.clip_point(point, Bias::Left);
4706
4707 let newline = "\n".to_string();
4708 edits.push((start_of_line..start_of_line, newline));
4709
4710 rows_inserted += 1;
4711 rows.push(row + rows_inserted);
4712 }
4713
4714 self.transact(window, cx, |editor, window, cx| {
4715 editor.edit(edits, cx);
4716
4717 editor.change_selections(Default::default(), window, cx, |s| {
4718 let mut index = 0;
4719 s.move_cursors_with(|map, _, _| {
4720 let row = rows[index];
4721 index += 1;
4722
4723 let point = Point::new(row, 0);
4724 let boundary = map.next_line_boundary(point).1;
4725 let clipped = map.clip_point(boundary, Bias::Left);
4726
4727 (clipped, SelectionGoal::None)
4728 });
4729 });
4730
4731 let mut indent_edits = Vec::new();
4732 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4733 for row in rows {
4734 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4735 for (row, indent) in indents {
4736 if indent.len == 0 {
4737 continue;
4738 }
4739
4740 let text = match indent.kind {
4741 IndentKind::Space => " ".repeat(indent.len as usize),
4742 IndentKind::Tab => "\t".repeat(indent.len as usize),
4743 };
4744 let point = Point::new(row.0, 0);
4745 indent_edits.push((point..point, text));
4746 }
4747 }
4748 editor.edit(indent_edits, cx);
4749 });
4750 }
4751
4752 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4753 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4754 original_indent_columns: Vec::new(),
4755 });
4756 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4757 }
4758
4759 fn insert_with_autoindent_mode(
4760 &mut self,
4761 text: &str,
4762 autoindent_mode: Option<AutoindentMode>,
4763 window: &mut Window,
4764 cx: &mut Context<Self>,
4765 ) {
4766 if self.read_only(cx) {
4767 return;
4768 }
4769
4770 let text: Arc<str> = text.into();
4771 self.transact(window, cx, |this, window, cx| {
4772 let old_selections = this.selections.all_adjusted(cx);
4773 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4774 let anchors = {
4775 let snapshot = buffer.read(cx);
4776 old_selections
4777 .iter()
4778 .map(|s| {
4779 let anchor = snapshot.anchor_after(s.head());
4780 s.map(|_| anchor)
4781 })
4782 .collect::<Vec<_>>()
4783 };
4784 buffer.edit(
4785 old_selections
4786 .iter()
4787 .map(|s| (s.start..s.end, text.clone())),
4788 autoindent_mode,
4789 cx,
4790 );
4791 anchors
4792 });
4793
4794 this.change_selections(Default::default(), window, cx, |s| {
4795 s.select_anchors(selection_anchors);
4796 });
4797
4798 cx.notify();
4799 });
4800 }
4801
4802 fn trigger_completion_on_input(
4803 &mut self,
4804 text: &str,
4805 trigger_in_words: bool,
4806 window: &mut Window,
4807 cx: &mut Context<Self>,
4808 ) {
4809 let completions_source = self
4810 .context_menu
4811 .borrow()
4812 .as_ref()
4813 .and_then(|menu| match menu {
4814 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4815 CodeContextMenu::CodeActions(_) => None,
4816 });
4817
4818 match completions_source {
4819 Some(CompletionsMenuSource::Words) => {
4820 self.show_word_completions(&ShowWordCompletions, window, cx)
4821 }
4822 Some(CompletionsMenuSource::Normal)
4823 | Some(CompletionsMenuSource::SnippetChoices)
4824 | None
4825 if self.is_completion_trigger(
4826 text,
4827 trigger_in_words,
4828 completions_source.is_some(),
4829 cx,
4830 ) =>
4831 {
4832 self.show_completions(
4833 &ShowCompletions {
4834 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4835 },
4836 window,
4837 cx,
4838 )
4839 }
4840 _ => {
4841 self.hide_context_menu(window, cx);
4842 }
4843 }
4844 }
4845
4846 fn is_completion_trigger(
4847 &self,
4848 text: &str,
4849 trigger_in_words: bool,
4850 menu_is_open: bool,
4851 cx: &mut Context<Self>,
4852 ) -> bool {
4853 let position = self.selections.newest_anchor().head();
4854 let multibuffer = self.buffer.read(cx);
4855 let Some(buffer) = position
4856 .buffer_id
4857 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
4858 else {
4859 return false;
4860 };
4861
4862 if let Some(completion_provider) = &self.completion_provider {
4863 completion_provider.is_completion_trigger(
4864 &buffer,
4865 position.text_anchor,
4866 text,
4867 trigger_in_words,
4868 menu_is_open,
4869 cx,
4870 )
4871 } else {
4872 false
4873 }
4874 }
4875
4876 /// If any empty selections is touching the start of its innermost containing autoclose
4877 /// region, expand it to select the brackets.
4878 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4879 let selections = self.selections.all::<usize>(cx);
4880 let buffer = self.buffer.read(cx).read(cx);
4881 let new_selections = self
4882 .selections_with_autoclose_regions(selections, &buffer)
4883 .map(|(mut selection, region)| {
4884 if !selection.is_empty() {
4885 return selection;
4886 }
4887
4888 if let Some(region) = region {
4889 let mut range = region.range.to_offset(&buffer);
4890 if selection.start == range.start && range.start >= region.pair.start.len() {
4891 range.start -= region.pair.start.len();
4892 if buffer.contains_str_at(range.start, ®ion.pair.start)
4893 && buffer.contains_str_at(range.end, ®ion.pair.end)
4894 {
4895 range.end += region.pair.end.len();
4896 selection.start = range.start;
4897 selection.end = range.end;
4898
4899 return selection;
4900 }
4901 }
4902 }
4903
4904 let always_treat_brackets_as_autoclosed = buffer
4905 .language_settings_at(selection.start, cx)
4906 .always_treat_brackets_as_autoclosed;
4907
4908 if !always_treat_brackets_as_autoclosed {
4909 return selection;
4910 }
4911
4912 if let Some(scope) = buffer.language_scope_at(selection.start) {
4913 for (pair, enabled) in scope.brackets() {
4914 if !enabled || !pair.close {
4915 continue;
4916 }
4917
4918 if buffer.contains_str_at(selection.start, &pair.end) {
4919 let pair_start_len = pair.start.len();
4920 if buffer.contains_str_at(
4921 selection.start.saturating_sub(pair_start_len),
4922 &pair.start,
4923 ) {
4924 selection.start -= pair_start_len;
4925 selection.end += pair.end.len();
4926
4927 return selection;
4928 }
4929 }
4930 }
4931 }
4932
4933 selection
4934 })
4935 .collect();
4936
4937 drop(buffer);
4938 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
4939 selections.select(new_selections)
4940 });
4941 }
4942
4943 /// Iterate the given selections, and for each one, find the smallest surrounding
4944 /// autoclose region. This uses the ordering of the selections and the autoclose
4945 /// regions to avoid repeated comparisons.
4946 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
4947 &'a self,
4948 selections: impl IntoIterator<Item = Selection<D>>,
4949 buffer: &'a MultiBufferSnapshot,
4950 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
4951 let mut i = 0;
4952 let mut regions = self.autoclose_regions.as_slice();
4953 selections.into_iter().map(move |selection| {
4954 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
4955
4956 let mut enclosing = None;
4957 while let Some(pair_state) = regions.get(i) {
4958 if pair_state.range.end.to_offset(buffer) < range.start {
4959 regions = ®ions[i + 1..];
4960 i = 0;
4961 } else if pair_state.range.start.to_offset(buffer) > range.end {
4962 break;
4963 } else {
4964 if pair_state.selection_id == selection.id {
4965 enclosing = Some(pair_state);
4966 }
4967 i += 1;
4968 }
4969 }
4970
4971 (selection, enclosing)
4972 })
4973 }
4974
4975 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
4976 fn invalidate_autoclose_regions(
4977 &mut self,
4978 mut selections: &[Selection<Anchor>],
4979 buffer: &MultiBufferSnapshot,
4980 ) {
4981 self.autoclose_regions.retain(|state| {
4982 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
4983 return false;
4984 }
4985
4986 let mut i = 0;
4987 while let Some(selection) = selections.get(i) {
4988 if selection.end.cmp(&state.range.start, buffer).is_lt() {
4989 selections = &selections[1..];
4990 continue;
4991 }
4992 if selection.start.cmp(&state.range.end, buffer).is_gt() {
4993 break;
4994 }
4995 if selection.id == state.selection_id {
4996 return true;
4997 } else {
4998 i += 1;
4999 }
5000 }
5001 false
5002 });
5003 }
5004
5005 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5006 let offset = position.to_offset(buffer);
5007 let (word_range, kind) = buffer.surrounding_word(offset, true);
5008 if offset > word_range.start && kind == Some(CharKind::Word) {
5009 Some(
5010 buffer
5011 .text_for_range(word_range.start..offset)
5012 .collect::<String>(),
5013 )
5014 } else {
5015 None
5016 }
5017 }
5018
5019 pub fn toggle_inline_values(
5020 &mut self,
5021 _: &ToggleInlineValues,
5022 _: &mut Window,
5023 cx: &mut Context<Self>,
5024 ) {
5025 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
5026
5027 self.refresh_inline_values(cx);
5028 }
5029
5030 pub fn toggle_inlay_hints(
5031 &mut self,
5032 _: &ToggleInlayHints,
5033 _: &mut Window,
5034 cx: &mut Context<Self>,
5035 ) {
5036 self.refresh_inlay_hints(
5037 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
5038 cx,
5039 );
5040 }
5041
5042 pub fn inlay_hints_enabled(&self) -> bool {
5043 self.inlay_hint_cache.enabled
5044 }
5045
5046 pub fn inline_values_enabled(&self) -> bool {
5047 self.inline_value_cache.enabled
5048 }
5049
5050 #[cfg(any(test, feature = "test-support"))]
5051 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
5052 self.display_map
5053 .read(cx)
5054 .current_inlays()
5055 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
5056 .cloned()
5057 .collect()
5058 }
5059
5060 #[cfg(any(test, feature = "test-support"))]
5061 pub fn all_inlays(&self, cx: &App) -> Vec<Inlay> {
5062 self.display_map
5063 .read(cx)
5064 .current_inlays()
5065 .cloned()
5066 .collect()
5067 }
5068
5069 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
5070 if self.semantics_provider.is_none() || !self.mode.is_full() {
5071 return;
5072 }
5073
5074 let reason_description = reason.description();
5075 let ignore_debounce = matches!(
5076 reason,
5077 InlayHintRefreshReason::SettingsChange(_)
5078 | InlayHintRefreshReason::Toggle(_)
5079 | InlayHintRefreshReason::ExcerptsRemoved(_)
5080 | InlayHintRefreshReason::ModifiersChanged(_)
5081 );
5082 let (invalidate_cache, required_languages) = match reason {
5083 InlayHintRefreshReason::ModifiersChanged(enabled) => {
5084 match self.inlay_hint_cache.modifiers_override(enabled) {
5085 Some(enabled) => {
5086 if enabled {
5087 (InvalidationStrategy::RefreshRequested, None)
5088 } else {
5089 self.splice_inlays(
5090 &self
5091 .visible_inlay_hints(cx)
5092 .iter()
5093 .map(|inlay| inlay.id)
5094 .collect::<Vec<InlayId>>(),
5095 Vec::new(),
5096 cx,
5097 );
5098 return;
5099 }
5100 }
5101 None => return,
5102 }
5103 }
5104 InlayHintRefreshReason::Toggle(enabled) => {
5105 if self.inlay_hint_cache.toggle(enabled) {
5106 if enabled {
5107 (InvalidationStrategy::RefreshRequested, None)
5108 } else {
5109 self.splice_inlays(
5110 &self
5111 .visible_inlay_hints(cx)
5112 .iter()
5113 .map(|inlay| inlay.id)
5114 .collect::<Vec<InlayId>>(),
5115 Vec::new(),
5116 cx,
5117 );
5118 return;
5119 }
5120 } else {
5121 return;
5122 }
5123 }
5124 InlayHintRefreshReason::SettingsChange(new_settings) => {
5125 match self.inlay_hint_cache.update_settings(
5126 &self.buffer,
5127 new_settings,
5128 self.visible_inlay_hints(cx),
5129 cx,
5130 ) {
5131 ControlFlow::Break(Some(InlaySplice {
5132 to_remove,
5133 to_insert,
5134 })) => {
5135 self.splice_inlays(&to_remove, to_insert, cx);
5136 return;
5137 }
5138 ControlFlow::Break(None) => return,
5139 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
5140 }
5141 }
5142 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
5143 if let Some(InlaySplice {
5144 to_remove,
5145 to_insert,
5146 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
5147 {
5148 self.splice_inlays(&to_remove, to_insert, cx);
5149 }
5150 self.display_map.update(cx, |display_map, _| {
5151 display_map.remove_inlays_for_excerpts(&excerpts_removed)
5152 });
5153 return;
5154 }
5155 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
5156 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
5157 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
5158 }
5159 InlayHintRefreshReason::RefreshRequested => {
5160 (InvalidationStrategy::RefreshRequested, None)
5161 }
5162 };
5163
5164 if let Some(InlaySplice {
5165 to_remove,
5166 to_insert,
5167 }) = self.inlay_hint_cache.spawn_hint_refresh(
5168 reason_description,
5169 self.visible_excerpts(required_languages.as_ref(), cx),
5170 invalidate_cache,
5171 ignore_debounce,
5172 cx,
5173 ) {
5174 self.splice_inlays(&to_remove, to_insert, cx);
5175 }
5176 }
5177
5178 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
5179 self.display_map
5180 .read(cx)
5181 .current_inlays()
5182 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
5183 .cloned()
5184 .collect()
5185 }
5186
5187 pub fn visible_excerpts(
5188 &self,
5189 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
5190 cx: &mut Context<Editor>,
5191 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5192 let Some(project) = self.project.as_ref() else {
5193 return HashMap::default();
5194 };
5195 let project = project.read(cx);
5196 let multi_buffer = self.buffer().read(cx);
5197 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5198 let multi_buffer_visible_start = self
5199 .scroll_manager
5200 .anchor()
5201 .anchor
5202 .to_point(&multi_buffer_snapshot);
5203 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5204 multi_buffer_visible_start
5205 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5206 Bias::Left,
5207 );
5208 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
5209 multi_buffer_snapshot
5210 .range_to_buffer_ranges(multi_buffer_visible_range)
5211 .into_iter()
5212 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5213 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5214 let buffer_file = project::File::from_dyn(buffer.file())?;
5215 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5216 let worktree_entry = buffer_worktree
5217 .read(cx)
5218 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
5219 if worktree_entry.is_ignored {
5220 return None;
5221 }
5222
5223 let language = buffer.language()?;
5224 if let Some(restrict_to_languages) = restrict_to_languages {
5225 if !restrict_to_languages.contains(language) {
5226 return None;
5227 }
5228 }
5229 Some((
5230 excerpt_id,
5231 (
5232 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5233 buffer.version().clone(),
5234 excerpt_visible_range,
5235 ),
5236 ))
5237 })
5238 .collect()
5239 }
5240
5241 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5242 TextLayoutDetails {
5243 text_system: window.text_system().clone(),
5244 editor_style: self.style.clone().unwrap(),
5245 rem_size: window.rem_size(),
5246 scroll_anchor: self.scroll_manager.anchor(),
5247 visible_rows: self.visible_line_count(),
5248 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5249 }
5250 }
5251
5252 pub fn splice_inlays(
5253 &self,
5254 to_remove: &[InlayId],
5255 to_insert: Vec<Inlay>,
5256 cx: &mut Context<Self>,
5257 ) {
5258 self.display_map.update(cx, |display_map, cx| {
5259 display_map.splice_inlays(to_remove, to_insert, cx)
5260 });
5261 cx.notify();
5262 }
5263
5264 fn trigger_on_type_formatting(
5265 &self,
5266 input: String,
5267 window: &mut Window,
5268 cx: &mut Context<Self>,
5269 ) -> Option<Task<Result<()>>> {
5270 if input.len() != 1 {
5271 return None;
5272 }
5273
5274 let project = self.project.as_ref()?;
5275 let position = self.selections.newest_anchor().head();
5276 let (buffer, buffer_position) = self
5277 .buffer
5278 .read(cx)
5279 .text_anchor_for_position(position, cx)?;
5280
5281 let settings = language_settings::language_settings(
5282 buffer
5283 .read(cx)
5284 .language_at(buffer_position)
5285 .map(|l| l.name()),
5286 buffer.read(cx).file(),
5287 cx,
5288 );
5289 if !settings.use_on_type_format {
5290 return None;
5291 }
5292
5293 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5294 // hence we do LSP request & edit on host side only — add formats to host's history.
5295 let push_to_lsp_host_history = true;
5296 // If this is not the host, append its history with new edits.
5297 let push_to_client_history = project.read(cx).is_via_collab();
5298
5299 let on_type_formatting = project.update(cx, |project, cx| {
5300 project.on_type_format(
5301 buffer.clone(),
5302 buffer_position,
5303 input,
5304 push_to_lsp_host_history,
5305 cx,
5306 )
5307 });
5308 Some(cx.spawn_in(window, async move |editor, cx| {
5309 if let Some(transaction) = on_type_formatting.await? {
5310 if push_to_client_history {
5311 buffer
5312 .update(cx, |buffer, _| {
5313 buffer.push_transaction(transaction, Instant::now());
5314 buffer.finalize_last_transaction();
5315 })
5316 .ok();
5317 }
5318 editor.update(cx, |editor, cx| {
5319 editor.refresh_document_highlights(cx);
5320 })?;
5321 }
5322 Ok(())
5323 }))
5324 }
5325
5326 pub fn show_word_completions(
5327 &mut self,
5328 _: &ShowWordCompletions,
5329 window: &mut Window,
5330 cx: &mut Context<Self>,
5331 ) {
5332 self.open_or_update_completions_menu(Some(CompletionsMenuSource::Words), None, window, cx);
5333 }
5334
5335 pub fn show_completions(
5336 &mut self,
5337 options: &ShowCompletions,
5338 window: &mut Window,
5339 cx: &mut Context<Self>,
5340 ) {
5341 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5342 }
5343
5344 fn open_or_update_completions_menu(
5345 &mut self,
5346 requested_source: Option<CompletionsMenuSource>,
5347 trigger: Option<&str>,
5348 window: &mut Window,
5349 cx: &mut Context<Self>,
5350 ) {
5351 if self.pending_rename.is_some() {
5352 return;
5353 }
5354
5355 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5356
5357 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5358 // inserted and selected. To handle that case, the start of the selection is used so that
5359 // the menu starts with all choices.
5360 let position = self
5361 .selections
5362 .newest_anchor()
5363 .start
5364 .bias_right(&multibuffer_snapshot);
5365 if position.diff_base_anchor.is_some() {
5366 return;
5367 }
5368 let (buffer, buffer_position) =
5369 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
5370 output
5371 } else {
5372 return;
5373 };
5374 let buffer_snapshot = buffer.read(cx).snapshot();
5375
5376 let query: Option<Arc<String>> =
5377 Self::completion_query(&multibuffer_snapshot, position).map(|query| query.into());
5378
5379 drop(multibuffer_snapshot);
5380
5381 let provider = match requested_source {
5382 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5383 Some(CompletionsMenuSource::Words) => None,
5384 Some(CompletionsMenuSource::SnippetChoices) => {
5385 log::error!("bug: SnippetChoices requested_source is not handled");
5386 None
5387 }
5388 };
5389
5390 let sort_completions = provider
5391 .as_ref()
5392 .map_or(false, |provider| provider.sort_completions());
5393
5394 let filter_completions = provider
5395 .as_ref()
5396 .map_or(true, |provider| provider.filter_completions());
5397
5398 let trigger_kind = match trigger {
5399 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5400 CompletionTriggerKind::TRIGGER_CHARACTER
5401 }
5402 _ => CompletionTriggerKind::INVOKED,
5403 };
5404 let completion_context = CompletionContext {
5405 trigger_character: trigger.and_then(|trigger| {
5406 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5407 Some(String::from(trigger))
5408 } else {
5409 None
5410 }
5411 }),
5412 trigger_kind,
5413 };
5414
5415 // Hide the current completions menu when a trigger char is typed. Without this, cached
5416 // completions from before the trigger char may be reused (#32774). Snippet choices could
5417 // involve trigger chars, so this is skipped in that case.
5418 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER && self.snippet_stack.is_empty()
5419 {
5420 let menu_is_open = matches!(
5421 self.context_menu.borrow().as_ref(),
5422 Some(CodeContextMenu::Completions(_))
5423 );
5424 if menu_is_open {
5425 self.hide_context_menu(window, cx);
5426 }
5427 }
5428
5429 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5430 if filter_completions {
5431 menu.filter(query.clone(), provider.clone(), window, cx);
5432 }
5433 // When `is_incomplete` is false, no need to re-query completions when the current query
5434 // is a suffix of the initial query.
5435 if !menu.is_incomplete {
5436 // If the new query is a suffix of the old query (typing more characters) and
5437 // the previous result was complete, the existing completions can be filtered.
5438 //
5439 // Note that this is always true for snippet completions.
5440 let query_matches = match (&menu.initial_query, &query) {
5441 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5442 (None, _) => true,
5443 _ => false,
5444 };
5445 if query_matches {
5446 let position_matches = if menu.initial_position == position {
5447 true
5448 } else {
5449 let snapshot = self.buffer.read(cx).read(cx);
5450 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5451 };
5452 if position_matches {
5453 return;
5454 }
5455 }
5456 }
5457 };
5458
5459 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5460 buffer_snapshot.surrounding_word(buffer_position, false)
5461 {
5462 let word_to_exclude = buffer_snapshot
5463 .text_for_range(word_range.clone())
5464 .collect::<String>();
5465 (
5466 buffer_snapshot.anchor_before(word_range.start)
5467 ..buffer_snapshot.anchor_after(buffer_position),
5468 Some(word_to_exclude),
5469 )
5470 } else {
5471 (buffer_position..buffer_position, None)
5472 };
5473
5474 let language = buffer_snapshot
5475 .language_at(buffer_position)
5476 .map(|language| language.name());
5477
5478 let completion_settings =
5479 language_settings(language.clone(), buffer_snapshot.file(), cx).completions;
5480
5481 let show_completion_documentation = buffer_snapshot
5482 .settings_at(buffer_position, cx)
5483 .show_completion_documentation;
5484
5485 // The document can be large, so stay in reasonable bounds when searching for words,
5486 // otherwise completion pop-up might be slow to appear.
5487 const WORD_LOOKUP_ROWS: u32 = 5_000;
5488 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5489 let min_word_search = buffer_snapshot.clip_point(
5490 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5491 Bias::Left,
5492 );
5493 let max_word_search = buffer_snapshot.clip_point(
5494 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5495 Bias::Right,
5496 );
5497 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5498 ..buffer_snapshot.point_to_offset(max_word_search);
5499
5500 let skip_digits = query
5501 .as_ref()
5502 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
5503
5504 let (mut words, provider_responses) = match &provider {
5505 Some(provider) => {
5506 let provider_responses = provider.completions(
5507 position.excerpt_id,
5508 &buffer,
5509 buffer_position,
5510 completion_context,
5511 window,
5512 cx,
5513 );
5514
5515 let words = match completion_settings.words {
5516 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
5517 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
5518 .background_spawn(async move {
5519 buffer_snapshot.words_in_range(WordsQuery {
5520 fuzzy_contents: None,
5521 range: word_search_range,
5522 skip_digits,
5523 })
5524 }),
5525 };
5526
5527 (words, provider_responses)
5528 }
5529 None => (
5530 cx.background_spawn(async move {
5531 buffer_snapshot.words_in_range(WordsQuery {
5532 fuzzy_contents: None,
5533 range: word_search_range,
5534 skip_digits,
5535 })
5536 }),
5537 Task::ready(Ok(Vec::new())),
5538 ),
5539 };
5540
5541 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5542
5543 let id = post_inc(&mut self.next_completion_id);
5544 let task = cx.spawn_in(window, async move |editor, cx| {
5545 let Ok(()) = editor.update(cx, |this, _| {
5546 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5547 }) else {
5548 return;
5549 };
5550
5551 // TODO: Ideally completions from different sources would be selectively re-queried, so
5552 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5553 let mut completions = Vec::new();
5554 let mut is_incomplete = false;
5555 if let Some(provider_responses) = provider_responses.await.log_err() {
5556 if !provider_responses.is_empty() {
5557 for response in provider_responses {
5558 completions.extend(response.completions);
5559 is_incomplete = is_incomplete || response.is_incomplete;
5560 }
5561 if completion_settings.words == WordsCompletionMode::Fallback {
5562 words = Task::ready(BTreeMap::default());
5563 }
5564 }
5565 }
5566
5567 let mut words = words.await;
5568 if let Some(word_to_exclude) = &word_to_exclude {
5569 words.remove(word_to_exclude);
5570 }
5571 for lsp_completion in &completions {
5572 words.remove(&lsp_completion.new_text);
5573 }
5574 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5575 replace_range: word_replace_range.clone(),
5576 new_text: word.clone(),
5577 label: CodeLabel::plain(word, None),
5578 icon_path: None,
5579 documentation: None,
5580 source: CompletionSource::BufferWord {
5581 word_range,
5582 resolved: false,
5583 },
5584 insert_text_mode: Some(InsertTextMode::AS_IS),
5585 confirm: None,
5586 }));
5587
5588 let menu = if completions.is_empty() {
5589 None
5590 } else {
5591 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5592 let languages = editor
5593 .workspace
5594 .as_ref()
5595 .and_then(|(workspace, _)| workspace.upgrade())
5596 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5597 let menu = CompletionsMenu::new(
5598 id,
5599 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5600 sort_completions,
5601 show_completion_documentation,
5602 position,
5603 query.clone(),
5604 is_incomplete,
5605 buffer.clone(),
5606 completions.into(),
5607 snippet_sort_order,
5608 languages,
5609 language,
5610 cx,
5611 );
5612
5613 let query = if filter_completions { query } else { None };
5614 let matches_task = if let Some(query) = query {
5615 menu.do_async_filtering(query, cx)
5616 } else {
5617 Task::ready(menu.unfiltered_matches())
5618 };
5619 (menu, matches_task)
5620 }) else {
5621 return;
5622 };
5623
5624 let matches = matches_task.await;
5625
5626 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5627 // Newer menu already set, so exit.
5628 match editor.context_menu.borrow().as_ref() {
5629 Some(CodeContextMenu::Completions(prev_menu)) => {
5630 if prev_menu.id > id {
5631 return;
5632 }
5633 }
5634 _ => {}
5635 };
5636
5637 // Only valid to take prev_menu because it the new menu is immediately set
5638 // below, or the menu is hidden.
5639 match editor.context_menu.borrow_mut().take() {
5640 Some(CodeContextMenu::Completions(prev_menu)) => {
5641 let position_matches =
5642 if prev_menu.initial_position == menu.initial_position {
5643 true
5644 } else {
5645 let snapshot = editor.buffer.read(cx).read(cx);
5646 prev_menu.initial_position.to_offset(&snapshot)
5647 == menu.initial_position.to_offset(&snapshot)
5648 };
5649 if position_matches {
5650 // Preserve markdown cache before `set_filter_results` because it will
5651 // try to populate the documentation cache.
5652 menu.preserve_markdown_cache(prev_menu);
5653 }
5654 }
5655 _ => {}
5656 };
5657
5658 menu.set_filter_results(matches, provider, window, cx);
5659 }) else {
5660 return;
5661 };
5662
5663 menu.visible().then_some(menu)
5664 };
5665
5666 editor
5667 .update_in(cx, |editor, window, cx| {
5668 if editor.focus_handle.is_focused(window) {
5669 if let Some(menu) = menu {
5670 *editor.context_menu.borrow_mut() =
5671 Some(CodeContextMenu::Completions(menu));
5672
5673 crate::hover_popover::hide_hover(editor, cx);
5674 if editor.show_edit_predictions_in_menu() {
5675 editor.update_visible_edit_prediction(window, cx);
5676 } else {
5677 editor.discard_edit_prediction(false, cx);
5678 }
5679
5680 cx.notify();
5681 return;
5682 }
5683 }
5684
5685 if editor.completion_tasks.len() <= 1 {
5686 // If there are no more completion tasks and the last menu was empty, we should hide it.
5687 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5688 // If it was already hidden and we don't show edit predictions in the menu,
5689 // we should also show the edit prediction when available.
5690 if was_hidden && editor.show_edit_predictions_in_menu() {
5691 editor.update_visible_edit_prediction(window, cx);
5692 }
5693 }
5694 })
5695 .ok();
5696 });
5697
5698 self.completion_tasks.push((id, task));
5699 }
5700
5701 #[cfg(feature = "test-support")]
5702 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5703 let menu = self.context_menu.borrow();
5704 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5705 let completions = menu.completions.borrow();
5706 Some(completions.to_vec())
5707 } else {
5708 None
5709 }
5710 }
5711
5712 pub fn with_completions_menu_matching_id<R>(
5713 &self,
5714 id: CompletionId,
5715 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5716 ) -> R {
5717 let mut context_menu = self.context_menu.borrow_mut();
5718 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5719 return f(None);
5720 };
5721 if completions_menu.id != id {
5722 return f(None);
5723 }
5724 f(Some(completions_menu))
5725 }
5726
5727 pub fn confirm_completion(
5728 &mut self,
5729 action: &ConfirmCompletion,
5730 window: &mut Window,
5731 cx: &mut Context<Self>,
5732 ) -> Option<Task<Result<()>>> {
5733 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5734 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5735 }
5736
5737 pub fn confirm_completion_insert(
5738 &mut self,
5739 _: &ConfirmCompletionInsert,
5740 window: &mut Window,
5741 cx: &mut Context<Self>,
5742 ) -> Option<Task<Result<()>>> {
5743 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5744 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5745 }
5746
5747 pub fn confirm_completion_replace(
5748 &mut self,
5749 _: &ConfirmCompletionReplace,
5750 window: &mut Window,
5751 cx: &mut Context<Self>,
5752 ) -> Option<Task<Result<()>>> {
5753 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5754 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5755 }
5756
5757 pub fn compose_completion(
5758 &mut self,
5759 action: &ComposeCompletion,
5760 window: &mut Window,
5761 cx: &mut Context<Self>,
5762 ) -> Option<Task<Result<()>>> {
5763 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5764 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5765 }
5766
5767 fn do_completion(
5768 &mut self,
5769 item_ix: Option<usize>,
5770 intent: CompletionIntent,
5771 window: &mut Window,
5772 cx: &mut Context<Editor>,
5773 ) -> Option<Task<Result<()>>> {
5774 use language::ToOffset as _;
5775
5776 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5777 else {
5778 return None;
5779 };
5780
5781 let candidate_id = {
5782 let entries = completions_menu.entries.borrow();
5783 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5784 if self.show_edit_predictions_in_menu() {
5785 self.discard_edit_prediction(true, cx);
5786 }
5787 mat.candidate_id
5788 };
5789
5790 let completion = completions_menu
5791 .completions
5792 .borrow()
5793 .get(candidate_id)?
5794 .clone();
5795 cx.stop_propagation();
5796
5797 let buffer_handle = completions_menu.buffer.clone();
5798
5799 let CompletionEdit {
5800 new_text,
5801 snippet,
5802 replace_range,
5803 } = process_completion_for_edit(
5804 &completion,
5805 intent,
5806 &buffer_handle,
5807 &completions_menu.initial_position.text_anchor,
5808 cx,
5809 );
5810
5811 let buffer = buffer_handle.read(cx);
5812 let snapshot = self.buffer.read(cx).snapshot(cx);
5813 let newest_anchor = self.selections.newest_anchor();
5814 let replace_range_multibuffer = {
5815 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5816 let multibuffer_anchor = snapshot
5817 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5818 .unwrap()
5819 ..snapshot
5820 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5821 .unwrap();
5822 multibuffer_anchor.start.to_offset(&snapshot)
5823 ..multibuffer_anchor.end.to_offset(&snapshot)
5824 };
5825 if newest_anchor.head().buffer_id != Some(buffer.remote_id()) {
5826 return None;
5827 }
5828
5829 let old_text = buffer
5830 .text_for_range(replace_range.clone())
5831 .collect::<String>();
5832 let lookbehind = newest_anchor
5833 .start
5834 .text_anchor
5835 .to_offset(buffer)
5836 .saturating_sub(replace_range.start);
5837 let lookahead = replace_range
5838 .end
5839 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5840 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5841 let suffix = &old_text[lookbehind.min(old_text.len())..];
5842
5843 let selections = self.selections.all::<usize>(cx);
5844 let mut ranges = Vec::new();
5845 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5846
5847 for selection in &selections {
5848 let range = if selection.id == newest_anchor.id {
5849 replace_range_multibuffer.clone()
5850 } else {
5851 let mut range = selection.range();
5852
5853 // if prefix is present, don't duplicate it
5854 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5855 range.start = range.start.saturating_sub(lookbehind);
5856
5857 // if suffix is also present, mimic the newest cursor and replace it
5858 if selection.id != newest_anchor.id
5859 && snapshot.contains_str_at(range.end, suffix)
5860 {
5861 range.end += lookahead;
5862 }
5863 }
5864 range
5865 };
5866
5867 ranges.push(range.clone());
5868
5869 if !self.linked_edit_ranges.is_empty() {
5870 let start_anchor = snapshot.anchor_before(range.start);
5871 let end_anchor = snapshot.anchor_after(range.end);
5872 if let Some(ranges) = self
5873 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5874 {
5875 for (buffer, edits) in ranges {
5876 linked_edits
5877 .entry(buffer.clone())
5878 .or_default()
5879 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5880 }
5881 }
5882 }
5883 }
5884
5885 let common_prefix_len = old_text
5886 .chars()
5887 .zip(new_text.chars())
5888 .take_while(|(a, b)| a == b)
5889 .map(|(a, _)| a.len_utf8())
5890 .sum::<usize>();
5891
5892 cx.emit(EditorEvent::InputHandled {
5893 utf16_range_to_replace: None,
5894 text: new_text[common_prefix_len..].into(),
5895 });
5896
5897 self.transact(window, cx, |editor, window, cx| {
5898 if let Some(mut snippet) = snippet {
5899 snippet.text = new_text.to_string();
5900 editor
5901 .insert_snippet(&ranges, snippet, window, cx)
5902 .log_err();
5903 } else {
5904 editor.buffer.update(cx, |multi_buffer, cx| {
5905 let auto_indent = match completion.insert_text_mode {
5906 Some(InsertTextMode::AS_IS) => None,
5907 _ => editor.autoindent_mode.clone(),
5908 };
5909 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5910 multi_buffer.edit(edits, auto_indent, cx);
5911 });
5912 }
5913 for (buffer, edits) in linked_edits {
5914 buffer.update(cx, |buffer, cx| {
5915 let snapshot = buffer.snapshot();
5916 let edits = edits
5917 .into_iter()
5918 .map(|(range, text)| {
5919 use text::ToPoint as TP;
5920 let end_point = TP::to_point(&range.end, &snapshot);
5921 let start_point = TP::to_point(&range.start, &snapshot);
5922 (start_point..end_point, text)
5923 })
5924 .sorted_by_key(|(range, _)| range.start);
5925 buffer.edit(edits, None, cx);
5926 })
5927 }
5928
5929 editor.refresh_edit_prediction(true, false, window, cx);
5930 });
5931 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), &snapshot);
5932
5933 let show_new_completions_on_confirm = completion
5934 .confirm
5935 .as_ref()
5936 .map_or(false, |confirm| confirm(intent, window, cx));
5937 if show_new_completions_on_confirm {
5938 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5939 }
5940
5941 let provider = self.completion_provider.as_ref()?;
5942 drop(completion);
5943 let apply_edits = provider.apply_additional_edits_for_completion(
5944 buffer_handle,
5945 completions_menu.completions.clone(),
5946 candidate_id,
5947 true,
5948 cx,
5949 );
5950
5951 let editor_settings = EditorSettings::get_global(cx);
5952 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5953 // After the code completion is finished, users often want to know what signatures are needed.
5954 // so we should automatically call signature_help
5955 self.show_signature_help(&ShowSignatureHelp, window, cx);
5956 }
5957
5958 Some(cx.foreground_executor().spawn(async move {
5959 apply_edits.await?;
5960 Ok(())
5961 }))
5962 }
5963
5964 pub fn toggle_code_actions(
5965 &mut self,
5966 action: &ToggleCodeActions,
5967 window: &mut Window,
5968 cx: &mut Context<Self>,
5969 ) {
5970 let quick_launch = action.quick_launch;
5971 let mut context_menu = self.context_menu.borrow_mut();
5972 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5973 if code_actions.deployed_from == action.deployed_from {
5974 // Toggle if we're selecting the same one
5975 *context_menu = None;
5976 cx.notify();
5977 return;
5978 } else {
5979 // Otherwise, clear it and start a new one
5980 *context_menu = None;
5981 cx.notify();
5982 }
5983 }
5984 drop(context_menu);
5985 let snapshot = self.snapshot(window, cx);
5986 let deployed_from = action.deployed_from.clone();
5987 let action = action.clone();
5988 self.completion_tasks.clear();
5989 self.discard_edit_prediction(false, cx);
5990
5991 let multibuffer_point = match &action.deployed_from {
5992 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
5993 DisplayPoint::new(*row, 0).to_point(&snapshot)
5994 }
5995 _ => self.selections.newest::<Point>(cx).head(),
5996 };
5997 let Some((buffer, buffer_row)) = snapshot
5998 .buffer_snapshot
5999 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6000 .and_then(|(buffer_snapshot, range)| {
6001 self.buffer()
6002 .read(cx)
6003 .buffer(buffer_snapshot.remote_id())
6004 .map(|buffer| (buffer, range.start.row))
6005 })
6006 else {
6007 return;
6008 };
6009 let buffer_id = buffer.read(cx).remote_id();
6010 let tasks = self
6011 .tasks
6012 .get(&(buffer_id, buffer_row))
6013 .map(|t| Arc::new(t.to_owned()));
6014
6015 if !self.focus_handle.is_focused(window) {
6016 return;
6017 }
6018 let project = self.project.clone();
6019
6020 let code_actions_task = match deployed_from {
6021 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6022 _ => self.code_actions(buffer_row, window, cx),
6023 };
6024
6025 let runnable_task = match deployed_from {
6026 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6027 _ => {
6028 let mut task_context_task = Task::ready(None);
6029 if let Some(tasks) = &tasks {
6030 if let Some(project) = project {
6031 task_context_task =
6032 Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
6033 }
6034 }
6035
6036 cx.spawn_in(window, {
6037 let buffer = buffer.clone();
6038 async move |editor, cx| {
6039 let task_context = task_context_task.await;
6040
6041 let resolved_tasks =
6042 tasks
6043 .zip(task_context.clone())
6044 .map(|(tasks, task_context)| ResolvedTasks {
6045 templates: tasks.resolve(&task_context).collect(),
6046 position: snapshot.buffer_snapshot.anchor_before(Point::new(
6047 multibuffer_point.row,
6048 tasks.column,
6049 )),
6050 });
6051 let debug_scenarios = editor
6052 .update(cx, |editor, cx| {
6053 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6054 })?
6055 .await;
6056 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6057 }
6058 })
6059 }
6060 };
6061
6062 cx.spawn_in(window, async move |editor, cx| {
6063 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6064 let code_actions = code_actions_task.await;
6065 let spawn_straight_away = quick_launch
6066 && resolved_tasks
6067 .as_ref()
6068 .map_or(false, |tasks| tasks.templates.len() == 1)
6069 && code_actions
6070 .as_ref()
6071 .map_or(true, |actions| actions.is_empty())
6072 && debug_scenarios.is_empty();
6073
6074 editor.update_in(cx, |editor, window, cx| {
6075 crate::hover_popover::hide_hover(editor, cx);
6076 let actions = CodeActionContents::new(
6077 resolved_tasks,
6078 code_actions,
6079 debug_scenarios,
6080 task_context.unwrap_or_default(),
6081 );
6082
6083 // Don't show the menu if there are no actions available
6084 if actions.is_empty() {
6085 cx.notify();
6086 return Task::ready(Ok(()));
6087 }
6088
6089 *editor.context_menu.borrow_mut() =
6090 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6091 buffer,
6092 actions,
6093 selected_item: Default::default(),
6094 scroll_handle: UniformListScrollHandle::default(),
6095 deployed_from,
6096 }));
6097 cx.notify();
6098 if spawn_straight_away {
6099 if let Some(task) = editor.confirm_code_action(
6100 &ConfirmCodeAction { item_ix: Some(0) },
6101 window,
6102 cx,
6103 ) {
6104 return task;
6105 }
6106 }
6107
6108 Task::ready(Ok(()))
6109 })
6110 })
6111 .detach_and_log_err(cx);
6112 }
6113
6114 fn debug_scenarios(
6115 &mut self,
6116 resolved_tasks: &Option<ResolvedTasks>,
6117 buffer: &Entity<Buffer>,
6118 cx: &mut App,
6119 ) -> Task<Vec<task::DebugScenario>> {
6120 maybe!({
6121 let project = self.project.as_ref()?;
6122 let dap_store = project.read(cx).dap_store();
6123 let mut scenarios = vec![];
6124 let resolved_tasks = resolved_tasks.as_ref()?;
6125 let buffer = buffer.read(cx);
6126 let language = buffer.language()?;
6127 let file = buffer.file();
6128 let debug_adapter = language_settings(language.name().into(), file, cx)
6129 .debuggers
6130 .first()
6131 .map(SharedString::from)
6132 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6133
6134 dap_store.update(cx, |dap_store, cx| {
6135 for (_, task) in &resolved_tasks.templates {
6136 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6137 task.original_task().clone(),
6138 debug_adapter.clone().into(),
6139 task.display_label().to_owned().into(),
6140 cx,
6141 );
6142 scenarios.push(maybe_scenario);
6143 }
6144 });
6145 Some(cx.background_spawn(async move {
6146 let scenarios = futures::future::join_all(scenarios)
6147 .await
6148 .into_iter()
6149 .flatten()
6150 .collect::<Vec<_>>();
6151 scenarios
6152 }))
6153 })
6154 .unwrap_or_else(|| Task::ready(vec![]))
6155 }
6156
6157 fn code_actions(
6158 &mut self,
6159 buffer_row: u32,
6160 window: &mut Window,
6161 cx: &mut Context<Self>,
6162 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6163 let mut task = self.code_actions_task.take();
6164 cx.spawn_in(window, async move |editor, cx| {
6165 while let Some(prev_task) = task {
6166 prev_task.await.log_err();
6167 task = editor
6168 .update(cx, |this, _| this.code_actions_task.take())
6169 .ok()?;
6170 }
6171
6172 editor
6173 .update(cx, |editor, cx| {
6174 editor
6175 .available_code_actions
6176 .clone()
6177 .and_then(|(location, code_actions)| {
6178 let snapshot = location.buffer.read(cx).snapshot();
6179 let point_range = location.range.to_point(&snapshot);
6180 let point_range = point_range.start.row..=point_range.end.row;
6181 if point_range.contains(&buffer_row) {
6182 Some(code_actions)
6183 } else {
6184 None
6185 }
6186 })
6187 })
6188 .ok()
6189 .flatten()
6190 })
6191 }
6192
6193 pub fn confirm_code_action(
6194 &mut self,
6195 action: &ConfirmCodeAction,
6196 window: &mut Window,
6197 cx: &mut Context<Self>,
6198 ) -> Option<Task<Result<()>>> {
6199 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6200
6201 let actions_menu =
6202 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6203 menu
6204 } else {
6205 return None;
6206 };
6207
6208 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6209 let action = actions_menu.actions.get(action_ix)?;
6210 let title = action.label();
6211 let buffer = actions_menu.buffer;
6212 let workspace = self.workspace()?;
6213
6214 match action {
6215 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6216 workspace.update(cx, |workspace, cx| {
6217 workspace.schedule_resolved_task(
6218 task_source_kind,
6219 resolved_task,
6220 false,
6221 window,
6222 cx,
6223 );
6224
6225 Some(Task::ready(Ok(())))
6226 })
6227 }
6228 CodeActionsItem::CodeAction {
6229 excerpt_id,
6230 action,
6231 provider,
6232 } => {
6233 let apply_code_action =
6234 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6235 let workspace = workspace.downgrade();
6236 Some(cx.spawn_in(window, async move |editor, cx| {
6237 let project_transaction = apply_code_action.await?;
6238 Self::open_project_transaction(
6239 &editor,
6240 workspace,
6241 project_transaction,
6242 title,
6243 cx,
6244 )
6245 .await
6246 }))
6247 }
6248 CodeActionsItem::DebugScenario(scenario) => {
6249 let context = actions_menu.actions.context.clone();
6250
6251 workspace.update(cx, |workspace, cx| {
6252 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6253 workspace.start_debug_session(
6254 scenario,
6255 context,
6256 Some(buffer),
6257 None,
6258 window,
6259 cx,
6260 );
6261 });
6262 Some(Task::ready(Ok(())))
6263 }
6264 }
6265 }
6266
6267 pub async fn open_project_transaction(
6268 this: &WeakEntity<Editor>,
6269 workspace: WeakEntity<Workspace>,
6270 transaction: ProjectTransaction,
6271 title: String,
6272 cx: &mut AsyncWindowContext,
6273 ) -> Result<()> {
6274 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6275 cx.update(|_, cx| {
6276 entries.sort_unstable_by_key(|(buffer, _)| {
6277 buffer.read(cx).file().map(|f| f.path().clone())
6278 });
6279 })?;
6280
6281 // If the project transaction's edits are all contained within this editor, then
6282 // avoid opening a new editor to display them.
6283
6284 if let Some((buffer, transaction)) = entries.first() {
6285 if entries.len() == 1 {
6286 let excerpt = this.update(cx, |editor, cx| {
6287 editor
6288 .buffer()
6289 .read(cx)
6290 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6291 })?;
6292 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
6293 if excerpted_buffer == *buffer {
6294 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6295 let excerpt_range = excerpt_range.to_offset(buffer);
6296 buffer
6297 .edited_ranges_for_transaction::<usize>(transaction)
6298 .all(|range| {
6299 excerpt_range.start <= range.start
6300 && excerpt_range.end >= range.end
6301 })
6302 })?;
6303
6304 if all_edits_within_excerpt {
6305 return Ok(());
6306 }
6307 }
6308 }
6309 }
6310 } else {
6311 return Ok(());
6312 }
6313
6314 let mut ranges_to_highlight = Vec::new();
6315 let excerpt_buffer = cx.new(|cx| {
6316 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6317 for (buffer_handle, transaction) in &entries {
6318 let edited_ranges = buffer_handle
6319 .read(cx)
6320 .edited_ranges_for_transaction::<Point>(transaction)
6321 .collect::<Vec<_>>();
6322 let (ranges, _) = multibuffer.set_excerpts_for_path(
6323 PathKey::for_buffer(buffer_handle, cx),
6324 buffer_handle.clone(),
6325 edited_ranges,
6326 DEFAULT_MULTIBUFFER_CONTEXT,
6327 cx,
6328 );
6329
6330 ranges_to_highlight.extend(ranges);
6331 }
6332 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6333 multibuffer
6334 })?;
6335
6336 workspace.update_in(cx, |workspace, window, cx| {
6337 let project = workspace.project().clone();
6338 let editor =
6339 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6340 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6341 editor.update(cx, |editor, cx| {
6342 editor.highlight_background::<Self>(
6343 &ranges_to_highlight,
6344 |theme| theme.colors().editor_highlighted_line_background,
6345 cx,
6346 );
6347 });
6348 })?;
6349
6350 Ok(())
6351 }
6352
6353 pub fn clear_code_action_providers(&mut self) {
6354 self.code_action_providers.clear();
6355 self.available_code_actions.take();
6356 }
6357
6358 pub fn add_code_action_provider(
6359 &mut self,
6360 provider: Rc<dyn CodeActionProvider>,
6361 window: &mut Window,
6362 cx: &mut Context<Self>,
6363 ) {
6364 if self
6365 .code_action_providers
6366 .iter()
6367 .any(|existing_provider| existing_provider.id() == provider.id())
6368 {
6369 return;
6370 }
6371
6372 self.code_action_providers.push(provider);
6373 self.refresh_code_actions(window, cx);
6374 }
6375
6376 pub fn remove_code_action_provider(
6377 &mut self,
6378 id: Arc<str>,
6379 window: &mut Window,
6380 cx: &mut Context<Self>,
6381 ) {
6382 self.code_action_providers
6383 .retain(|provider| provider.id() != id);
6384 self.refresh_code_actions(window, cx);
6385 }
6386
6387 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6388 !self.code_action_providers.is_empty()
6389 && EditorSettings::get_global(cx).toolbar.code_actions
6390 }
6391
6392 pub fn has_available_code_actions(&self) -> bool {
6393 self.available_code_actions
6394 .as_ref()
6395 .is_some_and(|(_, actions)| !actions.is_empty())
6396 }
6397
6398 fn render_inline_code_actions(
6399 &self,
6400 icon_size: ui::IconSize,
6401 display_row: DisplayRow,
6402 is_active: bool,
6403 cx: &mut Context<Self>,
6404 ) -> AnyElement {
6405 let show_tooltip = !self.context_menu_visible();
6406 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6407 .icon_size(icon_size)
6408 .shape(ui::IconButtonShape::Square)
6409 .icon_color(ui::Color::Hidden)
6410 .toggle_state(is_active)
6411 .when(show_tooltip, |this| {
6412 this.tooltip({
6413 let focus_handle = self.focus_handle.clone();
6414 move |window, cx| {
6415 Tooltip::for_action_in(
6416 "Toggle Code Actions",
6417 &ToggleCodeActions {
6418 deployed_from: None,
6419 quick_launch: false,
6420 },
6421 &focus_handle,
6422 window,
6423 cx,
6424 )
6425 }
6426 })
6427 })
6428 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6429 window.focus(&editor.focus_handle(cx));
6430 editor.toggle_code_actions(
6431 &crate::actions::ToggleCodeActions {
6432 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6433 display_row,
6434 )),
6435 quick_launch: false,
6436 },
6437 window,
6438 cx,
6439 );
6440 }))
6441 .into_any_element()
6442 }
6443
6444 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6445 &self.context_menu
6446 }
6447
6448 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
6449 let newest_selection = self.selections.newest_anchor().clone();
6450 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
6451 let buffer = self.buffer.read(cx);
6452 if newest_selection.head().diff_base_anchor.is_some() {
6453 return None;
6454 }
6455 let (start_buffer, start) =
6456 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6457 let (end_buffer, end) =
6458 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6459 if start_buffer != end_buffer {
6460 return None;
6461 }
6462
6463 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6464 cx.background_executor()
6465 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6466 .await;
6467
6468 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6469 let providers = this.code_action_providers.clone();
6470 let tasks = this
6471 .code_action_providers
6472 .iter()
6473 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6474 .collect::<Vec<_>>();
6475 (providers, tasks)
6476 })?;
6477
6478 let mut actions = Vec::new();
6479 for (provider, provider_actions) in
6480 providers.into_iter().zip(future::join_all(tasks).await)
6481 {
6482 if let Some(provider_actions) = provider_actions.log_err() {
6483 actions.extend(provider_actions.into_iter().map(|action| {
6484 AvailableCodeAction {
6485 excerpt_id: newest_selection.start.excerpt_id,
6486 action,
6487 provider: provider.clone(),
6488 }
6489 }));
6490 }
6491 }
6492
6493 this.update(cx, |this, cx| {
6494 this.available_code_actions = if actions.is_empty() {
6495 None
6496 } else {
6497 Some((
6498 Location {
6499 buffer: start_buffer,
6500 range: start..end,
6501 },
6502 actions.into(),
6503 ))
6504 };
6505 cx.notify();
6506 })
6507 }));
6508 None
6509 }
6510
6511 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6512 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6513 self.show_git_blame_inline = false;
6514
6515 self.show_git_blame_inline_delay_task =
6516 Some(cx.spawn_in(window, async move |this, cx| {
6517 cx.background_executor().timer(delay).await;
6518
6519 this.update(cx, |this, cx| {
6520 this.show_git_blame_inline = true;
6521 cx.notify();
6522 })
6523 .log_err();
6524 }));
6525 }
6526 }
6527
6528 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6529 let snapshot = self.snapshot(window, cx);
6530 let cursor = self.selections.newest::<Point>(cx).head();
6531 let Some((buffer, point, _)) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)
6532 else {
6533 return;
6534 };
6535
6536 let Some(blame) = self.blame.as_ref() else {
6537 return;
6538 };
6539
6540 let row_info = RowInfo {
6541 buffer_id: Some(buffer.remote_id()),
6542 buffer_row: Some(point.row),
6543 ..Default::default()
6544 };
6545 let Some(blame_entry) = blame
6546 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6547 .flatten()
6548 else {
6549 return;
6550 };
6551
6552 let anchor = self.selections.newest_anchor().head();
6553 let position = self.to_pixel_point(anchor, &snapshot, window);
6554 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6555 self.show_blame_popover(&blame_entry, position + last_bounds.origin, true, cx);
6556 };
6557 }
6558
6559 fn show_blame_popover(
6560 &mut self,
6561 blame_entry: &BlameEntry,
6562 position: gpui::Point<Pixels>,
6563 ignore_timeout: bool,
6564 cx: &mut Context<Self>,
6565 ) {
6566 if let Some(state) = &mut self.inline_blame_popover {
6567 state.hide_task.take();
6568 } else {
6569 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay;
6570 let blame_entry = blame_entry.clone();
6571 let show_task = cx.spawn(async move |editor, cx| {
6572 if !ignore_timeout {
6573 cx.background_executor()
6574 .timer(std::time::Duration::from_millis(blame_popover_delay))
6575 .await;
6576 }
6577 editor
6578 .update(cx, |editor, cx| {
6579 editor.inline_blame_popover_show_task.take();
6580 let Some(blame) = editor.blame.as_ref() else {
6581 return;
6582 };
6583 let blame = blame.read(cx);
6584 let details = blame.details_for_entry(&blame_entry);
6585 let markdown = cx.new(|cx| {
6586 Markdown::new(
6587 details
6588 .as_ref()
6589 .map(|message| message.message.clone())
6590 .unwrap_or_default(),
6591 None,
6592 None,
6593 cx,
6594 )
6595 });
6596 editor.inline_blame_popover = Some(InlineBlamePopover {
6597 position,
6598 hide_task: None,
6599 popover_bounds: None,
6600 popover_state: InlineBlamePopoverState {
6601 scroll_handle: ScrollHandle::new(),
6602 commit_message: details,
6603 markdown,
6604 },
6605 keyboard_grace: ignore_timeout,
6606 });
6607 cx.notify();
6608 })
6609 .ok();
6610 });
6611 self.inline_blame_popover_show_task = Some(show_task);
6612 }
6613 }
6614
6615 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6616 self.inline_blame_popover_show_task.take();
6617 if let Some(state) = &mut self.inline_blame_popover {
6618 let hide_task = cx.spawn(async move |editor, cx| {
6619 cx.background_executor()
6620 .timer(std::time::Duration::from_millis(100))
6621 .await;
6622 editor
6623 .update(cx, |editor, cx| {
6624 editor.inline_blame_popover.take();
6625 cx.notify();
6626 })
6627 .ok();
6628 });
6629 state.hide_task = Some(hide_task);
6630 }
6631 }
6632
6633 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6634 if self.pending_rename.is_some() {
6635 return None;
6636 }
6637
6638 let provider = self.semantics_provider.clone()?;
6639 let buffer = self.buffer.read(cx);
6640 let newest_selection = self.selections.newest_anchor().clone();
6641 let cursor_position = newest_selection.head();
6642 let (cursor_buffer, cursor_buffer_position) =
6643 buffer.text_anchor_for_position(cursor_position, cx)?;
6644 let (tail_buffer, tail_buffer_position) =
6645 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6646 if cursor_buffer != tail_buffer {
6647 return None;
6648 }
6649
6650 let snapshot = cursor_buffer.read(cx).snapshot();
6651 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, false);
6652 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, false);
6653 if start_word_range != end_word_range {
6654 self.document_highlights_task.take();
6655 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6656 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6657 return None;
6658 }
6659
6660 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6661 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6662 cx.background_executor()
6663 .timer(Duration::from_millis(debounce))
6664 .await;
6665
6666 let highlights = if let Some(highlights) = cx
6667 .update(|cx| {
6668 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6669 })
6670 .ok()
6671 .flatten()
6672 {
6673 highlights.await.log_err()
6674 } else {
6675 None
6676 };
6677
6678 if let Some(highlights) = highlights {
6679 this.update(cx, |this, cx| {
6680 if this.pending_rename.is_some() {
6681 return;
6682 }
6683
6684 let buffer_id = cursor_position.buffer_id;
6685 let buffer = this.buffer.read(cx);
6686 if !buffer
6687 .text_anchor_for_position(cursor_position, cx)
6688 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
6689 {
6690 return;
6691 }
6692
6693 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6694 let mut write_ranges = Vec::new();
6695 let mut read_ranges = Vec::new();
6696 for highlight in highlights {
6697 for (excerpt_id, excerpt_range) in
6698 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
6699 {
6700 let start = highlight
6701 .range
6702 .start
6703 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6704 let end = highlight
6705 .range
6706 .end
6707 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6708 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6709 continue;
6710 }
6711
6712 let range = Anchor {
6713 buffer_id,
6714 excerpt_id,
6715 text_anchor: start,
6716 diff_base_anchor: None,
6717 }..Anchor {
6718 buffer_id,
6719 excerpt_id,
6720 text_anchor: end,
6721 diff_base_anchor: None,
6722 };
6723 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6724 write_ranges.push(range);
6725 } else {
6726 read_ranges.push(range);
6727 }
6728 }
6729 }
6730
6731 this.highlight_background::<DocumentHighlightRead>(
6732 &read_ranges,
6733 |theme| theme.colors().editor_document_highlight_read_background,
6734 cx,
6735 );
6736 this.highlight_background::<DocumentHighlightWrite>(
6737 &write_ranges,
6738 |theme| theme.colors().editor_document_highlight_write_background,
6739 cx,
6740 );
6741 cx.notify();
6742 })
6743 .log_err();
6744 }
6745 }));
6746 None
6747 }
6748
6749 fn prepare_highlight_query_from_selection(
6750 &mut self,
6751 cx: &mut Context<Editor>,
6752 ) -> Option<(String, Range<Anchor>)> {
6753 if matches!(self.mode, EditorMode::SingleLine { .. }) {
6754 return None;
6755 }
6756 if !EditorSettings::get_global(cx).selection_highlight {
6757 return None;
6758 }
6759 if self.selections.count() != 1 || self.selections.line_mode {
6760 return None;
6761 }
6762 let selection = self.selections.newest::<Point>(cx);
6763 if selection.is_empty() || selection.start.row != selection.end.row {
6764 return None;
6765 }
6766 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6767 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6768 let query = multi_buffer_snapshot
6769 .text_for_range(selection_anchor_range.clone())
6770 .collect::<String>();
6771 if query.trim().is_empty() {
6772 return None;
6773 }
6774 Some((query, selection_anchor_range))
6775 }
6776
6777 fn update_selection_occurrence_highlights(
6778 &mut self,
6779 query_text: String,
6780 query_range: Range<Anchor>,
6781 multi_buffer_range_to_query: Range<Point>,
6782 use_debounce: bool,
6783 window: &mut Window,
6784 cx: &mut Context<Editor>,
6785 ) -> Task<()> {
6786 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6787 cx.spawn_in(window, async move |editor, cx| {
6788 if use_debounce {
6789 cx.background_executor()
6790 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6791 .await;
6792 }
6793 let match_task = cx.background_spawn(async move {
6794 let buffer_ranges = multi_buffer_snapshot
6795 .range_to_buffer_ranges(multi_buffer_range_to_query)
6796 .into_iter()
6797 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6798 let mut match_ranges = Vec::new();
6799 let Ok(regex) = project::search::SearchQuery::text(
6800 query_text.clone(),
6801 false,
6802 false,
6803 false,
6804 Default::default(),
6805 Default::default(),
6806 false,
6807 None,
6808 ) else {
6809 return Vec::default();
6810 };
6811 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6812 match_ranges.extend(
6813 regex
6814 .search(&buffer_snapshot, Some(search_range.clone()))
6815 .await
6816 .into_iter()
6817 .filter_map(|match_range| {
6818 let match_start = buffer_snapshot
6819 .anchor_after(search_range.start + match_range.start);
6820 let match_end = buffer_snapshot
6821 .anchor_before(search_range.start + match_range.end);
6822 let match_anchor_range = Anchor::range_in_buffer(
6823 excerpt_id,
6824 buffer_snapshot.remote_id(),
6825 match_start..match_end,
6826 );
6827 (match_anchor_range != query_range).then_some(match_anchor_range)
6828 }),
6829 );
6830 }
6831 match_ranges
6832 });
6833 let match_ranges = match_task.await;
6834 editor
6835 .update_in(cx, |editor, _, cx| {
6836 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6837 if !match_ranges.is_empty() {
6838 editor.highlight_background::<SelectedTextHighlight>(
6839 &match_ranges,
6840 |theme| theme.colors().editor_document_highlight_bracket_background,
6841 cx,
6842 )
6843 }
6844 })
6845 .log_err();
6846 })
6847 }
6848
6849 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
6850 struct NewlineFold;
6851 let type_id = std::any::TypeId::of::<NewlineFold>();
6852 if !self.mode.is_single_line() {
6853 return;
6854 }
6855 let snapshot = self.snapshot(window, cx);
6856 if snapshot.buffer_snapshot.max_point().row == 0 {
6857 return;
6858 }
6859 let task = cx.background_spawn(async move {
6860 let new_newlines = snapshot
6861 .buffer_chars_at(0)
6862 .filter_map(|(c, i)| {
6863 if c == '\n' {
6864 Some(
6865 snapshot.buffer_snapshot.anchor_after(i)
6866 ..snapshot.buffer_snapshot.anchor_before(i + 1),
6867 )
6868 } else {
6869 None
6870 }
6871 })
6872 .collect::<Vec<_>>();
6873 let existing_newlines = snapshot
6874 .folds_in_range(0..snapshot.buffer_snapshot.len())
6875 .filter_map(|fold| {
6876 if fold.placeholder.type_tag == Some(type_id) {
6877 Some(fold.range.start..fold.range.end)
6878 } else {
6879 None
6880 }
6881 })
6882 .collect::<Vec<_>>();
6883
6884 (new_newlines, existing_newlines)
6885 });
6886 self.folding_newlines = cx.spawn(async move |this, cx| {
6887 let (new_newlines, existing_newlines) = task.await;
6888 if new_newlines == existing_newlines {
6889 return;
6890 }
6891 let placeholder = FoldPlaceholder {
6892 render: Arc::new(move |_, _, cx| {
6893 div()
6894 .bg(cx.theme().status().hint_background)
6895 .border_b_1()
6896 .size_full()
6897 .font(ThemeSettings::get_global(cx).buffer_font.clone())
6898 .border_color(cx.theme().status().hint)
6899 .child("\\n")
6900 .into_any()
6901 }),
6902 constrain_width: false,
6903 merge_adjacent: false,
6904 type_tag: Some(type_id),
6905 };
6906 let creases = new_newlines
6907 .into_iter()
6908 .map(|range| Crease::simple(range, placeholder.clone()))
6909 .collect();
6910 this.update(cx, |this, cx| {
6911 this.display_map.update(cx, |display_map, cx| {
6912 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
6913 display_map.fold(creases, cx);
6914 });
6915 })
6916 .ok();
6917 });
6918 }
6919
6920 fn refresh_selected_text_highlights(
6921 &mut self,
6922 on_buffer_edit: bool,
6923 window: &mut Window,
6924 cx: &mut Context<Editor>,
6925 ) {
6926 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
6927 else {
6928 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6929 self.quick_selection_highlight_task.take();
6930 self.debounced_selection_highlight_task.take();
6931 return;
6932 };
6933 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6934 if on_buffer_edit
6935 || self
6936 .quick_selection_highlight_task
6937 .as_ref()
6938 .map_or(true, |(prev_anchor_range, _)| {
6939 prev_anchor_range != &query_range
6940 })
6941 {
6942 let multi_buffer_visible_start = self
6943 .scroll_manager
6944 .anchor()
6945 .anchor
6946 .to_point(&multi_buffer_snapshot);
6947 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
6948 multi_buffer_visible_start
6949 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
6950 Bias::Left,
6951 );
6952 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
6953 self.quick_selection_highlight_task = Some((
6954 query_range.clone(),
6955 self.update_selection_occurrence_highlights(
6956 query_text.clone(),
6957 query_range.clone(),
6958 multi_buffer_visible_range,
6959 false,
6960 window,
6961 cx,
6962 ),
6963 ));
6964 }
6965 if on_buffer_edit
6966 || self
6967 .debounced_selection_highlight_task
6968 .as_ref()
6969 .map_or(true, |(prev_anchor_range, _)| {
6970 prev_anchor_range != &query_range
6971 })
6972 {
6973 let multi_buffer_start = multi_buffer_snapshot
6974 .anchor_before(0)
6975 .to_point(&multi_buffer_snapshot);
6976 let multi_buffer_end = multi_buffer_snapshot
6977 .anchor_after(multi_buffer_snapshot.len())
6978 .to_point(&multi_buffer_snapshot);
6979 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
6980 self.debounced_selection_highlight_task = Some((
6981 query_range.clone(),
6982 self.update_selection_occurrence_highlights(
6983 query_text,
6984 query_range,
6985 multi_buffer_full_range,
6986 true,
6987 window,
6988 cx,
6989 ),
6990 ));
6991 }
6992 }
6993
6994 pub fn refresh_edit_prediction(
6995 &mut self,
6996 debounce: bool,
6997 user_requested: bool,
6998 window: &mut Window,
6999 cx: &mut Context<Self>,
7000 ) -> Option<()> {
7001 if DisableAiSettings::get_global(cx).disable_ai {
7002 return None;
7003 }
7004
7005 let provider = self.edit_prediction_provider()?;
7006 let cursor = self.selections.newest_anchor().head();
7007 let (buffer, cursor_buffer_position) =
7008 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7009
7010 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7011 self.discard_edit_prediction(false, cx);
7012 return None;
7013 }
7014
7015 if !user_requested
7016 && (!self.should_show_edit_predictions()
7017 || !self.is_focused(window)
7018 || buffer.read(cx).is_empty())
7019 {
7020 self.discard_edit_prediction(false, cx);
7021 return None;
7022 }
7023
7024 self.update_visible_edit_prediction(window, cx);
7025 provider.refresh(
7026 self.project.clone(),
7027 buffer,
7028 cursor_buffer_position,
7029 debounce,
7030 cx,
7031 );
7032 Some(())
7033 }
7034
7035 fn show_edit_predictions_in_menu(&self) -> bool {
7036 match self.edit_prediction_settings {
7037 EditPredictionSettings::Disabled => false,
7038 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7039 }
7040 }
7041
7042 pub fn edit_predictions_enabled(&self) -> bool {
7043 match self.edit_prediction_settings {
7044 EditPredictionSettings::Disabled => false,
7045 EditPredictionSettings::Enabled { .. } => true,
7046 }
7047 }
7048
7049 fn edit_prediction_requires_modifier(&self) -> bool {
7050 match self.edit_prediction_settings {
7051 EditPredictionSettings::Disabled => false,
7052 EditPredictionSettings::Enabled {
7053 preview_requires_modifier,
7054 ..
7055 } => preview_requires_modifier,
7056 }
7057 }
7058
7059 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7060 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7061 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7062 self.discard_edit_prediction(false, cx);
7063 } else {
7064 let selection = self.selections.newest_anchor();
7065 let cursor = selection.head();
7066
7067 if let Some((buffer, cursor_buffer_position)) =
7068 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7069 {
7070 self.edit_prediction_settings =
7071 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7072 }
7073 }
7074 }
7075
7076 fn edit_prediction_settings_at_position(
7077 &self,
7078 buffer: &Entity<Buffer>,
7079 buffer_position: language::Anchor,
7080 cx: &App,
7081 ) -> EditPredictionSettings {
7082 if !self.mode.is_full()
7083 || !self.show_edit_predictions_override.unwrap_or(true)
7084 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7085 {
7086 return EditPredictionSettings::Disabled;
7087 }
7088
7089 let buffer = buffer.read(cx);
7090
7091 let file = buffer.file();
7092
7093 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7094 return EditPredictionSettings::Disabled;
7095 };
7096
7097 let by_provider = matches!(
7098 self.menu_edit_predictions_policy,
7099 MenuEditPredictionsPolicy::ByProvider
7100 );
7101
7102 let show_in_menu = by_provider
7103 && self
7104 .edit_prediction_provider
7105 .as_ref()
7106 .map_or(false, |provider| {
7107 provider.provider.show_completions_in_menu()
7108 });
7109
7110 let preview_requires_modifier =
7111 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7112
7113 EditPredictionSettings::Enabled {
7114 show_in_menu,
7115 preview_requires_modifier,
7116 }
7117 }
7118
7119 fn should_show_edit_predictions(&self) -> bool {
7120 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7121 }
7122
7123 pub fn edit_prediction_preview_is_active(&self) -> bool {
7124 matches!(
7125 self.edit_prediction_preview,
7126 EditPredictionPreview::Active { .. }
7127 )
7128 }
7129
7130 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7131 let cursor = self.selections.newest_anchor().head();
7132 if let Some((buffer, cursor_position)) =
7133 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7134 {
7135 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7136 } else {
7137 false
7138 }
7139 }
7140
7141 pub fn supports_minimap(&self, cx: &App) -> bool {
7142 !self.minimap_visibility.disabled() && self.is_singleton(cx)
7143 }
7144
7145 fn edit_predictions_enabled_in_buffer(
7146 &self,
7147 buffer: &Entity<Buffer>,
7148 buffer_position: language::Anchor,
7149 cx: &App,
7150 ) -> bool {
7151 maybe!({
7152 if self.read_only(cx) {
7153 return Some(false);
7154 }
7155 let provider = self.edit_prediction_provider()?;
7156 if !provider.is_enabled(&buffer, buffer_position, cx) {
7157 return Some(false);
7158 }
7159 let buffer = buffer.read(cx);
7160 let Some(file) = buffer.file() else {
7161 return Some(true);
7162 };
7163 let settings = all_language_settings(Some(file), cx);
7164 Some(settings.edit_predictions_enabled_for_file(file, cx))
7165 })
7166 .unwrap_or(false)
7167 }
7168
7169 fn cycle_edit_prediction(
7170 &mut self,
7171 direction: Direction,
7172 window: &mut Window,
7173 cx: &mut Context<Self>,
7174 ) -> Option<()> {
7175 let provider = self.edit_prediction_provider()?;
7176 let cursor = self.selections.newest_anchor().head();
7177 let (buffer, cursor_buffer_position) =
7178 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7179 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7180 return None;
7181 }
7182
7183 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7184 self.update_visible_edit_prediction(window, cx);
7185
7186 Some(())
7187 }
7188
7189 pub fn show_edit_prediction(
7190 &mut self,
7191 _: &ShowEditPrediction,
7192 window: &mut Window,
7193 cx: &mut Context<Self>,
7194 ) {
7195 if !self.has_active_edit_prediction() {
7196 self.refresh_edit_prediction(false, true, window, cx);
7197 return;
7198 }
7199
7200 self.update_visible_edit_prediction(window, cx);
7201 }
7202
7203 pub fn display_cursor_names(
7204 &mut self,
7205 _: &DisplayCursorNames,
7206 window: &mut Window,
7207 cx: &mut Context<Self>,
7208 ) {
7209 self.show_cursor_names(window, cx);
7210 }
7211
7212 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7213 self.show_cursor_names = true;
7214 cx.notify();
7215 cx.spawn_in(window, async move |this, cx| {
7216 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7217 this.update(cx, |this, cx| {
7218 this.show_cursor_names = false;
7219 cx.notify()
7220 })
7221 .ok()
7222 })
7223 .detach();
7224 }
7225
7226 pub fn next_edit_prediction(
7227 &mut self,
7228 _: &NextEditPrediction,
7229 window: &mut Window,
7230 cx: &mut Context<Self>,
7231 ) {
7232 if self.has_active_edit_prediction() {
7233 self.cycle_edit_prediction(Direction::Next, window, cx);
7234 } else {
7235 let is_copilot_disabled = self
7236 .refresh_edit_prediction(false, true, window, cx)
7237 .is_none();
7238 if is_copilot_disabled {
7239 cx.propagate();
7240 }
7241 }
7242 }
7243
7244 pub fn previous_edit_prediction(
7245 &mut self,
7246 _: &PreviousEditPrediction,
7247 window: &mut Window,
7248 cx: &mut Context<Self>,
7249 ) {
7250 if self.has_active_edit_prediction() {
7251 self.cycle_edit_prediction(Direction::Prev, window, cx);
7252 } else {
7253 let is_copilot_disabled = self
7254 .refresh_edit_prediction(false, true, window, cx)
7255 .is_none();
7256 if is_copilot_disabled {
7257 cx.propagate();
7258 }
7259 }
7260 }
7261
7262 pub fn accept_edit_prediction(
7263 &mut self,
7264 _: &AcceptEditPrediction,
7265 window: &mut Window,
7266 cx: &mut Context<Self>,
7267 ) {
7268 if self.show_edit_predictions_in_menu() {
7269 self.hide_context_menu(window, cx);
7270 }
7271
7272 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7273 return;
7274 };
7275
7276 self.report_edit_prediction_event(active_edit_prediction.completion_id.clone(), true, cx);
7277
7278 match &active_edit_prediction.completion {
7279 EditPrediction::Move { target, .. } => {
7280 let target = *target;
7281
7282 if let Some(position_map) = &self.last_position_map {
7283 if position_map
7284 .visible_row_range
7285 .contains(&target.to_display_point(&position_map.snapshot).row())
7286 || !self.edit_prediction_requires_modifier()
7287 {
7288 self.unfold_ranges(&[target..target], true, false, cx);
7289 // Note that this is also done in vim's handler of the Tab action.
7290 self.change_selections(
7291 SelectionEffects::scroll(Autoscroll::newest()),
7292 window,
7293 cx,
7294 |selections| {
7295 selections.select_anchor_ranges([target..target]);
7296 },
7297 );
7298 self.clear_row_highlights::<EditPredictionPreview>();
7299
7300 self.edit_prediction_preview
7301 .set_previous_scroll_position(None);
7302 } else {
7303 self.edit_prediction_preview
7304 .set_previous_scroll_position(Some(
7305 position_map.snapshot.scroll_anchor,
7306 ));
7307
7308 self.highlight_rows::<EditPredictionPreview>(
7309 target..target,
7310 cx.theme().colors().editor_highlighted_line_background,
7311 RowHighlightOptions {
7312 autoscroll: true,
7313 ..Default::default()
7314 },
7315 cx,
7316 );
7317 self.request_autoscroll(Autoscroll::fit(), cx);
7318 }
7319 }
7320 }
7321 EditPrediction::Edit { edits, .. } => {
7322 if let Some(provider) = self.edit_prediction_provider() {
7323 provider.accept(cx);
7324 }
7325
7326 // Store the transaction ID and selections before applying the edit
7327 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7328
7329 let snapshot = self.buffer.read(cx).snapshot(cx);
7330 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7331
7332 self.buffer.update(cx, |buffer, cx| {
7333 buffer.edit(edits.iter().cloned(), None, cx)
7334 });
7335
7336 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7337 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7338 });
7339
7340 let selections = self.selections.disjoint_anchors();
7341 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7342 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7343 if has_new_transaction {
7344 self.selection_history
7345 .insert_transaction(transaction_id_now, selections);
7346 }
7347 }
7348
7349 self.update_visible_edit_prediction(window, cx);
7350 if self.active_edit_prediction.is_none() {
7351 self.refresh_edit_prediction(true, true, window, cx);
7352 }
7353
7354 cx.notify();
7355 }
7356 }
7357
7358 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7359 }
7360
7361 pub fn accept_partial_edit_prediction(
7362 &mut self,
7363 _: &AcceptPartialEditPrediction,
7364 window: &mut Window,
7365 cx: &mut Context<Self>,
7366 ) {
7367 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7368 return;
7369 };
7370 if self.selections.count() != 1 {
7371 return;
7372 }
7373
7374 self.report_edit_prediction_event(active_edit_prediction.completion_id.clone(), true, cx);
7375
7376 match &active_edit_prediction.completion {
7377 EditPrediction::Move { target, .. } => {
7378 let target = *target;
7379 self.change_selections(
7380 SelectionEffects::scroll(Autoscroll::newest()),
7381 window,
7382 cx,
7383 |selections| {
7384 selections.select_anchor_ranges([target..target]);
7385 },
7386 );
7387 }
7388 EditPrediction::Edit { edits, .. } => {
7389 // Find an insertion that starts at the cursor position.
7390 let snapshot = self.buffer.read(cx).snapshot(cx);
7391 let cursor_offset = self.selections.newest::<usize>(cx).head();
7392 let insertion = edits.iter().find_map(|(range, text)| {
7393 let range = range.to_offset(&snapshot);
7394 if range.is_empty() && range.start == cursor_offset {
7395 Some(text)
7396 } else {
7397 None
7398 }
7399 });
7400
7401 if let Some(text) = insertion {
7402 let mut partial_completion = text
7403 .chars()
7404 .by_ref()
7405 .take_while(|c| c.is_alphabetic())
7406 .collect::<String>();
7407 if partial_completion.is_empty() {
7408 partial_completion = text
7409 .chars()
7410 .by_ref()
7411 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7412 .collect::<String>();
7413 }
7414
7415 cx.emit(EditorEvent::InputHandled {
7416 utf16_range_to_replace: None,
7417 text: partial_completion.clone().into(),
7418 });
7419
7420 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7421
7422 self.refresh_edit_prediction(true, true, window, cx);
7423 cx.notify();
7424 } else {
7425 self.accept_edit_prediction(&Default::default(), window, cx);
7426 }
7427 }
7428 }
7429 }
7430
7431 fn discard_edit_prediction(
7432 &mut self,
7433 should_report_edit_prediction_event: bool,
7434 cx: &mut Context<Self>,
7435 ) -> bool {
7436 if should_report_edit_prediction_event {
7437 let completion_id = self
7438 .active_edit_prediction
7439 .as_ref()
7440 .and_then(|active_completion| active_completion.completion_id.clone());
7441
7442 self.report_edit_prediction_event(completion_id, false, cx);
7443 }
7444
7445 if let Some(provider) = self.edit_prediction_provider() {
7446 provider.discard(cx);
7447 }
7448
7449 self.take_active_edit_prediction(cx)
7450 }
7451
7452 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7453 let Some(provider) = self.edit_prediction_provider() else {
7454 return;
7455 };
7456
7457 let Some((_, buffer, _)) = self
7458 .buffer
7459 .read(cx)
7460 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7461 else {
7462 return;
7463 };
7464
7465 let extension = buffer
7466 .read(cx)
7467 .file()
7468 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
7469
7470 let event_type = match accepted {
7471 true => "Edit Prediction Accepted",
7472 false => "Edit Prediction Discarded",
7473 };
7474 telemetry::event!(
7475 event_type,
7476 provider = provider.name(),
7477 prediction_id = id,
7478 suggestion_accepted = accepted,
7479 file_extension = extension,
7480 );
7481 }
7482
7483 pub fn has_active_edit_prediction(&self) -> bool {
7484 self.active_edit_prediction.is_some()
7485 }
7486
7487 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7488 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7489 return false;
7490 };
7491
7492 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7493 self.clear_highlights::<EditPredictionHighlight>(cx);
7494 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7495 true
7496 }
7497
7498 /// Returns true when we're displaying the edit prediction popover below the cursor
7499 /// like we are not previewing and the LSP autocomplete menu is visible
7500 /// or we are in `when_holding_modifier` mode.
7501 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7502 if self.edit_prediction_preview_is_active()
7503 || !self.show_edit_predictions_in_menu()
7504 || !self.edit_predictions_enabled()
7505 {
7506 return false;
7507 }
7508
7509 if self.has_visible_completions_menu() {
7510 return true;
7511 }
7512
7513 has_completion && self.edit_prediction_requires_modifier()
7514 }
7515
7516 fn handle_modifiers_changed(
7517 &mut self,
7518 modifiers: Modifiers,
7519 position_map: &PositionMap,
7520 window: &mut Window,
7521 cx: &mut Context<Self>,
7522 ) {
7523 if self.show_edit_predictions_in_menu() {
7524 self.update_edit_prediction_preview(&modifiers, window, cx);
7525 }
7526
7527 self.update_selection_mode(&modifiers, position_map, window, cx);
7528
7529 let mouse_position = window.mouse_position();
7530 if !position_map.text_hitbox.is_hovered(window) {
7531 return;
7532 }
7533
7534 self.update_hovered_link(
7535 position_map.point_for_position(mouse_position),
7536 &position_map.snapshot,
7537 modifiers,
7538 window,
7539 cx,
7540 )
7541 }
7542
7543 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7544 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7545 if invert {
7546 match multi_cursor_setting {
7547 MultiCursorModifier::Alt => modifiers.alt,
7548 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7549 }
7550 } else {
7551 match multi_cursor_setting {
7552 MultiCursorModifier::Alt => modifiers.secondary(),
7553 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7554 }
7555 }
7556 }
7557
7558 fn columnar_selection_mode(
7559 modifiers: &Modifiers,
7560 cx: &mut Context<Self>,
7561 ) -> Option<ColumnarMode> {
7562 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7563 if Self::multi_cursor_modifier(false, modifiers, cx) {
7564 Some(ColumnarMode::FromMouse)
7565 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7566 Some(ColumnarMode::FromSelection)
7567 } else {
7568 None
7569 }
7570 } else {
7571 None
7572 }
7573 }
7574
7575 fn update_selection_mode(
7576 &mut self,
7577 modifiers: &Modifiers,
7578 position_map: &PositionMap,
7579 window: &mut Window,
7580 cx: &mut Context<Self>,
7581 ) {
7582 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7583 return;
7584 };
7585 if self.selections.pending.is_none() {
7586 return;
7587 }
7588
7589 let mouse_position = window.mouse_position();
7590 let point_for_position = position_map.point_for_position(mouse_position);
7591 let position = point_for_position.previous_valid;
7592
7593 self.select(
7594 SelectPhase::BeginColumnar {
7595 position,
7596 reset: false,
7597 mode,
7598 goal_column: point_for_position.exact_unclipped.column(),
7599 },
7600 window,
7601 cx,
7602 );
7603 }
7604
7605 fn update_edit_prediction_preview(
7606 &mut self,
7607 modifiers: &Modifiers,
7608 window: &mut Window,
7609 cx: &mut Context<Self>,
7610 ) {
7611 let mut modifiers_held = false;
7612 if let Some(accept_keystroke) = self
7613 .accept_edit_prediction_keybind(false, window, cx)
7614 .keystroke()
7615 {
7616 modifiers_held = modifiers_held
7617 || (&accept_keystroke.modifiers == modifiers
7618 && accept_keystroke.modifiers.modified());
7619 };
7620 if let Some(accept_partial_keystroke) = self
7621 .accept_edit_prediction_keybind(true, window, cx)
7622 .keystroke()
7623 {
7624 modifiers_held = modifiers_held
7625 || (&accept_partial_keystroke.modifiers == modifiers
7626 && accept_partial_keystroke.modifiers.modified());
7627 }
7628
7629 if modifiers_held {
7630 if matches!(
7631 self.edit_prediction_preview,
7632 EditPredictionPreview::Inactive { .. }
7633 ) {
7634 self.edit_prediction_preview = EditPredictionPreview::Active {
7635 previous_scroll_position: None,
7636 since: Instant::now(),
7637 };
7638
7639 self.update_visible_edit_prediction(window, cx);
7640 cx.notify();
7641 }
7642 } else if let EditPredictionPreview::Active {
7643 previous_scroll_position,
7644 since,
7645 } = self.edit_prediction_preview
7646 {
7647 if let (Some(previous_scroll_position), Some(position_map)) =
7648 (previous_scroll_position, self.last_position_map.as_ref())
7649 {
7650 self.set_scroll_position(
7651 previous_scroll_position
7652 .scroll_position(&position_map.snapshot.display_snapshot),
7653 window,
7654 cx,
7655 );
7656 }
7657
7658 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7659 released_too_fast: since.elapsed() < Duration::from_millis(200),
7660 };
7661 self.clear_row_highlights::<EditPredictionPreview>();
7662 self.update_visible_edit_prediction(window, cx);
7663 cx.notify();
7664 }
7665 }
7666
7667 fn update_visible_edit_prediction(
7668 &mut self,
7669 _window: &mut Window,
7670 cx: &mut Context<Self>,
7671 ) -> Option<()> {
7672 if DisableAiSettings::get_global(cx).disable_ai {
7673 return None;
7674 }
7675
7676 let selection = self.selections.newest_anchor();
7677 let cursor = selection.head();
7678 let multibuffer = self.buffer.read(cx).snapshot(cx);
7679 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7680 let excerpt_id = cursor.excerpt_id;
7681
7682 let show_in_menu = self.show_edit_predictions_in_menu();
7683 let completions_menu_has_precedence = !show_in_menu
7684 && (self.context_menu.borrow().is_some()
7685 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
7686
7687 if completions_menu_has_precedence
7688 || !offset_selection.is_empty()
7689 || self
7690 .active_edit_prediction
7691 .as_ref()
7692 .map_or(false, |completion| {
7693 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
7694 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7695 !invalidation_range.contains(&offset_selection.head())
7696 })
7697 {
7698 self.discard_edit_prediction(false, cx);
7699 return None;
7700 }
7701
7702 self.take_active_edit_prediction(cx);
7703 let Some(provider) = self.edit_prediction_provider() else {
7704 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7705 return None;
7706 };
7707
7708 let (buffer, cursor_buffer_position) =
7709 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7710
7711 self.edit_prediction_settings =
7712 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7713
7714 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7715
7716 if self.edit_prediction_indent_conflict {
7717 let cursor_point = cursor.to_point(&multibuffer);
7718
7719 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7720
7721 if let Some((_, indent)) = indents.iter().next() {
7722 if indent.len == cursor_point.column {
7723 self.edit_prediction_indent_conflict = false;
7724 }
7725 }
7726 }
7727
7728 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7729 let edits = edit_prediction
7730 .edits
7731 .into_iter()
7732 .flat_map(|(range, new_text)| {
7733 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7734 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7735 Some((start..end, new_text))
7736 })
7737 .collect::<Vec<_>>();
7738 if edits.is_empty() {
7739 return None;
7740 }
7741
7742 let first_edit_start = edits.first().unwrap().0.start;
7743 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7744 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7745
7746 let last_edit_end = edits.last().unwrap().0.end;
7747 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7748 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7749
7750 let cursor_row = cursor.to_point(&multibuffer).row;
7751
7752 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7753
7754 let mut inlay_ids = Vec::new();
7755 let invalidation_row_range;
7756 let move_invalidation_row_range = if cursor_row < edit_start_row {
7757 Some(cursor_row..edit_end_row)
7758 } else if cursor_row > edit_end_row {
7759 Some(edit_start_row..cursor_row)
7760 } else {
7761 None
7762 };
7763 let is_move =
7764 move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode;
7765 let completion = if is_move {
7766 invalidation_row_range =
7767 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7768 let target = first_edit_start;
7769 EditPrediction::Move { target, snapshot }
7770 } else {
7771 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7772 && !self.edit_predictions_hidden_for_vim_mode;
7773
7774 if show_completions_in_buffer {
7775 if edits
7776 .iter()
7777 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7778 {
7779 let mut inlays = Vec::new();
7780 for (range, new_text) in &edits {
7781 let inlay = Inlay::edit_prediction(
7782 post_inc(&mut self.next_inlay_id),
7783 range.start,
7784 new_text.as_str(),
7785 );
7786 inlay_ids.push(inlay.id);
7787 inlays.push(inlay);
7788 }
7789
7790 self.splice_inlays(&[], inlays, cx);
7791 } else {
7792 let background_color = cx.theme().status().deleted_background;
7793 self.highlight_text::<EditPredictionHighlight>(
7794 edits.iter().map(|(range, _)| range.clone()).collect(),
7795 HighlightStyle {
7796 background_color: Some(background_color),
7797 ..Default::default()
7798 },
7799 cx,
7800 );
7801 }
7802 }
7803
7804 invalidation_row_range = edit_start_row..edit_end_row;
7805
7806 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7807 if provider.show_tab_accept_marker() {
7808 EditDisplayMode::TabAccept
7809 } else {
7810 EditDisplayMode::Inline
7811 }
7812 } else {
7813 EditDisplayMode::DiffPopover
7814 };
7815
7816 EditPrediction::Edit {
7817 edits,
7818 edit_preview: edit_prediction.edit_preview,
7819 display_mode,
7820 snapshot,
7821 }
7822 };
7823
7824 let invalidation_range = multibuffer
7825 .anchor_before(Point::new(invalidation_row_range.start, 0))
7826 ..multibuffer.anchor_after(Point::new(
7827 invalidation_row_range.end,
7828 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7829 ));
7830
7831 self.stale_edit_prediction_in_menu = None;
7832 self.active_edit_prediction = Some(EditPredictionState {
7833 inlay_ids,
7834 completion,
7835 completion_id: edit_prediction.id,
7836 invalidation_range,
7837 });
7838
7839 cx.notify();
7840
7841 Some(())
7842 }
7843
7844 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionProviderHandle>> {
7845 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7846 }
7847
7848 fn clear_tasks(&mut self) {
7849 self.tasks.clear()
7850 }
7851
7852 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7853 if self.tasks.insert(key, value).is_some() {
7854 // This case should hopefully be rare, but just in case...
7855 log::error!(
7856 "multiple different run targets found on a single line, only the last target will be rendered"
7857 )
7858 }
7859 }
7860
7861 /// Get all display points of breakpoints that will be rendered within editor
7862 ///
7863 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7864 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7865 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7866 fn active_breakpoints(
7867 &self,
7868 range: Range<DisplayRow>,
7869 window: &mut Window,
7870 cx: &mut Context<Self>,
7871 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7872 let mut breakpoint_display_points = HashMap::default();
7873
7874 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7875 return breakpoint_display_points;
7876 };
7877
7878 let snapshot = self.snapshot(window, cx);
7879
7880 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
7881 let Some(project) = self.project.as_ref() else {
7882 return breakpoint_display_points;
7883 };
7884
7885 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7886 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7887
7888 for (buffer_snapshot, range, excerpt_id) in
7889 multi_buffer_snapshot.range_to_buffer_ranges(range)
7890 {
7891 let Some(buffer) = project
7892 .read(cx)
7893 .buffer_for_id(buffer_snapshot.remote_id(), cx)
7894 else {
7895 continue;
7896 };
7897 let breakpoints = breakpoint_store.read(cx).breakpoints(
7898 &buffer,
7899 Some(
7900 buffer_snapshot.anchor_before(range.start)
7901 ..buffer_snapshot.anchor_after(range.end),
7902 ),
7903 buffer_snapshot,
7904 cx,
7905 );
7906 for (breakpoint, state) in breakpoints {
7907 let multi_buffer_anchor =
7908 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
7909 let position = multi_buffer_anchor
7910 .to_point(&multi_buffer_snapshot)
7911 .to_display_point(&snapshot);
7912
7913 breakpoint_display_points.insert(
7914 position.row(),
7915 (multi_buffer_anchor, breakpoint.bp.clone(), state),
7916 );
7917 }
7918 }
7919
7920 breakpoint_display_points
7921 }
7922
7923 fn breakpoint_context_menu(
7924 &self,
7925 anchor: Anchor,
7926 window: &mut Window,
7927 cx: &mut Context<Self>,
7928 ) -> Entity<ui::ContextMenu> {
7929 let weak_editor = cx.weak_entity();
7930 let focus_handle = self.focus_handle(cx);
7931
7932 let row = self
7933 .buffer
7934 .read(cx)
7935 .snapshot(cx)
7936 .summary_for_anchor::<Point>(&anchor)
7937 .row;
7938
7939 let breakpoint = self
7940 .breakpoint_at_row(row, window, cx)
7941 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
7942
7943 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
7944 "Edit Log Breakpoint"
7945 } else {
7946 "Set Log Breakpoint"
7947 };
7948
7949 let condition_breakpoint_msg = if breakpoint
7950 .as_ref()
7951 .is_some_and(|bp| bp.1.condition.is_some())
7952 {
7953 "Edit Condition Breakpoint"
7954 } else {
7955 "Set Condition Breakpoint"
7956 };
7957
7958 let hit_condition_breakpoint_msg = if breakpoint
7959 .as_ref()
7960 .is_some_and(|bp| bp.1.hit_condition.is_some())
7961 {
7962 "Edit Hit Condition Breakpoint"
7963 } else {
7964 "Set Hit Condition Breakpoint"
7965 };
7966
7967 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
7968 "Unset Breakpoint"
7969 } else {
7970 "Set Breakpoint"
7971 };
7972
7973 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
7974
7975 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
7976 BreakpointState::Enabled => Some("Disable"),
7977 BreakpointState::Disabled => Some("Enable"),
7978 });
7979
7980 let (anchor, breakpoint) =
7981 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
7982
7983 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
7984 menu.on_blur_subscription(Subscription::new(|| {}))
7985 .context(focus_handle)
7986 .when(run_to_cursor, |this| {
7987 let weak_editor = weak_editor.clone();
7988 this.entry("Run to cursor", None, move |window, cx| {
7989 weak_editor
7990 .update(cx, |editor, cx| {
7991 editor.change_selections(
7992 SelectionEffects::no_scroll(),
7993 window,
7994 cx,
7995 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
7996 );
7997 })
7998 .ok();
7999
8000 window.dispatch_action(Box::new(RunToCursor), cx);
8001 })
8002 .separator()
8003 })
8004 .when_some(toggle_state_msg, |this, msg| {
8005 this.entry(msg, None, {
8006 let weak_editor = weak_editor.clone();
8007 let breakpoint = breakpoint.clone();
8008 move |_window, cx| {
8009 weak_editor
8010 .update(cx, |this, cx| {
8011 this.edit_breakpoint_at_anchor(
8012 anchor,
8013 breakpoint.as_ref().clone(),
8014 BreakpointEditAction::InvertState,
8015 cx,
8016 );
8017 })
8018 .log_err();
8019 }
8020 })
8021 })
8022 .entry(set_breakpoint_msg, None, {
8023 let weak_editor = weak_editor.clone();
8024 let breakpoint = breakpoint.clone();
8025 move |_window, cx| {
8026 weak_editor
8027 .update(cx, |this, cx| {
8028 this.edit_breakpoint_at_anchor(
8029 anchor,
8030 breakpoint.as_ref().clone(),
8031 BreakpointEditAction::Toggle,
8032 cx,
8033 );
8034 })
8035 .log_err();
8036 }
8037 })
8038 .entry(log_breakpoint_msg, None, {
8039 let breakpoint = breakpoint.clone();
8040 let weak_editor = weak_editor.clone();
8041 move |window, cx| {
8042 weak_editor
8043 .update(cx, |this, cx| {
8044 this.add_edit_breakpoint_block(
8045 anchor,
8046 breakpoint.as_ref(),
8047 BreakpointPromptEditAction::Log,
8048 window,
8049 cx,
8050 );
8051 })
8052 .log_err();
8053 }
8054 })
8055 .entry(condition_breakpoint_msg, None, {
8056 let breakpoint = breakpoint.clone();
8057 let weak_editor = weak_editor.clone();
8058 move |window, cx| {
8059 weak_editor
8060 .update(cx, |this, cx| {
8061 this.add_edit_breakpoint_block(
8062 anchor,
8063 breakpoint.as_ref(),
8064 BreakpointPromptEditAction::Condition,
8065 window,
8066 cx,
8067 );
8068 })
8069 .log_err();
8070 }
8071 })
8072 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8073 weak_editor
8074 .update(cx, |this, cx| {
8075 this.add_edit_breakpoint_block(
8076 anchor,
8077 breakpoint.as_ref(),
8078 BreakpointPromptEditAction::HitCondition,
8079 window,
8080 cx,
8081 );
8082 })
8083 .log_err();
8084 })
8085 })
8086 }
8087
8088 fn render_breakpoint(
8089 &self,
8090 position: Anchor,
8091 row: DisplayRow,
8092 breakpoint: &Breakpoint,
8093 state: Option<BreakpointSessionState>,
8094 cx: &mut Context<Self>,
8095 ) -> IconButton {
8096 let is_rejected = state.is_some_and(|s| !s.verified);
8097 // Is it a breakpoint that shows up when hovering over gutter?
8098 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8099 (false, false),
8100 |PhantomBreakpointIndicator {
8101 is_active,
8102 display_row,
8103 collides_with_existing_breakpoint,
8104 }| {
8105 (
8106 is_active && display_row == row,
8107 collides_with_existing_breakpoint,
8108 )
8109 },
8110 );
8111
8112 let (color, icon) = {
8113 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8114 (false, false) => ui::IconName::DebugBreakpoint,
8115 (true, false) => ui::IconName::DebugLogBreakpoint,
8116 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8117 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8118 };
8119
8120 let color = if is_phantom {
8121 Color::Hint
8122 } else if is_rejected {
8123 Color::Disabled
8124 } else {
8125 Color::Debugger
8126 };
8127
8128 (color, icon)
8129 };
8130
8131 let breakpoint = Arc::from(breakpoint.clone());
8132
8133 let alt_as_text = gpui::Keystroke {
8134 modifiers: Modifiers::secondary_key(),
8135 ..Default::default()
8136 };
8137 let primary_action_text = if breakpoint.is_disabled() {
8138 "Enable breakpoint"
8139 } else if is_phantom && !collides_with_existing {
8140 "Set breakpoint"
8141 } else {
8142 "Unset breakpoint"
8143 };
8144 let focus_handle = self.focus_handle.clone();
8145
8146 let meta = if is_rejected {
8147 SharedString::from("No executable code is associated with this line.")
8148 } else if collides_with_existing && !breakpoint.is_disabled() {
8149 SharedString::from(format!(
8150 "{alt_as_text}-click to disable,\nright-click for more options."
8151 ))
8152 } else {
8153 SharedString::from("Right-click for more options.")
8154 };
8155 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8156 .icon_size(IconSize::XSmall)
8157 .size(ui::ButtonSize::None)
8158 .when(is_rejected, |this| {
8159 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8160 })
8161 .icon_color(color)
8162 .style(ButtonStyle::Transparent)
8163 .on_click(cx.listener({
8164 let breakpoint = breakpoint.clone();
8165
8166 move |editor, event: &ClickEvent, window, cx| {
8167 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8168 BreakpointEditAction::InvertState
8169 } else {
8170 BreakpointEditAction::Toggle
8171 };
8172
8173 window.focus(&editor.focus_handle(cx));
8174 editor.edit_breakpoint_at_anchor(
8175 position,
8176 breakpoint.as_ref().clone(),
8177 edit_action,
8178 cx,
8179 );
8180 }
8181 }))
8182 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8183 editor.set_breakpoint_context_menu(
8184 row,
8185 Some(position),
8186 event.down.position,
8187 window,
8188 cx,
8189 );
8190 }))
8191 .tooltip(move |window, cx| {
8192 Tooltip::with_meta_in(
8193 primary_action_text,
8194 Some(&ToggleBreakpoint),
8195 meta.clone(),
8196 &focus_handle,
8197 window,
8198 cx,
8199 )
8200 })
8201 }
8202
8203 fn build_tasks_context(
8204 project: &Entity<Project>,
8205 buffer: &Entity<Buffer>,
8206 buffer_row: u32,
8207 tasks: &Arc<RunnableTasks>,
8208 cx: &mut Context<Self>,
8209 ) -> Task<Option<task::TaskContext>> {
8210 let position = Point::new(buffer_row, tasks.column);
8211 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8212 let location = Location {
8213 buffer: buffer.clone(),
8214 range: range_start..range_start,
8215 };
8216 // Fill in the environmental variables from the tree-sitter captures
8217 let mut captured_task_variables = TaskVariables::default();
8218 for (capture_name, value) in tasks.extra_variables.clone() {
8219 captured_task_variables.insert(
8220 task::VariableName::Custom(capture_name.into()),
8221 value.clone(),
8222 );
8223 }
8224 project.update(cx, |project, cx| {
8225 project.task_store().update(cx, |task_store, cx| {
8226 task_store.task_context_for_location(captured_task_variables, location, cx)
8227 })
8228 })
8229 }
8230
8231 pub fn spawn_nearest_task(
8232 &mut self,
8233 action: &SpawnNearestTask,
8234 window: &mut Window,
8235 cx: &mut Context<Self>,
8236 ) {
8237 let Some((workspace, _)) = self.workspace.clone() else {
8238 return;
8239 };
8240 let Some(project) = self.project.clone() else {
8241 return;
8242 };
8243
8244 // Try to find a closest, enclosing node using tree-sitter that has a task
8245 let Some((buffer, buffer_row, tasks)) = self
8246 .find_enclosing_node_task(cx)
8247 // Or find the task that's closest in row-distance.
8248 .or_else(|| self.find_closest_task(cx))
8249 else {
8250 return;
8251 };
8252
8253 let reveal_strategy = action.reveal;
8254 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8255 cx.spawn_in(window, async move |_, cx| {
8256 let context = task_context.await?;
8257 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8258
8259 let resolved = &mut resolved_task.resolved;
8260 resolved.reveal = reveal_strategy;
8261
8262 workspace
8263 .update_in(cx, |workspace, window, cx| {
8264 workspace.schedule_resolved_task(
8265 task_source_kind,
8266 resolved_task,
8267 false,
8268 window,
8269 cx,
8270 );
8271 })
8272 .ok()
8273 })
8274 .detach();
8275 }
8276
8277 fn find_closest_task(
8278 &mut self,
8279 cx: &mut Context<Self>,
8280 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8281 let cursor_row = self.selections.newest_adjusted(cx).head().row;
8282
8283 let ((buffer_id, row), tasks) = self
8284 .tasks
8285 .iter()
8286 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8287
8288 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8289 let tasks = Arc::new(tasks.to_owned());
8290 Some((buffer, *row, tasks))
8291 }
8292
8293 fn find_enclosing_node_task(
8294 &mut self,
8295 cx: &mut Context<Self>,
8296 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8297 let snapshot = self.buffer.read(cx).snapshot(cx);
8298 let offset = self.selections.newest::<usize>(cx).head();
8299 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8300 let buffer_id = excerpt.buffer().remote_id();
8301
8302 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8303 let mut cursor = layer.node().walk();
8304
8305 while cursor.goto_first_child_for_byte(offset).is_some() {
8306 if cursor.node().end_byte() == offset {
8307 cursor.goto_next_sibling();
8308 }
8309 }
8310
8311 // Ascend to the smallest ancestor that contains the range and has a task.
8312 loop {
8313 let node = cursor.node();
8314 let node_range = node.byte_range();
8315 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8316
8317 // Check if this node contains our offset
8318 if node_range.start <= offset && node_range.end >= offset {
8319 // If it contains offset, check for task
8320 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8321 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8322 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8323 }
8324 }
8325
8326 if !cursor.goto_parent() {
8327 break;
8328 }
8329 }
8330 None
8331 }
8332
8333 fn render_run_indicator(
8334 &self,
8335 _style: &EditorStyle,
8336 is_active: bool,
8337 row: DisplayRow,
8338 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8339 cx: &mut Context<Self>,
8340 ) -> IconButton {
8341 let color = Color::Muted;
8342 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8343
8344 IconButton::new(
8345 ("run_indicator", row.0 as usize),
8346 ui::IconName::PlayOutlined,
8347 )
8348 .shape(ui::IconButtonShape::Square)
8349 .icon_size(IconSize::XSmall)
8350 .icon_color(color)
8351 .toggle_state(is_active)
8352 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8353 let quick_launch = e.down.button == MouseButton::Left;
8354 window.focus(&editor.focus_handle(cx));
8355 editor.toggle_code_actions(
8356 &ToggleCodeActions {
8357 deployed_from: Some(CodeActionSource::RunMenu(row)),
8358 quick_launch,
8359 },
8360 window,
8361 cx,
8362 );
8363 }))
8364 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8365 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
8366 }))
8367 }
8368
8369 pub fn context_menu_visible(&self) -> bool {
8370 !self.edit_prediction_preview_is_active()
8371 && self
8372 .context_menu
8373 .borrow()
8374 .as_ref()
8375 .map_or(false, |menu| menu.visible())
8376 }
8377
8378 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8379 self.context_menu
8380 .borrow()
8381 .as_ref()
8382 .map(|menu| menu.origin())
8383 }
8384
8385 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8386 self.context_menu_options = Some(options);
8387 }
8388
8389 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
8390 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
8391
8392 fn render_edit_prediction_popover(
8393 &mut self,
8394 text_bounds: &Bounds<Pixels>,
8395 content_origin: gpui::Point<Pixels>,
8396 right_margin: Pixels,
8397 editor_snapshot: &EditorSnapshot,
8398 visible_row_range: Range<DisplayRow>,
8399 scroll_top: f32,
8400 scroll_bottom: f32,
8401 line_layouts: &[LineWithInvisibles],
8402 line_height: Pixels,
8403 scroll_pixel_position: gpui::Point<Pixels>,
8404 newest_selection_head: Option<DisplayPoint>,
8405 editor_width: Pixels,
8406 style: &EditorStyle,
8407 window: &mut Window,
8408 cx: &mut App,
8409 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8410 if self.mode().is_minimap() {
8411 return None;
8412 }
8413 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8414
8415 if self.edit_prediction_visible_in_cursor_popover(true) {
8416 return None;
8417 }
8418
8419 match &active_edit_prediction.completion {
8420 EditPrediction::Move { target, .. } => {
8421 let target_display_point = target.to_display_point(editor_snapshot);
8422
8423 if self.edit_prediction_requires_modifier() {
8424 if !self.edit_prediction_preview_is_active() {
8425 return None;
8426 }
8427
8428 self.render_edit_prediction_modifier_jump_popover(
8429 text_bounds,
8430 content_origin,
8431 visible_row_range,
8432 line_layouts,
8433 line_height,
8434 scroll_pixel_position,
8435 newest_selection_head,
8436 target_display_point,
8437 window,
8438 cx,
8439 )
8440 } else {
8441 self.render_edit_prediction_eager_jump_popover(
8442 text_bounds,
8443 content_origin,
8444 editor_snapshot,
8445 visible_row_range,
8446 scroll_top,
8447 scroll_bottom,
8448 line_height,
8449 scroll_pixel_position,
8450 target_display_point,
8451 editor_width,
8452 window,
8453 cx,
8454 )
8455 }
8456 }
8457 EditPrediction::Edit {
8458 display_mode: EditDisplayMode::Inline,
8459 ..
8460 } => None,
8461 EditPrediction::Edit {
8462 display_mode: EditDisplayMode::TabAccept,
8463 edits,
8464 ..
8465 } => {
8466 let range = &edits.first()?.0;
8467 let target_display_point = range.end.to_display_point(editor_snapshot);
8468
8469 self.render_edit_prediction_end_of_line_popover(
8470 "Accept",
8471 editor_snapshot,
8472 visible_row_range,
8473 target_display_point,
8474 line_height,
8475 scroll_pixel_position,
8476 content_origin,
8477 editor_width,
8478 window,
8479 cx,
8480 )
8481 }
8482 EditPrediction::Edit {
8483 edits,
8484 edit_preview,
8485 display_mode: EditDisplayMode::DiffPopover,
8486 snapshot,
8487 } => self.render_edit_prediction_diff_popover(
8488 text_bounds,
8489 content_origin,
8490 right_margin,
8491 editor_snapshot,
8492 visible_row_range,
8493 line_layouts,
8494 line_height,
8495 scroll_pixel_position,
8496 newest_selection_head,
8497 editor_width,
8498 style,
8499 edits,
8500 edit_preview,
8501 snapshot,
8502 window,
8503 cx,
8504 ),
8505 }
8506 }
8507
8508 fn render_edit_prediction_modifier_jump_popover(
8509 &mut self,
8510 text_bounds: &Bounds<Pixels>,
8511 content_origin: gpui::Point<Pixels>,
8512 visible_row_range: Range<DisplayRow>,
8513 line_layouts: &[LineWithInvisibles],
8514 line_height: Pixels,
8515 scroll_pixel_position: gpui::Point<Pixels>,
8516 newest_selection_head: Option<DisplayPoint>,
8517 target_display_point: DisplayPoint,
8518 window: &mut Window,
8519 cx: &mut App,
8520 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8521 let scrolled_content_origin =
8522 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
8523
8524 const SCROLL_PADDING_Y: Pixels = px(12.);
8525
8526 if target_display_point.row() < visible_row_range.start {
8527 return self.render_edit_prediction_scroll_popover(
8528 |_| SCROLL_PADDING_Y,
8529 IconName::ArrowUp,
8530 visible_row_range,
8531 line_layouts,
8532 newest_selection_head,
8533 scrolled_content_origin,
8534 window,
8535 cx,
8536 );
8537 } else if target_display_point.row() >= visible_row_range.end {
8538 return self.render_edit_prediction_scroll_popover(
8539 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8540 IconName::ArrowDown,
8541 visible_row_range,
8542 line_layouts,
8543 newest_selection_head,
8544 scrolled_content_origin,
8545 window,
8546 cx,
8547 );
8548 }
8549
8550 const POLE_WIDTH: Pixels = px(2.);
8551
8552 let line_layout =
8553 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8554 let target_column = target_display_point.column() as usize;
8555
8556 let target_x = line_layout.x_for_index(target_column);
8557 let target_y =
8558 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
8559
8560 let flag_on_right = target_x < text_bounds.size.width / 2.;
8561
8562 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8563 border_color.l += 0.001;
8564
8565 let mut element = v_flex()
8566 .items_end()
8567 .when(flag_on_right, |el| el.items_start())
8568 .child(if flag_on_right {
8569 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8570 .rounded_bl(px(0.))
8571 .rounded_tl(px(0.))
8572 .border_l_2()
8573 .border_color(border_color)
8574 } else {
8575 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8576 .rounded_br(px(0.))
8577 .rounded_tr(px(0.))
8578 .border_r_2()
8579 .border_color(border_color)
8580 })
8581 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8582 .into_any();
8583
8584 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8585
8586 let mut origin = scrolled_content_origin + point(target_x, target_y)
8587 - point(
8588 if flag_on_right {
8589 POLE_WIDTH
8590 } else {
8591 size.width - POLE_WIDTH
8592 },
8593 size.height - line_height,
8594 );
8595
8596 origin.x = origin.x.max(content_origin.x);
8597
8598 element.prepaint_at(origin, window, cx);
8599
8600 Some((element, origin))
8601 }
8602
8603 fn render_edit_prediction_scroll_popover(
8604 &mut self,
8605 to_y: impl Fn(Size<Pixels>) -> Pixels,
8606 scroll_icon: IconName,
8607 visible_row_range: Range<DisplayRow>,
8608 line_layouts: &[LineWithInvisibles],
8609 newest_selection_head: Option<DisplayPoint>,
8610 scrolled_content_origin: gpui::Point<Pixels>,
8611 window: &mut Window,
8612 cx: &mut App,
8613 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8614 let mut element = self
8615 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
8616 .into_any();
8617
8618 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8619
8620 let cursor = newest_selection_head?;
8621 let cursor_row_layout =
8622 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8623 let cursor_column = cursor.column() as usize;
8624
8625 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8626
8627 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8628
8629 element.prepaint_at(origin, window, cx);
8630 Some((element, origin))
8631 }
8632
8633 fn render_edit_prediction_eager_jump_popover(
8634 &mut self,
8635 text_bounds: &Bounds<Pixels>,
8636 content_origin: gpui::Point<Pixels>,
8637 editor_snapshot: &EditorSnapshot,
8638 visible_row_range: Range<DisplayRow>,
8639 scroll_top: f32,
8640 scroll_bottom: f32,
8641 line_height: Pixels,
8642 scroll_pixel_position: gpui::Point<Pixels>,
8643 target_display_point: DisplayPoint,
8644 editor_width: Pixels,
8645 window: &mut Window,
8646 cx: &mut App,
8647 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8648 if target_display_point.row().as_f32() < scroll_top {
8649 let mut element = self
8650 .render_edit_prediction_line_popover(
8651 "Jump to Edit",
8652 Some(IconName::ArrowUp),
8653 window,
8654 cx,
8655 )?
8656 .into_any();
8657
8658 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8659 let offset = point(
8660 (text_bounds.size.width - size.width) / 2.,
8661 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8662 );
8663
8664 let origin = text_bounds.origin + offset;
8665 element.prepaint_at(origin, window, cx);
8666 Some((element, origin))
8667 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
8668 let mut element = self
8669 .render_edit_prediction_line_popover(
8670 "Jump to Edit",
8671 Some(IconName::ArrowDown),
8672 window,
8673 cx,
8674 )?
8675 .into_any();
8676
8677 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8678 let offset = point(
8679 (text_bounds.size.width - size.width) / 2.,
8680 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8681 );
8682
8683 let origin = text_bounds.origin + offset;
8684 element.prepaint_at(origin, window, cx);
8685 Some((element, origin))
8686 } else {
8687 self.render_edit_prediction_end_of_line_popover(
8688 "Jump to Edit",
8689 editor_snapshot,
8690 visible_row_range,
8691 target_display_point,
8692 line_height,
8693 scroll_pixel_position,
8694 content_origin,
8695 editor_width,
8696 window,
8697 cx,
8698 )
8699 }
8700 }
8701
8702 fn render_edit_prediction_end_of_line_popover(
8703 self: &mut Editor,
8704 label: &'static str,
8705 editor_snapshot: &EditorSnapshot,
8706 visible_row_range: Range<DisplayRow>,
8707 target_display_point: DisplayPoint,
8708 line_height: Pixels,
8709 scroll_pixel_position: gpui::Point<Pixels>,
8710 content_origin: gpui::Point<Pixels>,
8711 editor_width: Pixels,
8712 window: &mut Window,
8713 cx: &mut App,
8714 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8715 let target_line_end = DisplayPoint::new(
8716 target_display_point.row(),
8717 editor_snapshot.line_len(target_display_point.row()),
8718 );
8719
8720 let mut element = self
8721 .render_edit_prediction_line_popover(label, None, window, cx)?
8722 .into_any();
8723
8724 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8725
8726 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8727
8728 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
8729 let mut origin = start_point
8730 + line_origin
8731 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8732 origin.x = origin.x.max(content_origin.x);
8733
8734 let max_x = content_origin.x + editor_width - size.width;
8735
8736 if origin.x > max_x {
8737 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8738
8739 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8740 origin.y += offset;
8741 IconName::ArrowUp
8742 } else {
8743 origin.y -= offset;
8744 IconName::ArrowDown
8745 };
8746
8747 element = self
8748 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
8749 .into_any();
8750
8751 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8752
8753 origin.x = content_origin.x + editor_width - size.width - px(2.);
8754 }
8755
8756 element.prepaint_at(origin, window, cx);
8757 Some((element, origin))
8758 }
8759
8760 fn render_edit_prediction_diff_popover(
8761 self: &Editor,
8762 text_bounds: &Bounds<Pixels>,
8763 content_origin: gpui::Point<Pixels>,
8764 right_margin: Pixels,
8765 editor_snapshot: &EditorSnapshot,
8766 visible_row_range: Range<DisplayRow>,
8767 line_layouts: &[LineWithInvisibles],
8768 line_height: Pixels,
8769 scroll_pixel_position: gpui::Point<Pixels>,
8770 newest_selection_head: Option<DisplayPoint>,
8771 editor_width: Pixels,
8772 style: &EditorStyle,
8773 edits: &Vec<(Range<Anchor>, String)>,
8774 edit_preview: &Option<language::EditPreview>,
8775 snapshot: &language::BufferSnapshot,
8776 window: &mut Window,
8777 cx: &mut App,
8778 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8779 let edit_start = edits
8780 .first()
8781 .unwrap()
8782 .0
8783 .start
8784 .to_display_point(editor_snapshot);
8785 let edit_end = edits
8786 .last()
8787 .unwrap()
8788 .0
8789 .end
8790 .to_display_point(editor_snapshot);
8791
8792 let is_visible = visible_row_range.contains(&edit_start.row())
8793 || visible_row_range.contains(&edit_end.row());
8794 if !is_visible {
8795 return None;
8796 }
8797
8798 let highlighted_edits =
8799 crate::edit_prediction_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
8800
8801 let styled_text = highlighted_edits.to_styled_text(&style.text);
8802 let line_count = highlighted_edits.text.lines().count();
8803
8804 const BORDER_WIDTH: Pixels = px(1.);
8805
8806 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8807 let has_keybind = keybind.is_some();
8808
8809 let mut element = h_flex()
8810 .items_start()
8811 .child(
8812 h_flex()
8813 .bg(cx.theme().colors().editor_background)
8814 .border(BORDER_WIDTH)
8815 .shadow_xs()
8816 .border_color(cx.theme().colors().border)
8817 .rounded_l_lg()
8818 .when(line_count > 1, |el| el.rounded_br_lg())
8819 .pr_1()
8820 .child(styled_text),
8821 )
8822 .child(
8823 h_flex()
8824 .h(line_height + BORDER_WIDTH * 2.)
8825 .px_1p5()
8826 .gap_1()
8827 // Workaround: For some reason, there's a gap if we don't do this
8828 .ml(-BORDER_WIDTH)
8829 .shadow(vec![gpui::BoxShadow {
8830 color: gpui::black().opacity(0.05),
8831 offset: point(px(1.), px(1.)),
8832 blur_radius: px(2.),
8833 spread_radius: px(0.),
8834 }])
8835 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8836 .border(BORDER_WIDTH)
8837 .border_color(cx.theme().colors().border)
8838 .rounded_r_lg()
8839 .id("edit_prediction_diff_popover_keybind")
8840 .when(!has_keybind, |el| {
8841 let status_colors = cx.theme().status();
8842
8843 el.bg(status_colors.error_background)
8844 .border_color(status_colors.error.opacity(0.6))
8845 .child(Icon::new(IconName::Info).color(Color::Error))
8846 .cursor_default()
8847 .hoverable_tooltip(move |_window, cx| {
8848 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8849 })
8850 })
8851 .children(keybind),
8852 )
8853 .into_any();
8854
8855 let longest_row =
8856 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8857 let longest_line_width = if visible_row_range.contains(&longest_row) {
8858 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8859 } else {
8860 layout_line(
8861 longest_row,
8862 editor_snapshot,
8863 style,
8864 editor_width,
8865 |_| false,
8866 window,
8867 cx,
8868 )
8869 .width
8870 };
8871
8872 let viewport_bounds =
8873 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8874 right: -right_margin,
8875 ..Default::default()
8876 });
8877
8878 let x_after_longest =
8879 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
8880 - scroll_pixel_position.x;
8881
8882 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8883
8884 // Fully visible if it can be displayed within the window (allow overlapping other
8885 // panes). However, this is only allowed if the popover starts within text_bounds.
8886 let can_position_to_the_right = x_after_longest < text_bounds.right()
8887 && x_after_longest + element_bounds.width < viewport_bounds.right();
8888
8889 let mut origin = if can_position_to_the_right {
8890 point(
8891 x_after_longest,
8892 text_bounds.origin.y + edit_start.row().as_f32() * line_height
8893 - scroll_pixel_position.y,
8894 )
8895 } else {
8896 let cursor_row = newest_selection_head.map(|head| head.row());
8897 let above_edit = edit_start
8898 .row()
8899 .0
8900 .checked_sub(line_count as u32)
8901 .map(DisplayRow);
8902 let below_edit = Some(edit_end.row() + 1);
8903 let above_cursor =
8904 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
8905 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
8906
8907 // Place the edit popover adjacent to the edit if there is a location
8908 // available that is onscreen and does not obscure the cursor. Otherwise,
8909 // place it adjacent to the cursor.
8910 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
8911 .into_iter()
8912 .flatten()
8913 .find(|&start_row| {
8914 let end_row = start_row + line_count as u32;
8915 visible_row_range.contains(&start_row)
8916 && visible_row_range.contains(&end_row)
8917 && cursor_row.map_or(true, |cursor_row| {
8918 !((start_row..end_row).contains(&cursor_row))
8919 })
8920 })?;
8921
8922 content_origin
8923 + point(
8924 -scroll_pixel_position.x,
8925 row_target.as_f32() * line_height - scroll_pixel_position.y,
8926 )
8927 };
8928
8929 origin.x -= BORDER_WIDTH;
8930
8931 window.defer_draw(element, origin, 1);
8932
8933 // Do not return an element, since it will already be drawn due to defer_draw.
8934 None
8935 }
8936
8937 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
8938 px(30.)
8939 }
8940
8941 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
8942 if self.read_only(cx) {
8943 cx.theme().players().read_only()
8944 } else {
8945 self.style.as_ref().unwrap().local_player
8946 }
8947 }
8948
8949 fn render_edit_prediction_accept_keybind(
8950 &self,
8951 window: &mut Window,
8952 cx: &App,
8953 ) -> Option<AnyElement> {
8954 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
8955 let accept_keystroke = accept_binding.keystroke()?;
8956
8957 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8958
8959 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
8960 Color::Accent
8961 } else {
8962 Color::Muted
8963 };
8964
8965 h_flex()
8966 .px_0p5()
8967 .when(is_platform_style_mac, |parent| parent.gap_0p5())
8968 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8969 .text_size(TextSize::XSmall.rems(cx))
8970 .child(h_flex().children(ui::render_modifiers(
8971 &accept_keystroke.modifiers,
8972 PlatformStyle::platform(),
8973 Some(modifiers_color),
8974 Some(IconSize::XSmall.rems().into()),
8975 true,
8976 )))
8977 .when(is_platform_style_mac, |parent| {
8978 parent.child(accept_keystroke.key.clone())
8979 })
8980 .when(!is_platform_style_mac, |parent| {
8981 parent.child(
8982 Key::new(
8983 util::capitalize(&accept_keystroke.key),
8984 Some(Color::Default),
8985 )
8986 .size(Some(IconSize::XSmall.rems().into())),
8987 )
8988 })
8989 .into_any()
8990 .into()
8991 }
8992
8993 fn render_edit_prediction_line_popover(
8994 &self,
8995 label: impl Into<SharedString>,
8996 icon: Option<IconName>,
8997 window: &mut Window,
8998 cx: &App,
8999 ) -> Option<Stateful<Div>> {
9000 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9001
9002 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9003 let has_keybind = keybind.is_some();
9004
9005 let result = h_flex()
9006 .id("ep-line-popover")
9007 .py_0p5()
9008 .pl_1()
9009 .pr(padding_right)
9010 .gap_1()
9011 .rounded_md()
9012 .border_1()
9013 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9014 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9015 .shadow_xs()
9016 .when(!has_keybind, |el| {
9017 let status_colors = cx.theme().status();
9018
9019 el.bg(status_colors.error_background)
9020 .border_color(status_colors.error.opacity(0.6))
9021 .pl_2()
9022 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9023 .cursor_default()
9024 .hoverable_tooltip(move |_window, cx| {
9025 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9026 })
9027 })
9028 .children(keybind)
9029 .child(
9030 Label::new(label)
9031 .size(LabelSize::Small)
9032 .when(!has_keybind, |el| {
9033 el.color(cx.theme().status().error.into()).strikethrough()
9034 }),
9035 )
9036 .when(!has_keybind, |el| {
9037 el.child(
9038 h_flex().ml_1().child(
9039 Icon::new(IconName::Info)
9040 .size(IconSize::Small)
9041 .color(cx.theme().status().error.into()),
9042 ),
9043 )
9044 })
9045 .when_some(icon, |element, icon| {
9046 element.child(
9047 div()
9048 .mt(px(1.5))
9049 .child(Icon::new(icon).size(IconSize::Small)),
9050 )
9051 });
9052
9053 Some(result)
9054 }
9055
9056 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9057 let accent_color = cx.theme().colors().text_accent;
9058 let editor_bg_color = cx.theme().colors().editor_background;
9059 editor_bg_color.blend(accent_color.opacity(0.1))
9060 }
9061
9062 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9063 let accent_color = cx.theme().colors().text_accent;
9064 let editor_bg_color = cx.theme().colors().editor_background;
9065 editor_bg_color.blend(accent_color.opacity(0.6))
9066 }
9067
9068 fn render_edit_prediction_cursor_popover(
9069 &self,
9070 min_width: Pixels,
9071 max_width: Pixels,
9072 cursor_point: Point,
9073 style: &EditorStyle,
9074 accept_keystroke: Option<&gpui::Keystroke>,
9075 _window: &Window,
9076 cx: &mut Context<Editor>,
9077 ) -> Option<AnyElement> {
9078 let provider = self.edit_prediction_provider.as_ref()?;
9079
9080 if provider.provider.needs_terms_acceptance(cx) {
9081 return Some(
9082 h_flex()
9083 .min_w(min_width)
9084 .flex_1()
9085 .px_2()
9086 .py_1()
9087 .gap_3()
9088 .elevation_2(cx)
9089 .hover(|style| style.bg(cx.theme().colors().element_hover))
9090 .id("accept-terms")
9091 .cursor_pointer()
9092 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
9093 .on_click(cx.listener(|this, _event, window, cx| {
9094 cx.stop_propagation();
9095 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
9096 window.dispatch_action(
9097 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
9098 cx,
9099 );
9100 }))
9101 .child(
9102 h_flex()
9103 .flex_1()
9104 .gap_2()
9105 .child(Icon::new(IconName::ZedPredict))
9106 .child(Label::new("Accept Terms of Service"))
9107 .child(div().w_full())
9108 .child(
9109 Icon::new(IconName::ArrowUpRight)
9110 .color(Color::Muted)
9111 .size(IconSize::Small),
9112 )
9113 .into_any_element(),
9114 )
9115 .into_any(),
9116 );
9117 }
9118
9119 let is_refreshing = provider.provider.is_refreshing(cx);
9120
9121 fn pending_completion_container() -> Div {
9122 h_flex()
9123 .h_full()
9124 .flex_1()
9125 .gap_2()
9126 .child(Icon::new(IconName::ZedPredict))
9127 }
9128
9129 let completion = match &self.active_edit_prediction {
9130 Some(prediction) => {
9131 if !self.has_visible_completions_menu() {
9132 const RADIUS: Pixels = px(6.);
9133 const BORDER_WIDTH: Pixels = px(1.);
9134
9135 return Some(
9136 h_flex()
9137 .elevation_2(cx)
9138 .border(BORDER_WIDTH)
9139 .border_color(cx.theme().colors().border)
9140 .when(accept_keystroke.is_none(), |el| {
9141 el.border_color(cx.theme().status().error)
9142 })
9143 .rounded(RADIUS)
9144 .rounded_tl(px(0.))
9145 .overflow_hidden()
9146 .child(div().px_1p5().child(match &prediction.completion {
9147 EditPrediction::Move { target, snapshot } => {
9148 use text::ToPoint as _;
9149 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
9150 {
9151 Icon::new(IconName::ZedPredictDown)
9152 } else {
9153 Icon::new(IconName::ZedPredictUp)
9154 }
9155 }
9156 EditPrediction::Edit { .. } => Icon::new(IconName::ZedPredict),
9157 }))
9158 .child(
9159 h_flex()
9160 .gap_1()
9161 .py_1()
9162 .px_2()
9163 .rounded_r(RADIUS - BORDER_WIDTH)
9164 .border_l_1()
9165 .border_color(cx.theme().colors().border)
9166 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9167 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9168 el.child(
9169 Label::new("Hold")
9170 .size(LabelSize::Small)
9171 .when(accept_keystroke.is_none(), |el| {
9172 el.strikethrough()
9173 })
9174 .line_height_style(LineHeightStyle::UiLabel),
9175 )
9176 })
9177 .id("edit_prediction_cursor_popover_keybind")
9178 .when(accept_keystroke.is_none(), |el| {
9179 let status_colors = cx.theme().status();
9180
9181 el.bg(status_colors.error_background)
9182 .border_color(status_colors.error.opacity(0.6))
9183 .child(Icon::new(IconName::Info).color(Color::Error))
9184 .cursor_default()
9185 .hoverable_tooltip(move |_window, cx| {
9186 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9187 .into()
9188 })
9189 })
9190 .when_some(
9191 accept_keystroke.as_ref(),
9192 |el, accept_keystroke| {
9193 el.child(h_flex().children(ui::render_modifiers(
9194 &accept_keystroke.modifiers,
9195 PlatformStyle::platform(),
9196 Some(Color::Default),
9197 Some(IconSize::XSmall.rems().into()),
9198 false,
9199 )))
9200 },
9201 ),
9202 )
9203 .into_any(),
9204 );
9205 }
9206
9207 self.render_edit_prediction_cursor_popover_preview(
9208 prediction,
9209 cursor_point,
9210 style,
9211 cx,
9212 )?
9213 }
9214
9215 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9216 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9217 stale_completion,
9218 cursor_point,
9219 style,
9220 cx,
9221 )?,
9222
9223 None => {
9224 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
9225 }
9226 },
9227
9228 None => pending_completion_container().child(Label::new("No Prediction")),
9229 };
9230
9231 let completion = if is_refreshing {
9232 completion
9233 .with_animation(
9234 "loading-completion",
9235 Animation::new(Duration::from_secs(2))
9236 .repeat()
9237 .with_easing(pulsating_between(0.4, 0.8)),
9238 |label, delta| label.opacity(delta),
9239 )
9240 .into_any_element()
9241 } else {
9242 completion.into_any_element()
9243 };
9244
9245 let has_completion = self.active_edit_prediction.is_some();
9246
9247 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9248 Some(
9249 h_flex()
9250 .min_w(min_width)
9251 .max_w(max_width)
9252 .flex_1()
9253 .elevation_2(cx)
9254 .border_color(cx.theme().colors().border)
9255 .child(
9256 div()
9257 .flex_1()
9258 .py_1()
9259 .px_2()
9260 .overflow_hidden()
9261 .child(completion),
9262 )
9263 .when_some(accept_keystroke, |el, accept_keystroke| {
9264 if !accept_keystroke.modifiers.modified() {
9265 return el;
9266 }
9267
9268 el.child(
9269 h_flex()
9270 .h_full()
9271 .border_l_1()
9272 .rounded_r_lg()
9273 .border_color(cx.theme().colors().border)
9274 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9275 .gap_1()
9276 .py_1()
9277 .px_2()
9278 .child(
9279 h_flex()
9280 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9281 .when(is_platform_style_mac, |parent| parent.gap_1())
9282 .child(h_flex().children(ui::render_modifiers(
9283 &accept_keystroke.modifiers,
9284 PlatformStyle::platform(),
9285 Some(if !has_completion {
9286 Color::Muted
9287 } else {
9288 Color::Default
9289 }),
9290 None,
9291 false,
9292 ))),
9293 )
9294 .child(Label::new("Preview").into_any_element())
9295 .opacity(if has_completion { 1.0 } else { 0.4 }),
9296 )
9297 })
9298 .into_any(),
9299 )
9300 }
9301
9302 fn render_edit_prediction_cursor_popover_preview(
9303 &self,
9304 completion: &EditPredictionState,
9305 cursor_point: Point,
9306 style: &EditorStyle,
9307 cx: &mut Context<Editor>,
9308 ) -> Option<Div> {
9309 use text::ToPoint as _;
9310
9311 fn render_relative_row_jump(
9312 prefix: impl Into<String>,
9313 current_row: u32,
9314 target_row: u32,
9315 ) -> Div {
9316 let (row_diff, arrow) = if target_row < current_row {
9317 (current_row - target_row, IconName::ArrowUp)
9318 } else {
9319 (target_row - current_row, IconName::ArrowDown)
9320 };
9321
9322 h_flex()
9323 .child(
9324 Label::new(format!("{}{}", prefix.into(), row_diff))
9325 .color(Color::Muted)
9326 .size(LabelSize::Small),
9327 )
9328 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9329 }
9330
9331 match &completion.completion {
9332 EditPrediction::Move {
9333 target, snapshot, ..
9334 } => Some(
9335 h_flex()
9336 .px_2()
9337 .gap_2()
9338 .flex_1()
9339 .child(
9340 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
9341 Icon::new(IconName::ZedPredictDown)
9342 } else {
9343 Icon::new(IconName::ZedPredictUp)
9344 },
9345 )
9346 .child(Label::new("Jump to Edit")),
9347 ),
9348
9349 EditPrediction::Edit {
9350 edits,
9351 edit_preview,
9352 snapshot,
9353 display_mode: _,
9354 } => {
9355 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
9356
9357 let (highlighted_edits, has_more_lines) = crate::edit_prediction_edit_text(
9358 &snapshot,
9359 &edits,
9360 edit_preview.as_ref()?,
9361 true,
9362 cx,
9363 )
9364 .first_line_preview();
9365
9366 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9367 .with_default_highlights(&style.text, highlighted_edits.highlights);
9368
9369 let preview = h_flex()
9370 .gap_1()
9371 .min_w_16()
9372 .child(styled_text)
9373 .when(has_more_lines, |parent| parent.child("…"));
9374
9375 let left = if first_edit_row != cursor_point.row {
9376 render_relative_row_jump("", cursor_point.row, first_edit_row)
9377 .into_any_element()
9378 } else {
9379 Icon::new(IconName::ZedPredict).into_any_element()
9380 };
9381
9382 Some(
9383 h_flex()
9384 .h_full()
9385 .flex_1()
9386 .gap_2()
9387 .pr_1()
9388 .overflow_x_hidden()
9389 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9390 .child(left)
9391 .child(preview),
9392 )
9393 }
9394 }
9395 }
9396
9397 pub fn render_context_menu(
9398 &self,
9399 style: &EditorStyle,
9400 max_height_in_lines: u32,
9401 window: &mut Window,
9402 cx: &mut Context<Editor>,
9403 ) -> Option<AnyElement> {
9404 let menu = self.context_menu.borrow();
9405 let menu = menu.as_ref()?;
9406 if !menu.visible() {
9407 return None;
9408 };
9409 Some(menu.render(style, max_height_in_lines, window, cx))
9410 }
9411
9412 fn render_context_menu_aside(
9413 &mut self,
9414 max_size: Size<Pixels>,
9415 window: &mut Window,
9416 cx: &mut Context<Editor>,
9417 ) -> Option<AnyElement> {
9418 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9419 if menu.visible() {
9420 menu.render_aside(max_size, window, cx)
9421 } else {
9422 None
9423 }
9424 })
9425 }
9426
9427 fn hide_context_menu(
9428 &mut self,
9429 window: &mut Window,
9430 cx: &mut Context<Self>,
9431 ) -> Option<CodeContextMenu> {
9432 cx.notify();
9433 self.completion_tasks.clear();
9434 let context_menu = self.context_menu.borrow_mut().take();
9435 self.stale_edit_prediction_in_menu.take();
9436 self.update_visible_edit_prediction(window, cx);
9437 if let Some(CodeContextMenu::Completions(_)) = &context_menu {
9438 if let Some(completion_provider) = &self.completion_provider {
9439 completion_provider.selection_changed(None, window, cx);
9440 }
9441 }
9442 context_menu
9443 }
9444
9445 fn show_snippet_choices(
9446 &mut self,
9447 choices: &Vec<String>,
9448 selection: Range<Anchor>,
9449 cx: &mut Context<Self>,
9450 ) {
9451 let buffer_id = match (&selection.start.buffer_id, &selection.end.buffer_id) {
9452 (Some(a), Some(b)) if a == b => a,
9453 _ => {
9454 log::error!("expected anchor range to have matching buffer IDs");
9455 return;
9456 }
9457 };
9458 let multi_buffer = self.buffer().read(cx);
9459 let Some(buffer) = multi_buffer.buffer(*buffer_id) else {
9460 return;
9461 };
9462
9463 let id = post_inc(&mut self.next_completion_id);
9464 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9465 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9466 CompletionsMenu::new_snippet_choices(
9467 id,
9468 true,
9469 choices,
9470 selection,
9471 buffer,
9472 snippet_sort_order,
9473 ),
9474 ));
9475 }
9476
9477 pub fn insert_snippet(
9478 &mut self,
9479 insertion_ranges: &[Range<usize>],
9480 snippet: Snippet,
9481 window: &mut Window,
9482 cx: &mut Context<Self>,
9483 ) -> Result<()> {
9484 struct Tabstop<T> {
9485 is_end_tabstop: bool,
9486 ranges: Vec<Range<T>>,
9487 choices: Option<Vec<String>>,
9488 }
9489
9490 let tabstops = self.buffer.update(cx, |buffer, cx| {
9491 let snippet_text: Arc<str> = snippet.text.clone().into();
9492 let edits = insertion_ranges
9493 .iter()
9494 .cloned()
9495 .map(|range| (range, snippet_text.clone()));
9496 let autoindent_mode = AutoindentMode::Block {
9497 original_indent_columns: Vec::new(),
9498 };
9499 buffer.edit(edits, Some(autoindent_mode), cx);
9500
9501 let snapshot = &*buffer.read(cx);
9502 let snippet = &snippet;
9503 snippet
9504 .tabstops
9505 .iter()
9506 .map(|tabstop| {
9507 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
9508 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9509 });
9510 let mut tabstop_ranges = tabstop
9511 .ranges
9512 .iter()
9513 .flat_map(|tabstop_range| {
9514 let mut delta = 0_isize;
9515 insertion_ranges.iter().map(move |insertion_range| {
9516 let insertion_start = insertion_range.start as isize + delta;
9517 delta +=
9518 snippet.text.len() as isize - insertion_range.len() as isize;
9519
9520 let start = ((insertion_start + tabstop_range.start) as usize)
9521 .min(snapshot.len());
9522 let end = ((insertion_start + tabstop_range.end) as usize)
9523 .min(snapshot.len());
9524 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9525 })
9526 })
9527 .collect::<Vec<_>>();
9528 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9529
9530 Tabstop {
9531 is_end_tabstop,
9532 ranges: tabstop_ranges,
9533 choices: tabstop.choices.clone(),
9534 }
9535 })
9536 .collect::<Vec<_>>()
9537 });
9538 if let Some(tabstop) = tabstops.first() {
9539 self.change_selections(Default::default(), window, cx, |s| {
9540 // Reverse order so that the first range is the newest created selection.
9541 // Completions will use it and autoscroll will prioritize it.
9542 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9543 });
9544
9545 if let Some(choices) = &tabstop.choices {
9546 if let Some(selection) = tabstop.ranges.first() {
9547 self.show_snippet_choices(choices, selection.clone(), cx)
9548 }
9549 }
9550
9551 // If we're already at the last tabstop and it's at the end of the snippet,
9552 // we're done, we don't need to keep the state around.
9553 if !tabstop.is_end_tabstop {
9554 let choices = tabstops
9555 .iter()
9556 .map(|tabstop| tabstop.choices.clone())
9557 .collect();
9558
9559 let ranges = tabstops
9560 .into_iter()
9561 .map(|tabstop| tabstop.ranges)
9562 .collect::<Vec<_>>();
9563
9564 self.snippet_stack.push(SnippetState {
9565 active_index: 0,
9566 ranges,
9567 choices,
9568 });
9569 }
9570
9571 // Check whether the just-entered snippet ends with an auto-closable bracket.
9572 if self.autoclose_regions.is_empty() {
9573 let snapshot = self.buffer.read(cx).snapshot(cx);
9574 let mut all_selections = self.selections.all::<Point>(cx);
9575 for selection in &mut all_selections {
9576 let selection_head = selection.head();
9577 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9578 continue;
9579 };
9580
9581 let mut bracket_pair = None;
9582 let max_lookup_length = scope
9583 .brackets()
9584 .map(|(pair, _)| {
9585 pair.start
9586 .as_str()
9587 .chars()
9588 .count()
9589 .max(pair.end.as_str().chars().count())
9590 })
9591 .max();
9592 if let Some(max_lookup_length) = max_lookup_length {
9593 let next_text = snapshot
9594 .chars_at(selection_head)
9595 .take(max_lookup_length)
9596 .collect::<String>();
9597 let prev_text = snapshot
9598 .reversed_chars_at(selection_head)
9599 .take(max_lookup_length)
9600 .collect::<String>();
9601
9602 for (pair, enabled) in scope.brackets() {
9603 if enabled
9604 && pair.close
9605 && prev_text.starts_with(pair.start.as_str())
9606 && next_text.starts_with(pair.end.as_str())
9607 {
9608 bracket_pair = Some(pair.clone());
9609 break;
9610 }
9611 }
9612 }
9613
9614 if let Some(pair) = bracket_pair {
9615 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9616 let autoclose_enabled =
9617 self.use_autoclose && snapshot_settings.use_autoclose;
9618 if autoclose_enabled {
9619 let start = snapshot.anchor_after(selection_head);
9620 let end = snapshot.anchor_after(selection_head);
9621 self.autoclose_regions.push(AutocloseRegion {
9622 selection_id: selection.id,
9623 range: start..end,
9624 pair,
9625 });
9626 }
9627 }
9628 }
9629 }
9630 }
9631 Ok(())
9632 }
9633
9634 pub fn move_to_next_snippet_tabstop(
9635 &mut self,
9636 window: &mut Window,
9637 cx: &mut Context<Self>,
9638 ) -> bool {
9639 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9640 }
9641
9642 pub fn move_to_prev_snippet_tabstop(
9643 &mut self,
9644 window: &mut Window,
9645 cx: &mut Context<Self>,
9646 ) -> bool {
9647 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9648 }
9649
9650 pub fn move_to_snippet_tabstop(
9651 &mut self,
9652 bias: Bias,
9653 window: &mut Window,
9654 cx: &mut Context<Self>,
9655 ) -> bool {
9656 if let Some(mut snippet) = self.snippet_stack.pop() {
9657 match bias {
9658 Bias::Left => {
9659 if snippet.active_index > 0 {
9660 snippet.active_index -= 1;
9661 } else {
9662 self.snippet_stack.push(snippet);
9663 return false;
9664 }
9665 }
9666 Bias::Right => {
9667 if snippet.active_index + 1 < snippet.ranges.len() {
9668 snippet.active_index += 1;
9669 } else {
9670 self.snippet_stack.push(snippet);
9671 return false;
9672 }
9673 }
9674 }
9675 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9676 self.change_selections(Default::default(), window, cx, |s| {
9677 // Reverse order so that the first range is the newest created selection.
9678 // Completions will use it and autoscroll will prioritize it.
9679 s.select_ranges(current_ranges.iter().rev().cloned())
9680 });
9681
9682 if let Some(choices) = &snippet.choices[snippet.active_index] {
9683 if let Some(selection) = current_ranges.first() {
9684 self.show_snippet_choices(&choices, selection.clone(), cx);
9685 }
9686 }
9687
9688 // If snippet state is not at the last tabstop, push it back on the stack
9689 if snippet.active_index + 1 < snippet.ranges.len() {
9690 self.snippet_stack.push(snippet);
9691 }
9692 return true;
9693 }
9694 }
9695
9696 false
9697 }
9698
9699 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9700 self.transact(window, cx, |this, window, cx| {
9701 this.select_all(&SelectAll, window, cx);
9702 this.insert("", window, cx);
9703 });
9704 }
9705
9706 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9707 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9708 self.transact(window, cx, |this, window, cx| {
9709 this.select_autoclose_pair(window, cx);
9710 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9711 if !this.linked_edit_ranges.is_empty() {
9712 let selections = this.selections.all::<MultiBufferPoint>(cx);
9713 let snapshot = this.buffer.read(cx).snapshot(cx);
9714
9715 for selection in selections.iter() {
9716 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9717 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9718 if selection_start.buffer_id != selection_end.buffer_id {
9719 continue;
9720 }
9721 if let Some(ranges) =
9722 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9723 {
9724 for (buffer, entries) in ranges {
9725 linked_ranges.entry(buffer).or_default().extend(entries);
9726 }
9727 }
9728 }
9729 }
9730
9731 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
9732 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9733 for selection in &mut selections {
9734 if selection.is_empty() {
9735 let old_head = selection.head();
9736 let mut new_head =
9737 movement::left(&display_map, old_head.to_display_point(&display_map))
9738 .to_point(&display_map);
9739 if let Some((buffer, line_buffer_range)) = display_map
9740 .buffer_snapshot
9741 .buffer_line_for_row(MultiBufferRow(old_head.row))
9742 {
9743 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9744 let indent_len = match indent_size.kind {
9745 IndentKind::Space => {
9746 buffer.settings_at(line_buffer_range.start, cx).tab_size
9747 }
9748 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9749 };
9750 if old_head.column <= indent_size.len && old_head.column > 0 {
9751 let indent_len = indent_len.get();
9752 new_head = cmp::min(
9753 new_head,
9754 MultiBufferPoint::new(
9755 old_head.row,
9756 ((old_head.column - 1) / indent_len) * indent_len,
9757 ),
9758 );
9759 }
9760 }
9761
9762 selection.set_head(new_head, SelectionGoal::None);
9763 }
9764 }
9765
9766 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9767 this.insert("", window, cx);
9768 let empty_str: Arc<str> = Arc::from("");
9769 for (buffer, edits) in linked_ranges {
9770 let snapshot = buffer.read(cx).snapshot();
9771 use text::ToPoint as TP;
9772
9773 let edits = edits
9774 .into_iter()
9775 .map(|range| {
9776 let end_point = TP::to_point(&range.end, &snapshot);
9777 let mut start_point = TP::to_point(&range.start, &snapshot);
9778
9779 if end_point == start_point {
9780 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9781 .saturating_sub(1);
9782 start_point =
9783 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9784 };
9785
9786 (start_point..end_point, empty_str.clone())
9787 })
9788 .sorted_by_key(|(range, _)| range.start)
9789 .collect::<Vec<_>>();
9790 buffer.update(cx, |this, cx| {
9791 this.edit(edits, None, cx);
9792 })
9793 }
9794 this.refresh_edit_prediction(true, false, window, cx);
9795 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
9796 });
9797 }
9798
9799 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9800 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9801 self.transact(window, cx, |this, window, cx| {
9802 this.change_selections(Default::default(), window, cx, |s| {
9803 s.move_with(|map, selection| {
9804 if selection.is_empty() {
9805 let cursor = movement::right(map, selection.head());
9806 selection.end = cursor;
9807 selection.reversed = true;
9808 selection.goal = SelectionGoal::None;
9809 }
9810 })
9811 });
9812 this.insert("", window, cx);
9813 this.refresh_edit_prediction(true, false, window, cx);
9814 });
9815 }
9816
9817 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9818 if self.mode.is_single_line() {
9819 cx.propagate();
9820 return;
9821 }
9822
9823 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9824 if self.move_to_prev_snippet_tabstop(window, cx) {
9825 return;
9826 }
9827 self.outdent(&Outdent, window, cx);
9828 }
9829
9830 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9831 if self.mode.is_single_line() {
9832 cx.propagate();
9833 return;
9834 }
9835
9836 if self.move_to_next_snippet_tabstop(window, cx) {
9837 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9838 return;
9839 }
9840 if self.read_only(cx) {
9841 return;
9842 }
9843 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9844 let mut selections = self.selections.all_adjusted(cx);
9845 let buffer = self.buffer.read(cx);
9846 let snapshot = buffer.snapshot(cx);
9847 let rows_iter = selections.iter().map(|s| s.head().row);
9848 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9849
9850 let has_some_cursor_in_whitespace = selections
9851 .iter()
9852 .filter(|selection| selection.is_empty())
9853 .any(|selection| {
9854 let cursor = selection.head();
9855 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9856 cursor.column < current_indent.len
9857 });
9858
9859 let mut edits = Vec::new();
9860 let mut prev_edited_row = 0;
9861 let mut row_delta = 0;
9862 for selection in &mut selections {
9863 if selection.start.row != prev_edited_row {
9864 row_delta = 0;
9865 }
9866 prev_edited_row = selection.end.row;
9867
9868 // If the selection is non-empty, then increase the indentation of the selected lines.
9869 if !selection.is_empty() {
9870 row_delta =
9871 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9872 continue;
9873 }
9874
9875 let cursor = selection.head();
9876 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9877 if let Some(suggested_indent) =
9878 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
9879 {
9880 // Don't do anything if already at suggested indent
9881 // and there is any other cursor which is not
9882 if has_some_cursor_in_whitespace
9883 && cursor.column == current_indent.len
9884 && current_indent.len == suggested_indent.len
9885 {
9886 continue;
9887 }
9888
9889 // Adjust line and move cursor to suggested indent
9890 // if cursor is not at suggested indent
9891 if cursor.column < suggested_indent.len
9892 && cursor.column <= current_indent.len
9893 && current_indent.len <= suggested_indent.len
9894 {
9895 selection.start = Point::new(cursor.row, suggested_indent.len);
9896 selection.end = selection.start;
9897 if row_delta == 0 {
9898 edits.extend(Buffer::edit_for_indent_size_adjustment(
9899 cursor.row,
9900 current_indent,
9901 suggested_indent,
9902 ));
9903 row_delta = suggested_indent.len - current_indent.len;
9904 }
9905 continue;
9906 }
9907
9908 // If current indent is more than suggested indent
9909 // only move cursor to current indent and skip indent
9910 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
9911 selection.start = Point::new(cursor.row, current_indent.len);
9912 selection.end = selection.start;
9913 continue;
9914 }
9915 }
9916
9917 // Otherwise, insert a hard or soft tab.
9918 let settings = buffer.language_settings_at(cursor, cx);
9919 let tab_size = if settings.hard_tabs {
9920 IndentSize::tab()
9921 } else {
9922 let tab_size = settings.tab_size.get();
9923 let indent_remainder = snapshot
9924 .text_for_range(Point::new(cursor.row, 0)..cursor)
9925 .flat_map(str::chars)
9926 .fold(row_delta % tab_size, |counter: u32, c| {
9927 if c == '\t' {
9928 0
9929 } else {
9930 (counter + 1) % tab_size
9931 }
9932 });
9933
9934 let chars_to_next_tab_stop = tab_size - indent_remainder;
9935 IndentSize::spaces(chars_to_next_tab_stop)
9936 };
9937 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
9938 selection.end = selection.start;
9939 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
9940 row_delta += tab_size.len;
9941 }
9942
9943 self.transact(window, cx, |this, window, cx| {
9944 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9945 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9946 this.refresh_edit_prediction(true, false, window, cx);
9947 });
9948 }
9949
9950 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
9951 if self.read_only(cx) {
9952 return;
9953 }
9954 if self.mode.is_single_line() {
9955 cx.propagate();
9956 return;
9957 }
9958
9959 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9960 let mut selections = self.selections.all::<Point>(cx);
9961 let mut prev_edited_row = 0;
9962 let mut row_delta = 0;
9963 let mut edits = Vec::new();
9964 let buffer = self.buffer.read(cx);
9965 let snapshot = buffer.snapshot(cx);
9966 for selection in &mut selections {
9967 if selection.start.row != prev_edited_row {
9968 row_delta = 0;
9969 }
9970 prev_edited_row = selection.end.row;
9971
9972 row_delta =
9973 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9974 }
9975
9976 self.transact(window, cx, |this, window, cx| {
9977 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9978 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9979 });
9980 }
9981
9982 fn indent_selection(
9983 buffer: &MultiBuffer,
9984 snapshot: &MultiBufferSnapshot,
9985 selection: &mut Selection<Point>,
9986 edits: &mut Vec<(Range<Point>, String)>,
9987 delta_for_start_row: u32,
9988 cx: &App,
9989 ) -> u32 {
9990 let settings = buffer.language_settings_at(selection.start, cx);
9991 let tab_size = settings.tab_size.get();
9992 let indent_kind = if settings.hard_tabs {
9993 IndentKind::Tab
9994 } else {
9995 IndentKind::Space
9996 };
9997 let mut start_row = selection.start.row;
9998 let mut end_row = selection.end.row + 1;
9999
10000 // If a selection ends at the beginning of a line, don't indent
10001 // that last line.
10002 if selection.end.column == 0 && selection.end.row > selection.start.row {
10003 end_row -= 1;
10004 }
10005
10006 // Avoid re-indenting a row that has already been indented by a
10007 // previous selection, but still update this selection's column
10008 // to reflect that indentation.
10009 if delta_for_start_row > 0 {
10010 start_row += 1;
10011 selection.start.column += delta_for_start_row;
10012 if selection.end.row == selection.start.row {
10013 selection.end.column += delta_for_start_row;
10014 }
10015 }
10016
10017 let mut delta_for_end_row = 0;
10018 let has_multiple_rows = start_row + 1 != end_row;
10019 for row in start_row..end_row {
10020 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10021 let indent_delta = match (current_indent.kind, indent_kind) {
10022 (IndentKind::Space, IndentKind::Space) => {
10023 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10024 IndentSize::spaces(columns_to_next_tab_stop)
10025 }
10026 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10027 (_, IndentKind::Tab) => IndentSize::tab(),
10028 };
10029
10030 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10031 0
10032 } else {
10033 selection.start.column
10034 };
10035 let row_start = Point::new(row, start);
10036 edits.push((
10037 row_start..row_start,
10038 indent_delta.chars().collect::<String>(),
10039 ));
10040
10041 // Update this selection's endpoints to reflect the indentation.
10042 if row == selection.start.row {
10043 selection.start.column += indent_delta.len;
10044 }
10045 if row == selection.end.row {
10046 selection.end.column += indent_delta.len;
10047 delta_for_end_row = indent_delta.len;
10048 }
10049 }
10050
10051 if selection.start.row == selection.end.row {
10052 delta_for_start_row + delta_for_end_row
10053 } else {
10054 delta_for_end_row
10055 }
10056 }
10057
10058 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10059 if self.read_only(cx) {
10060 return;
10061 }
10062 if self.mode.is_single_line() {
10063 cx.propagate();
10064 return;
10065 }
10066
10067 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10068 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10069 let selections = self.selections.all::<Point>(cx);
10070 let mut deletion_ranges = Vec::new();
10071 let mut last_outdent = None;
10072 {
10073 let buffer = self.buffer.read(cx);
10074 let snapshot = buffer.snapshot(cx);
10075 for selection in &selections {
10076 let settings = buffer.language_settings_at(selection.start, cx);
10077 let tab_size = settings.tab_size.get();
10078 let mut rows = selection.spanned_rows(false, &display_map);
10079
10080 // Avoid re-outdenting a row that has already been outdented by a
10081 // previous selection.
10082 if let Some(last_row) = last_outdent {
10083 if last_row == rows.start {
10084 rows.start = rows.start.next_row();
10085 }
10086 }
10087 let has_multiple_rows = rows.len() > 1;
10088 for row in rows.iter_rows() {
10089 let indent_size = snapshot.indent_size_for_line(row);
10090 if indent_size.len > 0 {
10091 let deletion_len = match indent_size.kind {
10092 IndentKind::Space => {
10093 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10094 if columns_to_prev_tab_stop == 0 {
10095 tab_size
10096 } else {
10097 columns_to_prev_tab_stop
10098 }
10099 }
10100 IndentKind::Tab => 1,
10101 };
10102 let start = if has_multiple_rows
10103 || deletion_len > selection.start.column
10104 || indent_size.len < selection.start.column
10105 {
10106 0
10107 } else {
10108 selection.start.column - deletion_len
10109 };
10110 deletion_ranges.push(
10111 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10112 );
10113 last_outdent = Some(row);
10114 }
10115 }
10116 }
10117 }
10118
10119 self.transact(window, cx, |this, window, cx| {
10120 this.buffer.update(cx, |buffer, cx| {
10121 let empty_str: Arc<str> = Arc::default();
10122 buffer.edit(
10123 deletion_ranges
10124 .into_iter()
10125 .map(|range| (range, empty_str.clone())),
10126 None,
10127 cx,
10128 );
10129 });
10130 let selections = this.selections.all::<usize>(cx);
10131 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10132 });
10133 }
10134
10135 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10136 if self.read_only(cx) {
10137 return;
10138 }
10139 if self.mode.is_single_line() {
10140 cx.propagate();
10141 return;
10142 }
10143
10144 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10145 let selections = self
10146 .selections
10147 .all::<usize>(cx)
10148 .into_iter()
10149 .map(|s| s.range());
10150
10151 self.transact(window, cx, |this, window, cx| {
10152 this.buffer.update(cx, |buffer, cx| {
10153 buffer.autoindent_ranges(selections, cx);
10154 });
10155 let selections = this.selections.all::<usize>(cx);
10156 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10157 });
10158 }
10159
10160 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10161 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10162 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10163 let selections = self.selections.all::<Point>(cx);
10164
10165 let mut new_cursors = Vec::new();
10166 let mut edit_ranges = Vec::new();
10167 let mut selections = selections.iter().peekable();
10168 while let Some(selection) = selections.next() {
10169 let mut rows = selection.spanned_rows(false, &display_map);
10170 let goal_display_column = selection.head().to_display_point(&display_map).column();
10171
10172 // Accumulate contiguous regions of rows that we want to delete.
10173 while let Some(next_selection) = selections.peek() {
10174 let next_rows = next_selection.spanned_rows(false, &display_map);
10175 if next_rows.start <= rows.end {
10176 rows.end = next_rows.end;
10177 selections.next().unwrap();
10178 } else {
10179 break;
10180 }
10181 }
10182
10183 let buffer = &display_map.buffer_snapshot;
10184 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
10185 let edit_end;
10186 let cursor_buffer_row;
10187 if buffer.max_point().row >= rows.end.0 {
10188 // If there's a line after the range, delete the \n from the end of the row range
10189 // and position the cursor on the next line.
10190 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
10191 cursor_buffer_row = rows.end;
10192 } else {
10193 // If there isn't a line after the range, delete the \n from the line before the
10194 // start of the row range and position the cursor there.
10195 edit_start = edit_start.saturating_sub(1);
10196 edit_end = buffer.len();
10197 cursor_buffer_row = rows.start.previous_row();
10198 }
10199
10200 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
10201 *cursor.column_mut() =
10202 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
10203
10204 new_cursors.push((
10205 selection.id,
10206 buffer.anchor_after(cursor.to_point(&display_map)),
10207 ));
10208 edit_ranges.push(edit_start..edit_end);
10209 }
10210
10211 self.transact(window, cx, |this, window, cx| {
10212 let buffer = this.buffer.update(cx, |buffer, cx| {
10213 let empty_str: Arc<str> = Arc::default();
10214 buffer.edit(
10215 edit_ranges
10216 .into_iter()
10217 .map(|range| (range, empty_str.clone())),
10218 None,
10219 cx,
10220 );
10221 buffer.snapshot(cx)
10222 });
10223 let new_selections = new_cursors
10224 .into_iter()
10225 .map(|(id, cursor)| {
10226 let cursor = cursor.to_point(&buffer);
10227 Selection {
10228 id,
10229 start: cursor,
10230 end: cursor,
10231 reversed: false,
10232 goal: SelectionGoal::None,
10233 }
10234 })
10235 .collect();
10236
10237 this.change_selections(Default::default(), window, cx, |s| {
10238 s.select(new_selections);
10239 });
10240 });
10241 }
10242
10243 pub fn join_lines_impl(
10244 &mut self,
10245 insert_whitespace: bool,
10246 window: &mut Window,
10247 cx: &mut Context<Self>,
10248 ) {
10249 if self.read_only(cx) {
10250 return;
10251 }
10252 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10253 for selection in self.selections.all::<Point>(cx) {
10254 let start = MultiBufferRow(selection.start.row);
10255 // Treat single line selections as if they include the next line. Otherwise this action
10256 // would do nothing for single line selections individual cursors.
10257 let end = if selection.start.row == selection.end.row {
10258 MultiBufferRow(selection.start.row + 1)
10259 } else {
10260 MultiBufferRow(selection.end.row)
10261 };
10262
10263 if let Some(last_row_range) = row_ranges.last_mut() {
10264 if start <= last_row_range.end {
10265 last_row_range.end = end;
10266 continue;
10267 }
10268 }
10269 row_ranges.push(start..end);
10270 }
10271
10272 let snapshot = self.buffer.read(cx).snapshot(cx);
10273 let mut cursor_positions = Vec::new();
10274 for row_range in &row_ranges {
10275 let anchor = snapshot.anchor_before(Point::new(
10276 row_range.end.previous_row().0,
10277 snapshot.line_len(row_range.end.previous_row()),
10278 ));
10279 cursor_positions.push(anchor..anchor);
10280 }
10281
10282 self.transact(window, cx, |this, window, cx| {
10283 for row_range in row_ranges.into_iter().rev() {
10284 for row in row_range.iter_rows().rev() {
10285 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10286 let next_line_row = row.next_row();
10287 let indent = snapshot.indent_size_for_line(next_line_row);
10288 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10289
10290 let replace =
10291 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10292 " "
10293 } else {
10294 ""
10295 };
10296
10297 this.buffer.update(cx, |buffer, cx| {
10298 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10299 });
10300 }
10301 }
10302
10303 this.change_selections(Default::default(), window, cx, |s| {
10304 s.select_anchor_ranges(cursor_positions)
10305 });
10306 });
10307 }
10308
10309 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10310 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10311 self.join_lines_impl(true, window, cx);
10312 }
10313
10314 pub fn sort_lines_case_sensitive(
10315 &mut self,
10316 _: &SortLinesCaseSensitive,
10317 window: &mut Window,
10318 cx: &mut Context<Self>,
10319 ) {
10320 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10321 }
10322
10323 pub fn sort_lines_by_length(
10324 &mut self,
10325 _: &SortLinesByLength,
10326 window: &mut Window,
10327 cx: &mut Context<Self>,
10328 ) {
10329 self.manipulate_immutable_lines(window, cx, |lines| {
10330 lines.sort_by_key(|&line| line.chars().count())
10331 })
10332 }
10333
10334 pub fn sort_lines_case_insensitive(
10335 &mut self,
10336 _: &SortLinesCaseInsensitive,
10337 window: &mut Window,
10338 cx: &mut Context<Self>,
10339 ) {
10340 self.manipulate_immutable_lines(window, cx, |lines| {
10341 lines.sort_by_key(|line| line.to_lowercase())
10342 })
10343 }
10344
10345 pub fn unique_lines_case_insensitive(
10346 &mut self,
10347 _: &UniqueLinesCaseInsensitive,
10348 window: &mut Window,
10349 cx: &mut Context<Self>,
10350 ) {
10351 self.manipulate_immutable_lines(window, cx, |lines| {
10352 let mut seen = HashSet::default();
10353 lines.retain(|line| seen.insert(line.to_lowercase()));
10354 })
10355 }
10356
10357 pub fn unique_lines_case_sensitive(
10358 &mut self,
10359 _: &UniqueLinesCaseSensitive,
10360 window: &mut Window,
10361 cx: &mut Context<Self>,
10362 ) {
10363 self.manipulate_immutable_lines(window, cx, |lines| {
10364 let mut seen = HashSet::default();
10365 lines.retain(|line| seen.insert(*line));
10366 })
10367 }
10368
10369 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10370 let Some(project) = self.project.clone() else {
10371 return;
10372 };
10373 self.reload(project, window, cx)
10374 .detach_and_notify_err(window, cx);
10375 }
10376
10377 pub fn restore_file(
10378 &mut self,
10379 _: &::git::RestoreFile,
10380 window: &mut Window,
10381 cx: &mut Context<Self>,
10382 ) {
10383 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10384 let mut buffer_ids = HashSet::default();
10385 let snapshot = self.buffer().read(cx).snapshot(cx);
10386 for selection in self.selections.all::<usize>(cx) {
10387 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10388 }
10389
10390 let buffer = self.buffer().read(cx);
10391 let ranges = buffer_ids
10392 .into_iter()
10393 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10394 .collect::<Vec<_>>();
10395
10396 self.restore_hunks_in_ranges(ranges, window, cx);
10397 }
10398
10399 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10400 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10401 let selections = self
10402 .selections
10403 .all(cx)
10404 .into_iter()
10405 .map(|s| s.range())
10406 .collect();
10407 self.restore_hunks_in_ranges(selections, window, cx);
10408 }
10409
10410 pub fn restore_hunks_in_ranges(
10411 &mut self,
10412 ranges: Vec<Range<Point>>,
10413 window: &mut Window,
10414 cx: &mut Context<Editor>,
10415 ) {
10416 let mut revert_changes = HashMap::default();
10417 let chunk_by = self
10418 .snapshot(window, cx)
10419 .hunks_for_ranges(ranges)
10420 .into_iter()
10421 .chunk_by(|hunk| hunk.buffer_id);
10422 for (buffer_id, hunks) in &chunk_by {
10423 let hunks = hunks.collect::<Vec<_>>();
10424 for hunk in &hunks {
10425 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10426 }
10427 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10428 }
10429 drop(chunk_by);
10430 if !revert_changes.is_empty() {
10431 self.transact(window, cx, |editor, window, cx| {
10432 editor.restore(revert_changes, window, cx);
10433 });
10434 }
10435 }
10436
10437 pub fn open_active_item_in_terminal(
10438 &mut self,
10439 _: &OpenInTerminal,
10440 window: &mut Window,
10441 cx: &mut Context<Self>,
10442 ) {
10443 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10444 let project_path = buffer.read(cx).project_path(cx)?;
10445 let project = self.project.as_ref()?.read(cx);
10446 let entry = project.entry_for_path(&project_path, cx)?;
10447 let parent = match &entry.canonical_path {
10448 Some(canonical_path) => canonical_path.to_path_buf(),
10449 None => project.absolute_path(&project_path, cx)?,
10450 }
10451 .parent()?
10452 .to_path_buf();
10453 Some(parent)
10454 }) {
10455 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10456 }
10457 }
10458
10459 fn set_breakpoint_context_menu(
10460 &mut self,
10461 display_row: DisplayRow,
10462 position: Option<Anchor>,
10463 clicked_point: gpui::Point<Pixels>,
10464 window: &mut Window,
10465 cx: &mut Context<Self>,
10466 ) {
10467 let source = self
10468 .buffer
10469 .read(cx)
10470 .snapshot(cx)
10471 .anchor_before(Point::new(display_row.0, 0u32));
10472
10473 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10474
10475 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10476 self,
10477 source,
10478 clicked_point,
10479 context_menu,
10480 window,
10481 cx,
10482 );
10483 }
10484
10485 fn add_edit_breakpoint_block(
10486 &mut self,
10487 anchor: Anchor,
10488 breakpoint: &Breakpoint,
10489 edit_action: BreakpointPromptEditAction,
10490 window: &mut Window,
10491 cx: &mut Context<Self>,
10492 ) {
10493 let weak_editor = cx.weak_entity();
10494 let bp_prompt = cx.new(|cx| {
10495 BreakpointPromptEditor::new(
10496 weak_editor,
10497 anchor,
10498 breakpoint.clone(),
10499 edit_action,
10500 window,
10501 cx,
10502 )
10503 });
10504
10505 let height = bp_prompt.update(cx, |this, cx| {
10506 this.prompt
10507 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10508 });
10509 let cloned_prompt = bp_prompt.clone();
10510 let blocks = vec![BlockProperties {
10511 style: BlockStyle::Sticky,
10512 placement: BlockPlacement::Above(anchor),
10513 height: Some(height),
10514 render: Arc::new(move |cx| {
10515 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10516 cloned_prompt.clone().into_any_element()
10517 }),
10518 priority: 0,
10519 }];
10520
10521 let focus_handle = bp_prompt.focus_handle(cx);
10522 window.focus(&focus_handle);
10523
10524 let block_ids = self.insert_blocks(blocks, None, cx);
10525 bp_prompt.update(cx, |prompt, _| {
10526 prompt.add_block_ids(block_ids);
10527 });
10528 }
10529
10530 pub(crate) fn breakpoint_at_row(
10531 &self,
10532 row: u32,
10533 window: &mut Window,
10534 cx: &mut Context<Self>,
10535 ) -> Option<(Anchor, Breakpoint)> {
10536 let snapshot = self.snapshot(window, cx);
10537 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
10538
10539 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10540 }
10541
10542 pub(crate) fn breakpoint_at_anchor(
10543 &self,
10544 breakpoint_position: Anchor,
10545 snapshot: &EditorSnapshot,
10546 cx: &mut Context<Self>,
10547 ) -> Option<(Anchor, Breakpoint)> {
10548 let project = self.project.clone()?;
10549
10550 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
10551 snapshot
10552 .buffer_snapshot
10553 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
10554 })?;
10555
10556 let enclosing_excerpt = breakpoint_position.excerpt_id;
10557 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
10558 let buffer_snapshot = buffer.read(cx).snapshot();
10559
10560 let row = buffer_snapshot
10561 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10562 .row;
10563
10564 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
10565 let anchor_end = snapshot
10566 .buffer_snapshot
10567 .anchor_after(Point::new(row, line_len));
10568
10569 let bp = self
10570 .breakpoint_store
10571 .as_ref()?
10572 .read_with(cx, |breakpoint_store, cx| {
10573 breakpoint_store
10574 .breakpoints(
10575 &buffer,
10576 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10577 &buffer_snapshot,
10578 cx,
10579 )
10580 .next()
10581 .and_then(|(bp, _)| {
10582 let breakpoint_row = buffer_snapshot
10583 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10584 .row;
10585
10586 if breakpoint_row == row {
10587 snapshot
10588 .buffer_snapshot
10589 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10590 .map(|position| (position, bp.bp.clone()))
10591 } else {
10592 None
10593 }
10594 })
10595 });
10596 bp
10597 }
10598
10599 pub fn edit_log_breakpoint(
10600 &mut self,
10601 _: &EditLogBreakpoint,
10602 window: &mut Window,
10603 cx: &mut Context<Self>,
10604 ) {
10605 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10606 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10607 message: None,
10608 state: BreakpointState::Enabled,
10609 condition: None,
10610 hit_condition: None,
10611 });
10612
10613 self.add_edit_breakpoint_block(
10614 anchor,
10615 &breakpoint,
10616 BreakpointPromptEditAction::Log,
10617 window,
10618 cx,
10619 );
10620 }
10621 }
10622
10623 fn breakpoints_at_cursors(
10624 &self,
10625 window: &mut Window,
10626 cx: &mut Context<Self>,
10627 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10628 let snapshot = self.snapshot(window, cx);
10629 let cursors = self
10630 .selections
10631 .disjoint_anchors()
10632 .into_iter()
10633 .map(|selection| {
10634 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
10635
10636 let breakpoint_position = self
10637 .breakpoint_at_row(cursor_position.row, window, cx)
10638 .map(|bp| bp.0)
10639 .unwrap_or_else(|| {
10640 snapshot
10641 .display_snapshot
10642 .buffer_snapshot
10643 .anchor_after(Point::new(cursor_position.row, 0))
10644 });
10645
10646 let breakpoint = self
10647 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10648 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10649
10650 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10651 })
10652 // 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.
10653 .collect::<HashMap<Anchor, _>>();
10654
10655 cursors.into_iter().collect()
10656 }
10657
10658 pub fn enable_breakpoint(
10659 &mut self,
10660 _: &crate::actions::EnableBreakpoint,
10661 window: &mut Window,
10662 cx: &mut Context<Self>,
10663 ) {
10664 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10665 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10666 continue;
10667 };
10668 self.edit_breakpoint_at_anchor(
10669 anchor,
10670 breakpoint,
10671 BreakpointEditAction::InvertState,
10672 cx,
10673 );
10674 }
10675 }
10676
10677 pub fn disable_breakpoint(
10678 &mut self,
10679 _: &crate::actions::DisableBreakpoint,
10680 window: &mut Window,
10681 cx: &mut Context<Self>,
10682 ) {
10683 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10684 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10685 continue;
10686 };
10687 self.edit_breakpoint_at_anchor(
10688 anchor,
10689 breakpoint,
10690 BreakpointEditAction::InvertState,
10691 cx,
10692 );
10693 }
10694 }
10695
10696 pub fn toggle_breakpoint(
10697 &mut self,
10698 _: &crate::actions::ToggleBreakpoint,
10699 window: &mut Window,
10700 cx: &mut Context<Self>,
10701 ) {
10702 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10703 if let Some(breakpoint) = breakpoint {
10704 self.edit_breakpoint_at_anchor(
10705 anchor,
10706 breakpoint,
10707 BreakpointEditAction::Toggle,
10708 cx,
10709 );
10710 } else {
10711 self.edit_breakpoint_at_anchor(
10712 anchor,
10713 Breakpoint::new_standard(),
10714 BreakpointEditAction::Toggle,
10715 cx,
10716 );
10717 }
10718 }
10719 }
10720
10721 pub fn edit_breakpoint_at_anchor(
10722 &mut self,
10723 breakpoint_position: Anchor,
10724 breakpoint: Breakpoint,
10725 edit_action: BreakpointEditAction,
10726 cx: &mut Context<Self>,
10727 ) {
10728 let Some(breakpoint_store) = &self.breakpoint_store else {
10729 return;
10730 };
10731
10732 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
10733 if breakpoint_position == Anchor::min() {
10734 self.buffer()
10735 .read(cx)
10736 .excerpt_buffer_ids()
10737 .into_iter()
10738 .next()
10739 } else {
10740 None
10741 }
10742 }) else {
10743 return;
10744 };
10745
10746 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
10747 return;
10748 };
10749
10750 breakpoint_store.update(cx, |breakpoint_store, cx| {
10751 breakpoint_store.toggle_breakpoint(
10752 buffer,
10753 BreakpointWithPosition {
10754 position: breakpoint_position.text_anchor,
10755 bp: breakpoint,
10756 },
10757 edit_action,
10758 cx,
10759 );
10760 });
10761
10762 cx.notify();
10763 }
10764
10765 #[cfg(any(test, feature = "test-support"))]
10766 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10767 self.breakpoint_store.clone()
10768 }
10769
10770 pub fn prepare_restore_change(
10771 &self,
10772 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10773 hunk: &MultiBufferDiffHunk,
10774 cx: &mut App,
10775 ) -> Option<()> {
10776 if hunk.is_created_file() {
10777 return None;
10778 }
10779 let buffer = self.buffer.read(cx);
10780 let diff = buffer.diff_for(hunk.buffer_id)?;
10781 let buffer = buffer.buffer(hunk.buffer_id)?;
10782 let buffer = buffer.read(cx);
10783 let original_text = diff
10784 .read(cx)
10785 .base_text()
10786 .as_rope()
10787 .slice(hunk.diff_base_byte_range.clone());
10788 let buffer_snapshot = buffer.snapshot();
10789 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
10790 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
10791 probe
10792 .0
10793 .start
10794 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
10795 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
10796 }) {
10797 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
10798 Some(())
10799 } else {
10800 None
10801 }
10802 }
10803
10804 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
10805 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
10806 }
10807
10808 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
10809 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
10810 }
10811
10812 fn manipulate_lines<M>(
10813 &mut self,
10814 window: &mut Window,
10815 cx: &mut Context<Self>,
10816 mut manipulate: M,
10817 ) where
10818 M: FnMut(&str) -> LineManipulationResult,
10819 {
10820 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10821
10822 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10823 let buffer = self.buffer.read(cx).snapshot(cx);
10824
10825 let mut edits = Vec::new();
10826
10827 let selections = self.selections.all::<Point>(cx);
10828 let mut selections = selections.iter().peekable();
10829 let mut contiguous_row_selections = Vec::new();
10830 let mut new_selections = Vec::new();
10831 let mut added_lines = 0;
10832 let mut removed_lines = 0;
10833
10834 while let Some(selection) = selections.next() {
10835 let (start_row, end_row) = consume_contiguous_rows(
10836 &mut contiguous_row_selections,
10837 selection,
10838 &display_map,
10839 &mut selections,
10840 );
10841
10842 let start_point = Point::new(start_row.0, 0);
10843 let end_point = Point::new(
10844 end_row.previous_row().0,
10845 buffer.line_len(end_row.previous_row()),
10846 );
10847 let text = buffer
10848 .text_for_range(start_point..end_point)
10849 .collect::<String>();
10850
10851 let LineManipulationResult {
10852 new_text,
10853 line_count_before,
10854 line_count_after,
10855 } = manipulate(&text);
10856
10857 edits.push((start_point..end_point, new_text));
10858
10859 // Selections must change based on added and removed line count
10860 let start_row =
10861 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
10862 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
10863 new_selections.push(Selection {
10864 id: selection.id,
10865 start: start_row,
10866 end: end_row,
10867 goal: SelectionGoal::None,
10868 reversed: selection.reversed,
10869 });
10870
10871 if line_count_after > line_count_before {
10872 added_lines += line_count_after - line_count_before;
10873 } else if line_count_before > line_count_after {
10874 removed_lines += line_count_before - line_count_after;
10875 }
10876 }
10877
10878 self.transact(window, cx, |this, window, cx| {
10879 let buffer = this.buffer.update(cx, |buffer, cx| {
10880 buffer.edit(edits, None, cx);
10881 buffer.snapshot(cx)
10882 });
10883
10884 // Recalculate offsets on newly edited buffer
10885 let new_selections = new_selections
10886 .iter()
10887 .map(|s| {
10888 let start_point = Point::new(s.start.0, 0);
10889 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
10890 Selection {
10891 id: s.id,
10892 start: buffer.point_to_offset(start_point),
10893 end: buffer.point_to_offset(end_point),
10894 goal: s.goal,
10895 reversed: s.reversed,
10896 }
10897 })
10898 .collect();
10899
10900 this.change_selections(Default::default(), window, cx, |s| {
10901 s.select(new_selections);
10902 });
10903
10904 this.request_autoscroll(Autoscroll::fit(), cx);
10905 });
10906 }
10907
10908 fn manipulate_immutable_lines<Fn>(
10909 &mut self,
10910 window: &mut Window,
10911 cx: &mut Context<Self>,
10912 mut callback: Fn,
10913 ) where
10914 Fn: FnMut(&mut Vec<&str>),
10915 {
10916 self.manipulate_lines(window, cx, |text| {
10917 let mut lines: Vec<&str> = text.split('\n').collect();
10918 let line_count_before = lines.len();
10919
10920 callback(&mut lines);
10921
10922 LineManipulationResult {
10923 new_text: lines.join("\n"),
10924 line_count_before,
10925 line_count_after: lines.len(),
10926 }
10927 });
10928 }
10929
10930 fn manipulate_mutable_lines<Fn>(
10931 &mut self,
10932 window: &mut Window,
10933 cx: &mut Context<Self>,
10934 mut callback: Fn,
10935 ) where
10936 Fn: FnMut(&mut Vec<Cow<'_, str>>),
10937 {
10938 self.manipulate_lines(window, cx, |text| {
10939 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
10940 let line_count_before = lines.len();
10941
10942 callback(&mut lines);
10943
10944 LineManipulationResult {
10945 new_text: lines.join("\n"),
10946 line_count_before,
10947 line_count_after: lines.len(),
10948 }
10949 });
10950 }
10951
10952 pub fn convert_indentation_to_spaces(
10953 &mut self,
10954 _: &ConvertIndentationToSpaces,
10955 window: &mut Window,
10956 cx: &mut Context<Self>,
10957 ) {
10958 let settings = self.buffer.read(cx).language_settings(cx);
10959 let tab_size = settings.tab_size.get() as usize;
10960
10961 self.manipulate_mutable_lines(window, cx, |lines| {
10962 // Allocates a reasonably sized scratch buffer once for the whole loop
10963 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
10964 // Avoids recomputing spaces that could be inserted many times
10965 let space_cache: Vec<Vec<char>> = (1..=tab_size)
10966 .map(|n| IndentSize::spaces(n as u32).chars().collect())
10967 .collect();
10968
10969 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
10970 let mut chars = line.as_ref().chars();
10971 let mut col = 0;
10972 let mut changed = false;
10973
10974 while let Some(ch) = chars.next() {
10975 match ch {
10976 ' ' => {
10977 reindented_line.push(' ');
10978 col += 1;
10979 }
10980 '\t' => {
10981 // \t are converted to spaces depending on the current column
10982 let spaces_len = tab_size - (col % tab_size);
10983 reindented_line.extend(&space_cache[spaces_len - 1]);
10984 col += spaces_len;
10985 changed = true;
10986 }
10987 _ => {
10988 // If we dont append before break, the character is consumed
10989 reindented_line.push(ch);
10990 break;
10991 }
10992 }
10993 }
10994
10995 if !changed {
10996 reindented_line.clear();
10997 continue;
10998 }
10999 // Append the rest of the line and replace old reference with new one
11000 reindented_line.extend(chars);
11001 *line = Cow::Owned(reindented_line.clone());
11002 reindented_line.clear();
11003 }
11004 });
11005 }
11006
11007 pub fn convert_indentation_to_tabs(
11008 &mut self,
11009 _: &ConvertIndentationToTabs,
11010 window: &mut Window,
11011 cx: &mut Context<Self>,
11012 ) {
11013 let settings = self.buffer.read(cx).language_settings(cx);
11014 let tab_size = settings.tab_size.get() as usize;
11015
11016 self.manipulate_mutable_lines(window, cx, |lines| {
11017 // Allocates a reasonably sized buffer once for the whole loop
11018 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11019 // Avoids recomputing spaces that could be inserted many times
11020 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11021 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11022 .collect();
11023
11024 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11025 let mut chars = line.chars();
11026 let mut spaces_count = 0;
11027 let mut first_non_indent_char = None;
11028 let mut changed = false;
11029
11030 while let Some(ch) = chars.next() {
11031 match ch {
11032 ' ' => {
11033 // Keep track of spaces. Append \t when we reach tab_size
11034 spaces_count += 1;
11035 changed = true;
11036 if spaces_count == tab_size {
11037 reindented_line.push('\t');
11038 spaces_count = 0;
11039 }
11040 }
11041 '\t' => {
11042 reindented_line.push('\t');
11043 spaces_count = 0;
11044 }
11045 _ => {
11046 // Dont append it yet, we might have remaining spaces
11047 first_non_indent_char = Some(ch);
11048 break;
11049 }
11050 }
11051 }
11052
11053 if !changed {
11054 reindented_line.clear();
11055 continue;
11056 }
11057 // Remaining spaces that didn't make a full tab stop
11058 if spaces_count > 0 {
11059 reindented_line.extend(&space_cache[spaces_count - 1]);
11060 }
11061 // If we consume an extra character that was not indentation, add it back
11062 if let Some(extra_char) = first_non_indent_char {
11063 reindented_line.push(extra_char);
11064 }
11065 // Append the rest of the line and replace old reference with new one
11066 reindented_line.extend(chars);
11067 *line = Cow::Owned(reindented_line.clone());
11068 reindented_line.clear();
11069 }
11070 });
11071 }
11072
11073 pub fn convert_to_upper_case(
11074 &mut self,
11075 _: &ConvertToUpperCase,
11076 window: &mut Window,
11077 cx: &mut Context<Self>,
11078 ) {
11079 self.manipulate_text(window, cx, |text| text.to_uppercase())
11080 }
11081
11082 pub fn convert_to_lower_case(
11083 &mut self,
11084 _: &ConvertToLowerCase,
11085 window: &mut Window,
11086 cx: &mut Context<Self>,
11087 ) {
11088 self.manipulate_text(window, cx, |text| text.to_lowercase())
11089 }
11090
11091 pub fn convert_to_title_case(
11092 &mut self,
11093 _: &ConvertToTitleCase,
11094 window: &mut Window,
11095 cx: &mut Context<Self>,
11096 ) {
11097 self.manipulate_text(window, cx, |text| {
11098 text.split('\n')
11099 .map(|line| line.to_case(Case::Title))
11100 .join("\n")
11101 })
11102 }
11103
11104 pub fn convert_to_snake_case(
11105 &mut self,
11106 _: &ConvertToSnakeCase,
11107 window: &mut Window,
11108 cx: &mut Context<Self>,
11109 ) {
11110 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11111 }
11112
11113 pub fn convert_to_kebab_case(
11114 &mut self,
11115 _: &ConvertToKebabCase,
11116 window: &mut Window,
11117 cx: &mut Context<Self>,
11118 ) {
11119 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11120 }
11121
11122 pub fn convert_to_upper_camel_case(
11123 &mut self,
11124 _: &ConvertToUpperCamelCase,
11125 window: &mut Window,
11126 cx: &mut Context<Self>,
11127 ) {
11128 self.manipulate_text(window, cx, |text| {
11129 text.split('\n')
11130 .map(|line| line.to_case(Case::UpperCamel))
11131 .join("\n")
11132 })
11133 }
11134
11135 pub fn convert_to_lower_camel_case(
11136 &mut self,
11137 _: &ConvertToLowerCamelCase,
11138 window: &mut Window,
11139 cx: &mut Context<Self>,
11140 ) {
11141 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11142 }
11143
11144 pub fn convert_to_opposite_case(
11145 &mut self,
11146 _: &ConvertToOppositeCase,
11147 window: &mut Window,
11148 cx: &mut Context<Self>,
11149 ) {
11150 self.manipulate_text(window, cx, |text| {
11151 text.chars()
11152 .fold(String::with_capacity(text.len()), |mut t, c| {
11153 if c.is_uppercase() {
11154 t.extend(c.to_lowercase());
11155 } else {
11156 t.extend(c.to_uppercase());
11157 }
11158 t
11159 })
11160 })
11161 }
11162
11163 pub fn convert_to_sentence_case(
11164 &mut self,
11165 _: &ConvertToSentenceCase,
11166 window: &mut Window,
11167 cx: &mut Context<Self>,
11168 ) {
11169 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11170 }
11171
11172 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11173 self.manipulate_text(window, cx, |text| {
11174 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11175 if has_upper_case_characters {
11176 text.to_lowercase()
11177 } else {
11178 text.to_uppercase()
11179 }
11180 })
11181 }
11182
11183 pub fn convert_to_rot13(
11184 &mut self,
11185 _: &ConvertToRot13,
11186 window: &mut Window,
11187 cx: &mut Context<Self>,
11188 ) {
11189 self.manipulate_text(window, cx, |text| {
11190 text.chars()
11191 .map(|c| match c {
11192 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11193 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11194 _ => c,
11195 })
11196 .collect()
11197 })
11198 }
11199
11200 pub fn convert_to_rot47(
11201 &mut self,
11202 _: &ConvertToRot47,
11203 window: &mut Window,
11204 cx: &mut Context<Self>,
11205 ) {
11206 self.manipulate_text(window, cx, |text| {
11207 text.chars()
11208 .map(|c| {
11209 let code_point = c as u32;
11210 if code_point >= 33 && code_point <= 126 {
11211 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11212 }
11213 c
11214 })
11215 .collect()
11216 })
11217 }
11218
11219 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11220 where
11221 Fn: FnMut(&str) -> String,
11222 {
11223 let buffer = self.buffer.read(cx).snapshot(cx);
11224
11225 let mut new_selections = Vec::new();
11226 let mut edits = Vec::new();
11227 let mut selection_adjustment = 0i32;
11228
11229 for selection in self.selections.all::<usize>(cx) {
11230 let selection_is_empty = selection.is_empty();
11231
11232 let (start, end) = if selection_is_empty {
11233 let (word_range, _) = buffer.surrounding_word(selection.start, false);
11234 (word_range.start, word_range.end)
11235 } else {
11236 (selection.start, selection.end)
11237 };
11238
11239 let text = buffer.text_for_range(start..end).collect::<String>();
11240 let old_length = text.len() as i32;
11241 let text = callback(&text);
11242
11243 new_selections.push(Selection {
11244 start: (start as i32 - selection_adjustment) as usize,
11245 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11246 goal: SelectionGoal::None,
11247 ..selection
11248 });
11249
11250 selection_adjustment += old_length - text.len() as i32;
11251
11252 edits.push((start..end, text));
11253 }
11254
11255 self.transact(window, cx, |this, window, cx| {
11256 this.buffer.update(cx, |buffer, cx| {
11257 buffer.edit(edits, None, cx);
11258 });
11259
11260 this.change_selections(Default::default(), window, cx, |s| {
11261 s.select(new_selections);
11262 });
11263
11264 this.request_autoscroll(Autoscroll::fit(), cx);
11265 });
11266 }
11267
11268 pub fn move_selection_on_drop(
11269 &mut self,
11270 selection: &Selection<Anchor>,
11271 target: DisplayPoint,
11272 is_cut: bool,
11273 window: &mut Window,
11274 cx: &mut Context<Self>,
11275 ) {
11276 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11277 let buffer = &display_map.buffer_snapshot;
11278 let mut edits = Vec::new();
11279 let insert_point = display_map
11280 .clip_point(target, Bias::Left)
11281 .to_point(&display_map);
11282 let text = buffer
11283 .text_for_range(selection.start..selection.end)
11284 .collect::<String>();
11285 if is_cut {
11286 edits.push(((selection.start..selection.end), String::new()));
11287 }
11288 let insert_anchor = buffer.anchor_before(insert_point);
11289 edits.push(((insert_anchor..insert_anchor), text));
11290 let last_edit_start = insert_anchor.bias_left(buffer);
11291 let last_edit_end = insert_anchor.bias_right(buffer);
11292 self.transact(window, cx, |this, window, cx| {
11293 this.buffer.update(cx, |buffer, cx| {
11294 buffer.edit(edits, None, cx);
11295 });
11296 this.change_selections(Default::default(), window, cx, |s| {
11297 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11298 });
11299 });
11300 }
11301
11302 pub fn clear_selection_drag_state(&mut self) {
11303 self.selection_drag_state = SelectionDragState::None;
11304 }
11305
11306 pub fn duplicate(
11307 &mut self,
11308 upwards: bool,
11309 whole_lines: bool,
11310 window: &mut Window,
11311 cx: &mut Context<Self>,
11312 ) {
11313 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11314
11315 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11316 let buffer = &display_map.buffer_snapshot;
11317 let selections = self.selections.all::<Point>(cx);
11318
11319 let mut edits = Vec::new();
11320 let mut selections_iter = selections.iter().peekable();
11321 while let Some(selection) = selections_iter.next() {
11322 let mut rows = selection.spanned_rows(false, &display_map);
11323 // duplicate line-wise
11324 if whole_lines || selection.start == selection.end {
11325 // Avoid duplicating the same lines twice.
11326 while let Some(next_selection) = selections_iter.peek() {
11327 let next_rows = next_selection.spanned_rows(false, &display_map);
11328 if next_rows.start < rows.end {
11329 rows.end = next_rows.end;
11330 selections_iter.next().unwrap();
11331 } else {
11332 break;
11333 }
11334 }
11335
11336 // Copy the text from the selected row region and splice it either at the start
11337 // or end of the region.
11338 let start = Point::new(rows.start.0, 0);
11339 let end = Point::new(
11340 rows.end.previous_row().0,
11341 buffer.line_len(rows.end.previous_row()),
11342 );
11343 let text = buffer
11344 .text_for_range(start..end)
11345 .chain(Some("\n"))
11346 .collect::<String>();
11347 let insert_location = if upwards {
11348 Point::new(rows.end.0, 0)
11349 } else {
11350 start
11351 };
11352 edits.push((insert_location..insert_location, text));
11353 } else {
11354 // duplicate character-wise
11355 let start = selection.start;
11356 let end = selection.end;
11357 let text = buffer.text_for_range(start..end).collect::<String>();
11358 edits.push((selection.end..selection.end, text));
11359 }
11360 }
11361
11362 self.transact(window, cx, |this, _, cx| {
11363 this.buffer.update(cx, |buffer, cx| {
11364 buffer.edit(edits, None, cx);
11365 });
11366
11367 this.request_autoscroll(Autoscroll::fit(), cx);
11368 });
11369 }
11370
11371 pub fn duplicate_line_up(
11372 &mut self,
11373 _: &DuplicateLineUp,
11374 window: &mut Window,
11375 cx: &mut Context<Self>,
11376 ) {
11377 self.duplicate(true, true, window, cx);
11378 }
11379
11380 pub fn duplicate_line_down(
11381 &mut self,
11382 _: &DuplicateLineDown,
11383 window: &mut Window,
11384 cx: &mut Context<Self>,
11385 ) {
11386 self.duplicate(false, true, window, cx);
11387 }
11388
11389 pub fn duplicate_selection(
11390 &mut self,
11391 _: &DuplicateSelection,
11392 window: &mut Window,
11393 cx: &mut Context<Self>,
11394 ) {
11395 self.duplicate(false, false, window, cx);
11396 }
11397
11398 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11399 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11400 if self.mode.is_single_line() {
11401 cx.propagate();
11402 return;
11403 }
11404
11405 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11406 let buffer = self.buffer.read(cx).snapshot(cx);
11407
11408 let mut edits = Vec::new();
11409 let mut unfold_ranges = Vec::new();
11410 let mut refold_creases = Vec::new();
11411
11412 let selections = self.selections.all::<Point>(cx);
11413 let mut selections = selections.iter().peekable();
11414 let mut contiguous_row_selections = Vec::new();
11415 let mut new_selections = Vec::new();
11416
11417 while let Some(selection) = selections.next() {
11418 // Find all the selections that span a contiguous row range
11419 let (start_row, end_row) = consume_contiguous_rows(
11420 &mut contiguous_row_selections,
11421 selection,
11422 &display_map,
11423 &mut selections,
11424 );
11425
11426 // Move the text spanned by the row range to be before the line preceding the row range
11427 if start_row.0 > 0 {
11428 let range_to_move = Point::new(
11429 start_row.previous_row().0,
11430 buffer.line_len(start_row.previous_row()),
11431 )
11432 ..Point::new(
11433 end_row.previous_row().0,
11434 buffer.line_len(end_row.previous_row()),
11435 );
11436 let insertion_point = display_map
11437 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11438 .0;
11439
11440 // Don't move lines across excerpts
11441 if buffer
11442 .excerpt_containing(insertion_point..range_to_move.end)
11443 .is_some()
11444 {
11445 let text = buffer
11446 .text_for_range(range_to_move.clone())
11447 .flat_map(|s| s.chars())
11448 .skip(1)
11449 .chain(['\n'])
11450 .collect::<String>();
11451
11452 edits.push((
11453 buffer.anchor_after(range_to_move.start)
11454 ..buffer.anchor_before(range_to_move.end),
11455 String::new(),
11456 ));
11457 let insertion_anchor = buffer.anchor_after(insertion_point);
11458 edits.push((insertion_anchor..insertion_anchor, text));
11459
11460 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11461
11462 // Move selections up
11463 new_selections.extend(contiguous_row_selections.drain(..).map(
11464 |mut selection| {
11465 selection.start.row -= row_delta;
11466 selection.end.row -= row_delta;
11467 selection
11468 },
11469 ));
11470
11471 // Move folds up
11472 unfold_ranges.push(range_to_move.clone());
11473 for fold in display_map.folds_in_range(
11474 buffer.anchor_before(range_to_move.start)
11475 ..buffer.anchor_after(range_to_move.end),
11476 ) {
11477 let mut start = fold.range.start.to_point(&buffer);
11478 let mut end = fold.range.end.to_point(&buffer);
11479 start.row -= row_delta;
11480 end.row -= row_delta;
11481 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11482 }
11483 }
11484 }
11485
11486 // If we didn't move line(s), preserve the existing selections
11487 new_selections.append(&mut contiguous_row_selections);
11488 }
11489
11490 self.transact(window, cx, |this, window, cx| {
11491 this.unfold_ranges(&unfold_ranges, true, true, cx);
11492 this.buffer.update(cx, |buffer, cx| {
11493 for (range, text) in edits {
11494 buffer.edit([(range, text)], None, cx);
11495 }
11496 });
11497 this.fold_creases(refold_creases, true, window, cx);
11498 this.change_selections(Default::default(), window, cx, |s| {
11499 s.select(new_selections);
11500 })
11501 });
11502 }
11503
11504 pub fn move_line_down(
11505 &mut self,
11506 _: &MoveLineDown,
11507 window: &mut Window,
11508 cx: &mut Context<Self>,
11509 ) {
11510 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11511 if self.mode.is_single_line() {
11512 cx.propagate();
11513 return;
11514 }
11515
11516 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11517 let buffer = self.buffer.read(cx).snapshot(cx);
11518
11519 let mut edits = Vec::new();
11520 let mut unfold_ranges = Vec::new();
11521 let mut refold_creases = Vec::new();
11522
11523 let selections = self.selections.all::<Point>(cx);
11524 let mut selections = selections.iter().peekable();
11525 let mut contiguous_row_selections = Vec::new();
11526 let mut new_selections = Vec::new();
11527
11528 while let Some(selection) = selections.next() {
11529 // Find all the selections that span a contiguous row range
11530 let (start_row, end_row) = consume_contiguous_rows(
11531 &mut contiguous_row_selections,
11532 selection,
11533 &display_map,
11534 &mut selections,
11535 );
11536
11537 // Move the text spanned by the row range to be after the last line of the row range
11538 if end_row.0 <= buffer.max_point().row {
11539 let range_to_move =
11540 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11541 let insertion_point = display_map
11542 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11543 .0;
11544
11545 // Don't move lines across excerpt boundaries
11546 if buffer
11547 .excerpt_containing(range_to_move.start..insertion_point)
11548 .is_some()
11549 {
11550 let mut text = String::from("\n");
11551 text.extend(buffer.text_for_range(range_to_move.clone()));
11552 text.pop(); // Drop trailing newline
11553 edits.push((
11554 buffer.anchor_after(range_to_move.start)
11555 ..buffer.anchor_before(range_to_move.end),
11556 String::new(),
11557 ));
11558 let insertion_anchor = buffer.anchor_after(insertion_point);
11559 edits.push((insertion_anchor..insertion_anchor, text));
11560
11561 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11562
11563 // Move selections down
11564 new_selections.extend(contiguous_row_selections.drain(..).map(
11565 |mut selection| {
11566 selection.start.row += row_delta;
11567 selection.end.row += row_delta;
11568 selection
11569 },
11570 ));
11571
11572 // Move folds down
11573 unfold_ranges.push(range_to_move.clone());
11574 for fold in display_map.folds_in_range(
11575 buffer.anchor_before(range_to_move.start)
11576 ..buffer.anchor_after(range_to_move.end),
11577 ) {
11578 let mut start = fold.range.start.to_point(&buffer);
11579 let mut end = fold.range.end.to_point(&buffer);
11580 start.row += row_delta;
11581 end.row += row_delta;
11582 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11583 }
11584 }
11585 }
11586
11587 // If we didn't move line(s), preserve the existing selections
11588 new_selections.append(&mut contiguous_row_selections);
11589 }
11590
11591 self.transact(window, cx, |this, window, cx| {
11592 this.unfold_ranges(&unfold_ranges, true, true, cx);
11593 this.buffer.update(cx, |buffer, cx| {
11594 for (range, text) in edits {
11595 buffer.edit([(range, text)], None, cx);
11596 }
11597 });
11598 this.fold_creases(refold_creases, true, window, cx);
11599 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
11600 });
11601 }
11602
11603 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11604 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11605 let text_layout_details = &self.text_layout_details(window);
11606 self.transact(window, cx, |this, window, cx| {
11607 let edits = this.change_selections(Default::default(), window, cx, |s| {
11608 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11609 s.move_with(|display_map, selection| {
11610 if !selection.is_empty() {
11611 return;
11612 }
11613
11614 let mut head = selection.head();
11615 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11616 if head.column() == display_map.line_len(head.row()) {
11617 transpose_offset = display_map
11618 .buffer_snapshot
11619 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11620 }
11621
11622 if transpose_offset == 0 {
11623 return;
11624 }
11625
11626 *head.column_mut() += 1;
11627 head = display_map.clip_point(head, Bias::Right);
11628 let goal = SelectionGoal::HorizontalPosition(
11629 display_map
11630 .x_for_display_point(head, text_layout_details)
11631 .into(),
11632 );
11633 selection.collapse_to(head, goal);
11634
11635 let transpose_start = display_map
11636 .buffer_snapshot
11637 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11638 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
11639 let transpose_end = display_map
11640 .buffer_snapshot
11641 .clip_offset(transpose_offset + 1, Bias::Right);
11642 if let Some(ch) =
11643 display_map.buffer_snapshot.chars_at(transpose_start).next()
11644 {
11645 edits.push((transpose_start..transpose_offset, String::new()));
11646 edits.push((transpose_end..transpose_end, ch.to_string()));
11647 }
11648 }
11649 });
11650 edits
11651 });
11652 this.buffer
11653 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11654 let selections = this.selections.all::<usize>(cx);
11655 this.change_selections(Default::default(), window, cx, |s| {
11656 s.select(selections);
11657 });
11658 });
11659 }
11660
11661 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
11662 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11663 if self.mode.is_single_line() {
11664 cx.propagate();
11665 return;
11666 }
11667
11668 self.rewrap_impl(RewrapOptions::default(), cx)
11669 }
11670
11671 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
11672 let buffer = self.buffer.read(cx).snapshot(cx);
11673 let selections = self.selections.all::<Point>(cx);
11674
11675 // Split selections to respect paragraph, indent, and comment prefix boundaries.
11676 let wrap_ranges = selections.into_iter().flat_map(|selection| {
11677 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
11678 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11679 .peekable();
11680
11681 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
11682 row
11683 } else {
11684 return Vec::new();
11685 };
11686
11687 let language_settings = buffer.language_settings_at(selection.head(), cx);
11688 let language_scope = buffer.language_scope_at(selection.head());
11689
11690 let indent_and_prefix_for_row =
11691 |row: u32| -> (IndentSize, Option<String>, Option<String>) {
11692 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
11693 let (comment_prefix, rewrap_prefix) =
11694 if let Some(language_scope) = &language_scope {
11695 let indent_end = Point::new(row, indent.len);
11696 let comment_prefix = language_scope
11697 .line_comment_prefixes()
11698 .iter()
11699 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11700 .map(|prefix| prefix.to_string());
11701 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11702 let line_text_after_indent = buffer
11703 .text_for_range(indent_end..line_end)
11704 .collect::<String>();
11705 let rewrap_prefix = language_scope
11706 .rewrap_prefixes()
11707 .iter()
11708 .find_map(|prefix_regex| {
11709 prefix_regex.find(&line_text_after_indent).map(|mat| {
11710 if mat.start() == 0 {
11711 Some(mat.as_str().to_string())
11712 } else {
11713 None
11714 }
11715 })
11716 })
11717 .flatten();
11718 (comment_prefix, rewrap_prefix)
11719 } else {
11720 (None, None)
11721 };
11722 (indent, comment_prefix, rewrap_prefix)
11723 };
11724
11725 let mut ranges = Vec::new();
11726 let from_empty_selection = selection.is_empty();
11727
11728 let mut current_range_start = first_row;
11729 let mut prev_row = first_row;
11730 let (
11731 mut current_range_indent,
11732 mut current_range_comment_prefix,
11733 mut current_range_rewrap_prefix,
11734 ) = indent_and_prefix_for_row(first_row);
11735
11736 for row in non_blank_rows_iter.skip(1) {
11737 let has_paragraph_break = row > prev_row + 1;
11738
11739 let (row_indent, row_comment_prefix, row_rewrap_prefix) =
11740 indent_and_prefix_for_row(row);
11741
11742 let has_indent_change = row_indent != current_range_indent;
11743 let has_comment_change = row_comment_prefix != current_range_comment_prefix;
11744
11745 let has_boundary_change = has_comment_change
11746 || row_rewrap_prefix.is_some()
11747 || (has_indent_change && current_range_comment_prefix.is_some());
11748
11749 if has_paragraph_break || has_boundary_change {
11750 ranges.push((
11751 language_settings.clone(),
11752 Point::new(current_range_start, 0)
11753 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
11754 current_range_indent,
11755 current_range_comment_prefix.clone(),
11756 current_range_rewrap_prefix.clone(),
11757 from_empty_selection,
11758 ));
11759 current_range_start = row;
11760 current_range_indent = row_indent;
11761 current_range_comment_prefix = row_comment_prefix;
11762 current_range_rewrap_prefix = row_rewrap_prefix;
11763 }
11764 prev_row = row;
11765 }
11766
11767 ranges.push((
11768 language_settings.clone(),
11769 Point::new(current_range_start, 0)
11770 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
11771 current_range_indent,
11772 current_range_comment_prefix,
11773 current_range_rewrap_prefix,
11774 from_empty_selection,
11775 ));
11776
11777 ranges
11778 });
11779
11780 let mut edits = Vec::new();
11781 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
11782
11783 for (
11784 language_settings,
11785 wrap_range,
11786 indent_size,
11787 comment_prefix,
11788 rewrap_prefix,
11789 from_empty_selection,
11790 ) in wrap_ranges
11791 {
11792 let mut start_row = wrap_range.start.row;
11793 let mut end_row = wrap_range.end.row;
11794
11795 // Skip selections that overlap with a range that has already been rewrapped.
11796 let selection_range = start_row..end_row;
11797 if rewrapped_row_ranges
11798 .iter()
11799 .any(|range| range.overlaps(&selection_range))
11800 {
11801 continue;
11802 }
11803
11804 let tab_size = language_settings.tab_size;
11805
11806 let indent_prefix = indent_size.chars().collect::<String>();
11807 let mut line_prefix = indent_prefix.clone();
11808 let mut inside_comment = false;
11809 if let Some(prefix) = &comment_prefix {
11810 line_prefix.push_str(prefix);
11811 inside_comment = true;
11812 }
11813 if let Some(prefix) = &rewrap_prefix {
11814 line_prefix.push_str(prefix);
11815 }
11816
11817 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
11818 RewrapBehavior::InComments => inside_comment,
11819 RewrapBehavior::InSelections => !wrap_range.is_empty(),
11820 RewrapBehavior::Anywhere => true,
11821 };
11822
11823 let should_rewrap = options.override_language_settings
11824 || allow_rewrap_based_on_language
11825 || self.hard_wrap.is_some();
11826 if !should_rewrap {
11827 continue;
11828 }
11829
11830 if from_empty_selection {
11831 'expand_upwards: while start_row > 0 {
11832 let prev_row = start_row - 1;
11833 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
11834 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
11835 && !buffer.is_line_blank(MultiBufferRow(prev_row))
11836 {
11837 start_row = prev_row;
11838 } else {
11839 break 'expand_upwards;
11840 }
11841 }
11842
11843 'expand_downwards: while end_row < buffer.max_point().row {
11844 let next_row = end_row + 1;
11845 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
11846 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
11847 && !buffer.is_line_blank(MultiBufferRow(next_row))
11848 {
11849 end_row = next_row;
11850 } else {
11851 break 'expand_downwards;
11852 }
11853 }
11854 }
11855
11856 let start = Point::new(start_row, 0);
11857 let start_offset = start.to_offset(&buffer);
11858 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
11859 let selection_text = buffer.text_for_range(start..end).collect::<String>();
11860 let Some(lines_without_prefixes) = selection_text
11861 .lines()
11862 .enumerate()
11863 .map(|(ix, line)| {
11864 let line_trimmed = line.trim_start();
11865 if rewrap_prefix.is_some() && ix > 0 {
11866 Ok(line_trimmed)
11867 } else {
11868 line_trimmed
11869 .strip_prefix(&line_prefix.trim_start())
11870 .with_context(|| {
11871 format!("line did not start with prefix {line_prefix:?}: {line:?}")
11872 })
11873 }
11874 })
11875 .collect::<Result<Vec<_>, _>>()
11876 .log_err()
11877 else {
11878 continue;
11879 };
11880
11881 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
11882 buffer
11883 .language_settings_at(Point::new(start_row, 0), cx)
11884 .preferred_line_length as usize
11885 });
11886
11887 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
11888 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
11889 } else {
11890 line_prefix.clone()
11891 };
11892
11893 let wrapped_text = wrap_with_prefix(
11894 line_prefix,
11895 subsequent_lines_prefix,
11896 lines_without_prefixes.join("\n"),
11897 wrap_column,
11898 tab_size,
11899 options.preserve_existing_whitespace,
11900 );
11901
11902 // TODO: should always use char-based diff while still supporting cursor behavior that
11903 // matches vim.
11904 let mut diff_options = DiffOptions::default();
11905 if options.override_language_settings {
11906 diff_options.max_word_diff_len = 0;
11907 diff_options.max_word_diff_line_count = 0;
11908 } else {
11909 diff_options.max_word_diff_len = usize::MAX;
11910 diff_options.max_word_diff_line_count = usize::MAX;
11911 }
11912
11913 for (old_range, new_text) in
11914 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
11915 {
11916 let edit_start = buffer.anchor_after(start_offset + old_range.start);
11917 let edit_end = buffer.anchor_after(start_offset + old_range.end);
11918 edits.push((edit_start..edit_end, new_text));
11919 }
11920
11921 rewrapped_row_ranges.push(start_row..=end_row);
11922 }
11923
11924 self.buffer
11925 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11926 }
11927
11928 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
11929 let mut text = String::new();
11930 let buffer = self.buffer.read(cx).snapshot(cx);
11931 let mut selections = self.selections.all::<Point>(cx);
11932 let mut clipboard_selections = Vec::with_capacity(selections.len());
11933 {
11934 let max_point = buffer.max_point();
11935 let mut is_first = true;
11936 for selection in &mut selections {
11937 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11938 if is_entire_line {
11939 selection.start = Point::new(selection.start.row, 0);
11940 if !selection.is_empty() && selection.end.column == 0 {
11941 selection.end = cmp::min(max_point, selection.end);
11942 } else {
11943 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
11944 }
11945 selection.goal = SelectionGoal::None;
11946 }
11947 if is_first {
11948 is_first = false;
11949 } else {
11950 text += "\n";
11951 }
11952 let mut len = 0;
11953 for chunk in buffer.text_for_range(selection.start..selection.end) {
11954 text.push_str(chunk);
11955 len += chunk.len();
11956 }
11957 clipboard_selections.push(ClipboardSelection {
11958 len,
11959 is_entire_line,
11960 first_line_indent: buffer
11961 .indent_size_for_line(MultiBufferRow(selection.start.row))
11962 .len,
11963 });
11964 }
11965 }
11966
11967 self.transact(window, cx, |this, window, cx| {
11968 this.change_selections(Default::default(), window, cx, |s| {
11969 s.select(selections);
11970 });
11971 this.insert("", window, cx);
11972 });
11973 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
11974 }
11975
11976 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
11977 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11978 let item = self.cut_common(window, cx);
11979 cx.write_to_clipboard(item);
11980 }
11981
11982 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
11983 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11984 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11985 s.move_with(|snapshot, sel| {
11986 if sel.is_empty() {
11987 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
11988 }
11989 });
11990 });
11991 let item = self.cut_common(window, cx);
11992 cx.set_global(KillRing(item))
11993 }
11994
11995 pub fn kill_ring_yank(
11996 &mut self,
11997 _: &KillRingYank,
11998 window: &mut Window,
11999 cx: &mut Context<Self>,
12000 ) {
12001 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12002 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12003 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12004 (kill_ring.text().to_string(), kill_ring.metadata_json())
12005 } else {
12006 return;
12007 }
12008 } else {
12009 return;
12010 };
12011 self.do_paste(&text, metadata, false, window, cx);
12012 }
12013
12014 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12015 self.do_copy(true, cx);
12016 }
12017
12018 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12019 self.do_copy(false, cx);
12020 }
12021
12022 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12023 let selections = self.selections.all::<Point>(cx);
12024 let buffer = self.buffer.read(cx).read(cx);
12025 let mut text = String::new();
12026
12027 let mut clipboard_selections = Vec::with_capacity(selections.len());
12028 {
12029 let max_point = buffer.max_point();
12030 let mut is_first = true;
12031 for selection in &selections {
12032 let mut start = selection.start;
12033 let mut end = selection.end;
12034 let is_entire_line = selection.is_empty() || self.selections.line_mode;
12035 if is_entire_line {
12036 start = Point::new(start.row, 0);
12037 end = cmp::min(max_point, Point::new(end.row + 1, 0));
12038 }
12039
12040 let mut trimmed_selections = Vec::new();
12041 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12042 let row = MultiBufferRow(start.row);
12043 let first_indent = buffer.indent_size_for_line(row);
12044 if first_indent.len == 0 || start.column > first_indent.len {
12045 trimmed_selections.push(start..end);
12046 } else {
12047 trimmed_selections.push(
12048 Point::new(row.0, first_indent.len)
12049 ..Point::new(row.0, buffer.line_len(row)),
12050 );
12051 for row in start.row + 1..=end.row {
12052 let mut line_len = buffer.line_len(MultiBufferRow(row));
12053 if row == end.row {
12054 line_len = end.column;
12055 }
12056 if line_len == 0 {
12057 trimmed_selections
12058 .push(Point::new(row, 0)..Point::new(row, line_len));
12059 continue;
12060 }
12061 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12062 if row_indent_size.len >= first_indent.len {
12063 trimmed_selections.push(
12064 Point::new(row, first_indent.len)..Point::new(row, line_len),
12065 );
12066 } else {
12067 trimmed_selections.clear();
12068 trimmed_selections.push(start..end);
12069 break;
12070 }
12071 }
12072 }
12073 } else {
12074 trimmed_selections.push(start..end);
12075 }
12076
12077 for trimmed_range in trimmed_selections {
12078 if is_first {
12079 is_first = false;
12080 } else {
12081 text += "\n";
12082 }
12083 let mut len = 0;
12084 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12085 text.push_str(chunk);
12086 len += chunk.len();
12087 }
12088 clipboard_selections.push(ClipboardSelection {
12089 len,
12090 is_entire_line,
12091 first_line_indent: buffer
12092 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12093 .len,
12094 });
12095 }
12096 }
12097 }
12098
12099 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12100 text,
12101 clipboard_selections,
12102 ));
12103 }
12104
12105 pub fn do_paste(
12106 &mut self,
12107 text: &String,
12108 clipboard_selections: Option<Vec<ClipboardSelection>>,
12109 handle_entire_lines: bool,
12110 window: &mut Window,
12111 cx: &mut Context<Self>,
12112 ) {
12113 if self.read_only(cx) {
12114 return;
12115 }
12116
12117 let clipboard_text = Cow::Borrowed(text);
12118
12119 self.transact(window, cx, |this, window, cx| {
12120 if let Some(mut clipboard_selections) = clipboard_selections {
12121 let old_selections = this.selections.all::<usize>(cx);
12122 let all_selections_were_entire_line =
12123 clipboard_selections.iter().all(|s| s.is_entire_line);
12124 let first_selection_indent_column =
12125 clipboard_selections.first().map(|s| s.first_line_indent);
12126 if clipboard_selections.len() != old_selections.len() {
12127 clipboard_selections.drain(..);
12128 }
12129 let cursor_offset = this.selections.last::<usize>(cx).head();
12130 let mut auto_indent_on_paste = true;
12131
12132 this.buffer.update(cx, |buffer, cx| {
12133 let snapshot = buffer.read(cx);
12134 auto_indent_on_paste = snapshot
12135 .language_settings_at(cursor_offset, cx)
12136 .auto_indent_on_paste;
12137
12138 let mut start_offset = 0;
12139 let mut edits = Vec::new();
12140 let mut original_indent_columns = Vec::new();
12141 for (ix, selection) in old_selections.iter().enumerate() {
12142 let to_insert;
12143 let entire_line;
12144 let original_indent_column;
12145 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12146 let end_offset = start_offset + clipboard_selection.len;
12147 to_insert = &clipboard_text[start_offset..end_offset];
12148 entire_line = clipboard_selection.is_entire_line;
12149 start_offset = end_offset + 1;
12150 original_indent_column = Some(clipboard_selection.first_line_indent);
12151 } else {
12152 to_insert = clipboard_text.as_str();
12153 entire_line = all_selections_were_entire_line;
12154 original_indent_column = first_selection_indent_column
12155 }
12156
12157 // If the corresponding selection was empty when this slice of the
12158 // clipboard text was written, then the entire line containing the
12159 // selection was copied. If this selection is also currently empty,
12160 // then paste the line before the current line of the buffer.
12161 let range = if selection.is_empty() && handle_entire_lines && entire_line {
12162 let column = selection.start.to_point(&snapshot).column as usize;
12163 let line_start = selection.start - column;
12164 line_start..line_start
12165 } else {
12166 selection.range()
12167 };
12168
12169 edits.push((range, to_insert));
12170 original_indent_columns.push(original_indent_column);
12171 }
12172 drop(snapshot);
12173
12174 buffer.edit(
12175 edits,
12176 if auto_indent_on_paste {
12177 Some(AutoindentMode::Block {
12178 original_indent_columns,
12179 })
12180 } else {
12181 None
12182 },
12183 cx,
12184 );
12185 });
12186
12187 let selections = this.selections.all::<usize>(cx);
12188 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12189 } else {
12190 this.insert(&clipboard_text, window, cx);
12191 }
12192 });
12193 }
12194
12195 pub fn diff_clipboard_with_selection(
12196 &mut self,
12197 _: &DiffClipboardWithSelection,
12198 window: &mut Window,
12199 cx: &mut Context<Self>,
12200 ) {
12201 let selections = self.selections.all::<usize>(cx);
12202
12203 if selections.is_empty() {
12204 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12205 return;
12206 };
12207
12208 let clipboard_text = match cx.read_from_clipboard() {
12209 Some(item) => match item.entries().first() {
12210 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
12211 _ => None,
12212 },
12213 None => None,
12214 };
12215
12216 let Some(clipboard_text) = clipboard_text else {
12217 log::warn!("Clipboard doesn't contain text.");
12218 return;
12219 };
12220
12221 window.dispatch_action(
12222 Box::new(DiffClipboardWithSelectionData {
12223 clipboard_text,
12224 editor: cx.entity(),
12225 }),
12226 cx,
12227 );
12228 }
12229
12230 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12231 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12232 if let Some(item) = cx.read_from_clipboard() {
12233 let entries = item.entries();
12234
12235 match entries.first() {
12236 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12237 // of all the pasted entries.
12238 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12239 .do_paste(
12240 clipboard_string.text(),
12241 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12242 true,
12243 window,
12244 cx,
12245 ),
12246 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12247 }
12248 }
12249 }
12250
12251 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12252 if self.read_only(cx) {
12253 return;
12254 }
12255
12256 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12257
12258 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12259 if let Some((selections, _)) =
12260 self.selection_history.transaction(transaction_id).cloned()
12261 {
12262 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12263 s.select_anchors(selections.to_vec());
12264 });
12265 } else {
12266 log::error!(
12267 "No entry in selection_history found for undo. \
12268 This may correspond to a bug where undo does not update the selection. \
12269 If this is occurring, please add details to \
12270 https://github.com/zed-industries/zed/issues/22692"
12271 );
12272 }
12273 self.request_autoscroll(Autoscroll::fit(), cx);
12274 self.unmark_text(window, cx);
12275 self.refresh_edit_prediction(true, false, window, cx);
12276 cx.emit(EditorEvent::Edited { transaction_id });
12277 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12278 }
12279 }
12280
12281 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12282 if self.read_only(cx) {
12283 return;
12284 }
12285
12286 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12287
12288 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12289 if let Some((_, Some(selections))) =
12290 self.selection_history.transaction(transaction_id).cloned()
12291 {
12292 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12293 s.select_anchors(selections.to_vec());
12294 });
12295 } else {
12296 log::error!(
12297 "No entry in selection_history found for redo. \
12298 This may correspond to a bug where undo does not update the selection. \
12299 If this is occurring, please add details to \
12300 https://github.com/zed-industries/zed/issues/22692"
12301 );
12302 }
12303 self.request_autoscroll(Autoscroll::fit(), cx);
12304 self.unmark_text(window, cx);
12305 self.refresh_edit_prediction(true, false, window, cx);
12306 cx.emit(EditorEvent::Edited { transaction_id });
12307 }
12308 }
12309
12310 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12311 self.buffer
12312 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12313 }
12314
12315 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12316 self.buffer
12317 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12318 }
12319
12320 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
12321 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12322 self.change_selections(Default::default(), window, cx, |s| {
12323 s.move_with(|map, selection| {
12324 let cursor = if selection.is_empty() {
12325 movement::left(map, selection.start)
12326 } else {
12327 selection.start
12328 };
12329 selection.collapse_to(cursor, SelectionGoal::None);
12330 });
12331 })
12332 }
12333
12334 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12335 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12336 self.change_selections(Default::default(), window, cx, |s| {
12337 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12338 })
12339 }
12340
12341 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
12342 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12343 self.change_selections(Default::default(), window, cx, |s| {
12344 s.move_with(|map, selection| {
12345 let cursor = if selection.is_empty() {
12346 movement::right(map, selection.end)
12347 } else {
12348 selection.end
12349 };
12350 selection.collapse_to(cursor, SelectionGoal::None)
12351 });
12352 })
12353 }
12354
12355 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12356 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12357 self.change_selections(Default::default(), window, cx, |s| {
12358 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
12359 })
12360 }
12361
12362 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
12363 if self.take_rename(true, window, cx).is_some() {
12364 return;
12365 }
12366
12367 if self.mode.is_single_line() {
12368 cx.propagate();
12369 return;
12370 }
12371
12372 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12373
12374 let text_layout_details = &self.text_layout_details(window);
12375 let selection_count = self.selections.count();
12376 let first_selection = self.selections.first_anchor();
12377
12378 self.change_selections(Default::default(), window, cx, |s| {
12379 s.move_with(|map, selection| {
12380 if !selection.is_empty() {
12381 selection.goal = SelectionGoal::None;
12382 }
12383 let (cursor, goal) = movement::up(
12384 map,
12385 selection.start,
12386 selection.goal,
12387 false,
12388 text_layout_details,
12389 );
12390 selection.collapse_to(cursor, goal);
12391 });
12392 });
12393
12394 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12395 {
12396 cx.propagate();
12397 }
12398 }
12399
12400 pub fn move_up_by_lines(
12401 &mut self,
12402 action: &MoveUpByLines,
12403 window: &mut Window,
12404 cx: &mut Context<Self>,
12405 ) {
12406 if self.take_rename(true, window, cx).is_some() {
12407 return;
12408 }
12409
12410 if self.mode.is_single_line() {
12411 cx.propagate();
12412 return;
12413 }
12414
12415 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12416
12417 let text_layout_details = &self.text_layout_details(window);
12418
12419 self.change_selections(Default::default(), window, cx, |s| {
12420 s.move_with(|map, selection| {
12421 if !selection.is_empty() {
12422 selection.goal = SelectionGoal::None;
12423 }
12424 let (cursor, goal) = movement::up_by_rows(
12425 map,
12426 selection.start,
12427 action.lines,
12428 selection.goal,
12429 false,
12430 text_layout_details,
12431 );
12432 selection.collapse_to(cursor, goal);
12433 });
12434 })
12435 }
12436
12437 pub fn move_down_by_lines(
12438 &mut self,
12439 action: &MoveDownByLines,
12440 window: &mut Window,
12441 cx: &mut Context<Self>,
12442 ) {
12443 if self.take_rename(true, window, cx).is_some() {
12444 return;
12445 }
12446
12447 if self.mode.is_single_line() {
12448 cx.propagate();
12449 return;
12450 }
12451
12452 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12453
12454 let text_layout_details = &self.text_layout_details(window);
12455
12456 self.change_selections(Default::default(), window, cx, |s| {
12457 s.move_with(|map, selection| {
12458 if !selection.is_empty() {
12459 selection.goal = SelectionGoal::None;
12460 }
12461 let (cursor, goal) = movement::down_by_rows(
12462 map,
12463 selection.start,
12464 action.lines,
12465 selection.goal,
12466 false,
12467 text_layout_details,
12468 );
12469 selection.collapse_to(cursor, goal);
12470 });
12471 })
12472 }
12473
12474 pub fn select_down_by_lines(
12475 &mut self,
12476 action: &SelectDownByLines,
12477 window: &mut Window,
12478 cx: &mut Context<Self>,
12479 ) {
12480 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12481 let text_layout_details = &self.text_layout_details(window);
12482 self.change_selections(Default::default(), window, cx, |s| {
12483 s.move_heads_with(|map, head, goal| {
12484 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
12485 })
12486 })
12487 }
12488
12489 pub fn select_up_by_lines(
12490 &mut self,
12491 action: &SelectUpByLines,
12492 window: &mut Window,
12493 cx: &mut Context<Self>,
12494 ) {
12495 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12496 let text_layout_details = &self.text_layout_details(window);
12497 self.change_selections(Default::default(), window, cx, |s| {
12498 s.move_heads_with(|map, head, goal| {
12499 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
12500 })
12501 })
12502 }
12503
12504 pub fn select_page_up(
12505 &mut self,
12506 _: &SelectPageUp,
12507 window: &mut Window,
12508 cx: &mut Context<Self>,
12509 ) {
12510 let Some(row_count) = self.visible_row_count() else {
12511 return;
12512 };
12513
12514 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12515
12516 let text_layout_details = &self.text_layout_details(window);
12517
12518 self.change_selections(Default::default(), window, cx, |s| {
12519 s.move_heads_with(|map, head, goal| {
12520 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
12521 })
12522 })
12523 }
12524
12525 pub fn move_page_up(
12526 &mut self,
12527 action: &MovePageUp,
12528 window: &mut Window,
12529 cx: &mut Context<Self>,
12530 ) {
12531 if self.take_rename(true, window, cx).is_some() {
12532 return;
12533 }
12534
12535 if self
12536 .context_menu
12537 .borrow_mut()
12538 .as_mut()
12539 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
12540 .unwrap_or(false)
12541 {
12542 return;
12543 }
12544
12545 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12546 cx.propagate();
12547 return;
12548 }
12549
12550 let Some(row_count) = self.visible_row_count() else {
12551 return;
12552 };
12553
12554 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12555
12556 let effects = if action.center_cursor {
12557 SelectionEffects::scroll(Autoscroll::center())
12558 } else {
12559 SelectionEffects::default()
12560 };
12561
12562 let text_layout_details = &self.text_layout_details(window);
12563
12564 self.change_selections(effects, window, cx, |s| {
12565 s.move_with(|map, selection| {
12566 if !selection.is_empty() {
12567 selection.goal = SelectionGoal::None;
12568 }
12569 let (cursor, goal) = movement::up_by_rows(
12570 map,
12571 selection.end,
12572 row_count,
12573 selection.goal,
12574 false,
12575 text_layout_details,
12576 );
12577 selection.collapse_to(cursor, goal);
12578 });
12579 });
12580 }
12581
12582 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
12583 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12584 let text_layout_details = &self.text_layout_details(window);
12585 self.change_selections(Default::default(), window, cx, |s| {
12586 s.move_heads_with(|map, head, goal| {
12587 movement::up(map, head, goal, false, text_layout_details)
12588 })
12589 })
12590 }
12591
12592 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
12593 self.take_rename(true, window, cx);
12594
12595 if self.mode.is_single_line() {
12596 cx.propagate();
12597 return;
12598 }
12599
12600 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12601
12602 let text_layout_details = &self.text_layout_details(window);
12603 let selection_count = self.selections.count();
12604 let first_selection = self.selections.first_anchor();
12605
12606 self.change_selections(Default::default(), window, cx, |s| {
12607 s.move_with(|map, selection| {
12608 if !selection.is_empty() {
12609 selection.goal = SelectionGoal::None;
12610 }
12611 let (cursor, goal) = movement::down(
12612 map,
12613 selection.end,
12614 selection.goal,
12615 false,
12616 text_layout_details,
12617 );
12618 selection.collapse_to(cursor, goal);
12619 });
12620 });
12621
12622 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12623 {
12624 cx.propagate();
12625 }
12626 }
12627
12628 pub fn select_page_down(
12629 &mut self,
12630 _: &SelectPageDown,
12631 window: &mut Window,
12632 cx: &mut Context<Self>,
12633 ) {
12634 let Some(row_count) = self.visible_row_count() else {
12635 return;
12636 };
12637
12638 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12639
12640 let text_layout_details = &self.text_layout_details(window);
12641
12642 self.change_selections(Default::default(), window, cx, |s| {
12643 s.move_heads_with(|map, head, goal| {
12644 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
12645 })
12646 })
12647 }
12648
12649 pub fn move_page_down(
12650 &mut self,
12651 action: &MovePageDown,
12652 window: &mut Window,
12653 cx: &mut Context<Self>,
12654 ) {
12655 if self.take_rename(true, window, cx).is_some() {
12656 return;
12657 }
12658
12659 if self
12660 .context_menu
12661 .borrow_mut()
12662 .as_mut()
12663 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
12664 .unwrap_or(false)
12665 {
12666 return;
12667 }
12668
12669 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12670 cx.propagate();
12671 return;
12672 }
12673
12674 let Some(row_count) = self.visible_row_count() else {
12675 return;
12676 };
12677
12678 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12679
12680 let effects = if action.center_cursor {
12681 SelectionEffects::scroll(Autoscroll::center())
12682 } else {
12683 SelectionEffects::default()
12684 };
12685
12686 let text_layout_details = &self.text_layout_details(window);
12687 self.change_selections(effects, window, cx, |s| {
12688 s.move_with(|map, selection| {
12689 if !selection.is_empty() {
12690 selection.goal = SelectionGoal::None;
12691 }
12692 let (cursor, goal) = movement::down_by_rows(
12693 map,
12694 selection.end,
12695 row_count,
12696 selection.goal,
12697 false,
12698 text_layout_details,
12699 );
12700 selection.collapse_to(cursor, goal);
12701 });
12702 });
12703 }
12704
12705 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
12706 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12707 let text_layout_details = &self.text_layout_details(window);
12708 self.change_selections(Default::default(), window, cx, |s| {
12709 s.move_heads_with(|map, head, goal| {
12710 movement::down(map, head, goal, false, text_layout_details)
12711 })
12712 });
12713 }
12714
12715 pub fn context_menu_first(
12716 &mut self,
12717 _: &ContextMenuFirst,
12718 window: &mut Window,
12719 cx: &mut Context<Self>,
12720 ) {
12721 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12722 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
12723 }
12724 }
12725
12726 pub fn context_menu_prev(
12727 &mut self,
12728 _: &ContextMenuPrevious,
12729 window: &mut Window,
12730 cx: &mut Context<Self>,
12731 ) {
12732 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12733 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
12734 }
12735 }
12736
12737 pub fn context_menu_next(
12738 &mut self,
12739 _: &ContextMenuNext,
12740 window: &mut Window,
12741 cx: &mut Context<Self>,
12742 ) {
12743 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12744 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
12745 }
12746 }
12747
12748 pub fn context_menu_last(
12749 &mut self,
12750 _: &ContextMenuLast,
12751 window: &mut Window,
12752 cx: &mut Context<Self>,
12753 ) {
12754 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12755 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
12756 }
12757 }
12758
12759 pub fn signature_help_prev(
12760 &mut self,
12761 _: &SignatureHelpPrevious,
12762 _: &mut Window,
12763 cx: &mut Context<Self>,
12764 ) {
12765 if let Some(popover) = self.signature_help_state.popover_mut() {
12766 if popover.current_signature == 0 {
12767 popover.current_signature = popover.signatures.len() - 1;
12768 } else {
12769 popover.current_signature -= 1;
12770 }
12771 cx.notify();
12772 }
12773 }
12774
12775 pub fn signature_help_next(
12776 &mut self,
12777 _: &SignatureHelpNext,
12778 _: &mut Window,
12779 cx: &mut Context<Self>,
12780 ) {
12781 if let Some(popover) = self.signature_help_state.popover_mut() {
12782 if popover.current_signature + 1 == popover.signatures.len() {
12783 popover.current_signature = 0;
12784 } else {
12785 popover.current_signature += 1;
12786 }
12787 cx.notify();
12788 }
12789 }
12790
12791 pub fn move_to_previous_word_start(
12792 &mut self,
12793 _: &MoveToPreviousWordStart,
12794 window: &mut Window,
12795 cx: &mut Context<Self>,
12796 ) {
12797 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12798 self.change_selections(Default::default(), window, cx, |s| {
12799 s.move_cursors_with(|map, head, _| {
12800 (
12801 movement::previous_word_start(map, head),
12802 SelectionGoal::None,
12803 )
12804 });
12805 })
12806 }
12807
12808 pub fn move_to_previous_subword_start(
12809 &mut self,
12810 _: &MoveToPreviousSubwordStart,
12811 window: &mut Window,
12812 cx: &mut Context<Self>,
12813 ) {
12814 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12815 self.change_selections(Default::default(), window, cx, |s| {
12816 s.move_cursors_with(|map, head, _| {
12817 (
12818 movement::previous_subword_start(map, head),
12819 SelectionGoal::None,
12820 )
12821 });
12822 })
12823 }
12824
12825 pub fn select_to_previous_word_start(
12826 &mut self,
12827 _: &SelectToPreviousWordStart,
12828 window: &mut Window,
12829 cx: &mut Context<Self>,
12830 ) {
12831 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12832 self.change_selections(Default::default(), window, cx, |s| {
12833 s.move_heads_with(|map, head, _| {
12834 (
12835 movement::previous_word_start(map, head),
12836 SelectionGoal::None,
12837 )
12838 });
12839 })
12840 }
12841
12842 pub fn select_to_previous_subword_start(
12843 &mut self,
12844 _: &SelectToPreviousSubwordStart,
12845 window: &mut Window,
12846 cx: &mut Context<Self>,
12847 ) {
12848 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12849 self.change_selections(Default::default(), window, cx, |s| {
12850 s.move_heads_with(|map, head, _| {
12851 (
12852 movement::previous_subword_start(map, head),
12853 SelectionGoal::None,
12854 )
12855 });
12856 })
12857 }
12858
12859 pub fn delete_to_previous_word_start(
12860 &mut self,
12861 action: &DeleteToPreviousWordStart,
12862 window: &mut Window,
12863 cx: &mut Context<Self>,
12864 ) {
12865 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12866 self.transact(window, cx, |this, window, cx| {
12867 this.select_autoclose_pair(window, cx);
12868 this.change_selections(Default::default(), window, cx, |s| {
12869 s.move_with(|map, selection| {
12870 if selection.is_empty() {
12871 let cursor = if action.ignore_newlines {
12872 movement::previous_word_start(map, selection.head())
12873 } else {
12874 movement::previous_word_start_or_newline(map, selection.head())
12875 };
12876 selection.set_head(cursor, SelectionGoal::None);
12877 }
12878 });
12879 });
12880 this.insert("", window, cx);
12881 });
12882 }
12883
12884 pub fn delete_to_previous_subword_start(
12885 &mut self,
12886 _: &DeleteToPreviousSubwordStart,
12887 window: &mut Window,
12888 cx: &mut Context<Self>,
12889 ) {
12890 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12891 self.transact(window, cx, |this, window, cx| {
12892 this.select_autoclose_pair(window, cx);
12893 this.change_selections(Default::default(), window, cx, |s| {
12894 s.move_with(|map, selection| {
12895 if selection.is_empty() {
12896 let cursor = movement::previous_subword_start(map, selection.head());
12897 selection.set_head(cursor, SelectionGoal::None);
12898 }
12899 });
12900 });
12901 this.insert("", window, cx);
12902 });
12903 }
12904
12905 pub fn move_to_next_word_end(
12906 &mut self,
12907 _: &MoveToNextWordEnd,
12908 window: &mut Window,
12909 cx: &mut Context<Self>,
12910 ) {
12911 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12912 self.change_selections(Default::default(), window, cx, |s| {
12913 s.move_cursors_with(|map, head, _| {
12914 (movement::next_word_end(map, head), SelectionGoal::None)
12915 });
12916 })
12917 }
12918
12919 pub fn move_to_next_subword_end(
12920 &mut self,
12921 _: &MoveToNextSubwordEnd,
12922 window: &mut Window,
12923 cx: &mut Context<Self>,
12924 ) {
12925 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12926 self.change_selections(Default::default(), window, cx, |s| {
12927 s.move_cursors_with(|map, head, _| {
12928 (movement::next_subword_end(map, head), SelectionGoal::None)
12929 });
12930 })
12931 }
12932
12933 pub fn select_to_next_word_end(
12934 &mut self,
12935 _: &SelectToNextWordEnd,
12936 window: &mut Window,
12937 cx: &mut Context<Self>,
12938 ) {
12939 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12940 self.change_selections(Default::default(), window, cx, |s| {
12941 s.move_heads_with(|map, head, _| {
12942 (movement::next_word_end(map, head), SelectionGoal::None)
12943 });
12944 })
12945 }
12946
12947 pub fn select_to_next_subword_end(
12948 &mut self,
12949 _: &SelectToNextSubwordEnd,
12950 window: &mut Window,
12951 cx: &mut Context<Self>,
12952 ) {
12953 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12954 self.change_selections(Default::default(), window, cx, |s| {
12955 s.move_heads_with(|map, head, _| {
12956 (movement::next_subword_end(map, head), SelectionGoal::None)
12957 });
12958 })
12959 }
12960
12961 pub fn delete_to_next_word_end(
12962 &mut self,
12963 action: &DeleteToNextWordEnd,
12964 window: &mut Window,
12965 cx: &mut Context<Self>,
12966 ) {
12967 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12968 self.transact(window, cx, |this, window, cx| {
12969 this.change_selections(Default::default(), window, cx, |s| {
12970 s.move_with(|map, selection| {
12971 if selection.is_empty() {
12972 let cursor = if action.ignore_newlines {
12973 movement::next_word_end(map, selection.head())
12974 } else {
12975 movement::next_word_end_or_newline(map, selection.head())
12976 };
12977 selection.set_head(cursor, SelectionGoal::None);
12978 }
12979 });
12980 });
12981 this.insert("", window, cx);
12982 });
12983 }
12984
12985 pub fn delete_to_next_subword_end(
12986 &mut self,
12987 _: &DeleteToNextSubwordEnd,
12988 window: &mut Window,
12989 cx: &mut Context<Self>,
12990 ) {
12991 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12992 self.transact(window, cx, |this, window, cx| {
12993 this.change_selections(Default::default(), window, cx, |s| {
12994 s.move_with(|map, selection| {
12995 if selection.is_empty() {
12996 let cursor = movement::next_subword_end(map, selection.head());
12997 selection.set_head(cursor, SelectionGoal::None);
12998 }
12999 });
13000 });
13001 this.insert("", window, cx);
13002 });
13003 }
13004
13005 pub fn move_to_beginning_of_line(
13006 &mut self,
13007 action: &MoveToBeginningOfLine,
13008 window: &mut Window,
13009 cx: &mut Context<Self>,
13010 ) {
13011 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13012 self.change_selections(Default::default(), window, cx, |s| {
13013 s.move_cursors_with(|map, head, _| {
13014 (
13015 movement::indented_line_beginning(
13016 map,
13017 head,
13018 action.stop_at_soft_wraps,
13019 action.stop_at_indent,
13020 ),
13021 SelectionGoal::None,
13022 )
13023 });
13024 })
13025 }
13026
13027 pub fn select_to_beginning_of_line(
13028 &mut self,
13029 action: &SelectToBeginningOfLine,
13030 window: &mut Window,
13031 cx: &mut Context<Self>,
13032 ) {
13033 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13034 self.change_selections(Default::default(), window, cx, |s| {
13035 s.move_heads_with(|map, head, _| {
13036 (
13037 movement::indented_line_beginning(
13038 map,
13039 head,
13040 action.stop_at_soft_wraps,
13041 action.stop_at_indent,
13042 ),
13043 SelectionGoal::None,
13044 )
13045 });
13046 });
13047 }
13048
13049 pub fn delete_to_beginning_of_line(
13050 &mut self,
13051 action: &DeleteToBeginningOfLine,
13052 window: &mut Window,
13053 cx: &mut Context<Self>,
13054 ) {
13055 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13056 self.transact(window, cx, |this, window, cx| {
13057 this.change_selections(Default::default(), window, cx, |s| {
13058 s.move_with(|_, selection| {
13059 selection.reversed = true;
13060 });
13061 });
13062
13063 this.select_to_beginning_of_line(
13064 &SelectToBeginningOfLine {
13065 stop_at_soft_wraps: false,
13066 stop_at_indent: action.stop_at_indent,
13067 },
13068 window,
13069 cx,
13070 );
13071 this.backspace(&Backspace, window, cx);
13072 });
13073 }
13074
13075 pub fn move_to_end_of_line(
13076 &mut self,
13077 action: &MoveToEndOfLine,
13078 window: &mut Window,
13079 cx: &mut Context<Self>,
13080 ) {
13081 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13082 self.change_selections(Default::default(), window, cx, |s| {
13083 s.move_cursors_with(|map, head, _| {
13084 (
13085 movement::line_end(map, head, action.stop_at_soft_wraps),
13086 SelectionGoal::None,
13087 )
13088 });
13089 })
13090 }
13091
13092 pub fn select_to_end_of_line(
13093 &mut self,
13094 action: &SelectToEndOfLine,
13095 window: &mut Window,
13096 cx: &mut Context<Self>,
13097 ) {
13098 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13099 self.change_selections(Default::default(), window, cx, |s| {
13100 s.move_heads_with(|map, head, _| {
13101 (
13102 movement::line_end(map, head, action.stop_at_soft_wraps),
13103 SelectionGoal::None,
13104 )
13105 });
13106 })
13107 }
13108
13109 pub fn delete_to_end_of_line(
13110 &mut self,
13111 _: &DeleteToEndOfLine,
13112 window: &mut Window,
13113 cx: &mut Context<Self>,
13114 ) {
13115 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13116 self.transact(window, cx, |this, window, cx| {
13117 this.select_to_end_of_line(
13118 &SelectToEndOfLine {
13119 stop_at_soft_wraps: false,
13120 },
13121 window,
13122 cx,
13123 );
13124 this.delete(&Delete, window, cx);
13125 });
13126 }
13127
13128 pub fn cut_to_end_of_line(
13129 &mut self,
13130 _: &CutToEndOfLine,
13131 window: &mut Window,
13132 cx: &mut Context<Self>,
13133 ) {
13134 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13135 self.transact(window, cx, |this, window, cx| {
13136 this.select_to_end_of_line(
13137 &SelectToEndOfLine {
13138 stop_at_soft_wraps: false,
13139 },
13140 window,
13141 cx,
13142 );
13143 this.cut(&Cut, window, cx);
13144 });
13145 }
13146
13147 pub fn move_to_start_of_paragraph(
13148 &mut self,
13149 _: &MoveToStartOfParagraph,
13150 window: &mut Window,
13151 cx: &mut Context<Self>,
13152 ) {
13153 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13154 cx.propagate();
13155 return;
13156 }
13157 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13158 self.change_selections(Default::default(), window, cx, |s| {
13159 s.move_with(|map, selection| {
13160 selection.collapse_to(
13161 movement::start_of_paragraph(map, selection.head(), 1),
13162 SelectionGoal::None,
13163 )
13164 });
13165 })
13166 }
13167
13168 pub fn move_to_end_of_paragraph(
13169 &mut self,
13170 _: &MoveToEndOfParagraph,
13171 window: &mut Window,
13172 cx: &mut Context<Self>,
13173 ) {
13174 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13175 cx.propagate();
13176 return;
13177 }
13178 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13179 self.change_selections(Default::default(), window, cx, |s| {
13180 s.move_with(|map, selection| {
13181 selection.collapse_to(
13182 movement::end_of_paragraph(map, selection.head(), 1),
13183 SelectionGoal::None,
13184 )
13185 });
13186 })
13187 }
13188
13189 pub fn select_to_start_of_paragraph(
13190 &mut self,
13191 _: &SelectToStartOfParagraph,
13192 window: &mut Window,
13193 cx: &mut Context<Self>,
13194 ) {
13195 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13196 cx.propagate();
13197 return;
13198 }
13199 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13200 self.change_selections(Default::default(), window, cx, |s| {
13201 s.move_heads_with(|map, head, _| {
13202 (
13203 movement::start_of_paragraph(map, head, 1),
13204 SelectionGoal::None,
13205 )
13206 });
13207 })
13208 }
13209
13210 pub fn select_to_end_of_paragraph(
13211 &mut self,
13212 _: &SelectToEndOfParagraph,
13213 window: &mut Window,
13214 cx: &mut Context<Self>,
13215 ) {
13216 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13217 cx.propagate();
13218 return;
13219 }
13220 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13221 self.change_selections(Default::default(), window, cx, |s| {
13222 s.move_heads_with(|map, head, _| {
13223 (
13224 movement::end_of_paragraph(map, head, 1),
13225 SelectionGoal::None,
13226 )
13227 });
13228 })
13229 }
13230
13231 pub fn move_to_start_of_excerpt(
13232 &mut self,
13233 _: &MoveToStartOfExcerpt,
13234 window: &mut Window,
13235 cx: &mut Context<Self>,
13236 ) {
13237 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13238 cx.propagate();
13239 return;
13240 }
13241 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13242 self.change_selections(Default::default(), window, cx, |s| {
13243 s.move_with(|map, selection| {
13244 selection.collapse_to(
13245 movement::start_of_excerpt(
13246 map,
13247 selection.head(),
13248 workspace::searchable::Direction::Prev,
13249 ),
13250 SelectionGoal::None,
13251 )
13252 });
13253 })
13254 }
13255
13256 pub fn move_to_start_of_next_excerpt(
13257 &mut self,
13258 _: &MoveToStartOfNextExcerpt,
13259 window: &mut Window,
13260 cx: &mut Context<Self>,
13261 ) {
13262 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13263 cx.propagate();
13264 return;
13265 }
13266
13267 self.change_selections(Default::default(), window, cx, |s| {
13268 s.move_with(|map, selection| {
13269 selection.collapse_to(
13270 movement::start_of_excerpt(
13271 map,
13272 selection.head(),
13273 workspace::searchable::Direction::Next,
13274 ),
13275 SelectionGoal::None,
13276 )
13277 });
13278 })
13279 }
13280
13281 pub fn move_to_end_of_excerpt(
13282 &mut self,
13283 _: &MoveToEndOfExcerpt,
13284 window: &mut Window,
13285 cx: &mut Context<Self>,
13286 ) {
13287 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13288 cx.propagate();
13289 return;
13290 }
13291 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13292 self.change_selections(Default::default(), window, cx, |s| {
13293 s.move_with(|map, selection| {
13294 selection.collapse_to(
13295 movement::end_of_excerpt(
13296 map,
13297 selection.head(),
13298 workspace::searchable::Direction::Next,
13299 ),
13300 SelectionGoal::None,
13301 )
13302 });
13303 })
13304 }
13305
13306 pub fn move_to_end_of_previous_excerpt(
13307 &mut self,
13308 _: &MoveToEndOfPreviousExcerpt,
13309 window: &mut Window,
13310 cx: &mut Context<Self>,
13311 ) {
13312 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13313 cx.propagate();
13314 return;
13315 }
13316 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13317 self.change_selections(Default::default(), window, cx, |s| {
13318 s.move_with(|map, selection| {
13319 selection.collapse_to(
13320 movement::end_of_excerpt(
13321 map,
13322 selection.head(),
13323 workspace::searchable::Direction::Prev,
13324 ),
13325 SelectionGoal::None,
13326 )
13327 });
13328 })
13329 }
13330
13331 pub fn select_to_start_of_excerpt(
13332 &mut self,
13333 _: &SelectToStartOfExcerpt,
13334 window: &mut Window,
13335 cx: &mut Context<Self>,
13336 ) {
13337 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13338 cx.propagate();
13339 return;
13340 }
13341 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13342 self.change_selections(Default::default(), window, cx, |s| {
13343 s.move_heads_with(|map, head, _| {
13344 (
13345 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13346 SelectionGoal::None,
13347 )
13348 });
13349 })
13350 }
13351
13352 pub fn select_to_start_of_next_excerpt(
13353 &mut self,
13354 _: &SelectToStartOfNextExcerpt,
13355 window: &mut Window,
13356 cx: &mut Context<Self>,
13357 ) {
13358 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13359 cx.propagate();
13360 return;
13361 }
13362 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13363 self.change_selections(Default::default(), window, cx, |s| {
13364 s.move_heads_with(|map, head, _| {
13365 (
13366 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
13367 SelectionGoal::None,
13368 )
13369 });
13370 })
13371 }
13372
13373 pub fn select_to_end_of_excerpt(
13374 &mut self,
13375 _: &SelectToEndOfExcerpt,
13376 window: &mut Window,
13377 cx: &mut Context<Self>,
13378 ) {
13379 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13380 cx.propagate();
13381 return;
13382 }
13383 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13384 self.change_selections(Default::default(), window, cx, |s| {
13385 s.move_heads_with(|map, head, _| {
13386 (
13387 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
13388 SelectionGoal::None,
13389 )
13390 });
13391 })
13392 }
13393
13394 pub fn select_to_end_of_previous_excerpt(
13395 &mut self,
13396 _: &SelectToEndOfPreviousExcerpt,
13397 window: &mut Window,
13398 cx: &mut Context<Self>,
13399 ) {
13400 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13401 cx.propagate();
13402 return;
13403 }
13404 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13405 self.change_selections(Default::default(), window, cx, |s| {
13406 s.move_heads_with(|map, head, _| {
13407 (
13408 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13409 SelectionGoal::None,
13410 )
13411 });
13412 })
13413 }
13414
13415 pub fn move_to_beginning(
13416 &mut self,
13417 _: &MoveToBeginning,
13418 window: &mut Window,
13419 cx: &mut Context<Self>,
13420 ) {
13421 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13422 cx.propagate();
13423 return;
13424 }
13425 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13426 self.change_selections(Default::default(), window, cx, |s| {
13427 s.select_ranges(vec![0..0]);
13428 });
13429 }
13430
13431 pub fn select_to_beginning(
13432 &mut self,
13433 _: &SelectToBeginning,
13434 window: &mut Window,
13435 cx: &mut Context<Self>,
13436 ) {
13437 let mut selection = self.selections.last::<Point>(cx);
13438 selection.set_head(Point::zero(), SelectionGoal::None);
13439 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13440 self.change_selections(Default::default(), window, cx, |s| {
13441 s.select(vec![selection]);
13442 });
13443 }
13444
13445 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
13446 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13447 cx.propagate();
13448 return;
13449 }
13450 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13451 let cursor = self.buffer.read(cx).read(cx).len();
13452 self.change_selections(Default::default(), window, cx, |s| {
13453 s.select_ranges(vec![cursor..cursor])
13454 });
13455 }
13456
13457 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
13458 self.nav_history = nav_history;
13459 }
13460
13461 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
13462 self.nav_history.as_ref()
13463 }
13464
13465 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
13466 self.push_to_nav_history(
13467 self.selections.newest_anchor().head(),
13468 None,
13469 false,
13470 true,
13471 cx,
13472 );
13473 }
13474
13475 fn push_to_nav_history(
13476 &mut self,
13477 cursor_anchor: Anchor,
13478 new_position: Option<Point>,
13479 is_deactivate: bool,
13480 always: bool,
13481 cx: &mut Context<Self>,
13482 ) {
13483 if let Some(nav_history) = self.nav_history.as_mut() {
13484 let buffer = self.buffer.read(cx).read(cx);
13485 let cursor_position = cursor_anchor.to_point(&buffer);
13486 let scroll_state = self.scroll_manager.anchor();
13487 let scroll_top_row = scroll_state.top_row(&buffer);
13488 drop(buffer);
13489
13490 if let Some(new_position) = new_position {
13491 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
13492 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
13493 return;
13494 }
13495 }
13496
13497 nav_history.push(
13498 Some(NavigationData {
13499 cursor_anchor,
13500 cursor_position,
13501 scroll_anchor: scroll_state,
13502 scroll_top_row,
13503 }),
13504 cx,
13505 );
13506 cx.emit(EditorEvent::PushedToNavHistory {
13507 anchor: cursor_anchor,
13508 is_deactivate,
13509 })
13510 }
13511 }
13512
13513 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
13514 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13515 let buffer = self.buffer.read(cx).snapshot(cx);
13516 let mut selection = self.selections.first::<usize>(cx);
13517 selection.set_head(buffer.len(), SelectionGoal::None);
13518 self.change_selections(Default::default(), window, cx, |s| {
13519 s.select(vec![selection]);
13520 });
13521 }
13522
13523 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
13524 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13525 let end = self.buffer.read(cx).read(cx).len();
13526 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13527 s.select_ranges(vec![0..end]);
13528 });
13529 }
13530
13531 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
13532 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13533 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13534 let mut selections = self.selections.all::<Point>(cx);
13535 let max_point = display_map.buffer_snapshot.max_point();
13536 for selection in &mut selections {
13537 let rows = selection.spanned_rows(true, &display_map);
13538 selection.start = Point::new(rows.start.0, 0);
13539 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
13540 selection.reversed = false;
13541 }
13542 self.change_selections(Default::default(), window, cx, |s| {
13543 s.select(selections);
13544 });
13545 }
13546
13547 pub fn split_selection_into_lines(
13548 &mut self,
13549 _: &SplitSelectionIntoLines,
13550 window: &mut Window,
13551 cx: &mut Context<Self>,
13552 ) {
13553 let selections = self
13554 .selections
13555 .all::<Point>(cx)
13556 .into_iter()
13557 .map(|selection| selection.start..selection.end)
13558 .collect::<Vec<_>>();
13559 self.unfold_ranges(&selections, true, true, cx);
13560
13561 let mut new_selection_ranges = Vec::new();
13562 {
13563 let buffer = self.buffer.read(cx).read(cx);
13564 for selection in selections {
13565 for row in selection.start.row..selection.end.row {
13566 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
13567 new_selection_ranges.push(cursor..cursor);
13568 }
13569
13570 let is_multiline_selection = selection.start.row != selection.end.row;
13571 // Don't insert last one if it's a multi-line selection ending at the start of a line,
13572 // so this action feels more ergonomic when paired with other selection operations
13573 let should_skip_last = is_multiline_selection && selection.end.column == 0;
13574 if !should_skip_last {
13575 new_selection_ranges.push(selection.end..selection.end);
13576 }
13577 }
13578 }
13579 self.change_selections(Default::default(), window, cx, |s| {
13580 s.select_ranges(new_selection_ranges);
13581 });
13582 }
13583
13584 pub fn add_selection_above(
13585 &mut self,
13586 _: &AddSelectionAbove,
13587 window: &mut Window,
13588 cx: &mut Context<Self>,
13589 ) {
13590 self.add_selection(true, window, cx);
13591 }
13592
13593 pub fn add_selection_below(
13594 &mut self,
13595 _: &AddSelectionBelow,
13596 window: &mut Window,
13597 cx: &mut Context<Self>,
13598 ) {
13599 self.add_selection(false, window, cx);
13600 }
13601
13602 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
13603 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13604
13605 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13606 let all_selections = self.selections.all::<Point>(cx);
13607 let text_layout_details = self.text_layout_details(window);
13608
13609 let (mut columnar_selections, new_selections_to_columnarize) = {
13610 if let Some(state) = self.add_selections_state.as_ref() {
13611 let columnar_selection_ids: HashSet<_> = state
13612 .groups
13613 .iter()
13614 .flat_map(|group| group.stack.iter())
13615 .copied()
13616 .collect();
13617
13618 all_selections
13619 .into_iter()
13620 .partition(|s| columnar_selection_ids.contains(&s.id))
13621 } else {
13622 (Vec::new(), all_selections)
13623 }
13624 };
13625
13626 let mut state = self
13627 .add_selections_state
13628 .take()
13629 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
13630
13631 for selection in new_selections_to_columnarize {
13632 let range = selection.display_range(&display_map).sorted();
13633 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
13634 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
13635 let positions = start_x.min(end_x)..start_x.max(end_x);
13636 let mut stack = Vec::new();
13637 for row in range.start.row().0..=range.end.row().0 {
13638 if let Some(selection) = self.selections.build_columnar_selection(
13639 &display_map,
13640 DisplayRow(row),
13641 &positions,
13642 selection.reversed,
13643 &text_layout_details,
13644 ) {
13645 stack.push(selection.id);
13646 columnar_selections.push(selection);
13647 }
13648 }
13649 if !stack.is_empty() {
13650 if above {
13651 stack.reverse();
13652 }
13653 state.groups.push(AddSelectionsGroup { above, stack });
13654 }
13655 }
13656
13657 let mut final_selections = Vec::new();
13658 let end_row = if above {
13659 DisplayRow(0)
13660 } else {
13661 display_map.max_point().row()
13662 };
13663
13664 let mut last_added_item_per_group = HashMap::default();
13665 for group in state.groups.iter_mut() {
13666 if let Some(last_id) = group.stack.last() {
13667 last_added_item_per_group.insert(*last_id, group);
13668 }
13669 }
13670
13671 for selection in columnar_selections {
13672 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
13673 if above == group.above {
13674 let range = selection.display_range(&display_map).sorted();
13675 debug_assert_eq!(range.start.row(), range.end.row());
13676 let mut row = range.start.row();
13677 let positions =
13678 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
13679 px(start)..px(end)
13680 } else {
13681 let start_x =
13682 display_map.x_for_display_point(range.start, &text_layout_details);
13683 let end_x =
13684 display_map.x_for_display_point(range.end, &text_layout_details);
13685 start_x.min(end_x)..start_x.max(end_x)
13686 };
13687
13688 let mut maybe_new_selection = None;
13689 while row != end_row {
13690 if above {
13691 row.0 -= 1;
13692 } else {
13693 row.0 += 1;
13694 }
13695 if let Some(new_selection) = self.selections.build_columnar_selection(
13696 &display_map,
13697 row,
13698 &positions,
13699 selection.reversed,
13700 &text_layout_details,
13701 ) {
13702 maybe_new_selection = Some(new_selection);
13703 break;
13704 }
13705 }
13706
13707 if let Some(new_selection) = maybe_new_selection {
13708 group.stack.push(new_selection.id);
13709 if above {
13710 final_selections.push(new_selection);
13711 final_selections.push(selection);
13712 } else {
13713 final_selections.push(selection);
13714 final_selections.push(new_selection);
13715 }
13716 } else {
13717 final_selections.push(selection);
13718 }
13719 } else {
13720 group.stack.pop();
13721 }
13722 } else {
13723 final_selections.push(selection);
13724 }
13725 }
13726
13727 self.change_selections(Default::default(), window, cx, |s| {
13728 s.select(final_selections);
13729 });
13730
13731 let final_selection_ids: HashSet<_> = self
13732 .selections
13733 .all::<Point>(cx)
13734 .iter()
13735 .map(|s| s.id)
13736 .collect();
13737 state.groups.retain_mut(|group| {
13738 // selections might get merged above so we remove invalid items from stacks
13739 group.stack.retain(|id| final_selection_ids.contains(id));
13740
13741 // single selection in stack can be treated as initial state
13742 group.stack.len() > 1
13743 });
13744
13745 if !state.groups.is_empty() {
13746 self.add_selections_state = Some(state);
13747 }
13748 }
13749
13750 fn select_match_ranges(
13751 &mut self,
13752 range: Range<usize>,
13753 reversed: bool,
13754 replace_newest: bool,
13755 auto_scroll: Option<Autoscroll>,
13756 window: &mut Window,
13757 cx: &mut Context<Editor>,
13758 ) {
13759 self.unfold_ranges(
13760 std::slice::from_ref(&range),
13761 false,
13762 auto_scroll.is_some(),
13763 cx,
13764 );
13765 let effects = if let Some(scroll) = auto_scroll {
13766 SelectionEffects::scroll(scroll)
13767 } else {
13768 SelectionEffects::no_scroll()
13769 };
13770 self.change_selections(effects, window, cx, |s| {
13771 if replace_newest {
13772 s.delete(s.newest_anchor().id);
13773 }
13774 if reversed {
13775 s.insert_range(range.end..range.start);
13776 } else {
13777 s.insert_range(range);
13778 }
13779 });
13780 }
13781
13782 pub fn select_next_match_internal(
13783 &mut self,
13784 display_map: &DisplaySnapshot,
13785 replace_newest: bool,
13786 autoscroll: Option<Autoscroll>,
13787 window: &mut Window,
13788 cx: &mut Context<Self>,
13789 ) -> Result<()> {
13790 let buffer = &display_map.buffer_snapshot;
13791 let mut selections = self.selections.all::<usize>(cx);
13792 if let Some(mut select_next_state) = self.select_next_state.take() {
13793 let query = &select_next_state.query;
13794 if !select_next_state.done {
13795 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13796 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13797 let mut next_selected_range = None;
13798
13799 let bytes_after_last_selection =
13800 buffer.bytes_in_range(last_selection.end..buffer.len());
13801 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
13802 let query_matches = query
13803 .stream_find_iter(bytes_after_last_selection)
13804 .map(|result| (last_selection.end, result))
13805 .chain(
13806 query
13807 .stream_find_iter(bytes_before_first_selection)
13808 .map(|result| (0, result)),
13809 );
13810
13811 for (start_offset, query_match) in query_matches {
13812 let query_match = query_match.unwrap(); // can only fail due to I/O
13813 let offset_range =
13814 start_offset + query_match.start()..start_offset + query_match.end();
13815
13816 if !select_next_state.wordwise
13817 || (!buffer.is_inside_word(offset_range.start, false)
13818 && !buffer.is_inside_word(offset_range.end, false))
13819 {
13820 // TODO: This is n^2, because we might check all the selections
13821 if !selections
13822 .iter()
13823 .any(|selection| selection.range().overlaps(&offset_range))
13824 {
13825 next_selected_range = Some(offset_range);
13826 break;
13827 }
13828 }
13829 }
13830
13831 if let Some(next_selected_range) = next_selected_range {
13832 self.select_match_ranges(
13833 next_selected_range,
13834 last_selection.reversed,
13835 replace_newest,
13836 autoscroll,
13837 window,
13838 cx,
13839 );
13840 } else {
13841 select_next_state.done = true;
13842 }
13843 }
13844
13845 self.select_next_state = Some(select_next_state);
13846 } else {
13847 let mut only_carets = true;
13848 let mut same_text_selected = true;
13849 let mut selected_text = None;
13850
13851 let mut selections_iter = selections.iter().peekable();
13852 while let Some(selection) = selections_iter.next() {
13853 if selection.start != selection.end {
13854 only_carets = false;
13855 }
13856
13857 if same_text_selected {
13858 if selected_text.is_none() {
13859 selected_text =
13860 Some(buffer.text_for_range(selection.range()).collect::<String>());
13861 }
13862
13863 if let Some(next_selection) = selections_iter.peek() {
13864 if next_selection.range().len() == selection.range().len() {
13865 let next_selected_text = buffer
13866 .text_for_range(next_selection.range())
13867 .collect::<String>();
13868 if Some(next_selected_text) != selected_text {
13869 same_text_selected = false;
13870 selected_text = None;
13871 }
13872 } else {
13873 same_text_selected = false;
13874 selected_text = None;
13875 }
13876 }
13877 }
13878 }
13879
13880 if only_carets {
13881 for selection in &mut selections {
13882 let (word_range, _) = buffer.surrounding_word(selection.start, false);
13883 selection.start = word_range.start;
13884 selection.end = word_range.end;
13885 selection.goal = SelectionGoal::None;
13886 selection.reversed = false;
13887 self.select_match_ranges(
13888 selection.start..selection.end,
13889 selection.reversed,
13890 replace_newest,
13891 autoscroll,
13892 window,
13893 cx,
13894 );
13895 }
13896
13897 if selections.len() == 1 {
13898 let selection = selections
13899 .last()
13900 .expect("ensured that there's only one selection");
13901 let query = buffer
13902 .text_for_range(selection.start..selection.end)
13903 .collect::<String>();
13904 let is_empty = query.is_empty();
13905 let select_state = SelectNextState {
13906 query: AhoCorasick::new(&[query])?,
13907 wordwise: true,
13908 done: is_empty,
13909 };
13910 self.select_next_state = Some(select_state);
13911 } else {
13912 self.select_next_state = None;
13913 }
13914 } else if let Some(selected_text) = selected_text {
13915 self.select_next_state = Some(SelectNextState {
13916 query: AhoCorasick::new(&[selected_text])?,
13917 wordwise: false,
13918 done: false,
13919 });
13920 self.select_next_match_internal(
13921 display_map,
13922 replace_newest,
13923 autoscroll,
13924 window,
13925 cx,
13926 )?;
13927 }
13928 }
13929 Ok(())
13930 }
13931
13932 pub fn select_all_matches(
13933 &mut self,
13934 _action: &SelectAllMatches,
13935 window: &mut Window,
13936 cx: &mut Context<Self>,
13937 ) -> Result<()> {
13938 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13939
13940 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13941
13942 self.select_next_match_internal(&display_map, false, None, window, cx)?;
13943 let Some(select_next_state) = self.select_next_state.as_mut() else {
13944 return Ok(());
13945 };
13946 if select_next_state.done {
13947 return Ok(());
13948 }
13949
13950 let mut new_selections = Vec::new();
13951
13952 let reversed = self.selections.oldest::<usize>(cx).reversed;
13953 let buffer = &display_map.buffer_snapshot;
13954 let query_matches = select_next_state
13955 .query
13956 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
13957
13958 for query_match in query_matches.into_iter() {
13959 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
13960 let offset_range = if reversed {
13961 query_match.end()..query_match.start()
13962 } else {
13963 query_match.start()..query_match.end()
13964 };
13965
13966 if !select_next_state.wordwise
13967 || (!buffer.is_inside_word(offset_range.start, false)
13968 && !buffer.is_inside_word(offset_range.end, false))
13969 {
13970 new_selections.push(offset_range.start..offset_range.end);
13971 }
13972 }
13973
13974 select_next_state.done = true;
13975
13976 if new_selections.is_empty() {
13977 log::error!("bug: new_selections is empty in select_all_matches");
13978 return Ok(());
13979 }
13980
13981 self.unfold_ranges(&new_selections.clone(), false, false, cx);
13982 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
13983 selections.select_ranges(new_selections)
13984 });
13985
13986 Ok(())
13987 }
13988
13989 pub fn select_next(
13990 &mut self,
13991 action: &SelectNext,
13992 window: &mut Window,
13993 cx: &mut Context<Self>,
13994 ) -> Result<()> {
13995 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13996 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13997 self.select_next_match_internal(
13998 &display_map,
13999 action.replace_newest,
14000 Some(Autoscroll::newest()),
14001 window,
14002 cx,
14003 )?;
14004 Ok(())
14005 }
14006
14007 pub fn select_previous(
14008 &mut self,
14009 action: &SelectPrevious,
14010 window: &mut Window,
14011 cx: &mut Context<Self>,
14012 ) -> Result<()> {
14013 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14014 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14015 let buffer = &display_map.buffer_snapshot;
14016 let mut selections = self.selections.all::<usize>(cx);
14017 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14018 let query = &select_prev_state.query;
14019 if !select_prev_state.done {
14020 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14021 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14022 let mut next_selected_range = None;
14023 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14024 let bytes_before_last_selection =
14025 buffer.reversed_bytes_in_range(0..last_selection.start);
14026 let bytes_after_first_selection =
14027 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14028 let query_matches = query
14029 .stream_find_iter(bytes_before_last_selection)
14030 .map(|result| (last_selection.start, result))
14031 .chain(
14032 query
14033 .stream_find_iter(bytes_after_first_selection)
14034 .map(|result| (buffer.len(), result)),
14035 );
14036 for (end_offset, query_match) in query_matches {
14037 let query_match = query_match.unwrap(); // can only fail due to I/O
14038 let offset_range =
14039 end_offset - query_match.end()..end_offset - query_match.start();
14040
14041 if !select_prev_state.wordwise
14042 || (!buffer.is_inside_word(offset_range.start, false)
14043 && !buffer.is_inside_word(offset_range.end, false))
14044 {
14045 next_selected_range = Some(offset_range);
14046 break;
14047 }
14048 }
14049
14050 if let Some(next_selected_range) = next_selected_range {
14051 self.select_match_ranges(
14052 next_selected_range,
14053 last_selection.reversed,
14054 action.replace_newest,
14055 Some(Autoscroll::newest()),
14056 window,
14057 cx,
14058 );
14059 } else {
14060 select_prev_state.done = true;
14061 }
14062 }
14063
14064 self.select_prev_state = Some(select_prev_state);
14065 } else {
14066 let mut only_carets = true;
14067 let mut same_text_selected = true;
14068 let mut selected_text = None;
14069
14070 let mut selections_iter = selections.iter().peekable();
14071 while let Some(selection) = selections_iter.next() {
14072 if selection.start != selection.end {
14073 only_carets = false;
14074 }
14075
14076 if same_text_selected {
14077 if selected_text.is_none() {
14078 selected_text =
14079 Some(buffer.text_for_range(selection.range()).collect::<String>());
14080 }
14081
14082 if let Some(next_selection) = selections_iter.peek() {
14083 if next_selection.range().len() == selection.range().len() {
14084 let next_selected_text = buffer
14085 .text_for_range(next_selection.range())
14086 .collect::<String>();
14087 if Some(next_selected_text) != selected_text {
14088 same_text_selected = false;
14089 selected_text = None;
14090 }
14091 } else {
14092 same_text_selected = false;
14093 selected_text = None;
14094 }
14095 }
14096 }
14097 }
14098
14099 if only_carets {
14100 for selection in &mut selections {
14101 let (word_range, _) = buffer.surrounding_word(selection.start, false);
14102 selection.start = word_range.start;
14103 selection.end = word_range.end;
14104 selection.goal = SelectionGoal::None;
14105 selection.reversed = false;
14106 self.select_match_ranges(
14107 selection.start..selection.end,
14108 selection.reversed,
14109 action.replace_newest,
14110 Some(Autoscroll::newest()),
14111 window,
14112 cx,
14113 );
14114 }
14115 if selections.len() == 1 {
14116 let selection = selections
14117 .last()
14118 .expect("ensured that there's only one selection");
14119 let query = buffer
14120 .text_for_range(selection.start..selection.end)
14121 .collect::<String>();
14122 let is_empty = query.is_empty();
14123 let select_state = SelectNextState {
14124 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
14125 wordwise: true,
14126 done: is_empty,
14127 };
14128 self.select_prev_state = Some(select_state);
14129 } else {
14130 self.select_prev_state = None;
14131 }
14132 } else if let Some(selected_text) = selected_text {
14133 self.select_prev_state = Some(SelectNextState {
14134 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
14135 wordwise: false,
14136 done: false,
14137 });
14138 self.select_previous(action, window, cx)?;
14139 }
14140 }
14141 Ok(())
14142 }
14143
14144 pub fn find_next_match(
14145 &mut self,
14146 _: &FindNextMatch,
14147 window: &mut Window,
14148 cx: &mut Context<Self>,
14149 ) -> Result<()> {
14150 let selections = self.selections.disjoint_anchors();
14151 match selections.first() {
14152 Some(first) if selections.len() >= 2 => {
14153 self.change_selections(Default::default(), window, cx, |s| {
14154 s.select_ranges([first.range()]);
14155 });
14156 }
14157 _ => self.select_next(
14158 &SelectNext {
14159 replace_newest: true,
14160 },
14161 window,
14162 cx,
14163 )?,
14164 }
14165 Ok(())
14166 }
14167
14168 pub fn find_previous_match(
14169 &mut self,
14170 _: &FindPreviousMatch,
14171 window: &mut Window,
14172 cx: &mut Context<Self>,
14173 ) -> Result<()> {
14174 let selections = self.selections.disjoint_anchors();
14175 match selections.last() {
14176 Some(last) if selections.len() >= 2 => {
14177 self.change_selections(Default::default(), window, cx, |s| {
14178 s.select_ranges([last.range()]);
14179 });
14180 }
14181 _ => self.select_previous(
14182 &SelectPrevious {
14183 replace_newest: true,
14184 },
14185 window,
14186 cx,
14187 )?,
14188 }
14189 Ok(())
14190 }
14191
14192 pub fn toggle_comments(
14193 &mut self,
14194 action: &ToggleComments,
14195 window: &mut Window,
14196 cx: &mut Context<Self>,
14197 ) {
14198 if self.read_only(cx) {
14199 return;
14200 }
14201 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14202 let text_layout_details = &self.text_layout_details(window);
14203 self.transact(window, cx, |this, window, cx| {
14204 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
14205 let mut edits = Vec::new();
14206 let mut selection_edit_ranges = Vec::new();
14207 let mut last_toggled_row = None;
14208 let snapshot = this.buffer.read(cx).read(cx);
14209 let empty_str: Arc<str> = Arc::default();
14210 let mut suffixes_inserted = Vec::new();
14211 let ignore_indent = action.ignore_indent;
14212
14213 fn comment_prefix_range(
14214 snapshot: &MultiBufferSnapshot,
14215 row: MultiBufferRow,
14216 comment_prefix: &str,
14217 comment_prefix_whitespace: &str,
14218 ignore_indent: bool,
14219 ) -> Range<Point> {
14220 let indent_size = if ignore_indent {
14221 0
14222 } else {
14223 snapshot.indent_size_for_line(row).len
14224 };
14225
14226 let start = Point::new(row.0, indent_size);
14227
14228 let mut line_bytes = snapshot
14229 .bytes_in_range(start..snapshot.max_point())
14230 .flatten()
14231 .copied();
14232
14233 // If this line currently begins with the line comment prefix, then record
14234 // the range containing the prefix.
14235 if line_bytes
14236 .by_ref()
14237 .take(comment_prefix.len())
14238 .eq(comment_prefix.bytes())
14239 {
14240 // Include any whitespace that matches the comment prefix.
14241 let matching_whitespace_len = line_bytes
14242 .zip(comment_prefix_whitespace.bytes())
14243 .take_while(|(a, b)| a == b)
14244 .count() as u32;
14245 let end = Point::new(
14246 start.row,
14247 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14248 );
14249 start..end
14250 } else {
14251 start..start
14252 }
14253 }
14254
14255 fn comment_suffix_range(
14256 snapshot: &MultiBufferSnapshot,
14257 row: MultiBufferRow,
14258 comment_suffix: &str,
14259 comment_suffix_has_leading_space: bool,
14260 ) -> Range<Point> {
14261 let end = Point::new(row.0, snapshot.line_len(row));
14262 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14263
14264 let mut line_end_bytes = snapshot
14265 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14266 .flatten()
14267 .copied();
14268
14269 let leading_space_len = if suffix_start_column > 0
14270 && line_end_bytes.next() == Some(b' ')
14271 && comment_suffix_has_leading_space
14272 {
14273 1
14274 } else {
14275 0
14276 };
14277
14278 // If this line currently begins with the line comment prefix, then record
14279 // the range containing the prefix.
14280 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
14281 let start = Point::new(end.row, suffix_start_column - leading_space_len);
14282 start..end
14283 } else {
14284 end..end
14285 }
14286 }
14287
14288 // TODO: Handle selections that cross excerpts
14289 for selection in &mut selections {
14290 let start_column = snapshot
14291 .indent_size_for_line(MultiBufferRow(selection.start.row))
14292 .len;
14293 let language = if let Some(language) =
14294 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
14295 {
14296 language
14297 } else {
14298 continue;
14299 };
14300
14301 selection_edit_ranges.clear();
14302
14303 // If multiple selections contain a given row, avoid processing that
14304 // row more than once.
14305 let mut start_row = MultiBufferRow(selection.start.row);
14306 if last_toggled_row == Some(start_row) {
14307 start_row = start_row.next_row();
14308 }
14309 let end_row =
14310 if selection.end.row > selection.start.row && selection.end.column == 0 {
14311 MultiBufferRow(selection.end.row - 1)
14312 } else {
14313 MultiBufferRow(selection.end.row)
14314 };
14315 last_toggled_row = Some(end_row);
14316
14317 if start_row > end_row {
14318 continue;
14319 }
14320
14321 // If the language has line comments, toggle those.
14322 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
14323
14324 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
14325 if ignore_indent {
14326 full_comment_prefixes = full_comment_prefixes
14327 .into_iter()
14328 .map(|s| Arc::from(s.trim_end()))
14329 .collect();
14330 }
14331
14332 if !full_comment_prefixes.is_empty() {
14333 let first_prefix = full_comment_prefixes
14334 .first()
14335 .expect("prefixes is non-empty");
14336 let prefix_trimmed_lengths = full_comment_prefixes
14337 .iter()
14338 .map(|p| p.trim_end_matches(' ').len())
14339 .collect::<SmallVec<[usize; 4]>>();
14340
14341 let mut all_selection_lines_are_comments = true;
14342
14343 for row in start_row.0..=end_row.0 {
14344 let row = MultiBufferRow(row);
14345 if start_row < end_row && snapshot.is_line_blank(row) {
14346 continue;
14347 }
14348
14349 let prefix_range = full_comment_prefixes
14350 .iter()
14351 .zip(prefix_trimmed_lengths.iter().copied())
14352 .map(|(prefix, trimmed_prefix_len)| {
14353 comment_prefix_range(
14354 snapshot.deref(),
14355 row,
14356 &prefix[..trimmed_prefix_len],
14357 &prefix[trimmed_prefix_len..],
14358 ignore_indent,
14359 )
14360 })
14361 .max_by_key(|range| range.end.column - range.start.column)
14362 .expect("prefixes is non-empty");
14363
14364 if prefix_range.is_empty() {
14365 all_selection_lines_are_comments = false;
14366 }
14367
14368 selection_edit_ranges.push(prefix_range);
14369 }
14370
14371 if all_selection_lines_are_comments {
14372 edits.extend(
14373 selection_edit_ranges
14374 .iter()
14375 .cloned()
14376 .map(|range| (range, empty_str.clone())),
14377 );
14378 } else {
14379 let min_column = selection_edit_ranges
14380 .iter()
14381 .map(|range| range.start.column)
14382 .min()
14383 .unwrap_or(0);
14384 edits.extend(selection_edit_ranges.iter().map(|range| {
14385 let position = Point::new(range.start.row, min_column);
14386 (position..position, first_prefix.clone())
14387 }));
14388 }
14389 } else if let Some(BlockCommentConfig {
14390 start: full_comment_prefix,
14391 end: comment_suffix,
14392 ..
14393 }) = language.block_comment()
14394 {
14395 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
14396 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
14397 let prefix_range = comment_prefix_range(
14398 snapshot.deref(),
14399 start_row,
14400 comment_prefix,
14401 comment_prefix_whitespace,
14402 ignore_indent,
14403 );
14404 let suffix_range = comment_suffix_range(
14405 snapshot.deref(),
14406 end_row,
14407 comment_suffix.trim_start_matches(' '),
14408 comment_suffix.starts_with(' '),
14409 );
14410
14411 if prefix_range.is_empty() || suffix_range.is_empty() {
14412 edits.push((
14413 prefix_range.start..prefix_range.start,
14414 full_comment_prefix.clone(),
14415 ));
14416 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
14417 suffixes_inserted.push((end_row, comment_suffix.len()));
14418 } else {
14419 edits.push((prefix_range, empty_str.clone()));
14420 edits.push((suffix_range, empty_str.clone()));
14421 }
14422 } else {
14423 continue;
14424 }
14425 }
14426
14427 drop(snapshot);
14428 this.buffer.update(cx, |buffer, cx| {
14429 buffer.edit(edits, None, cx);
14430 });
14431
14432 // Adjust selections so that they end before any comment suffixes that
14433 // were inserted.
14434 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
14435 let mut selections = this.selections.all::<Point>(cx);
14436 let snapshot = this.buffer.read(cx).read(cx);
14437 for selection in &mut selections {
14438 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
14439 match row.cmp(&MultiBufferRow(selection.end.row)) {
14440 Ordering::Less => {
14441 suffixes_inserted.next();
14442 continue;
14443 }
14444 Ordering::Greater => break,
14445 Ordering::Equal => {
14446 if selection.end.column == snapshot.line_len(row) {
14447 if selection.is_empty() {
14448 selection.start.column -= suffix_len as u32;
14449 }
14450 selection.end.column -= suffix_len as u32;
14451 }
14452 break;
14453 }
14454 }
14455 }
14456 }
14457
14458 drop(snapshot);
14459 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
14460
14461 let selections = this.selections.all::<Point>(cx);
14462 let selections_on_single_row = selections.windows(2).all(|selections| {
14463 selections[0].start.row == selections[1].start.row
14464 && selections[0].end.row == selections[1].end.row
14465 && selections[0].start.row == selections[0].end.row
14466 });
14467 let selections_selecting = selections
14468 .iter()
14469 .any(|selection| selection.start != selection.end);
14470 let advance_downwards = action.advance_downwards
14471 && selections_on_single_row
14472 && !selections_selecting
14473 && !matches!(this.mode, EditorMode::SingleLine { .. });
14474
14475 if advance_downwards {
14476 let snapshot = this.buffer.read(cx).snapshot(cx);
14477
14478 this.change_selections(Default::default(), window, cx, |s| {
14479 s.move_cursors_with(|display_snapshot, display_point, _| {
14480 let mut point = display_point.to_point(display_snapshot);
14481 point.row += 1;
14482 point = snapshot.clip_point(point, Bias::Left);
14483 let display_point = point.to_display_point(display_snapshot);
14484 let goal = SelectionGoal::HorizontalPosition(
14485 display_snapshot
14486 .x_for_display_point(display_point, text_layout_details)
14487 .into(),
14488 );
14489 (display_point, goal)
14490 })
14491 });
14492 }
14493 });
14494 }
14495
14496 pub fn select_enclosing_symbol(
14497 &mut self,
14498 _: &SelectEnclosingSymbol,
14499 window: &mut Window,
14500 cx: &mut Context<Self>,
14501 ) {
14502 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14503
14504 let buffer = self.buffer.read(cx).snapshot(cx);
14505 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
14506
14507 fn update_selection(
14508 selection: &Selection<usize>,
14509 buffer_snap: &MultiBufferSnapshot,
14510 ) -> Option<Selection<usize>> {
14511 let cursor = selection.head();
14512 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
14513 for symbol in symbols.iter().rev() {
14514 let start = symbol.range.start.to_offset(buffer_snap);
14515 let end = symbol.range.end.to_offset(buffer_snap);
14516 let new_range = start..end;
14517 if start < selection.start || end > selection.end {
14518 return Some(Selection {
14519 id: selection.id,
14520 start: new_range.start,
14521 end: new_range.end,
14522 goal: SelectionGoal::None,
14523 reversed: selection.reversed,
14524 });
14525 }
14526 }
14527 None
14528 }
14529
14530 let mut selected_larger_symbol = false;
14531 let new_selections = old_selections
14532 .iter()
14533 .map(|selection| match update_selection(selection, &buffer) {
14534 Some(new_selection) => {
14535 if new_selection.range() != selection.range() {
14536 selected_larger_symbol = true;
14537 }
14538 new_selection
14539 }
14540 None => selection.clone(),
14541 })
14542 .collect::<Vec<_>>();
14543
14544 if selected_larger_symbol {
14545 self.change_selections(Default::default(), window, cx, |s| {
14546 s.select(new_selections);
14547 });
14548 }
14549 }
14550
14551 pub fn select_larger_syntax_node(
14552 &mut self,
14553 _: &SelectLargerSyntaxNode,
14554 window: &mut Window,
14555 cx: &mut Context<Self>,
14556 ) {
14557 let Some(visible_row_count) = self.visible_row_count() else {
14558 return;
14559 };
14560 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
14561 if old_selections.is_empty() {
14562 return;
14563 }
14564
14565 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14566
14567 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14568 let buffer = self.buffer.read(cx).snapshot(cx);
14569
14570 let mut selected_larger_node = false;
14571 let mut new_selections = old_selections
14572 .iter()
14573 .map(|selection| {
14574 let old_range = selection.start..selection.end;
14575
14576 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
14577 // manually select word at selection
14578 if ["string_content", "inline"].contains(&node.kind()) {
14579 let (word_range, _) = buffer.surrounding_word(old_range.start, false);
14580 // ignore if word is already selected
14581 if !word_range.is_empty() && old_range != word_range {
14582 let (last_word_range, _) =
14583 buffer.surrounding_word(old_range.end, false);
14584 // only select word if start and end point belongs to same word
14585 if word_range == last_word_range {
14586 selected_larger_node = true;
14587 return Selection {
14588 id: selection.id,
14589 start: word_range.start,
14590 end: word_range.end,
14591 goal: SelectionGoal::None,
14592 reversed: selection.reversed,
14593 };
14594 }
14595 }
14596 }
14597 }
14598
14599 let mut new_range = old_range.clone();
14600 while let Some((_node, containing_range)) =
14601 buffer.syntax_ancestor(new_range.clone())
14602 {
14603 new_range = match containing_range {
14604 MultiOrSingleBufferOffsetRange::Single(_) => break,
14605 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14606 };
14607 if !display_map.intersects_fold(new_range.start)
14608 && !display_map.intersects_fold(new_range.end)
14609 {
14610 break;
14611 }
14612 }
14613
14614 selected_larger_node |= new_range != old_range;
14615 Selection {
14616 id: selection.id,
14617 start: new_range.start,
14618 end: new_range.end,
14619 goal: SelectionGoal::None,
14620 reversed: selection.reversed,
14621 }
14622 })
14623 .collect::<Vec<_>>();
14624
14625 if !selected_larger_node {
14626 return; // don't put this call in the history
14627 }
14628
14629 // scroll based on transformation done to the last selection created by the user
14630 let (last_old, last_new) = old_selections
14631 .last()
14632 .zip(new_selections.last().cloned())
14633 .expect("old_selections isn't empty");
14634
14635 // revert selection
14636 let is_selection_reversed = {
14637 let should_newest_selection_be_reversed = last_old.start != last_new.start;
14638 new_selections.last_mut().expect("checked above").reversed =
14639 should_newest_selection_be_reversed;
14640 should_newest_selection_be_reversed
14641 };
14642
14643 if selected_larger_node {
14644 self.select_syntax_node_history.disable_clearing = true;
14645 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14646 s.select(new_selections.clone());
14647 });
14648 self.select_syntax_node_history.disable_clearing = false;
14649 }
14650
14651 let start_row = last_new.start.to_display_point(&display_map).row().0;
14652 let end_row = last_new.end.to_display_point(&display_map).row().0;
14653 let selection_height = end_row - start_row + 1;
14654 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
14655
14656 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
14657 let scroll_behavior = if fits_on_the_screen {
14658 self.request_autoscroll(Autoscroll::fit(), cx);
14659 SelectSyntaxNodeScrollBehavior::FitSelection
14660 } else if is_selection_reversed {
14661 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14662 SelectSyntaxNodeScrollBehavior::CursorTop
14663 } else {
14664 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14665 SelectSyntaxNodeScrollBehavior::CursorBottom
14666 };
14667
14668 self.select_syntax_node_history.push((
14669 old_selections,
14670 scroll_behavior,
14671 is_selection_reversed,
14672 ));
14673 }
14674
14675 pub fn select_smaller_syntax_node(
14676 &mut self,
14677 _: &SelectSmallerSyntaxNode,
14678 window: &mut Window,
14679 cx: &mut Context<Self>,
14680 ) {
14681 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14682
14683 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
14684 self.select_syntax_node_history.pop()
14685 {
14686 if let Some(selection) = selections.last_mut() {
14687 selection.reversed = is_selection_reversed;
14688 }
14689
14690 self.select_syntax_node_history.disable_clearing = true;
14691 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14692 s.select(selections.to_vec());
14693 });
14694 self.select_syntax_node_history.disable_clearing = false;
14695
14696 match scroll_behavior {
14697 SelectSyntaxNodeScrollBehavior::CursorTop => {
14698 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14699 }
14700 SelectSyntaxNodeScrollBehavior::FitSelection => {
14701 self.request_autoscroll(Autoscroll::fit(), cx);
14702 }
14703 SelectSyntaxNodeScrollBehavior::CursorBottom => {
14704 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14705 }
14706 }
14707 }
14708 }
14709
14710 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
14711 if !EditorSettings::get_global(cx).gutter.runnables {
14712 self.clear_tasks();
14713 return Task::ready(());
14714 }
14715 let project = self.project.as_ref().map(Entity::downgrade);
14716 let task_sources = self.lsp_task_sources(cx);
14717 let multi_buffer = self.buffer.downgrade();
14718 cx.spawn_in(window, async move |editor, cx| {
14719 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
14720 let Some(project) = project.and_then(|p| p.upgrade()) else {
14721 return;
14722 };
14723 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
14724 this.display_map.update(cx, |map, cx| map.snapshot(cx))
14725 }) else {
14726 return;
14727 };
14728
14729 let hide_runnables = project
14730 .update(cx, |project, cx| {
14731 // Do not display any test indicators in non-dev server remote projects.
14732 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
14733 })
14734 .unwrap_or(true);
14735 if hide_runnables {
14736 return;
14737 }
14738 let new_rows =
14739 cx.background_spawn({
14740 let snapshot = display_snapshot.clone();
14741 async move {
14742 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
14743 }
14744 })
14745 .await;
14746 let Ok(lsp_tasks) =
14747 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
14748 else {
14749 return;
14750 };
14751 let lsp_tasks = lsp_tasks.await;
14752
14753 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
14754 lsp_tasks
14755 .into_iter()
14756 .flat_map(|(kind, tasks)| {
14757 tasks.into_iter().filter_map(move |(location, task)| {
14758 Some((kind.clone(), location?, task))
14759 })
14760 })
14761 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
14762 let buffer = location.target.buffer;
14763 let buffer_snapshot = buffer.read(cx).snapshot();
14764 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
14765 |(excerpt_id, snapshot, _)| {
14766 if snapshot.remote_id() == buffer_snapshot.remote_id() {
14767 display_snapshot
14768 .buffer_snapshot
14769 .anchor_in_excerpt(excerpt_id, location.target.range.start)
14770 } else {
14771 None
14772 }
14773 },
14774 );
14775 if let Some(offset) = offset {
14776 let task_buffer_range =
14777 location.target.range.to_point(&buffer_snapshot);
14778 let context_buffer_range =
14779 task_buffer_range.to_offset(&buffer_snapshot);
14780 let context_range = BufferOffset(context_buffer_range.start)
14781 ..BufferOffset(context_buffer_range.end);
14782
14783 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
14784 .or_insert_with(|| RunnableTasks {
14785 templates: Vec::new(),
14786 offset,
14787 column: task_buffer_range.start.column,
14788 extra_variables: HashMap::default(),
14789 context_range,
14790 })
14791 .templates
14792 .push((kind, task.original_task().clone()));
14793 }
14794
14795 acc
14796 })
14797 }) else {
14798 return;
14799 };
14800
14801 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
14802 buffer.language_settings(cx).tasks.prefer_lsp
14803 }) else {
14804 return;
14805 };
14806
14807 let rows = Self::runnable_rows(
14808 project,
14809 display_snapshot,
14810 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
14811 new_rows,
14812 cx.clone(),
14813 )
14814 .await;
14815 editor
14816 .update(cx, |editor, _| {
14817 editor.clear_tasks();
14818 for (key, mut value) in rows {
14819 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
14820 value.templates.extend(lsp_tasks.templates);
14821 }
14822
14823 editor.insert_tasks(key, value);
14824 }
14825 for (key, value) in lsp_tasks_by_rows {
14826 editor.insert_tasks(key, value);
14827 }
14828 })
14829 .ok();
14830 })
14831 }
14832 fn fetch_runnable_ranges(
14833 snapshot: &DisplaySnapshot,
14834 range: Range<Anchor>,
14835 ) -> Vec<language::RunnableRange> {
14836 snapshot.buffer_snapshot.runnable_ranges(range).collect()
14837 }
14838
14839 fn runnable_rows(
14840 project: Entity<Project>,
14841 snapshot: DisplaySnapshot,
14842 prefer_lsp: bool,
14843 runnable_ranges: Vec<RunnableRange>,
14844 cx: AsyncWindowContext,
14845 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
14846 cx.spawn(async move |cx| {
14847 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
14848 for mut runnable in runnable_ranges {
14849 let Some(tasks) = cx
14850 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
14851 .ok()
14852 else {
14853 continue;
14854 };
14855 let mut tasks = tasks.await;
14856
14857 if prefer_lsp {
14858 tasks.retain(|(task_kind, _)| {
14859 !matches!(task_kind, TaskSourceKind::Language { .. })
14860 });
14861 }
14862 if tasks.is_empty() {
14863 continue;
14864 }
14865
14866 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
14867 let Some(row) = snapshot
14868 .buffer_snapshot
14869 .buffer_line_for_row(MultiBufferRow(point.row))
14870 .map(|(_, range)| range.start.row)
14871 else {
14872 continue;
14873 };
14874
14875 let context_range =
14876 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
14877 runnable_rows.push((
14878 (runnable.buffer_id, row),
14879 RunnableTasks {
14880 templates: tasks,
14881 offset: snapshot
14882 .buffer_snapshot
14883 .anchor_before(runnable.run_range.start),
14884 context_range,
14885 column: point.column,
14886 extra_variables: runnable.extra_captures,
14887 },
14888 ));
14889 }
14890 runnable_rows
14891 })
14892 }
14893
14894 fn templates_with_tags(
14895 project: &Entity<Project>,
14896 runnable: &mut Runnable,
14897 cx: &mut App,
14898 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
14899 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
14900 let (worktree_id, file) = project
14901 .buffer_for_id(runnable.buffer, cx)
14902 .and_then(|buffer| buffer.read(cx).file())
14903 .map(|file| (file.worktree_id(cx), file.clone()))
14904 .unzip();
14905
14906 (
14907 project.task_store().read(cx).task_inventory().cloned(),
14908 worktree_id,
14909 file,
14910 )
14911 });
14912
14913 let tags = mem::take(&mut runnable.tags);
14914 let language = runnable.language.clone();
14915 cx.spawn(async move |cx| {
14916 let mut templates_with_tags = Vec::new();
14917 if let Some(inventory) = inventory {
14918 for RunnableTag(tag) in tags {
14919 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
14920 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
14921 }) else {
14922 return templates_with_tags;
14923 };
14924 templates_with_tags.extend(new_tasks.await.into_iter().filter(
14925 move |(_, template)| {
14926 template.tags.iter().any(|source_tag| source_tag == &tag)
14927 },
14928 ));
14929 }
14930 }
14931 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
14932
14933 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
14934 // Strongest source wins; if we have worktree tag binding, prefer that to
14935 // global and language bindings;
14936 // if we have a global binding, prefer that to language binding.
14937 let first_mismatch = templates_with_tags
14938 .iter()
14939 .position(|(tag_source, _)| tag_source != leading_tag_source);
14940 if let Some(index) = first_mismatch {
14941 templates_with_tags.truncate(index);
14942 }
14943 }
14944
14945 templates_with_tags
14946 })
14947 }
14948
14949 pub fn move_to_enclosing_bracket(
14950 &mut self,
14951 _: &MoveToEnclosingBracket,
14952 window: &mut Window,
14953 cx: &mut Context<Self>,
14954 ) {
14955 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14956 self.change_selections(Default::default(), window, cx, |s| {
14957 s.move_offsets_with(|snapshot, selection| {
14958 let Some(enclosing_bracket_ranges) =
14959 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
14960 else {
14961 return;
14962 };
14963
14964 let mut best_length = usize::MAX;
14965 let mut best_inside = false;
14966 let mut best_in_bracket_range = false;
14967 let mut best_destination = None;
14968 for (open, close) in enclosing_bracket_ranges {
14969 let close = close.to_inclusive();
14970 let length = close.end() - open.start;
14971 let inside = selection.start >= open.end && selection.end <= *close.start();
14972 let in_bracket_range = open.to_inclusive().contains(&selection.head())
14973 || close.contains(&selection.head());
14974
14975 // If best is next to a bracket and current isn't, skip
14976 if !in_bracket_range && best_in_bracket_range {
14977 continue;
14978 }
14979
14980 // Prefer smaller lengths unless best is inside and current isn't
14981 if length > best_length && (best_inside || !inside) {
14982 continue;
14983 }
14984
14985 best_length = length;
14986 best_inside = inside;
14987 best_in_bracket_range = in_bracket_range;
14988 best_destination = Some(
14989 if close.contains(&selection.start) && close.contains(&selection.end) {
14990 if inside { open.end } else { open.start }
14991 } else if inside {
14992 *close.start()
14993 } else {
14994 *close.end()
14995 },
14996 );
14997 }
14998
14999 if let Some(destination) = best_destination {
15000 selection.collapse_to(destination, SelectionGoal::None);
15001 }
15002 })
15003 });
15004 }
15005
15006 pub fn undo_selection(
15007 &mut self,
15008 _: &UndoSelection,
15009 window: &mut Window,
15010 cx: &mut Context<Self>,
15011 ) {
15012 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15013 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
15014 self.selection_history.mode = SelectionHistoryMode::Undoing;
15015 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15016 this.end_selection(window, cx);
15017 this.change_selections(
15018 SelectionEffects::scroll(Autoscroll::newest()),
15019 window,
15020 cx,
15021 |s| s.select_anchors(entry.selections.to_vec()),
15022 );
15023 });
15024 self.selection_history.mode = SelectionHistoryMode::Normal;
15025
15026 self.select_next_state = entry.select_next_state;
15027 self.select_prev_state = entry.select_prev_state;
15028 self.add_selections_state = entry.add_selections_state;
15029 }
15030 }
15031
15032 pub fn redo_selection(
15033 &mut self,
15034 _: &RedoSelection,
15035 window: &mut Window,
15036 cx: &mut Context<Self>,
15037 ) {
15038 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15039 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
15040 self.selection_history.mode = SelectionHistoryMode::Redoing;
15041 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15042 this.end_selection(window, cx);
15043 this.change_selections(
15044 SelectionEffects::scroll(Autoscroll::newest()),
15045 window,
15046 cx,
15047 |s| s.select_anchors(entry.selections.to_vec()),
15048 );
15049 });
15050 self.selection_history.mode = SelectionHistoryMode::Normal;
15051
15052 self.select_next_state = entry.select_next_state;
15053 self.select_prev_state = entry.select_prev_state;
15054 self.add_selections_state = entry.add_selections_state;
15055 }
15056 }
15057
15058 pub fn expand_excerpts(
15059 &mut self,
15060 action: &ExpandExcerpts,
15061 _: &mut Window,
15062 cx: &mut Context<Self>,
15063 ) {
15064 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
15065 }
15066
15067 pub fn expand_excerpts_down(
15068 &mut self,
15069 action: &ExpandExcerptsDown,
15070 _: &mut Window,
15071 cx: &mut Context<Self>,
15072 ) {
15073 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
15074 }
15075
15076 pub fn expand_excerpts_up(
15077 &mut self,
15078 action: &ExpandExcerptsUp,
15079 _: &mut Window,
15080 cx: &mut Context<Self>,
15081 ) {
15082 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
15083 }
15084
15085 pub fn expand_excerpts_for_direction(
15086 &mut self,
15087 lines: u32,
15088 direction: ExpandExcerptDirection,
15089
15090 cx: &mut Context<Self>,
15091 ) {
15092 let selections = self.selections.disjoint_anchors();
15093
15094 let lines = if lines == 0 {
15095 EditorSettings::get_global(cx).expand_excerpt_lines
15096 } else {
15097 lines
15098 };
15099
15100 self.buffer.update(cx, |buffer, cx| {
15101 let snapshot = buffer.snapshot(cx);
15102 let mut excerpt_ids = selections
15103 .iter()
15104 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
15105 .collect::<Vec<_>>();
15106 excerpt_ids.sort();
15107 excerpt_ids.dedup();
15108 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
15109 })
15110 }
15111
15112 pub fn expand_excerpt(
15113 &mut self,
15114 excerpt: ExcerptId,
15115 direction: ExpandExcerptDirection,
15116 window: &mut Window,
15117 cx: &mut Context<Self>,
15118 ) {
15119 let current_scroll_position = self.scroll_position(cx);
15120 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
15121 let mut should_scroll_up = false;
15122
15123 if direction == ExpandExcerptDirection::Down {
15124 let multi_buffer = self.buffer.read(cx);
15125 let snapshot = multi_buffer.snapshot(cx);
15126 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
15127 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
15128 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
15129 let buffer_snapshot = buffer.read(cx).snapshot();
15130 let excerpt_end_row =
15131 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
15132 let last_row = buffer_snapshot.max_point().row;
15133 let lines_below = last_row.saturating_sub(excerpt_end_row);
15134 should_scroll_up = lines_below >= lines_to_expand;
15135 }
15136 }
15137 }
15138 }
15139
15140 self.buffer.update(cx, |buffer, cx| {
15141 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
15142 });
15143
15144 if should_scroll_up {
15145 let new_scroll_position =
15146 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
15147 self.set_scroll_position(new_scroll_position, window, cx);
15148 }
15149 }
15150
15151 pub fn go_to_singleton_buffer_point(
15152 &mut self,
15153 point: Point,
15154 window: &mut Window,
15155 cx: &mut Context<Self>,
15156 ) {
15157 self.go_to_singleton_buffer_range(point..point, window, cx);
15158 }
15159
15160 pub fn go_to_singleton_buffer_range(
15161 &mut self,
15162 range: Range<Point>,
15163 window: &mut Window,
15164 cx: &mut Context<Self>,
15165 ) {
15166 let multibuffer = self.buffer().read(cx);
15167 let Some(buffer) = multibuffer.as_singleton() else {
15168 return;
15169 };
15170 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
15171 return;
15172 };
15173 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
15174 return;
15175 };
15176 self.change_selections(
15177 SelectionEffects::default().nav_history(true),
15178 window,
15179 cx,
15180 |s| s.select_anchor_ranges([start..end]),
15181 );
15182 }
15183
15184 pub fn go_to_diagnostic(
15185 &mut self,
15186 action: &GoToDiagnostic,
15187 window: &mut Window,
15188 cx: &mut Context<Self>,
15189 ) {
15190 if !self.diagnostics_enabled() {
15191 return;
15192 }
15193 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15194 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
15195 }
15196
15197 pub fn go_to_prev_diagnostic(
15198 &mut self,
15199 action: &GoToPreviousDiagnostic,
15200 window: &mut Window,
15201 cx: &mut Context<Self>,
15202 ) {
15203 if !self.diagnostics_enabled() {
15204 return;
15205 }
15206 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15207 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
15208 }
15209
15210 pub fn go_to_diagnostic_impl(
15211 &mut self,
15212 direction: Direction,
15213 severity: GoToDiagnosticSeverityFilter,
15214 window: &mut Window,
15215 cx: &mut Context<Self>,
15216 ) {
15217 let buffer = self.buffer.read(cx).snapshot(cx);
15218 let selection = self.selections.newest::<usize>(cx);
15219
15220 let mut active_group_id = None;
15221 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
15222 if active_group.active_range.start.to_offset(&buffer) == selection.start {
15223 active_group_id = Some(active_group.group_id);
15224 }
15225 }
15226
15227 fn filtered(
15228 snapshot: EditorSnapshot,
15229 severity: GoToDiagnosticSeverityFilter,
15230 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
15231 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
15232 diagnostics
15233 .filter(move |entry| severity.matches(entry.diagnostic.severity))
15234 .filter(|entry| entry.range.start != entry.range.end)
15235 .filter(|entry| !entry.diagnostic.is_unnecessary)
15236 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
15237 }
15238
15239 let snapshot = self.snapshot(window, cx);
15240 let before = filtered(
15241 snapshot.clone(),
15242 severity,
15243 buffer
15244 .diagnostics_in_range(0..selection.start)
15245 .filter(|entry| entry.range.start <= selection.start),
15246 );
15247 let after = filtered(
15248 snapshot,
15249 severity,
15250 buffer
15251 .diagnostics_in_range(selection.start..buffer.len())
15252 .filter(|entry| entry.range.start >= selection.start),
15253 );
15254
15255 let mut found: Option<DiagnosticEntry<usize>> = None;
15256 if direction == Direction::Prev {
15257 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
15258 {
15259 for diagnostic in prev_diagnostics.into_iter().rev() {
15260 if diagnostic.range.start != selection.start
15261 || active_group_id
15262 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
15263 {
15264 found = Some(diagnostic);
15265 break 'outer;
15266 }
15267 }
15268 }
15269 } else {
15270 for diagnostic in after.chain(before) {
15271 if diagnostic.range.start != selection.start
15272 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
15273 {
15274 found = Some(diagnostic);
15275 break;
15276 }
15277 }
15278 }
15279 let Some(next_diagnostic) = found else {
15280 return;
15281 };
15282
15283 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
15284 return;
15285 };
15286 self.change_selections(Default::default(), window, cx, |s| {
15287 s.select_ranges(vec![
15288 next_diagnostic.range.start..next_diagnostic.range.start,
15289 ])
15290 });
15291 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
15292 self.refresh_edit_prediction(false, true, window, cx);
15293 }
15294
15295 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
15296 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15297 let snapshot = self.snapshot(window, cx);
15298 let selection = self.selections.newest::<Point>(cx);
15299 self.go_to_hunk_before_or_after_position(
15300 &snapshot,
15301 selection.head(),
15302 Direction::Next,
15303 window,
15304 cx,
15305 );
15306 }
15307
15308 pub fn go_to_hunk_before_or_after_position(
15309 &mut self,
15310 snapshot: &EditorSnapshot,
15311 position: Point,
15312 direction: Direction,
15313 window: &mut Window,
15314 cx: &mut Context<Editor>,
15315 ) {
15316 let row = if direction == Direction::Next {
15317 self.hunk_after_position(snapshot, position)
15318 .map(|hunk| hunk.row_range.start)
15319 } else {
15320 self.hunk_before_position(snapshot, position)
15321 };
15322
15323 if let Some(row) = row {
15324 let destination = Point::new(row.0, 0);
15325 let autoscroll = Autoscroll::center();
15326
15327 self.unfold_ranges(&[destination..destination], false, false, cx);
15328 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
15329 s.select_ranges([destination..destination]);
15330 });
15331 }
15332 }
15333
15334 fn hunk_after_position(
15335 &mut self,
15336 snapshot: &EditorSnapshot,
15337 position: Point,
15338 ) -> Option<MultiBufferDiffHunk> {
15339 snapshot
15340 .buffer_snapshot
15341 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
15342 .find(|hunk| hunk.row_range.start.0 > position.row)
15343 .or_else(|| {
15344 snapshot
15345 .buffer_snapshot
15346 .diff_hunks_in_range(Point::zero()..position)
15347 .find(|hunk| hunk.row_range.end.0 < position.row)
15348 })
15349 }
15350
15351 fn go_to_prev_hunk(
15352 &mut self,
15353 _: &GoToPreviousHunk,
15354 window: &mut Window,
15355 cx: &mut Context<Self>,
15356 ) {
15357 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15358 let snapshot = self.snapshot(window, cx);
15359 let selection = self.selections.newest::<Point>(cx);
15360 self.go_to_hunk_before_or_after_position(
15361 &snapshot,
15362 selection.head(),
15363 Direction::Prev,
15364 window,
15365 cx,
15366 );
15367 }
15368
15369 fn hunk_before_position(
15370 &mut self,
15371 snapshot: &EditorSnapshot,
15372 position: Point,
15373 ) -> Option<MultiBufferRow> {
15374 snapshot
15375 .buffer_snapshot
15376 .diff_hunk_before(position)
15377 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
15378 }
15379
15380 fn go_to_next_change(
15381 &mut self,
15382 _: &GoToNextChange,
15383 window: &mut Window,
15384 cx: &mut Context<Self>,
15385 ) {
15386 if let Some(selections) = self
15387 .change_list
15388 .next_change(1, Direction::Next)
15389 .map(|s| s.to_vec())
15390 {
15391 self.change_selections(Default::default(), window, cx, |s| {
15392 let map = s.display_map();
15393 s.select_display_ranges(selections.iter().map(|a| {
15394 let point = a.to_display_point(&map);
15395 point..point
15396 }))
15397 })
15398 }
15399 }
15400
15401 fn go_to_previous_change(
15402 &mut self,
15403 _: &GoToPreviousChange,
15404 window: &mut Window,
15405 cx: &mut Context<Self>,
15406 ) {
15407 if let Some(selections) = self
15408 .change_list
15409 .next_change(1, Direction::Prev)
15410 .map(|s| s.to_vec())
15411 {
15412 self.change_selections(Default::default(), window, cx, |s| {
15413 let map = s.display_map();
15414 s.select_display_ranges(selections.iter().map(|a| {
15415 let point = a.to_display_point(&map);
15416 point..point
15417 }))
15418 })
15419 }
15420 }
15421
15422 fn go_to_line<T: 'static>(
15423 &mut self,
15424 position: Anchor,
15425 highlight_color: Option<Hsla>,
15426 window: &mut Window,
15427 cx: &mut Context<Self>,
15428 ) {
15429 let snapshot = self.snapshot(window, cx).display_snapshot;
15430 let position = position.to_point(&snapshot.buffer_snapshot);
15431 let start = snapshot
15432 .buffer_snapshot
15433 .clip_point(Point::new(position.row, 0), Bias::Left);
15434 let end = start + Point::new(1, 0);
15435 let start = snapshot.buffer_snapshot.anchor_before(start);
15436 let end = snapshot.buffer_snapshot.anchor_before(end);
15437
15438 self.highlight_rows::<T>(
15439 start..end,
15440 highlight_color
15441 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
15442 Default::default(),
15443 cx,
15444 );
15445
15446 if self.buffer.read(cx).is_singleton() {
15447 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
15448 }
15449 }
15450
15451 pub fn go_to_definition(
15452 &mut self,
15453 _: &GoToDefinition,
15454 window: &mut Window,
15455 cx: &mut Context<Self>,
15456 ) -> Task<Result<Navigated>> {
15457 let definition =
15458 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
15459 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
15460 cx.spawn_in(window, async move |editor, cx| {
15461 if definition.await? == Navigated::Yes {
15462 return Ok(Navigated::Yes);
15463 }
15464 match fallback_strategy {
15465 GoToDefinitionFallback::None => Ok(Navigated::No),
15466 GoToDefinitionFallback::FindAllReferences => {
15467 match editor.update_in(cx, |editor, window, cx| {
15468 editor.find_all_references(&FindAllReferences, window, cx)
15469 })? {
15470 Some(references) => references.await,
15471 None => Ok(Navigated::No),
15472 }
15473 }
15474 }
15475 })
15476 }
15477
15478 pub fn go_to_declaration(
15479 &mut self,
15480 _: &GoToDeclaration,
15481 window: &mut Window,
15482 cx: &mut Context<Self>,
15483 ) -> Task<Result<Navigated>> {
15484 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
15485 }
15486
15487 pub fn go_to_declaration_split(
15488 &mut self,
15489 _: &GoToDeclaration,
15490 window: &mut Window,
15491 cx: &mut Context<Self>,
15492 ) -> Task<Result<Navigated>> {
15493 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
15494 }
15495
15496 pub fn go_to_implementation(
15497 &mut self,
15498 _: &GoToImplementation,
15499 window: &mut Window,
15500 cx: &mut Context<Self>,
15501 ) -> Task<Result<Navigated>> {
15502 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
15503 }
15504
15505 pub fn go_to_implementation_split(
15506 &mut self,
15507 _: &GoToImplementationSplit,
15508 window: &mut Window,
15509 cx: &mut Context<Self>,
15510 ) -> Task<Result<Navigated>> {
15511 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
15512 }
15513
15514 pub fn go_to_type_definition(
15515 &mut self,
15516 _: &GoToTypeDefinition,
15517 window: &mut Window,
15518 cx: &mut Context<Self>,
15519 ) -> Task<Result<Navigated>> {
15520 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
15521 }
15522
15523 pub fn go_to_definition_split(
15524 &mut self,
15525 _: &GoToDefinitionSplit,
15526 window: &mut Window,
15527 cx: &mut Context<Self>,
15528 ) -> Task<Result<Navigated>> {
15529 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
15530 }
15531
15532 pub fn go_to_type_definition_split(
15533 &mut self,
15534 _: &GoToTypeDefinitionSplit,
15535 window: &mut Window,
15536 cx: &mut Context<Self>,
15537 ) -> Task<Result<Navigated>> {
15538 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
15539 }
15540
15541 fn go_to_definition_of_kind(
15542 &mut self,
15543 kind: GotoDefinitionKind,
15544 split: bool,
15545 window: &mut Window,
15546 cx: &mut Context<Self>,
15547 ) -> Task<Result<Navigated>> {
15548 let Some(provider) = self.semantics_provider.clone() else {
15549 return Task::ready(Ok(Navigated::No));
15550 };
15551 let head = self.selections.newest::<usize>(cx).head();
15552 let buffer = self.buffer.read(cx);
15553 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
15554 text_anchor
15555 } else {
15556 return Task::ready(Ok(Navigated::No));
15557 };
15558
15559 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
15560 return Task::ready(Ok(Navigated::No));
15561 };
15562
15563 cx.spawn_in(window, async move |editor, cx| {
15564 let definitions = definitions.await?;
15565 let navigated = editor
15566 .update_in(cx, |editor, window, cx| {
15567 editor.navigate_to_hover_links(
15568 Some(kind),
15569 definitions
15570 .into_iter()
15571 .filter(|location| {
15572 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
15573 })
15574 .map(HoverLink::Text)
15575 .collect::<Vec<_>>(),
15576 split,
15577 window,
15578 cx,
15579 )
15580 })?
15581 .await?;
15582 anyhow::Ok(navigated)
15583 })
15584 }
15585
15586 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
15587 let selection = self.selections.newest_anchor();
15588 let head = selection.head();
15589 let tail = selection.tail();
15590
15591 let Some((buffer, start_position)) =
15592 self.buffer.read(cx).text_anchor_for_position(head, cx)
15593 else {
15594 return;
15595 };
15596
15597 let end_position = if head != tail {
15598 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
15599 return;
15600 };
15601 Some(pos)
15602 } else {
15603 None
15604 };
15605
15606 let url_finder = cx.spawn_in(window, async move |editor, cx| {
15607 let url = if let Some(end_pos) = end_position {
15608 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
15609 } else {
15610 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
15611 };
15612
15613 if let Some(url) = url {
15614 editor.update(cx, |_, cx| {
15615 cx.open_url(&url);
15616 })
15617 } else {
15618 Ok(())
15619 }
15620 });
15621
15622 url_finder.detach();
15623 }
15624
15625 pub fn open_selected_filename(
15626 &mut self,
15627 _: &OpenSelectedFilename,
15628 window: &mut Window,
15629 cx: &mut Context<Self>,
15630 ) {
15631 let Some(workspace) = self.workspace() else {
15632 return;
15633 };
15634
15635 let position = self.selections.newest_anchor().head();
15636
15637 let Some((buffer, buffer_position)) =
15638 self.buffer.read(cx).text_anchor_for_position(position, cx)
15639 else {
15640 return;
15641 };
15642
15643 let project = self.project.clone();
15644
15645 cx.spawn_in(window, async move |_, cx| {
15646 let result = find_file(&buffer, project, buffer_position, cx).await;
15647
15648 if let Some((_, path)) = result {
15649 workspace
15650 .update_in(cx, |workspace, window, cx| {
15651 workspace.open_resolved_path(path, window, cx)
15652 })?
15653 .await?;
15654 }
15655 anyhow::Ok(())
15656 })
15657 .detach();
15658 }
15659
15660 pub(crate) fn navigate_to_hover_links(
15661 &mut self,
15662 kind: Option<GotoDefinitionKind>,
15663 mut definitions: Vec<HoverLink>,
15664 split: bool,
15665 window: &mut Window,
15666 cx: &mut Context<Editor>,
15667 ) -> Task<Result<Navigated>> {
15668 // If there is one definition, just open it directly
15669 if definitions.len() == 1 {
15670 let definition = definitions.pop().unwrap();
15671
15672 enum TargetTaskResult {
15673 Location(Option<Location>),
15674 AlreadyNavigated,
15675 }
15676
15677 let target_task = match definition {
15678 HoverLink::Text(link) => {
15679 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
15680 }
15681 HoverLink::InlayHint(lsp_location, server_id) => {
15682 let computation =
15683 self.compute_target_location(lsp_location, server_id, window, cx);
15684 cx.background_spawn(async move {
15685 let location = computation.await?;
15686 Ok(TargetTaskResult::Location(location))
15687 })
15688 }
15689 HoverLink::Url(url) => {
15690 cx.open_url(&url);
15691 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
15692 }
15693 HoverLink::File(path) => {
15694 if let Some(workspace) = self.workspace() {
15695 cx.spawn_in(window, async move |_, cx| {
15696 workspace
15697 .update_in(cx, |workspace, window, cx| {
15698 workspace.open_resolved_path(path, window, cx)
15699 })?
15700 .await
15701 .map(|_| TargetTaskResult::AlreadyNavigated)
15702 })
15703 } else {
15704 Task::ready(Ok(TargetTaskResult::Location(None)))
15705 }
15706 }
15707 };
15708 cx.spawn_in(window, async move |editor, cx| {
15709 let target = match target_task.await.context("target resolution task")? {
15710 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
15711 TargetTaskResult::Location(None) => return Ok(Navigated::No),
15712 TargetTaskResult::Location(Some(target)) => target,
15713 };
15714
15715 editor.update_in(cx, |editor, window, cx| {
15716 let Some(workspace) = editor.workspace() else {
15717 return Navigated::No;
15718 };
15719 let pane = workspace.read(cx).active_pane().clone();
15720
15721 let range = target.range.to_point(target.buffer.read(cx));
15722 let range = editor.range_for_match(&range);
15723 let range = collapse_multiline_range(range);
15724
15725 if !split
15726 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
15727 {
15728 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
15729 } else {
15730 window.defer(cx, move |window, cx| {
15731 let target_editor: Entity<Self> =
15732 workspace.update(cx, |workspace, cx| {
15733 let pane = if split {
15734 workspace.adjacent_pane(window, cx)
15735 } else {
15736 workspace.active_pane().clone()
15737 };
15738
15739 workspace.open_project_item(
15740 pane,
15741 target.buffer.clone(),
15742 true,
15743 true,
15744 window,
15745 cx,
15746 )
15747 });
15748 target_editor.update(cx, |target_editor, cx| {
15749 // When selecting a definition in a different buffer, disable the nav history
15750 // to avoid creating a history entry at the previous cursor location.
15751 pane.update(cx, |pane, _| pane.disable_history());
15752 target_editor.go_to_singleton_buffer_range(range, window, cx);
15753 pane.update(cx, |pane, _| pane.enable_history());
15754 });
15755 });
15756 }
15757 Navigated::Yes
15758 })
15759 })
15760 } else if !definitions.is_empty() {
15761 cx.spawn_in(window, async move |editor, cx| {
15762 let (title, location_tasks, workspace) = editor
15763 .update_in(cx, |editor, window, cx| {
15764 let tab_kind = match kind {
15765 Some(GotoDefinitionKind::Implementation) => "Implementations",
15766 _ => "Definitions",
15767 };
15768 let title = definitions
15769 .iter()
15770 .find_map(|definition| match definition {
15771 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
15772 let buffer = origin.buffer.read(cx);
15773 format!(
15774 "{} for {}",
15775 tab_kind,
15776 buffer
15777 .text_for_range(origin.range.clone())
15778 .collect::<String>()
15779 )
15780 }),
15781 HoverLink::InlayHint(_, _) => None,
15782 HoverLink::Url(_) => None,
15783 HoverLink::File(_) => None,
15784 })
15785 .unwrap_or(tab_kind.to_string());
15786 let location_tasks = definitions
15787 .into_iter()
15788 .map(|definition| match definition {
15789 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
15790 HoverLink::InlayHint(lsp_location, server_id) => editor
15791 .compute_target_location(lsp_location, server_id, window, cx),
15792 HoverLink::Url(_) => Task::ready(Ok(None)),
15793 HoverLink::File(_) => Task::ready(Ok(None)),
15794 })
15795 .collect::<Vec<_>>();
15796 (title, location_tasks, editor.workspace().clone())
15797 })
15798 .context("location tasks preparation")?;
15799
15800 let locations: Vec<Location> = future::join_all(location_tasks)
15801 .await
15802 .into_iter()
15803 .filter_map(|location| location.transpose())
15804 .collect::<Result<_>>()
15805 .context("location tasks")?;
15806
15807 if locations.is_empty() {
15808 return Ok(Navigated::No);
15809 }
15810
15811 let Some(workspace) = workspace else {
15812 return Ok(Navigated::No);
15813 };
15814
15815 let opened = workspace
15816 .update_in(cx, |workspace, window, cx| {
15817 Self::open_locations_in_multibuffer(
15818 workspace,
15819 locations,
15820 title,
15821 split,
15822 MultibufferSelectionMode::First,
15823 window,
15824 cx,
15825 )
15826 })
15827 .ok();
15828
15829 anyhow::Ok(Navigated::from_bool(opened.is_some()))
15830 })
15831 } else {
15832 Task::ready(Ok(Navigated::No))
15833 }
15834 }
15835
15836 fn compute_target_location(
15837 &self,
15838 lsp_location: lsp::Location,
15839 server_id: LanguageServerId,
15840 window: &mut Window,
15841 cx: &mut Context<Self>,
15842 ) -> Task<anyhow::Result<Option<Location>>> {
15843 let Some(project) = self.project.clone() else {
15844 return Task::ready(Ok(None));
15845 };
15846
15847 cx.spawn_in(window, async move |editor, cx| {
15848 let location_task = editor.update(cx, |_, cx| {
15849 project.update(cx, |project, cx| {
15850 let language_server_name = project
15851 .language_server_statuses(cx)
15852 .find(|(id, _)| server_id == *id)
15853 .map(|(_, status)| status.name.clone());
15854 language_server_name.map(|language_server_name| {
15855 project.open_local_buffer_via_lsp(
15856 lsp_location.uri.clone(),
15857 server_id,
15858 language_server_name,
15859 cx,
15860 )
15861 })
15862 })
15863 })?;
15864 let location = match location_task {
15865 Some(task) => Some({
15866 let target_buffer_handle = task.await.context("open local buffer")?;
15867 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
15868 let target_start = target_buffer
15869 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
15870 let target_end = target_buffer
15871 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
15872 target_buffer.anchor_after(target_start)
15873 ..target_buffer.anchor_before(target_end)
15874 })?;
15875 Location {
15876 buffer: target_buffer_handle,
15877 range,
15878 }
15879 }),
15880 None => None,
15881 };
15882 Ok(location)
15883 })
15884 }
15885
15886 pub fn find_all_references(
15887 &mut self,
15888 _: &FindAllReferences,
15889 window: &mut Window,
15890 cx: &mut Context<Self>,
15891 ) -> Option<Task<Result<Navigated>>> {
15892 let selection = self.selections.newest::<usize>(cx);
15893 let multi_buffer = self.buffer.read(cx);
15894 let head = selection.head();
15895
15896 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
15897 let head_anchor = multi_buffer_snapshot.anchor_at(
15898 head,
15899 if head < selection.tail() {
15900 Bias::Right
15901 } else {
15902 Bias::Left
15903 },
15904 );
15905
15906 match self
15907 .find_all_references_task_sources
15908 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15909 {
15910 Ok(_) => {
15911 log::info!(
15912 "Ignoring repeated FindAllReferences invocation with the position of already running task"
15913 );
15914 return None;
15915 }
15916 Err(i) => {
15917 self.find_all_references_task_sources.insert(i, head_anchor);
15918 }
15919 }
15920
15921 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
15922 let workspace = self.workspace()?;
15923 let project = workspace.read(cx).project().clone();
15924 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
15925 Some(cx.spawn_in(window, async move |editor, cx| {
15926 let _cleanup = cx.on_drop(&editor, move |editor, _| {
15927 if let Ok(i) = editor
15928 .find_all_references_task_sources
15929 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15930 {
15931 editor.find_all_references_task_sources.remove(i);
15932 }
15933 });
15934
15935 let locations = references.await?;
15936 if locations.is_empty() {
15937 return anyhow::Ok(Navigated::No);
15938 }
15939
15940 workspace.update_in(cx, |workspace, window, cx| {
15941 let title = locations
15942 .first()
15943 .as_ref()
15944 .map(|location| {
15945 let buffer = location.buffer.read(cx);
15946 format!(
15947 "References to `{}`",
15948 buffer
15949 .text_for_range(location.range.clone())
15950 .collect::<String>()
15951 )
15952 })
15953 .unwrap();
15954 Self::open_locations_in_multibuffer(
15955 workspace,
15956 locations,
15957 title,
15958 false,
15959 MultibufferSelectionMode::First,
15960 window,
15961 cx,
15962 );
15963 Navigated::Yes
15964 })
15965 }))
15966 }
15967
15968 /// Opens a multibuffer with the given project locations in it
15969 pub fn open_locations_in_multibuffer(
15970 workspace: &mut Workspace,
15971 mut locations: Vec<Location>,
15972 title: String,
15973 split: bool,
15974 multibuffer_selection_mode: MultibufferSelectionMode,
15975 window: &mut Window,
15976 cx: &mut Context<Workspace>,
15977 ) {
15978 if locations.is_empty() {
15979 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
15980 return;
15981 }
15982
15983 // If there are multiple definitions, open them in a multibuffer
15984 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
15985 let mut locations = locations.into_iter().peekable();
15986 let mut ranges: Vec<Range<Anchor>> = Vec::new();
15987 let capability = workspace.project().read(cx).capability();
15988
15989 let excerpt_buffer = cx.new(|cx| {
15990 let mut multibuffer = MultiBuffer::new(capability);
15991 while let Some(location) = locations.next() {
15992 let buffer = location.buffer.read(cx);
15993 let mut ranges_for_buffer = Vec::new();
15994 let range = location.range.to_point(buffer);
15995 ranges_for_buffer.push(range.clone());
15996
15997 while let Some(next_location) = locations.peek() {
15998 if next_location.buffer == location.buffer {
15999 ranges_for_buffer.push(next_location.range.to_point(buffer));
16000 locations.next();
16001 } else {
16002 break;
16003 }
16004 }
16005
16006 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
16007 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
16008 PathKey::for_buffer(&location.buffer, cx),
16009 location.buffer.clone(),
16010 ranges_for_buffer,
16011 DEFAULT_MULTIBUFFER_CONTEXT,
16012 cx,
16013 );
16014 ranges.extend(new_ranges)
16015 }
16016
16017 multibuffer.with_title(title)
16018 });
16019
16020 let editor = cx.new(|cx| {
16021 Editor::for_multibuffer(
16022 excerpt_buffer,
16023 Some(workspace.project().clone()),
16024 window,
16025 cx,
16026 )
16027 });
16028 editor.update(cx, |editor, cx| {
16029 match multibuffer_selection_mode {
16030 MultibufferSelectionMode::First => {
16031 if let Some(first_range) = ranges.first() {
16032 editor.change_selections(
16033 SelectionEffects::no_scroll(),
16034 window,
16035 cx,
16036 |selections| {
16037 selections.clear_disjoint();
16038 selections
16039 .select_anchor_ranges(std::iter::once(first_range.clone()));
16040 },
16041 );
16042 }
16043 editor.highlight_background::<Self>(
16044 &ranges,
16045 |theme| theme.colors().editor_highlighted_line_background,
16046 cx,
16047 );
16048 }
16049 MultibufferSelectionMode::All => {
16050 editor.change_selections(
16051 SelectionEffects::no_scroll(),
16052 window,
16053 cx,
16054 |selections| {
16055 selections.clear_disjoint();
16056 selections.select_anchor_ranges(ranges);
16057 },
16058 );
16059 }
16060 }
16061 editor.register_buffers_with_language_servers(cx);
16062 });
16063
16064 let item = Box::new(editor);
16065 let item_id = item.item_id();
16066
16067 if split {
16068 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
16069 } else {
16070 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
16071 let (preview_item_id, preview_item_idx) =
16072 workspace.active_pane().read_with(cx, |pane, _| {
16073 (pane.preview_item_id(), pane.preview_item_idx())
16074 });
16075
16076 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
16077
16078 if let Some(preview_item_id) = preview_item_id {
16079 workspace.active_pane().update(cx, |pane, cx| {
16080 pane.remove_item(preview_item_id, false, false, window, cx);
16081 });
16082 }
16083 } else {
16084 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
16085 }
16086 }
16087 workspace.active_pane().update(cx, |pane, cx| {
16088 pane.set_preview_item_id(Some(item_id), cx);
16089 });
16090 }
16091
16092 pub fn rename(
16093 &mut self,
16094 _: &Rename,
16095 window: &mut Window,
16096 cx: &mut Context<Self>,
16097 ) -> Option<Task<Result<()>>> {
16098 use language::ToOffset as _;
16099
16100 let provider = self.semantics_provider.clone()?;
16101 let selection = self.selections.newest_anchor().clone();
16102 let (cursor_buffer, cursor_buffer_position) = self
16103 .buffer
16104 .read(cx)
16105 .text_anchor_for_position(selection.head(), cx)?;
16106 let (tail_buffer, cursor_buffer_position_end) = self
16107 .buffer
16108 .read(cx)
16109 .text_anchor_for_position(selection.tail(), cx)?;
16110 if tail_buffer != cursor_buffer {
16111 return None;
16112 }
16113
16114 let snapshot = cursor_buffer.read(cx).snapshot();
16115 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
16116 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
16117 let prepare_rename = provider
16118 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
16119 .unwrap_or_else(|| Task::ready(Ok(None)));
16120 drop(snapshot);
16121
16122 Some(cx.spawn_in(window, async move |this, cx| {
16123 let rename_range = if let Some(range) = prepare_rename.await? {
16124 Some(range)
16125 } else {
16126 this.update(cx, |this, cx| {
16127 let buffer = this.buffer.read(cx).snapshot(cx);
16128 let mut buffer_highlights = this
16129 .document_highlights_for_position(selection.head(), &buffer)
16130 .filter(|highlight| {
16131 highlight.start.excerpt_id == selection.head().excerpt_id
16132 && highlight.end.excerpt_id == selection.head().excerpt_id
16133 });
16134 buffer_highlights
16135 .next()
16136 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
16137 })?
16138 };
16139 if let Some(rename_range) = rename_range {
16140 this.update_in(cx, |this, window, cx| {
16141 let snapshot = cursor_buffer.read(cx).snapshot();
16142 let rename_buffer_range = rename_range.to_offset(&snapshot);
16143 let cursor_offset_in_rename_range =
16144 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
16145 let cursor_offset_in_rename_range_end =
16146 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
16147
16148 this.take_rename(false, window, cx);
16149 let buffer = this.buffer.read(cx).read(cx);
16150 let cursor_offset = selection.head().to_offset(&buffer);
16151 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
16152 let rename_end = rename_start + rename_buffer_range.len();
16153 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
16154 let mut old_highlight_id = None;
16155 let old_name: Arc<str> = buffer
16156 .chunks(rename_start..rename_end, true)
16157 .map(|chunk| {
16158 if old_highlight_id.is_none() {
16159 old_highlight_id = chunk.syntax_highlight_id;
16160 }
16161 chunk.text
16162 })
16163 .collect::<String>()
16164 .into();
16165
16166 drop(buffer);
16167
16168 // Position the selection in the rename editor so that it matches the current selection.
16169 this.show_local_selections = false;
16170 let rename_editor = cx.new(|cx| {
16171 let mut editor = Editor::single_line(window, cx);
16172 editor.buffer.update(cx, |buffer, cx| {
16173 buffer.edit([(0..0, old_name.clone())], None, cx)
16174 });
16175 let rename_selection_range = match cursor_offset_in_rename_range
16176 .cmp(&cursor_offset_in_rename_range_end)
16177 {
16178 Ordering::Equal => {
16179 editor.select_all(&SelectAll, window, cx);
16180 return editor;
16181 }
16182 Ordering::Less => {
16183 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
16184 }
16185 Ordering::Greater => {
16186 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
16187 }
16188 };
16189 if rename_selection_range.end > old_name.len() {
16190 editor.select_all(&SelectAll, window, cx);
16191 } else {
16192 editor.change_selections(Default::default(), window, cx, |s| {
16193 s.select_ranges([rename_selection_range]);
16194 });
16195 }
16196 editor
16197 });
16198 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
16199 if e == &EditorEvent::Focused {
16200 cx.emit(EditorEvent::FocusedIn)
16201 }
16202 })
16203 .detach();
16204
16205 let write_highlights =
16206 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
16207 let read_highlights =
16208 this.clear_background_highlights::<DocumentHighlightRead>(cx);
16209 let ranges = write_highlights
16210 .iter()
16211 .flat_map(|(_, ranges)| ranges.iter())
16212 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
16213 .cloned()
16214 .collect();
16215
16216 this.highlight_text::<Rename>(
16217 ranges,
16218 HighlightStyle {
16219 fade_out: Some(0.6),
16220 ..Default::default()
16221 },
16222 cx,
16223 );
16224 let rename_focus_handle = rename_editor.focus_handle(cx);
16225 window.focus(&rename_focus_handle);
16226 let block_id = this.insert_blocks(
16227 [BlockProperties {
16228 style: BlockStyle::Flex,
16229 placement: BlockPlacement::Below(range.start),
16230 height: Some(1),
16231 render: Arc::new({
16232 let rename_editor = rename_editor.clone();
16233 move |cx: &mut BlockContext| {
16234 let mut text_style = cx.editor_style.text.clone();
16235 if let Some(highlight_style) = old_highlight_id
16236 .and_then(|h| h.style(&cx.editor_style.syntax))
16237 {
16238 text_style = text_style.highlight(highlight_style);
16239 }
16240 div()
16241 .block_mouse_except_scroll()
16242 .pl(cx.anchor_x)
16243 .child(EditorElement::new(
16244 &rename_editor,
16245 EditorStyle {
16246 background: cx.theme().system().transparent,
16247 local_player: cx.editor_style.local_player,
16248 text: text_style,
16249 scrollbar_width: cx.editor_style.scrollbar_width,
16250 syntax: cx.editor_style.syntax.clone(),
16251 status: cx.editor_style.status.clone(),
16252 inlay_hints_style: HighlightStyle {
16253 font_weight: Some(FontWeight::BOLD),
16254 ..make_inlay_hints_style(cx.app)
16255 },
16256 edit_prediction_styles: make_suggestion_styles(
16257 cx.app,
16258 ),
16259 ..EditorStyle::default()
16260 },
16261 ))
16262 .into_any_element()
16263 }
16264 }),
16265 priority: 0,
16266 }],
16267 Some(Autoscroll::fit()),
16268 cx,
16269 )[0];
16270 this.pending_rename = Some(RenameState {
16271 range,
16272 old_name,
16273 editor: rename_editor,
16274 block_id,
16275 });
16276 })?;
16277 }
16278
16279 Ok(())
16280 }))
16281 }
16282
16283 pub fn confirm_rename(
16284 &mut self,
16285 _: &ConfirmRename,
16286 window: &mut Window,
16287 cx: &mut Context<Self>,
16288 ) -> Option<Task<Result<()>>> {
16289 let rename = self.take_rename(false, window, cx)?;
16290 let workspace = self.workspace()?.downgrade();
16291 let (buffer, start) = self
16292 .buffer
16293 .read(cx)
16294 .text_anchor_for_position(rename.range.start, cx)?;
16295 let (end_buffer, _) = self
16296 .buffer
16297 .read(cx)
16298 .text_anchor_for_position(rename.range.end, cx)?;
16299 if buffer != end_buffer {
16300 return None;
16301 }
16302
16303 let old_name = rename.old_name;
16304 let new_name = rename.editor.read(cx).text(cx);
16305
16306 let rename = self.semantics_provider.as_ref()?.perform_rename(
16307 &buffer,
16308 start,
16309 new_name.clone(),
16310 cx,
16311 )?;
16312
16313 Some(cx.spawn_in(window, async move |editor, cx| {
16314 let project_transaction = rename.await?;
16315 Self::open_project_transaction(
16316 &editor,
16317 workspace,
16318 project_transaction,
16319 format!("Rename: {} → {}", old_name, new_name),
16320 cx,
16321 )
16322 .await?;
16323
16324 editor.update(cx, |editor, cx| {
16325 editor.refresh_document_highlights(cx);
16326 })?;
16327 Ok(())
16328 }))
16329 }
16330
16331 fn take_rename(
16332 &mut self,
16333 moving_cursor: bool,
16334 window: &mut Window,
16335 cx: &mut Context<Self>,
16336 ) -> Option<RenameState> {
16337 let rename = self.pending_rename.take()?;
16338 if rename.editor.focus_handle(cx).is_focused(window) {
16339 window.focus(&self.focus_handle);
16340 }
16341
16342 self.remove_blocks(
16343 [rename.block_id].into_iter().collect(),
16344 Some(Autoscroll::fit()),
16345 cx,
16346 );
16347 self.clear_highlights::<Rename>(cx);
16348 self.show_local_selections = true;
16349
16350 if moving_cursor {
16351 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
16352 editor.selections.newest::<usize>(cx).head()
16353 });
16354
16355 // Update the selection to match the position of the selection inside
16356 // the rename editor.
16357 let snapshot = self.buffer.read(cx).read(cx);
16358 let rename_range = rename.range.to_offset(&snapshot);
16359 let cursor_in_editor = snapshot
16360 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
16361 .min(rename_range.end);
16362 drop(snapshot);
16363
16364 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16365 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
16366 });
16367 } else {
16368 self.refresh_document_highlights(cx);
16369 }
16370
16371 Some(rename)
16372 }
16373
16374 pub fn pending_rename(&self) -> Option<&RenameState> {
16375 self.pending_rename.as_ref()
16376 }
16377
16378 fn format(
16379 &mut self,
16380 _: &Format,
16381 window: &mut Window,
16382 cx: &mut Context<Self>,
16383 ) -> Option<Task<Result<()>>> {
16384 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16385
16386 let project = match &self.project {
16387 Some(project) => project.clone(),
16388 None => return None,
16389 };
16390
16391 Some(self.perform_format(
16392 project,
16393 FormatTrigger::Manual,
16394 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
16395 window,
16396 cx,
16397 ))
16398 }
16399
16400 fn format_selections(
16401 &mut self,
16402 _: &FormatSelections,
16403 window: &mut Window,
16404 cx: &mut Context<Self>,
16405 ) -> Option<Task<Result<()>>> {
16406 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16407
16408 let project = match &self.project {
16409 Some(project) => project.clone(),
16410 None => return None,
16411 };
16412
16413 let ranges = self
16414 .selections
16415 .all_adjusted(cx)
16416 .into_iter()
16417 .map(|selection| selection.range())
16418 .collect_vec();
16419
16420 Some(self.perform_format(
16421 project,
16422 FormatTrigger::Manual,
16423 FormatTarget::Ranges(ranges),
16424 window,
16425 cx,
16426 ))
16427 }
16428
16429 fn perform_format(
16430 &mut self,
16431 project: Entity<Project>,
16432 trigger: FormatTrigger,
16433 target: FormatTarget,
16434 window: &mut Window,
16435 cx: &mut Context<Self>,
16436 ) -> Task<Result<()>> {
16437 let buffer = self.buffer.clone();
16438 let (buffers, target) = match target {
16439 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
16440 FormatTarget::Ranges(selection_ranges) => {
16441 let multi_buffer = buffer.read(cx);
16442 let snapshot = multi_buffer.read(cx);
16443 let mut buffers = HashSet::default();
16444 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
16445 BTreeMap::new();
16446 for selection_range in selection_ranges {
16447 for (buffer, buffer_range, _) in
16448 snapshot.range_to_buffer_ranges(selection_range)
16449 {
16450 let buffer_id = buffer.remote_id();
16451 let start = buffer.anchor_before(buffer_range.start);
16452 let end = buffer.anchor_after(buffer_range.end);
16453 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
16454 buffer_id_to_ranges
16455 .entry(buffer_id)
16456 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
16457 .or_insert_with(|| vec![start..end]);
16458 }
16459 }
16460 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
16461 }
16462 };
16463
16464 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
16465 let selections_prev = transaction_id_prev
16466 .and_then(|transaction_id_prev| {
16467 // default to selections as they were after the last edit, if we have them,
16468 // instead of how they are now.
16469 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
16470 // will take you back to where you made the last edit, instead of staying where you scrolled
16471 self.selection_history
16472 .transaction(transaction_id_prev)
16473 .map(|t| t.0.clone())
16474 })
16475 .unwrap_or_else(|| {
16476 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
16477 self.selections.disjoint_anchors()
16478 });
16479
16480 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
16481 let format = project.update(cx, |project, cx| {
16482 project.format(buffers, target, true, trigger, cx)
16483 });
16484
16485 cx.spawn_in(window, async move |editor, cx| {
16486 let transaction = futures::select_biased! {
16487 transaction = format.log_err().fuse() => transaction,
16488 () = timeout => {
16489 log::warn!("timed out waiting for formatting");
16490 None
16491 }
16492 };
16493
16494 buffer
16495 .update(cx, |buffer, cx| {
16496 if let Some(transaction) = transaction {
16497 if !buffer.is_singleton() {
16498 buffer.push_transaction(&transaction.0, cx);
16499 }
16500 }
16501 cx.notify();
16502 })
16503 .ok();
16504
16505 if let Some(transaction_id_now) =
16506 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
16507 {
16508 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
16509 if has_new_transaction {
16510 _ = editor.update(cx, |editor, _| {
16511 editor
16512 .selection_history
16513 .insert_transaction(transaction_id_now, selections_prev);
16514 });
16515 }
16516 }
16517
16518 Ok(())
16519 })
16520 }
16521
16522 fn organize_imports(
16523 &mut self,
16524 _: &OrganizeImports,
16525 window: &mut Window,
16526 cx: &mut Context<Self>,
16527 ) -> Option<Task<Result<()>>> {
16528 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16529 let project = match &self.project {
16530 Some(project) => project.clone(),
16531 None => return None,
16532 };
16533 Some(self.perform_code_action_kind(
16534 project,
16535 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
16536 window,
16537 cx,
16538 ))
16539 }
16540
16541 fn perform_code_action_kind(
16542 &mut self,
16543 project: Entity<Project>,
16544 kind: CodeActionKind,
16545 window: &mut Window,
16546 cx: &mut Context<Self>,
16547 ) -> Task<Result<()>> {
16548 let buffer = self.buffer.clone();
16549 let buffers = buffer.read(cx).all_buffers();
16550 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
16551 let apply_action = project.update(cx, |project, cx| {
16552 project.apply_code_action_kind(buffers, kind, true, cx)
16553 });
16554 cx.spawn_in(window, async move |_, cx| {
16555 let transaction = futures::select_biased! {
16556 () = timeout => {
16557 log::warn!("timed out waiting for executing code action");
16558 None
16559 }
16560 transaction = apply_action.log_err().fuse() => transaction,
16561 };
16562 buffer
16563 .update(cx, |buffer, cx| {
16564 // check if we need this
16565 if let Some(transaction) = transaction {
16566 if !buffer.is_singleton() {
16567 buffer.push_transaction(&transaction.0, cx);
16568 }
16569 }
16570 cx.notify();
16571 })
16572 .ok();
16573 Ok(())
16574 })
16575 }
16576
16577 pub fn restart_language_server(
16578 &mut self,
16579 _: &RestartLanguageServer,
16580 _: &mut Window,
16581 cx: &mut Context<Self>,
16582 ) {
16583 if let Some(project) = self.project.clone() {
16584 self.buffer.update(cx, |multi_buffer, cx| {
16585 project.update(cx, |project, cx| {
16586 project.restart_language_servers_for_buffers(
16587 multi_buffer.all_buffers().into_iter().collect(),
16588 HashSet::default(),
16589 cx,
16590 );
16591 });
16592 })
16593 }
16594 }
16595
16596 pub fn stop_language_server(
16597 &mut self,
16598 _: &StopLanguageServer,
16599 _: &mut Window,
16600 cx: &mut Context<Self>,
16601 ) {
16602 if let Some(project) = self.project.clone() {
16603 self.buffer.update(cx, |multi_buffer, cx| {
16604 project.update(cx, |project, cx| {
16605 project.stop_language_servers_for_buffers(
16606 multi_buffer.all_buffers().into_iter().collect(),
16607 HashSet::default(),
16608 cx,
16609 );
16610 cx.emit(project::Event::RefreshInlayHints);
16611 });
16612 });
16613 }
16614 }
16615
16616 fn cancel_language_server_work(
16617 workspace: &mut Workspace,
16618 _: &actions::CancelLanguageServerWork,
16619 _: &mut Window,
16620 cx: &mut Context<Workspace>,
16621 ) {
16622 let project = workspace.project();
16623 let buffers = workspace
16624 .active_item(cx)
16625 .and_then(|item| item.act_as::<Editor>(cx))
16626 .map_or(HashSet::default(), |editor| {
16627 editor.read(cx).buffer.read(cx).all_buffers()
16628 });
16629 project.update(cx, |project, cx| {
16630 project.cancel_language_server_work_for_buffers(buffers, cx);
16631 });
16632 }
16633
16634 fn show_character_palette(
16635 &mut self,
16636 _: &ShowCharacterPalette,
16637 window: &mut Window,
16638 _: &mut Context<Self>,
16639 ) {
16640 window.show_character_palette();
16641 }
16642
16643 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
16644 if !self.diagnostics_enabled() {
16645 return;
16646 }
16647
16648 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
16649 let buffer = self.buffer.read(cx).snapshot(cx);
16650 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
16651 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
16652 let is_valid = buffer
16653 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
16654 .any(|entry| {
16655 entry.diagnostic.is_primary
16656 && !entry.range.is_empty()
16657 && entry.range.start == primary_range_start
16658 && entry.diagnostic.message == active_diagnostics.active_message
16659 });
16660
16661 if !is_valid {
16662 self.dismiss_diagnostics(cx);
16663 }
16664 }
16665 }
16666
16667 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
16668 match &self.active_diagnostics {
16669 ActiveDiagnostic::Group(group) => Some(group),
16670 _ => None,
16671 }
16672 }
16673
16674 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
16675 if !self.diagnostics_enabled() {
16676 return;
16677 }
16678 self.dismiss_diagnostics(cx);
16679 self.active_diagnostics = ActiveDiagnostic::All;
16680 }
16681
16682 fn activate_diagnostics(
16683 &mut self,
16684 buffer_id: BufferId,
16685 diagnostic: DiagnosticEntry<usize>,
16686 window: &mut Window,
16687 cx: &mut Context<Self>,
16688 ) {
16689 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16690 return;
16691 }
16692 self.dismiss_diagnostics(cx);
16693 let snapshot = self.snapshot(window, cx);
16694 let buffer = self.buffer.read(cx).snapshot(cx);
16695 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
16696 return;
16697 };
16698
16699 let diagnostic_group = buffer
16700 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
16701 .collect::<Vec<_>>();
16702
16703 let blocks =
16704 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
16705
16706 let blocks = self.display_map.update(cx, |display_map, cx| {
16707 display_map.insert_blocks(blocks, cx).into_iter().collect()
16708 });
16709 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
16710 active_range: buffer.anchor_before(diagnostic.range.start)
16711 ..buffer.anchor_after(diagnostic.range.end),
16712 active_message: diagnostic.diagnostic.message.clone(),
16713 group_id: diagnostic.diagnostic.group_id,
16714 blocks,
16715 });
16716 cx.notify();
16717 }
16718
16719 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
16720 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16721 return;
16722 };
16723
16724 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
16725 if let ActiveDiagnostic::Group(group) = prev {
16726 self.display_map.update(cx, |display_map, cx| {
16727 display_map.remove_blocks(group.blocks, cx);
16728 });
16729 cx.notify();
16730 }
16731 }
16732
16733 /// Disable inline diagnostics rendering for this editor.
16734 pub fn disable_inline_diagnostics(&mut self) {
16735 self.inline_diagnostics_enabled = false;
16736 self.inline_diagnostics_update = Task::ready(());
16737 self.inline_diagnostics.clear();
16738 }
16739
16740 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
16741 self.diagnostics_enabled = false;
16742 self.dismiss_diagnostics(cx);
16743 self.inline_diagnostics_update = Task::ready(());
16744 self.inline_diagnostics.clear();
16745 }
16746
16747 pub fn diagnostics_enabled(&self) -> bool {
16748 self.diagnostics_enabled && self.mode.is_full()
16749 }
16750
16751 pub fn inline_diagnostics_enabled(&self) -> bool {
16752 self.inline_diagnostics_enabled && self.diagnostics_enabled()
16753 }
16754
16755 pub fn show_inline_diagnostics(&self) -> bool {
16756 self.show_inline_diagnostics
16757 }
16758
16759 pub fn toggle_inline_diagnostics(
16760 &mut self,
16761 _: &ToggleInlineDiagnostics,
16762 window: &mut Window,
16763 cx: &mut Context<Editor>,
16764 ) {
16765 self.show_inline_diagnostics = !self.show_inline_diagnostics;
16766 self.refresh_inline_diagnostics(false, window, cx);
16767 }
16768
16769 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
16770 self.diagnostics_max_severity = severity;
16771 self.display_map.update(cx, |display_map, _| {
16772 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
16773 });
16774 }
16775
16776 pub fn toggle_diagnostics(
16777 &mut self,
16778 _: &ToggleDiagnostics,
16779 window: &mut Window,
16780 cx: &mut Context<Editor>,
16781 ) {
16782 if !self.diagnostics_enabled() {
16783 return;
16784 }
16785
16786 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16787 EditorSettings::get_global(cx)
16788 .diagnostics_max_severity
16789 .filter(|severity| severity != &DiagnosticSeverity::Off)
16790 .unwrap_or(DiagnosticSeverity::Hint)
16791 } else {
16792 DiagnosticSeverity::Off
16793 };
16794 self.set_max_diagnostics_severity(new_severity, cx);
16795 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16796 self.active_diagnostics = ActiveDiagnostic::None;
16797 self.inline_diagnostics_update = Task::ready(());
16798 self.inline_diagnostics.clear();
16799 } else {
16800 self.refresh_inline_diagnostics(false, window, cx);
16801 }
16802
16803 cx.notify();
16804 }
16805
16806 pub fn toggle_minimap(
16807 &mut self,
16808 _: &ToggleMinimap,
16809 window: &mut Window,
16810 cx: &mut Context<Editor>,
16811 ) {
16812 if self.supports_minimap(cx) {
16813 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
16814 }
16815 }
16816
16817 fn refresh_inline_diagnostics(
16818 &mut self,
16819 debounce: bool,
16820 window: &mut Window,
16821 cx: &mut Context<Self>,
16822 ) {
16823 let max_severity = ProjectSettings::get_global(cx)
16824 .diagnostics
16825 .inline
16826 .max_severity
16827 .unwrap_or(self.diagnostics_max_severity);
16828
16829 if !self.inline_diagnostics_enabled()
16830 || !self.show_inline_diagnostics
16831 || max_severity == DiagnosticSeverity::Off
16832 {
16833 self.inline_diagnostics_update = Task::ready(());
16834 self.inline_diagnostics.clear();
16835 return;
16836 }
16837
16838 let debounce_ms = ProjectSettings::get_global(cx)
16839 .diagnostics
16840 .inline
16841 .update_debounce_ms;
16842 let debounce = if debounce && debounce_ms > 0 {
16843 Some(Duration::from_millis(debounce_ms))
16844 } else {
16845 None
16846 };
16847 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
16848 if let Some(debounce) = debounce {
16849 cx.background_executor().timer(debounce).await;
16850 }
16851 let Some(snapshot) = editor.upgrade().and_then(|editor| {
16852 editor
16853 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
16854 .ok()
16855 }) else {
16856 return;
16857 };
16858
16859 let new_inline_diagnostics = cx
16860 .background_spawn(async move {
16861 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
16862 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
16863 let message = diagnostic_entry
16864 .diagnostic
16865 .message
16866 .split_once('\n')
16867 .map(|(line, _)| line)
16868 .map(SharedString::new)
16869 .unwrap_or_else(|| {
16870 SharedString::from(diagnostic_entry.diagnostic.message)
16871 });
16872 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
16873 let (Ok(i) | Err(i)) = inline_diagnostics
16874 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
16875 inline_diagnostics.insert(
16876 i,
16877 (
16878 start_anchor,
16879 InlineDiagnostic {
16880 message,
16881 group_id: diagnostic_entry.diagnostic.group_id,
16882 start: diagnostic_entry.range.start.to_point(&snapshot),
16883 is_primary: diagnostic_entry.diagnostic.is_primary,
16884 severity: diagnostic_entry.diagnostic.severity,
16885 },
16886 ),
16887 );
16888 }
16889 inline_diagnostics
16890 })
16891 .await;
16892
16893 editor
16894 .update(cx, |editor, cx| {
16895 editor.inline_diagnostics = new_inline_diagnostics;
16896 cx.notify();
16897 })
16898 .ok();
16899 });
16900 }
16901
16902 fn pull_diagnostics(
16903 &mut self,
16904 buffer_id: Option<BufferId>,
16905 window: &Window,
16906 cx: &mut Context<Self>,
16907 ) -> Option<()> {
16908 if !self.mode().is_full() {
16909 return None;
16910 }
16911 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
16912 .diagnostics
16913 .lsp_pull_diagnostics;
16914 if !pull_diagnostics_settings.enabled {
16915 return None;
16916 }
16917 let project = self.project.as_ref()?.downgrade();
16918 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
16919 let mut buffers = self.buffer.read(cx).all_buffers();
16920 if let Some(buffer_id) = buffer_id {
16921 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
16922 }
16923
16924 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
16925 cx.background_executor().timer(debounce).await;
16926
16927 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
16928 buffers
16929 .into_iter()
16930 .filter_map(|buffer| {
16931 project
16932 .update(cx, |project, cx| {
16933 project.lsp_store().update(cx, |lsp_store, cx| {
16934 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
16935 })
16936 })
16937 .ok()
16938 })
16939 .collect::<FuturesUnordered<_>>()
16940 }) else {
16941 return;
16942 };
16943
16944 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
16945 match pull_task {
16946 Ok(()) => {
16947 if editor
16948 .update_in(cx, |editor, window, cx| {
16949 editor.update_diagnostics_state(window, cx);
16950 })
16951 .is_err()
16952 {
16953 return;
16954 }
16955 }
16956 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
16957 }
16958 }
16959 });
16960
16961 Some(())
16962 }
16963
16964 pub fn set_selections_from_remote(
16965 &mut self,
16966 selections: Vec<Selection<Anchor>>,
16967 pending_selection: Option<Selection<Anchor>>,
16968 window: &mut Window,
16969 cx: &mut Context<Self>,
16970 ) {
16971 let old_cursor_position = self.selections.newest_anchor().head();
16972 self.selections.change_with(cx, |s| {
16973 s.select_anchors(selections);
16974 if let Some(pending_selection) = pending_selection {
16975 s.set_pending(pending_selection, SelectMode::Character);
16976 } else {
16977 s.clear_pending();
16978 }
16979 });
16980 self.selections_did_change(
16981 false,
16982 &old_cursor_position,
16983 SelectionEffects::default(),
16984 window,
16985 cx,
16986 );
16987 }
16988
16989 pub fn transact(
16990 &mut self,
16991 window: &mut Window,
16992 cx: &mut Context<Self>,
16993 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
16994 ) -> Option<TransactionId> {
16995 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16996 this.start_transaction_at(Instant::now(), window, cx);
16997 update(this, window, cx);
16998 this.end_transaction_at(Instant::now(), cx)
16999 })
17000 }
17001
17002 pub fn start_transaction_at(
17003 &mut self,
17004 now: Instant,
17005 window: &mut Window,
17006 cx: &mut Context<Self>,
17007 ) -> Option<TransactionId> {
17008 self.end_selection(window, cx);
17009 if let Some(tx_id) = self
17010 .buffer
17011 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
17012 {
17013 self.selection_history
17014 .insert_transaction(tx_id, self.selections.disjoint_anchors());
17015 cx.emit(EditorEvent::TransactionBegun {
17016 transaction_id: tx_id,
17017 });
17018 Some(tx_id)
17019 } else {
17020 None
17021 }
17022 }
17023
17024 pub fn end_transaction_at(
17025 &mut self,
17026 now: Instant,
17027 cx: &mut Context<Self>,
17028 ) -> Option<TransactionId> {
17029 if let Some(transaction_id) = self
17030 .buffer
17031 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
17032 {
17033 if let Some((_, end_selections)) =
17034 self.selection_history.transaction_mut(transaction_id)
17035 {
17036 *end_selections = Some(self.selections.disjoint_anchors());
17037 } else {
17038 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
17039 }
17040
17041 cx.emit(EditorEvent::Edited { transaction_id });
17042 Some(transaction_id)
17043 } else {
17044 None
17045 }
17046 }
17047
17048 pub fn modify_transaction_selection_history(
17049 &mut self,
17050 transaction_id: TransactionId,
17051 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
17052 ) -> bool {
17053 self.selection_history
17054 .transaction_mut(transaction_id)
17055 .map(modify)
17056 .is_some()
17057 }
17058
17059 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
17060 if self.selection_mark_mode {
17061 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17062 s.move_with(|_, sel| {
17063 sel.collapse_to(sel.head(), SelectionGoal::None);
17064 });
17065 })
17066 }
17067 self.selection_mark_mode = true;
17068 cx.notify();
17069 }
17070
17071 pub fn swap_selection_ends(
17072 &mut self,
17073 _: &actions::SwapSelectionEnds,
17074 window: &mut Window,
17075 cx: &mut Context<Self>,
17076 ) {
17077 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17078 s.move_with(|_, sel| {
17079 if sel.start != sel.end {
17080 sel.reversed = !sel.reversed
17081 }
17082 });
17083 });
17084 self.request_autoscroll(Autoscroll::newest(), cx);
17085 cx.notify();
17086 }
17087
17088 pub fn toggle_focus(
17089 workspace: &mut Workspace,
17090 _: &actions::ToggleFocus,
17091 window: &mut Window,
17092 cx: &mut Context<Workspace>,
17093 ) {
17094 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
17095 return;
17096 };
17097 workspace.activate_item(&item, true, true, window, cx);
17098 }
17099
17100 pub fn toggle_fold(
17101 &mut self,
17102 _: &actions::ToggleFold,
17103 window: &mut Window,
17104 cx: &mut Context<Self>,
17105 ) {
17106 if self.is_singleton(cx) {
17107 let selection = self.selections.newest::<Point>(cx);
17108
17109 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17110 let range = if selection.is_empty() {
17111 let point = selection.head().to_display_point(&display_map);
17112 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17113 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17114 .to_point(&display_map);
17115 start..end
17116 } else {
17117 selection.range()
17118 };
17119 if display_map.folds_in_range(range).next().is_some() {
17120 self.unfold_lines(&Default::default(), window, cx)
17121 } else {
17122 self.fold(&Default::default(), window, cx)
17123 }
17124 } else {
17125 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17126 let buffer_ids: HashSet<_> = self
17127 .selections
17128 .disjoint_anchor_ranges()
17129 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17130 .collect();
17131
17132 let should_unfold = buffer_ids
17133 .iter()
17134 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17135
17136 for buffer_id in buffer_ids {
17137 if should_unfold {
17138 self.unfold_buffer(buffer_id, cx);
17139 } else {
17140 self.fold_buffer(buffer_id, cx);
17141 }
17142 }
17143 }
17144 }
17145
17146 pub fn toggle_fold_recursive(
17147 &mut self,
17148 _: &actions::ToggleFoldRecursive,
17149 window: &mut Window,
17150 cx: &mut Context<Self>,
17151 ) {
17152 let selection = self.selections.newest::<Point>(cx);
17153
17154 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17155 let range = if selection.is_empty() {
17156 let point = selection.head().to_display_point(&display_map);
17157 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17158 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17159 .to_point(&display_map);
17160 start..end
17161 } else {
17162 selection.range()
17163 };
17164 if display_map.folds_in_range(range).next().is_some() {
17165 self.unfold_recursive(&Default::default(), window, cx)
17166 } else {
17167 self.fold_recursive(&Default::default(), window, cx)
17168 }
17169 }
17170
17171 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
17172 if self.is_singleton(cx) {
17173 let mut to_fold = Vec::new();
17174 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17175 let selections = self.selections.all_adjusted(cx);
17176
17177 for selection in selections {
17178 let range = selection.range().sorted();
17179 let buffer_start_row = range.start.row;
17180
17181 if range.start.row != range.end.row {
17182 let mut found = false;
17183 let mut row = range.start.row;
17184 while row <= range.end.row {
17185 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
17186 {
17187 found = true;
17188 row = crease.range().end.row + 1;
17189 to_fold.push(crease);
17190 } else {
17191 row += 1
17192 }
17193 }
17194 if found {
17195 continue;
17196 }
17197 }
17198
17199 for row in (0..=range.start.row).rev() {
17200 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17201 if crease.range().end.row >= buffer_start_row {
17202 to_fold.push(crease);
17203 if row <= range.start.row {
17204 break;
17205 }
17206 }
17207 }
17208 }
17209 }
17210
17211 self.fold_creases(to_fold, true, window, cx);
17212 } else {
17213 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17214 let buffer_ids = self
17215 .selections
17216 .disjoint_anchor_ranges()
17217 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17218 .collect::<HashSet<_>>();
17219 for buffer_id in buffer_ids {
17220 self.fold_buffer(buffer_id, cx);
17221 }
17222 }
17223 }
17224
17225 pub fn toggle_fold_all(
17226 &mut self,
17227 _: &actions::ToggleFoldAll,
17228 window: &mut Window,
17229 cx: &mut Context<Self>,
17230 ) {
17231 if self.buffer.read(cx).is_singleton() {
17232 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17233 let has_folds = display_map
17234 .folds_in_range(0..display_map.buffer_snapshot.len())
17235 .next()
17236 .is_some();
17237
17238 if has_folds {
17239 self.unfold_all(&actions::UnfoldAll, window, cx);
17240 } else {
17241 self.fold_all(&actions::FoldAll, window, cx);
17242 }
17243 } else {
17244 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
17245 let should_unfold = buffer_ids
17246 .iter()
17247 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17248
17249 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17250 editor
17251 .update_in(cx, |editor, _, cx| {
17252 for buffer_id in buffer_ids {
17253 if should_unfold {
17254 editor.unfold_buffer(buffer_id, cx);
17255 } else {
17256 editor.fold_buffer(buffer_id, cx);
17257 }
17258 }
17259 })
17260 .ok();
17261 });
17262 }
17263 }
17264
17265 fn fold_at_level(
17266 &mut self,
17267 fold_at: &FoldAtLevel,
17268 window: &mut Window,
17269 cx: &mut Context<Self>,
17270 ) {
17271 if !self.buffer.read(cx).is_singleton() {
17272 return;
17273 }
17274
17275 let fold_at_level = fold_at.0;
17276 let snapshot = self.buffer.read(cx).snapshot(cx);
17277 let mut to_fold = Vec::new();
17278 let mut stack = vec![(0, snapshot.max_row().0, 1)];
17279
17280 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
17281 while start_row < end_row {
17282 match self
17283 .snapshot(window, cx)
17284 .crease_for_buffer_row(MultiBufferRow(start_row))
17285 {
17286 Some(crease) => {
17287 let nested_start_row = crease.range().start.row + 1;
17288 let nested_end_row = crease.range().end.row;
17289
17290 if current_level < fold_at_level {
17291 stack.push((nested_start_row, nested_end_row, current_level + 1));
17292 } else if current_level == fold_at_level {
17293 to_fold.push(crease);
17294 }
17295
17296 start_row = nested_end_row + 1;
17297 }
17298 None => start_row += 1,
17299 }
17300 }
17301 }
17302
17303 self.fold_creases(to_fold, true, window, cx);
17304 }
17305
17306 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
17307 if self.buffer.read(cx).is_singleton() {
17308 let mut fold_ranges = Vec::new();
17309 let snapshot = self.buffer.read(cx).snapshot(cx);
17310
17311 for row in 0..snapshot.max_row().0 {
17312 if let Some(foldable_range) = self
17313 .snapshot(window, cx)
17314 .crease_for_buffer_row(MultiBufferRow(row))
17315 {
17316 fold_ranges.push(foldable_range);
17317 }
17318 }
17319
17320 self.fold_creases(fold_ranges, true, window, cx);
17321 } else {
17322 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17323 editor
17324 .update_in(cx, |editor, _, cx| {
17325 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17326 editor.fold_buffer(buffer_id, cx);
17327 }
17328 })
17329 .ok();
17330 });
17331 }
17332 }
17333
17334 pub fn fold_function_bodies(
17335 &mut self,
17336 _: &actions::FoldFunctionBodies,
17337 window: &mut Window,
17338 cx: &mut Context<Self>,
17339 ) {
17340 let snapshot = self.buffer.read(cx).snapshot(cx);
17341
17342 let ranges = snapshot
17343 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
17344 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
17345 .collect::<Vec<_>>();
17346
17347 let creases = ranges
17348 .into_iter()
17349 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
17350 .collect();
17351
17352 self.fold_creases(creases, true, window, cx);
17353 }
17354
17355 pub fn fold_recursive(
17356 &mut self,
17357 _: &actions::FoldRecursive,
17358 window: &mut Window,
17359 cx: &mut Context<Self>,
17360 ) {
17361 let mut to_fold = Vec::new();
17362 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17363 let selections = self.selections.all_adjusted(cx);
17364
17365 for selection in selections {
17366 let range = selection.range().sorted();
17367 let buffer_start_row = range.start.row;
17368
17369 if range.start.row != range.end.row {
17370 let mut found = false;
17371 for row in range.start.row..=range.end.row {
17372 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17373 found = true;
17374 to_fold.push(crease);
17375 }
17376 }
17377 if found {
17378 continue;
17379 }
17380 }
17381
17382 for row in (0..=range.start.row).rev() {
17383 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17384 if crease.range().end.row >= buffer_start_row {
17385 to_fold.push(crease);
17386 } else {
17387 break;
17388 }
17389 }
17390 }
17391 }
17392
17393 self.fold_creases(to_fold, true, window, cx);
17394 }
17395
17396 pub fn fold_at(
17397 &mut self,
17398 buffer_row: MultiBufferRow,
17399 window: &mut Window,
17400 cx: &mut Context<Self>,
17401 ) {
17402 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17403
17404 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
17405 let autoscroll = self
17406 .selections
17407 .all::<Point>(cx)
17408 .iter()
17409 .any(|selection| crease.range().overlaps(&selection.range()));
17410
17411 self.fold_creases(vec![crease], autoscroll, window, cx);
17412 }
17413 }
17414
17415 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
17416 if self.is_singleton(cx) {
17417 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17418 let buffer = &display_map.buffer_snapshot;
17419 let selections = self.selections.all::<Point>(cx);
17420 let ranges = selections
17421 .iter()
17422 .map(|s| {
17423 let range = s.display_range(&display_map).sorted();
17424 let mut start = range.start.to_point(&display_map);
17425 let mut end = range.end.to_point(&display_map);
17426 start.column = 0;
17427 end.column = buffer.line_len(MultiBufferRow(end.row));
17428 start..end
17429 })
17430 .collect::<Vec<_>>();
17431
17432 self.unfold_ranges(&ranges, true, true, cx);
17433 } else {
17434 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17435 let buffer_ids = self
17436 .selections
17437 .disjoint_anchor_ranges()
17438 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17439 .collect::<HashSet<_>>();
17440 for buffer_id in buffer_ids {
17441 self.unfold_buffer(buffer_id, cx);
17442 }
17443 }
17444 }
17445
17446 pub fn unfold_recursive(
17447 &mut self,
17448 _: &UnfoldRecursive,
17449 _window: &mut Window,
17450 cx: &mut Context<Self>,
17451 ) {
17452 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17453 let selections = self.selections.all::<Point>(cx);
17454 let ranges = selections
17455 .iter()
17456 .map(|s| {
17457 let mut range = s.display_range(&display_map).sorted();
17458 *range.start.column_mut() = 0;
17459 *range.end.column_mut() = display_map.line_len(range.end.row());
17460 let start = range.start.to_point(&display_map);
17461 let end = range.end.to_point(&display_map);
17462 start..end
17463 })
17464 .collect::<Vec<_>>();
17465
17466 self.unfold_ranges(&ranges, true, true, cx);
17467 }
17468
17469 pub fn unfold_at(
17470 &mut self,
17471 buffer_row: MultiBufferRow,
17472 _window: &mut Window,
17473 cx: &mut Context<Self>,
17474 ) {
17475 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17476
17477 let intersection_range = Point::new(buffer_row.0, 0)
17478 ..Point::new(
17479 buffer_row.0,
17480 display_map.buffer_snapshot.line_len(buffer_row),
17481 );
17482
17483 let autoscroll = self
17484 .selections
17485 .all::<Point>(cx)
17486 .iter()
17487 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
17488
17489 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
17490 }
17491
17492 pub fn unfold_all(
17493 &mut self,
17494 _: &actions::UnfoldAll,
17495 _window: &mut Window,
17496 cx: &mut Context<Self>,
17497 ) {
17498 if self.buffer.read(cx).is_singleton() {
17499 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17500 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
17501 } else {
17502 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
17503 editor
17504 .update(cx, |editor, cx| {
17505 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17506 editor.unfold_buffer(buffer_id, cx);
17507 }
17508 })
17509 .ok();
17510 });
17511 }
17512 }
17513
17514 pub fn fold_selected_ranges(
17515 &mut self,
17516 _: &FoldSelectedRanges,
17517 window: &mut Window,
17518 cx: &mut Context<Self>,
17519 ) {
17520 let selections = self.selections.all_adjusted(cx);
17521 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17522 let ranges = selections
17523 .into_iter()
17524 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
17525 .collect::<Vec<_>>();
17526 self.fold_creases(ranges, true, window, cx);
17527 }
17528
17529 pub fn fold_ranges<T: ToOffset + Clone>(
17530 &mut self,
17531 ranges: Vec<Range<T>>,
17532 auto_scroll: bool,
17533 window: &mut Window,
17534 cx: &mut Context<Self>,
17535 ) {
17536 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17537 let ranges = ranges
17538 .into_iter()
17539 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
17540 .collect::<Vec<_>>();
17541 self.fold_creases(ranges, auto_scroll, window, cx);
17542 }
17543
17544 pub fn fold_creases<T: ToOffset + Clone>(
17545 &mut self,
17546 creases: Vec<Crease<T>>,
17547 auto_scroll: bool,
17548 _window: &mut Window,
17549 cx: &mut Context<Self>,
17550 ) {
17551 if creases.is_empty() {
17552 return;
17553 }
17554
17555 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
17556
17557 if auto_scroll {
17558 self.request_autoscroll(Autoscroll::fit(), cx);
17559 }
17560
17561 cx.notify();
17562
17563 self.scrollbar_marker_state.dirty = true;
17564 self.folds_did_change(cx);
17565 }
17566
17567 /// Removes any folds whose ranges intersect any of the given ranges.
17568 pub fn unfold_ranges<T: ToOffset + Clone>(
17569 &mut self,
17570 ranges: &[Range<T>],
17571 inclusive: bool,
17572 auto_scroll: bool,
17573 cx: &mut Context<Self>,
17574 ) {
17575 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17576 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
17577 });
17578 self.folds_did_change(cx);
17579 }
17580
17581 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17582 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
17583 return;
17584 }
17585 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17586 self.display_map.update(cx, |display_map, cx| {
17587 display_map.fold_buffers([buffer_id], cx)
17588 });
17589 cx.emit(EditorEvent::BufferFoldToggled {
17590 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
17591 folded: true,
17592 });
17593 cx.notify();
17594 }
17595
17596 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17597 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
17598 return;
17599 }
17600 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17601 self.display_map.update(cx, |display_map, cx| {
17602 display_map.unfold_buffers([buffer_id], cx);
17603 });
17604 cx.emit(EditorEvent::BufferFoldToggled {
17605 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
17606 folded: false,
17607 });
17608 cx.notify();
17609 }
17610
17611 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
17612 self.display_map.read(cx).is_buffer_folded(buffer)
17613 }
17614
17615 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
17616 self.display_map.read(cx).folded_buffers()
17617 }
17618
17619 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17620 self.display_map.update(cx, |display_map, cx| {
17621 display_map.disable_header_for_buffer(buffer_id, cx);
17622 });
17623 cx.notify();
17624 }
17625
17626 /// Removes any folds with the given ranges.
17627 pub fn remove_folds_with_type<T: ToOffset + Clone>(
17628 &mut self,
17629 ranges: &[Range<T>],
17630 type_id: TypeId,
17631 auto_scroll: bool,
17632 cx: &mut Context<Self>,
17633 ) {
17634 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17635 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
17636 });
17637 self.folds_did_change(cx);
17638 }
17639
17640 fn remove_folds_with<T: ToOffset + Clone>(
17641 &mut self,
17642 ranges: &[Range<T>],
17643 auto_scroll: bool,
17644 cx: &mut Context<Self>,
17645 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
17646 ) {
17647 if ranges.is_empty() {
17648 return;
17649 }
17650
17651 let mut buffers_affected = HashSet::default();
17652 let multi_buffer = self.buffer().read(cx);
17653 for range in ranges {
17654 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
17655 buffers_affected.insert(buffer.read(cx).remote_id());
17656 };
17657 }
17658
17659 self.display_map.update(cx, update);
17660
17661 if auto_scroll {
17662 self.request_autoscroll(Autoscroll::fit(), cx);
17663 }
17664
17665 cx.notify();
17666 self.scrollbar_marker_state.dirty = true;
17667 self.active_indent_guides_state.dirty = true;
17668 }
17669
17670 pub fn update_renderer_widths(
17671 &mut self,
17672 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
17673 cx: &mut Context<Self>,
17674 ) -> bool {
17675 self.display_map
17676 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
17677 }
17678
17679 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
17680 self.display_map.read(cx).fold_placeholder.clone()
17681 }
17682
17683 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
17684 self.buffer.update(cx, |buffer, cx| {
17685 buffer.set_all_diff_hunks_expanded(cx);
17686 });
17687 }
17688
17689 pub fn expand_all_diff_hunks(
17690 &mut self,
17691 _: &ExpandAllDiffHunks,
17692 _window: &mut Window,
17693 cx: &mut Context<Self>,
17694 ) {
17695 self.buffer.update(cx, |buffer, cx| {
17696 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
17697 });
17698 }
17699
17700 pub fn toggle_selected_diff_hunks(
17701 &mut self,
17702 _: &ToggleSelectedDiffHunks,
17703 _window: &mut Window,
17704 cx: &mut Context<Self>,
17705 ) {
17706 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17707 self.toggle_diff_hunks_in_ranges(ranges, cx);
17708 }
17709
17710 pub fn diff_hunks_in_ranges<'a>(
17711 &'a self,
17712 ranges: &'a [Range<Anchor>],
17713 buffer: &'a MultiBufferSnapshot,
17714 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
17715 ranges.iter().flat_map(move |range| {
17716 let end_excerpt_id = range.end.excerpt_id;
17717 let range = range.to_point(buffer);
17718 let mut peek_end = range.end;
17719 if range.end.row < buffer.max_row().0 {
17720 peek_end = Point::new(range.end.row + 1, 0);
17721 }
17722 buffer
17723 .diff_hunks_in_range(range.start..peek_end)
17724 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
17725 })
17726 }
17727
17728 pub fn has_stageable_diff_hunks_in_ranges(
17729 &self,
17730 ranges: &[Range<Anchor>],
17731 snapshot: &MultiBufferSnapshot,
17732 ) -> bool {
17733 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
17734 hunks.any(|hunk| hunk.status().has_secondary_hunk())
17735 }
17736
17737 pub fn toggle_staged_selected_diff_hunks(
17738 &mut self,
17739 _: &::git::ToggleStaged,
17740 _: &mut Window,
17741 cx: &mut Context<Self>,
17742 ) {
17743 let snapshot = self.buffer.read(cx).snapshot(cx);
17744 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17745 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
17746 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17747 }
17748
17749 pub fn set_render_diff_hunk_controls(
17750 &mut self,
17751 render_diff_hunk_controls: RenderDiffHunkControlsFn,
17752 cx: &mut Context<Self>,
17753 ) {
17754 self.render_diff_hunk_controls = render_diff_hunk_controls;
17755 cx.notify();
17756 }
17757
17758 pub fn stage_and_next(
17759 &mut self,
17760 _: &::git::StageAndNext,
17761 window: &mut Window,
17762 cx: &mut Context<Self>,
17763 ) {
17764 self.do_stage_or_unstage_and_next(true, window, cx);
17765 }
17766
17767 pub fn unstage_and_next(
17768 &mut self,
17769 _: &::git::UnstageAndNext,
17770 window: &mut Window,
17771 cx: &mut Context<Self>,
17772 ) {
17773 self.do_stage_or_unstage_and_next(false, window, cx);
17774 }
17775
17776 pub fn stage_or_unstage_diff_hunks(
17777 &mut self,
17778 stage: bool,
17779 ranges: Vec<Range<Anchor>>,
17780 cx: &mut Context<Self>,
17781 ) {
17782 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
17783 cx.spawn(async move |this, cx| {
17784 task.await?;
17785 this.update(cx, |this, cx| {
17786 let snapshot = this.buffer.read(cx).snapshot(cx);
17787 let chunk_by = this
17788 .diff_hunks_in_ranges(&ranges, &snapshot)
17789 .chunk_by(|hunk| hunk.buffer_id);
17790 for (buffer_id, hunks) in &chunk_by {
17791 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
17792 }
17793 })
17794 })
17795 .detach_and_log_err(cx);
17796 }
17797
17798 fn save_buffers_for_ranges_if_needed(
17799 &mut self,
17800 ranges: &[Range<Anchor>],
17801 cx: &mut Context<Editor>,
17802 ) -> Task<Result<()>> {
17803 let multibuffer = self.buffer.read(cx);
17804 let snapshot = multibuffer.read(cx);
17805 let buffer_ids: HashSet<_> = ranges
17806 .iter()
17807 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
17808 .collect();
17809 drop(snapshot);
17810
17811 let mut buffers = HashSet::default();
17812 for buffer_id in buffer_ids {
17813 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
17814 let buffer = buffer_entity.read(cx);
17815 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
17816 {
17817 buffers.insert(buffer_entity);
17818 }
17819 }
17820 }
17821
17822 if let Some(project) = &self.project {
17823 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
17824 } else {
17825 Task::ready(Ok(()))
17826 }
17827 }
17828
17829 fn do_stage_or_unstage_and_next(
17830 &mut self,
17831 stage: bool,
17832 window: &mut Window,
17833 cx: &mut Context<Self>,
17834 ) {
17835 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
17836
17837 if ranges.iter().any(|range| range.start != range.end) {
17838 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17839 return;
17840 }
17841
17842 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17843 let snapshot = self.snapshot(window, cx);
17844 let position = self.selections.newest::<Point>(cx).head();
17845 let mut row = snapshot
17846 .buffer_snapshot
17847 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
17848 .find(|hunk| hunk.row_range.start.0 > position.row)
17849 .map(|hunk| hunk.row_range.start);
17850
17851 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
17852 // Outside of the project diff editor, wrap around to the beginning.
17853 if !all_diff_hunks_expanded {
17854 row = row.or_else(|| {
17855 snapshot
17856 .buffer_snapshot
17857 .diff_hunks_in_range(Point::zero()..position)
17858 .find(|hunk| hunk.row_range.end.0 < position.row)
17859 .map(|hunk| hunk.row_range.start)
17860 });
17861 }
17862
17863 if let Some(row) = row {
17864 let destination = Point::new(row.0, 0);
17865 let autoscroll = Autoscroll::center();
17866
17867 self.unfold_ranges(&[destination..destination], false, false, cx);
17868 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17869 s.select_ranges([destination..destination]);
17870 });
17871 }
17872 }
17873
17874 fn do_stage_or_unstage(
17875 &self,
17876 stage: bool,
17877 buffer_id: BufferId,
17878 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
17879 cx: &mut App,
17880 ) -> Option<()> {
17881 let project = self.project.as_ref()?;
17882 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
17883 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
17884 let buffer_snapshot = buffer.read(cx).snapshot();
17885 let file_exists = buffer_snapshot
17886 .file()
17887 .is_some_and(|file| file.disk_state().exists());
17888 diff.update(cx, |diff, cx| {
17889 diff.stage_or_unstage_hunks(
17890 stage,
17891 &hunks
17892 .map(|hunk| buffer_diff::DiffHunk {
17893 buffer_range: hunk.buffer_range,
17894 diff_base_byte_range: hunk.diff_base_byte_range,
17895 secondary_status: hunk.secondary_status,
17896 range: Point::zero()..Point::zero(), // unused
17897 })
17898 .collect::<Vec<_>>(),
17899 &buffer_snapshot,
17900 file_exists,
17901 cx,
17902 )
17903 });
17904 None
17905 }
17906
17907 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
17908 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17909 self.buffer
17910 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
17911 }
17912
17913 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
17914 self.buffer.update(cx, |buffer, cx| {
17915 let ranges = vec![Anchor::min()..Anchor::max()];
17916 if !buffer.all_diff_hunks_expanded()
17917 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
17918 {
17919 buffer.collapse_diff_hunks(ranges, cx);
17920 true
17921 } else {
17922 false
17923 }
17924 })
17925 }
17926
17927 fn toggle_diff_hunks_in_ranges(
17928 &mut self,
17929 ranges: Vec<Range<Anchor>>,
17930 cx: &mut Context<Editor>,
17931 ) {
17932 self.buffer.update(cx, |buffer, cx| {
17933 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
17934 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
17935 })
17936 }
17937
17938 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
17939 self.buffer.update(cx, |buffer, cx| {
17940 let snapshot = buffer.snapshot(cx);
17941 let excerpt_id = range.end.excerpt_id;
17942 let point_range = range.to_point(&snapshot);
17943 let expand = !buffer.single_hunk_is_expanded(range, cx);
17944 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
17945 })
17946 }
17947
17948 pub(crate) fn apply_all_diff_hunks(
17949 &mut self,
17950 _: &ApplyAllDiffHunks,
17951 window: &mut Window,
17952 cx: &mut Context<Self>,
17953 ) {
17954 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17955
17956 let buffers = self.buffer.read(cx).all_buffers();
17957 for branch_buffer in buffers {
17958 branch_buffer.update(cx, |branch_buffer, cx| {
17959 branch_buffer.merge_into_base(Vec::new(), cx);
17960 });
17961 }
17962
17963 if let Some(project) = self.project.clone() {
17964 self.save(
17965 SaveOptions {
17966 format: true,
17967 autosave: false,
17968 },
17969 project,
17970 window,
17971 cx,
17972 )
17973 .detach_and_log_err(cx);
17974 }
17975 }
17976
17977 pub(crate) fn apply_selected_diff_hunks(
17978 &mut self,
17979 _: &ApplyDiffHunk,
17980 window: &mut Window,
17981 cx: &mut Context<Self>,
17982 ) {
17983 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17984 let snapshot = self.snapshot(window, cx);
17985 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
17986 let mut ranges_by_buffer = HashMap::default();
17987 self.transact(window, cx, |editor, _window, cx| {
17988 for hunk in hunks {
17989 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
17990 ranges_by_buffer
17991 .entry(buffer.clone())
17992 .or_insert_with(Vec::new)
17993 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
17994 }
17995 }
17996
17997 for (buffer, ranges) in ranges_by_buffer {
17998 buffer.update(cx, |buffer, cx| {
17999 buffer.merge_into_base(ranges, cx);
18000 });
18001 }
18002 });
18003
18004 if let Some(project) = self.project.clone() {
18005 self.save(
18006 SaveOptions {
18007 format: true,
18008 autosave: false,
18009 },
18010 project,
18011 window,
18012 cx,
18013 )
18014 .detach_and_log_err(cx);
18015 }
18016 }
18017
18018 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
18019 if hovered != self.gutter_hovered {
18020 self.gutter_hovered = hovered;
18021 cx.notify();
18022 }
18023 }
18024
18025 pub fn insert_blocks(
18026 &mut self,
18027 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
18028 autoscroll: Option<Autoscroll>,
18029 cx: &mut Context<Self>,
18030 ) -> Vec<CustomBlockId> {
18031 let blocks = self
18032 .display_map
18033 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
18034 if let Some(autoscroll) = autoscroll {
18035 self.request_autoscroll(autoscroll, cx);
18036 }
18037 cx.notify();
18038 blocks
18039 }
18040
18041 pub fn resize_blocks(
18042 &mut self,
18043 heights: HashMap<CustomBlockId, u32>,
18044 autoscroll: Option<Autoscroll>,
18045 cx: &mut Context<Self>,
18046 ) {
18047 self.display_map
18048 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
18049 if let Some(autoscroll) = autoscroll {
18050 self.request_autoscroll(autoscroll, cx);
18051 }
18052 cx.notify();
18053 }
18054
18055 pub fn replace_blocks(
18056 &mut self,
18057 renderers: HashMap<CustomBlockId, RenderBlock>,
18058 autoscroll: Option<Autoscroll>,
18059 cx: &mut Context<Self>,
18060 ) {
18061 self.display_map
18062 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
18063 if let Some(autoscroll) = autoscroll {
18064 self.request_autoscroll(autoscroll, cx);
18065 }
18066 cx.notify();
18067 }
18068
18069 pub fn remove_blocks(
18070 &mut self,
18071 block_ids: HashSet<CustomBlockId>,
18072 autoscroll: Option<Autoscroll>,
18073 cx: &mut Context<Self>,
18074 ) {
18075 self.display_map.update(cx, |display_map, cx| {
18076 display_map.remove_blocks(block_ids, cx)
18077 });
18078 if let Some(autoscroll) = autoscroll {
18079 self.request_autoscroll(autoscroll, cx);
18080 }
18081 cx.notify();
18082 }
18083
18084 pub fn row_for_block(
18085 &self,
18086 block_id: CustomBlockId,
18087 cx: &mut Context<Self>,
18088 ) -> Option<DisplayRow> {
18089 self.display_map
18090 .update(cx, |map, cx| map.row_for_block(block_id, cx))
18091 }
18092
18093 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
18094 self.focused_block = Some(focused_block);
18095 }
18096
18097 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
18098 self.focused_block.take()
18099 }
18100
18101 pub fn insert_creases(
18102 &mut self,
18103 creases: impl IntoIterator<Item = Crease<Anchor>>,
18104 cx: &mut Context<Self>,
18105 ) -> Vec<CreaseId> {
18106 self.display_map
18107 .update(cx, |map, cx| map.insert_creases(creases, cx))
18108 }
18109
18110 pub fn remove_creases(
18111 &mut self,
18112 ids: impl IntoIterator<Item = CreaseId>,
18113 cx: &mut Context<Self>,
18114 ) -> Vec<(CreaseId, Range<Anchor>)> {
18115 self.display_map
18116 .update(cx, |map, cx| map.remove_creases(ids, cx))
18117 }
18118
18119 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
18120 self.display_map
18121 .update(cx, |map, cx| map.snapshot(cx))
18122 .longest_row()
18123 }
18124
18125 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
18126 self.display_map
18127 .update(cx, |map, cx| map.snapshot(cx))
18128 .max_point()
18129 }
18130
18131 pub fn text(&self, cx: &App) -> String {
18132 self.buffer.read(cx).read(cx).text()
18133 }
18134
18135 pub fn is_empty(&self, cx: &App) -> bool {
18136 self.buffer.read(cx).read(cx).is_empty()
18137 }
18138
18139 pub fn text_option(&self, cx: &App) -> Option<String> {
18140 let text = self.text(cx);
18141 let text = text.trim();
18142
18143 if text.is_empty() {
18144 return None;
18145 }
18146
18147 Some(text.to_string())
18148 }
18149
18150 pub fn set_text(
18151 &mut self,
18152 text: impl Into<Arc<str>>,
18153 window: &mut Window,
18154 cx: &mut Context<Self>,
18155 ) {
18156 self.transact(window, cx, |this, _, cx| {
18157 this.buffer
18158 .read(cx)
18159 .as_singleton()
18160 .expect("you can only call set_text on editors for singleton buffers")
18161 .update(cx, |buffer, cx| buffer.set_text(text, cx));
18162 });
18163 }
18164
18165 pub fn display_text(&self, cx: &mut App) -> String {
18166 self.display_map
18167 .update(cx, |map, cx| map.snapshot(cx))
18168 .text()
18169 }
18170
18171 fn create_minimap(
18172 &self,
18173 minimap_settings: MinimapSettings,
18174 window: &mut Window,
18175 cx: &mut Context<Self>,
18176 ) -> Option<Entity<Self>> {
18177 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
18178 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
18179 }
18180
18181 fn initialize_new_minimap(
18182 &self,
18183 minimap_settings: MinimapSettings,
18184 window: &mut Window,
18185 cx: &mut Context<Self>,
18186 ) -> Entity<Self> {
18187 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
18188
18189 let mut minimap = Editor::new_internal(
18190 EditorMode::Minimap {
18191 parent: cx.weak_entity(),
18192 },
18193 self.buffer.clone(),
18194 None,
18195 Some(self.display_map.clone()),
18196 window,
18197 cx,
18198 );
18199 minimap.scroll_manager.clone_state(&self.scroll_manager);
18200 minimap.set_text_style_refinement(TextStyleRefinement {
18201 font_size: Some(MINIMAP_FONT_SIZE),
18202 font_weight: Some(MINIMAP_FONT_WEIGHT),
18203 ..Default::default()
18204 });
18205 minimap.update_minimap_configuration(minimap_settings, cx);
18206 cx.new(|_| minimap)
18207 }
18208
18209 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
18210 let current_line_highlight = minimap_settings
18211 .current_line_highlight
18212 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
18213 self.set_current_line_highlight(Some(current_line_highlight));
18214 }
18215
18216 pub fn minimap(&self) -> Option<&Entity<Self>> {
18217 self.minimap
18218 .as_ref()
18219 .filter(|_| self.minimap_visibility.visible())
18220 }
18221
18222 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
18223 let mut wrap_guides = smallvec![];
18224
18225 if self.show_wrap_guides == Some(false) {
18226 return wrap_guides;
18227 }
18228
18229 let settings = self.buffer.read(cx).language_settings(cx);
18230 if settings.show_wrap_guides {
18231 match self.soft_wrap_mode(cx) {
18232 SoftWrap::Column(soft_wrap) => {
18233 wrap_guides.push((soft_wrap as usize, true));
18234 }
18235 SoftWrap::Bounded(soft_wrap) => {
18236 wrap_guides.push((soft_wrap as usize, true));
18237 }
18238 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
18239 }
18240 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
18241 }
18242
18243 wrap_guides
18244 }
18245
18246 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
18247 let settings = self.buffer.read(cx).language_settings(cx);
18248 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
18249 match mode {
18250 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
18251 SoftWrap::None
18252 }
18253 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
18254 language_settings::SoftWrap::PreferredLineLength => {
18255 SoftWrap::Column(settings.preferred_line_length)
18256 }
18257 language_settings::SoftWrap::Bounded => {
18258 SoftWrap::Bounded(settings.preferred_line_length)
18259 }
18260 }
18261 }
18262
18263 pub fn set_soft_wrap_mode(
18264 &mut self,
18265 mode: language_settings::SoftWrap,
18266
18267 cx: &mut Context<Self>,
18268 ) {
18269 self.soft_wrap_mode_override = Some(mode);
18270 cx.notify();
18271 }
18272
18273 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
18274 self.hard_wrap = hard_wrap;
18275 cx.notify();
18276 }
18277
18278 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
18279 self.text_style_refinement = Some(style);
18280 }
18281
18282 /// called by the Element so we know what style we were most recently rendered with.
18283 pub(crate) fn set_style(
18284 &mut self,
18285 style: EditorStyle,
18286 window: &mut Window,
18287 cx: &mut Context<Self>,
18288 ) {
18289 // We intentionally do not inform the display map about the minimap style
18290 // so that wrapping is not recalculated and stays consistent for the editor
18291 // and its linked minimap.
18292 if !self.mode.is_minimap() {
18293 let rem_size = window.rem_size();
18294 self.display_map.update(cx, |map, cx| {
18295 map.set_font(
18296 style.text.font(),
18297 style.text.font_size.to_pixels(rem_size),
18298 cx,
18299 )
18300 });
18301 }
18302 self.style = Some(style);
18303 }
18304
18305 pub fn style(&self) -> Option<&EditorStyle> {
18306 self.style.as_ref()
18307 }
18308
18309 // Called by the element. This method is not designed to be called outside of the editor
18310 // element's layout code because it does not notify when rewrapping is computed synchronously.
18311 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
18312 self.display_map
18313 .update(cx, |map, cx| map.set_wrap_width(width, cx))
18314 }
18315
18316 pub fn set_soft_wrap(&mut self) {
18317 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
18318 }
18319
18320 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
18321 if self.soft_wrap_mode_override.is_some() {
18322 self.soft_wrap_mode_override.take();
18323 } else {
18324 let soft_wrap = match self.soft_wrap_mode(cx) {
18325 SoftWrap::GitDiff => return,
18326 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
18327 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
18328 language_settings::SoftWrap::None
18329 }
18330 };
18331 self.soft_wrap_mode_override = Some(soft_wrap);
18332 }
18333 cx.notify();
18334 }
18335
18336 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
18337 let Some(workspace) = self.workspace() else {
18338 return;
18339 };
18340 let fs = workspace.read(cx).app_state().fs.clone();
18341 let current_show = TabBarSettings::get_global(cx).show;
18342 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
18343 setting.show = Some(!current_show);
18344 });
18345 }
18346
18347 pub fn toggle_indent_guides(
18348 &mut self,
18349 _: &ToggleIndentGuides,
18350 _: &mut Window,
18351 cx: &mut Context<Self>,
18352 ) {
18353 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
18354 self.buffer
18355 .read(cx)
18356 .language_settings(cx)
18357 .indent_guides
18358 .enabled
18359 });
18360 self.show_indent_guides = Some(!currently_enabled);
18361 cx.notify();
18362 }
18363
18364 fn should_show_indent_guides(&self) -> Option<bool> {
18365 self.show_indent_guides
18366 }
18367
18368 pub fn toggle_line_numbers(
18369 &mut self,
18370 _: &ToggleLineNumbers,
18371 _: &mut Window,
18372 cx: &mut Context<Self>,
18373 ) {
18374 let mut editor_settings = EditorSettings::get_global(cx).clone();
18375 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
18376 EditorSettings::override_global(editor_settings, cx);
18377 }
18378
18379 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
18380 if let Some(show_line_numbers) = self.show_line_numbers {
18381 return show_line_numbers;
18382 }
18383 EditorSettings::get_global(cx).gutter.line_numbers
18384 }
18385
18386 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
18387 self.use_relative_line_numbers
18388 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
18389 }
18390
18391 pub fn toggle_relative_line_numbers(
18392 &mut self,
18393 _: &ToggleRelativeLineNumbers,
18394 _: &mut Window,
18395 cx: &mut Context<Self>,
18396 ) {
18397 let is_relative = self.should_use_relative_line_numbers(cx);
18398 self.set_relative_line_number(Some(!is_relative), cx)
18399 }
18400
18401 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
18402 self.use_relative_line_numbers = is_relative;
18403 cx.notify();
18404 }
18405
18406 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
18407 self.show_gutter = show_gutter;
18408 cx.notify();
18409 }
18410
18411 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
18412 self.show_scrollbars = ScrollbarAxes {
18413 horizontal: show,
18414 vertical: show,
18415 };
18416 cx.notify();
18417 }
18418
18419 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18420 self.show_scrollbars.vertical = show;
18421 cx.notify();
18422 }
18423
18424 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18425 self.show_scrollbars.horizontal = show;
18426 cx.notify();
18427 }
18428
18429 pub fn set_minimap_visibility(
18430 &mut self,
18431 minimap_visibility: MinimapVisibility,
18432 window: &mut Window,
18433 cx: &mut Context<Self>,
18434 ) {
18435 if self.minimap_visibility != minimap_visibility {
18436 if minimap_visibility.visible() && self.minimap.is_none() {
18437 let minimap_settings = EditorSettings::get_global(cx).minimap;
18438 self.minimap =
18439 self.create_minimap(minimap_settings.with_show_override(), window, cx);
18440 }
18441 self.minimap_visibility = minimap_visibility;
18442 cx.notify();
18443 }
18444 }
18445
18446 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18447 self.set_show_scrollbars(false, cx);
18448 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
18449 }
18450
18451 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18452 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
18453 }
18454
18455 /// Normally the text in full mode and auto height editors is padded on the
18456 /// left side by roughly half a character width for improved hit testing.
18457 ///
18458 /// Use this method to disable this for cases where this is not wanted (e.g.
18459 /// if you want to align the editor text with some other text above or below)
18460 /// or if you want to add this padding to single-line editors.
18461 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
18462 self.offset_content = offset_content;
18463 cx.notify();
18464 }
18465
18466 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
18467 self.show_line_numbers = Some(show_line_numbers);
18468 cx.notify();
18469 }
18470
18471 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
18472 self.disable_expand_excerpt_buttons = true;
18473 cx.notify();
18474 }
18475
18476 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
18477 self.show_git_diff_gutter = Some(show_git_diff_gutter);
18478 cx.notify();
18479 }
18480
18481 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
18482 self.show_code_actions = Some(show_code_actions);
18483 cx.notify();
18484 }
18485
18486 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
18487 self.show_runnables = Some(show_runnables);
18488 cx.notify();
18489 }
18490
18491 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
18492 self.show_breakpoints = Some(show_breakpoints);
18493 cx.notify();
18494 }
18495
18496 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
18497 if self.display_map.read(cx).masked != masked {
18498 self.display_map.update(cx, |map, _| map.masked = masked);
18499 }
18500 cx.notify()
18501 }
18502
18503 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
18504 self.show_wrap_guides = Some(show_wrap_guides);
18505 cx.notify();
18506 }
18507
18508 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
18509 self.show_indent_guides = Some(show_indent_guides);
18510 cx.notify();
18511 }
18512
18513 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
18514 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
18515 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
18516 if let Some(dir) = file.abs_path(cx).parent() {
18517 return Some(dir.to_owned());
18518 }
18519 }
18520
18521 if let Some(project_path) = buffer.read(cx).project_path(cx) {
18522 return Some(project_path.path.to_path_buf());
18523 }
18524 }
18525
18526 None
18527 }
18528
18529 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
18530 self.active_excerpt(cx)?
18531 .1
18532 .read(cx)
18533 .file()
18534 .and_then(|f| f.as_local())
18535 }
18536
18537 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18538 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18539 let buffer = buffer.read(cx);
18540 if let Some(project_path) = buffer.project_path(cx) {
18541 let project = self.project.as_ref()?.read(cx);
18542 project.absolute_path(&project_path, cx)
18543 } else {
18544 buffer
18545 .file()
18546 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
18547 }
18548 })
18549 }
18550
18551 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18552 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18553 let project_path = buffer.read(cx).project_path(cx)?;
18554 let project = self.project.as_ref()?.read(cx);
18555 let entry = project.entry_for_path(&project_path, cx)?;
18556 let path = entry.path.to_path_buf();
18557 Some(path)
18558 })
18559 }
18560
18561 pub fn reveal_in_finder(
18562 &mut self,
18563 _: &RevealInFileManager,
18564 _window: &mut Window,
18565 cx: &mut Context<Self>,
18566 ) {
18567 if let Some(target) = self.target_file(cx) {
18568 cx.reveal_path(&target.abs_path(cx));
18569 }
18570 }
18571
18572 pub fn copy_path(
18573 &mut self,
18574 _: &zed_actions::workspace::CopyPath,
18575 _window: &mut Window,
18576 cx: &mut Context<Self>,
18577 ) {
18578 if let Some(path) = self.target_file_abs_path(cx) {
18579 if let Some(path) = path.to_str() {
18580 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18581 }
18582 }
18583 }
18584
18585 pub fn copy_relative_path(
18586 &mut self,
18587 _: &zed_actions::workspace::CopyRelativePath,
18588 _window: &mut Window,
18589 cx: &mut Context<Self>,
18590 ) {
18591 if let Some(path) = self.target_file_path(cx) {
18592 if let Some(path) = path.to_str() {
18593 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18594 }
18595 }
18596 }
18597
18598 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
18599 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
18600 buffer.read(cx).project_path(cx)
18601 } else {
18602 None
18603 }
18604 }
18605
18606 // Returns true if the editor handled a go-to-line request
18607 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
18608 maybe!({
18609 let breakpoint_store = self.breakpoint_store.as_ref()?;
18610
18611 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
18612 else {
18613 self.clear_row_highlights::<ActiveDebugLine>();
18614 return None;
18615 };
18616
18617 let position = active_stack_frame.position;
18618 let buffer_id = position.buffer_id?;
18619 let snapshot = self
18620 .project
18621 .as_ref()?
18622 .read(cx)
18623 .buffer_for_id(buffer_id, cx)?
18624 .read(cx)
18625 .snapshot();
18626
18627 let mut handled = false;
18628 for (id, ExcerptRange { context, .. }) in
18629 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
18630 {
18631 if context.start.cmp(&position, &snapshot).is_ge()
18632 || context.end.cmp(&position, &snapshot).is_lt()
18633 {
18634 continue;
18635 }
18636 let snapshot = self.buffer.read(cx).snapshot(cx);
18637 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
18638
18639 handled = true;
18640 self.clear_row_highlights::<ActiveDebugLine>();
18641
18642 self.go_to_line::<ActiveDebugLine>(
18643 multibuffer_anchor,
18644 Some(cx.theme().colors().editor_debugger_active_line_background),
18645 window,
18646 cx,
18647 );
18648
18649 cx.notify();
18650 }
18651
18652 handled.then_some(())
18653 })
18654 .is_some()
18655 }
18656
18657 pub fn copy_file_name_without_extension(
18658 &mut self,
18659 _: &CopyFileNameWithoutExtension,
18660 _: &mut Window,
18661 cx: &mut Context<Self>,
18662 ) {
18663 if let Some(file) = self.target_file(cx) {
18664 if let Some(file_stem) = file.path().file_stem() {
18665 if let Some(name) = file_stem.to_str() {
18666 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18667 }
18668 }
18669 }
18670 }
18671
18672 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
18673 if let Some(file) = self.target_file(cx) {
18674 if let Some(file_name) = file.path().file_name() {
18675 if let Some(name) = file_name.to_str() {
18676 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18677 }
18678 }
18679 }
18680 }
18681
18682 pub fn toggle_git_blame(
18683 &mut self,
18684 _: &::git::Blame,
18685 window: &mut Window,
18686 cx: &mut Context<Self>,
18687 ) {
18688 self.show_git_blame_gutter = !self.show_git_blame_gutter;
18689
18690 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
18691 self.start_git_blame(true, window, cx);
18692 }
18693
18694 cx.notify();
18695 }
18696
18697 pub fn toggle_git_blame_inline(
18698 &mut self,
18699 _: &ToggleGitBlameInline,
18700 window: &mut Window,
18701 cx: &mut Context<Self>,
18702 ) {
18703 self.toggle_git_blame_inline_internal(true, window, cx);
18704 cx.notify();
18705 }
18706
18707 pub fn open_git_blame_commit(
18708 &mut self,
18709 _: &OpenGitBlameCommit,
18710 window: &mut Window,
18711 cx: &mut Context<Self>,
18712 ) {
18713 self.open_git_blame_commit_internal(window, cx);
18714 }
18715
18716 fn open_git_blame_commit_internal(
18717 &mut self,
18718 window: &mut Window,
18719 cx: &mut Context<Self>,
18720 ) -> Option<()> {
18721 let blame = self.blame.as_ref()?;
18722 let snapshot = self.snapshot(window, cx);
18723 let cursor = self.selections.newest::<Point>(cx).head();
18724 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
18725 let blame_entry = blame
18726 .update(cx, |blame, cx| {
18727 blame
18728 .blame_for_rows(
18729 &[RowInfo {
18730 buffer_id: Some(buffer.remote_id()),
18731 buffer_row: Some(point.row),
18732 ..Default::default()
18733 }],
18734 cx,
18735 )
18736 .next()
18737 })
18738 .flatten()?;
18739 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
18740 let repo = blame.read(cx).repository(cx)?;
18741 let workspace = self.workspace()?.downgrade();
18742 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
18743 None
18744 }
18745
18746 pub fn git_blame_inline_enabled(&self) -> bool {
18747 self.git_blame_inline_enabled
18748 }
18749
18750 pub fn toggle_selection_menu(
18751 &mut self,
18752 _: &ToggleSelectionMenu,
18753 _: &mut Window,
18754 cx: &mut Context<Self>,
18755 ) {
18756 self.show_selection_menu = self
18757 .show_selection_menu
18758 .map(|show_selections_menu| !show_selections_menu)
18759 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
18760
18761 cx.notify();
18762 }
18763
18764 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
18765 self.show_selection_menu
18766 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
18767 }
18768
18769 fn start_git_blame(
18770 &mut self,
18771 user_triggered: bool,
18772 window: &mut Window,
18773 cx: &mut Context<Self>,
18774 ) {
18775 if let Some(project) = self.project.as_ref() {
18776 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
18777 return;
18778 };
18779
18780 if buffer.read(cx).file().is_none() {
18781 return;
18782 }
18783
18784 let focused = self.focus_handle(cx).contains_focused(window, cx);
18785
18786 let project = project.clone();
18787 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
18788 self.blame_subscription =
18789 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
18790 self.blame = Some(blame);
18791 }
18792 }
18793
18794 fn toggle_git_blame_inline_internal(
18795 &mut self,
18796 user_triggered: bool,
18797 window: &mut Window,
18798 cx: &mut Context<Self>,
18799 ) {
18800 if self.git_blame_inline_enabled {
18801 self.git_blame_inline_enabled = false;
18802 self.show_git_blame_inline = false;
18803 self.show_git_blame_inline_delay_task.take();
18804 } else {
18805 self.git_blame_inline_enabled = true;
18806 self.start_git_blame_inline(user_triggered, window, cx);
18807 }
18808
18809 cx.notify();
18810 }
18811
18812 fn start_git_blame_inline(
18813 &mut self,
18814 user_triggered: bool,
18815 window: &mut Window,
18816 cx: &mut Context<Self>,
18817 ) {
18818 self.start_git_blame(user_triggered, window, cx);
18819
18820 if ProjectSettings::get_global(cx)
18821 .git
18822 .inline_blame_delay()
18823 .is_some()
18824 {
18825 self.start_inline_blame_timer(window, cx);
18826 } else {
18827 self.show_git_blame_inline = true
18828 }
18829 }
18830
18831 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
18832 self.blame.as_ref()
18833 }
18834
18835 pub fn show_git_blame_gutter(&self) -> bool {
18836 self.show_git_blame_gutter
18837 }
18838
18839 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
18840 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
18841 }
18842
18843 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
18844 self.show_git_blame_inline
18845 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
18846 && !self.newest_selection_head_on_empty_line(cx)
18847 && self.has_blame_entries(cx)
18848 }
18849
18850 fn has_blame_entries(&self, cx: &App) -> bool {
18851 self.blame()
18852 .map_or(false, |blame| blame.read(cx).has_generated_entries())
18853 }
18854
18855 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
18856 let cursor_anchor = self.selections.newest_anchor().head();
18857
18858 let snapshot = self.buffer.read(cx).snapshot(cx);
18859 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
18860
18861 snapshot.line_len(buffer_row) == 0
18862 }
18863
18864 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
18865 let buffer_and_selection = maybe!({
18866 let selection = self.selections.newest::<Point>(cx);
18867 let selection_range = selection.range();
18868
18869 let multi_buffer = self.buffer().read(cx);
18870 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18871 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
18872
18873 let (buffer, range, _) = if selection.reversed {
18874 buffer_ranges.first()
18875 } else {
18876 buffer_ranges.last()
18877 }?;
18878
18879 let selection = text::ToPoint::to_point(&range.start, &buffer).row
18880 ..text::ToPoint::to_point(&range.end, &buffer).row;
18881 Some((
18882 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
18883 selection,
18884 ))
18885 });
18886
18887 let Some((buffer, selection)) = buffer_and_selection else {
18888 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
18889 };
18890
18891 let Some(project) = self.project.as_ref() else {
18892 return Task::ready(Err(anyhow!("editor does not have project")));
18893 };
18894
18895 project.update(cx, |project, cx| {
18896 project.get_permalink_to_line(&buffer, selection, cx)
18897 })
18898 }
18899
18900 pub fn copy_permalink_to_line(
18901 &mut self,
18902 _: &CopyPermalinkToLine,
18903 window: &mut Window,
18904 cx: &mut Context<Self>,
18905 ) {
18906 let permalink_task = self.get_permalink_to_line(cx);
18907 let workspace = self.workspace();
18908
18909 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18910 Ok(permalink) => {
18911 cx.update(|_, cx| {
18912 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
18913 })
18914 .ok();
18915 }
18916 Err(err) => {
18917 let message = format!("Failed to copy permalink: {err}");
18918
18919 anyhow::Result::<()>::Err(err).log_err();
18920
18921 if let Some(workspace) = workspace {
18922 workspace
18923 .update_in(cx, |workspace, _, cx| {
18924 struct CopyPermalinkToLine;
18925
18926 workspace.show_toast(
18927 Toast::new(
18928 NotificationId::unique::<CopyPermalinkToLine>(),
18929 message,
18930 ),
18931 cx,
18932 )
18933 })
18934 .ok();
18935 }
18936 }
18937 })
18938 .detach();
18939 }
18940
18941 pub fn copy_file_location(
18942 &mut self,
18943 _: &CopyFileLocation,
18944 _: &mut Window,
18945 cx: &mut Context<Self>,
18946 ) {
18947 let selection = self.selections.newest::<Point>(cx).start.row + 1;
18948 if let Some(file) = self.target_file(cx) {
18949 if let Some(path) = file.path().to_str() {
18950 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
18951 }
18952 }
18953 }
18954
18955 pub fn open_permalink_to_line(
18956 &mut self,
18957 _: &OpenPermalinkToLine,
18958 window: &mut Window,
18959 cx: &mut Context<Self>,
18960 ) {
18961 let permalink_task = self.get_permalink_to_line(cx);
18962 let workspace = self.workspace();
18963
18964 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18965 Ok(permalink) => {
18966 cx.update(|_, cx| {
18967 cx.open_url(permalink.as_ref());
18968 })
18969 .ok();
18970 }
18971 Err(err) => {
18972 let message = format!("Failed to open permalink: {err}");
18973
18974 anyhow::Result::<()>::Err(err).log_err();
18975
18976 if let Some(workspace) = workspace {
18977 workspace
18978 .update(cx, |workspace, cx| {
18979 struct OpenPermalinkToLine;
18980
18981 workspace.show_toast(
18982 Toast::new(
18983 NotificationId::unique::<OpenPermalinkToLine>(),
18984 message,
18985 ),
18986 cx,
18987 )
18988 })
18989 .ok();
18990 }
18991 }
18992 })
18993 .detach();
18994 }
18995
18996 pub fn insert_uuid_v4(
18997 &mut self,
18998 _: &InsertUuidV4,
18999 window: &mut Window,
19000 cx: &mut Context<Self>,
19001 ) {
19002 self.insert_uuid(UuidVersion::V4, window, cx);
19003 }
19004
19005 pub fn insert_uuid_v7(
19006 &mut self,
19007 _: &InsertUuidV7,
19008 window: &mut Window,
19009 cx: &mut Context<Self>,
19010 ) {
19011 self.insert_uuid(UuidVersion::V7, window, cx);
19012 }
19013
19014 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
19015 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19016 self.transact(window, cx, |this, window, cx| {
19017 let edits = this
19018 .selections
19019 .all::<Point>(cx)
19020 .into_iter()
19021 .map(|selection| {
19022 let uuid = match version {
19023 UuidVersion::V4 => uuid::Uuid::new_v4(),
19024 UuidVersion::V7 => uuid::Uuid::now_v7(),
19025 };
19026
19027 (selection.range(), uuid.to_string())
19028 });
19029 this.edit(edits, cx);
19030 this.refresh_edit_prediction(true, false, window, cx);
19031 });
19032 }
19033
19034 pub fn open_selections_in_multibuffer(
19035 &mut self,
19036 _: &OpenSelectionsInMultibuffer,
19037 window: &mut Window,
19038 cx: &mut Context<Self>,
19039 ) {
19040 let multibuffer = self.buffer.read(cx);
19041
19042 let Some(buffer) = multibuffer.as_singleton() else {
19043 return;
19044 };
19045
19046 let Some(workspace) = self.workspace() else {
19047 return;
19048 };
19049
19050 let title = multibuffer.title(cx).to_string();
19051
19052 let locations = self
19053 .selections
19054 .all_anchors(cx)
19055 .into_iter()
19056 .map(|selection| Location {
19057 buffer: buffer.clone(),
19058 range: selection.start.text_anchor..selection.end.text_anchor,
19059 })
19060 .collect::<Vec<_>>();
19061
19062 cx.spawn_in(window, async move |_, cx| {
19063 workspace.update_in(cx, |workspace, window, cx| {
19064 Self::open_locations_in_multibuffer(
19065 workspace,
19066 locations,
19067 format!("Selections for '{title}'"),
19068 false,
19069 MultibufferSelectionMode::All,
19070 window,
19071 cx,
19072 );
19073 })
19074 })
19075 .detach();
19076 }
19077
19078 /// Adds a row highlight for the given range. If a row has multiple highlights, the
19079 /// last highlight added will be used.
19080 ///
19081 /// If the range ends at the beginning of a line, then that line will not be highlighted.
19082 pub fn highlight_rows<T: 'static>(
19083 &mut self,
19084 range: Range<Anchor>,
19085 color: Hsla,
19086 options: RowHighlightOptions,
19087 cx: &mut Context<Self>,
19088 ) {
19089 let snapshot = self.buffer().read(cx).snapshot(cx);
19090 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19091 let ix = row_highlights.binary_search_by(|highlight| {
19092 Ordering::Equal
19093 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
19094 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
19095 });
19096
19097 if let Err(mut ix) = ix {
19098 let index = post_inc(&mut self.highlight_order);
19099
19100 // If this range intersects with the preceding highlight, then merge it with
19101 // the preceding highlight. Otherwise insert a new highlight.
19102 let mut merged = false;
19103 if ix > 0 {
19104 let prev_highlight = &mut row_highlights[ix - 1];
19105 if prev_highlight
19106 .range
19107 .end
19108 .cmp(&range.start, &snapshot)
19109 .is_ge()
19110 {
19111 ix -= 1;
19112 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
19113 prev_highlight.range.end = range.end;
19114 }
19115 merged = true;
19116 prev_highlight.index = index;
19117 prev_highlight.color = color;
19118 prev_highlight.options = options;
19119 }
19120 }
19121
19122 if !merged {
19123 row_highlights.insert(
19124 ix,
19125 RowHighlight {
19126 range: range.clone(),
19127 index,
19128 color,
19129 options,
19130 type_id: TypeId::of::<T>(),
19131 },
19132 );
19133 }
19134
19135 // If any of the following highlights intersect with this one, merge them.
19136 while let Some(next_highlight) = row_highlights.get(ix + 1) {
19137 let highlight = &row_highlights[ix];
19138 if next_highlight
19139 .range
19140 .start
19141 .cmp(&highlight.range.end, &snapshot)
19142 .is_le()
19143 {
19144 if next_highlight
19145 .range
19146 .end
19147 .cmp(&highlight.range.end, &snapshot)
19148 .is_gt()
19149 {
19150 row_highlights[ix].range.end = next_highlight.range.end;
19151 }
19152 row_highlights.remove(ix + 1);
19153 } else {
19154 break;
19155 }
19156 }
19157 }
19158 }
19159
19160 /// Remove any highlighted row ranges of the given type that intersect the
19161 /// given ranges.
19162 pub fn remove_highlighted_rows<T: 'static>(
19163 &mut self,
19164 ranges_to_remove: Vec<Range<Anchor>>,
19165 cx: &mut Context<Self>,
19166 ) {
19167 let snapshot = self.buffer().read(cx).snapshot(cx);
19168 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19169 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19170 row_highlights.retain(|highlight| {
19171 while let Some(range_to_remove) = ranges_to_remove.peek() {
19172 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
19173 Ordering::Less | Ordering::Equal => {
19174 ranges_to_remove.next();
19175 }
19176 Ordering::Greater => {
19177 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
19178 Ordering::Less | Ordering::Equal => {
19179 return false;
19180 }
19181 Ordering::Greater => break,
19182 }
19183 }
19184 }
19185 }
19186
19187 true
19188 })
19189 }
19190
19191 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
19192 pub fn clear_row_highlights<T: 'static>(&mut self) {
19193 self.highlighted_rows.remove(&TypeId::of::<T>());
19194 }
19195
19196 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
19197 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
19198 self.highlighted_rows
19199 .get(&TypeId::of::<T>())
19200 .map_or(&[] as &[_], |vec| vec.as_slice())
19201 .iter()
19202 .map(|highlight| (highlight.range.clone(), highlight.color))
19203 }
19204
19205 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
19206 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
19207 /// Allows to ignore certain kinds of highlights.
19208 pub fn highlighted_display_rows(
19209 &self,
19210 window: &mut Window,
19211 cx: &mut App,
19212 ) -> BTreeMap<DisplayRow, LineHighlight> {
19213 let snapshot = self.snapshot(window, cx);
19214 let mut used_highlight_orders = HashMap::default();
19215 self.highlighted_rows
19216 .iter()
19217 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
19218 .fold(
19219 BTreeMap::<DisplayRow, LineHighlight>::new(),
19220 |mut unique_rows, highlight| {
19221 let start = highlight.range.start.to_display_point(&snapshot);
19222 let end = highlight.range.end.to_display_point(&snapshot);
19223 let start_row = start.row().0;
19224 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
19225 && end.column() == 0
19226 {
19227 end.row().0.saturating_sub(1)
19228 } else {
19229 end.row().0
19230 };
19231 for row in start_row..=end_row {
19232 let used_index =
19233 used_highlight_orders.entry(row).or_insert(highlight.index);
19234 if highlight.index >= *used_index {
19235 *used_index = highlight.index;
19236 unique_rows.insert(
19237 DisplayRow(row),
19238 LineHighlight {
19239 include_gutter: highlight.options.include_gutter,
19240 border: None,
19241 background: highlight.color.into(),
19242 type_id: Some(highlight.type_id),
19243 },
19244 );
19245 }
19246 }
19247 unique_rows
19248 },
19249 )
19250 }
19251
19252 pub fn highlighted_display_row_for_autoscroll(
19253 &self,
19254 snapshot: &DisplaySnapshot,
19255 ) -> Option<DisplayRow> {
19256 self.highlighted_rows
19257 .values()
19258 .flat_map(|highlighted_rows| highlighted_rows.iter())
19259 .filter_map(|highlight| {
19260 if highlight.options.autoscroll {
19261 Some(highlight.range.start.to_display_point(snapshot).row())
19262 } else {
19263 None
19264 }
19265 })
19266 .min()
19267 }
19268
19269 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
19270 self.highlight_background::<SearchWithinRange>(
19271 ranges,
19272 |colors| colors.colors().editor_document_highlight_read_background,
19273 cx,
19274 )
19275 }
19276
19277 pub fn set_breadcrumb_header(&mut self, new_header: String) {
19278 self.breadcrumb_header = Some(new_header);
19279 }
19280
19281 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
19282 self.clear_background_highlights::<SearchWithinRange>(cx);
19283 }
19284
19285 pub fn highlight_background<T: 'static>(
19286 &mut self,
19287 ranges: &[Range<Anchor>],
19288 color_fetcher: fn(&Theme) -> Hsla,
19289 cx: &mut Context<Self>,
19290 ) {
19291 self.background_highlights.insert(
19292 HighlightKey::Type(TypeId::of::<T>()),
19293 (color_fetcher, Arc::from(ranges)),
19294 );
19295 self.scrollbar_marker_state.dirty = true;
19296 cx.notify();
19297 }
19298
19299 pub fn highlight_background_key<T: 'static>(
19300 &mut self,
19301 key: usize,
19302 ranges: &[Range<Anchor>],
19303 color_fetcher: fn(&Theme) -> Hsla,
19304 cx: &mut Context<Self>,
19305 ) {
19306 self.background_highlights.insert(
19307 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19308 (color_fetcher, Arc::from(ranges)),
19309 );
19310 self.scrollbar_marker_state.dirty = true;
19311 cx.notify();
19312 }
19313
19314 pub fn clear_background_highlights<T: 'static>(
19315 &mut self,
19316 cx: &mut Context<Self>,
19317 ) -> Option<BackgroundHighlight> {
19318 let text_highlights = self
19319 .background_highlights
19320 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
19321 if !text_highlights.1.is_empty() {
19322 self.scrollbar_marker_state.dirty = true;
19323 cx.notify();
19324 }
19325 Some(text_highlights)
19326 }
19327
19328 pub fn highlight_gutter<T: 'static>(
19329 &mut self,
19330 ranges: impl Into<Vec<Range<Anchor>>>,
19331 color_fetcher: fn(&App) -> Hsla,
19332 cx: &mut Context<Self>,
19333 ) {
19334 self.gutter_highlights
19335 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
19336 cx.notify();
19337 }
19338
19339 pub fn clear_gutter_highlights<T: 'static>(
19340 &mut self,
19341 cx: &mut Context<Self>,
19342 ) -> Option<GutterHighlight> {
19343 cx.notify();
19344 self.gutter_highlights.remove(&TypeId::of::<T>())
19345 }
19346
19347 pub fn insert_gutter_highlight<T: 'static>(
19348 &mut self,
19349 range: Range<Anchor>,
19350 color_fetcher: fn(&App) -> Hsla,
19351 cx: &mut Context<Self>,
19352 ) {
19353 let snapshot = self.buffer().read(cx).snapshot(cx);
19354 let mut highlights = self
19355 .gutter_highlights
19356 .remove(&TypeId::of::<T>())
19357 .map(|(_, highlights)| highlights)
19358 .unwrap_or_default();
19359 let ix = highlights.binary_search_by(|highlight| {
19360 Ordering::Equal
19361 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
19362 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
19363 });
19364 if let Err(ix) = ix {
19365 highlights.insert(ix, range);
19366 }
19367 self.gutter_highlights
19368 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
19369 }
19370
19371 pub fn remove_gutter_highlights<T: 'static>(
19372 &mut self,
19373 ranges_to_remove: Vec<Range<Anchor>>,
19374 cx: &mut Context<Self>,
19375 ) {
19376 let snapshot = self.buffer().read(cx).snapshot(cx);
19377 let Some((color_fetcher, mut gutter_highlights)) =
19378 self.gutter_highlights.remove(&TypeId::of::<T>())
19379 else {
19380 return;
19381 };
19382 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19383 gutter_highlights.retain(|highlight| {
19384 while let Some(range_to_remove) = ranges_to_remove.peek() {
19385 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
19386 Ordering::Less | Ordering::Equal => {
19387 ranges_to_remove.next();
19388 }
19389 Ordering::Greater => {
19390 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
19391 Ordering::Less | Ordering::Equal => {
19392 return false;
19393 }
19394 Ordering::Greater => break,
19395 }
19396 }
19397 }
19398 }
19399
19400 true
19401 });
19402 self.gutter_highlights
19403 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
19404 }
19405
19406 #[cfg(feature = "test-support")]
19407 pub fn all_text_highlights(
19408 &self,
19409 window: &mut Window,
19410 cx: &mut Context<Self>,
19411 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
19412 let snapshot = self.snapshot(window, cx);
19413 self.display_map.update(cx, |display_map, _| {
19414 display_map
19415 .all_text_highlights()
19416 .map(|highlight| {
19417 let (style, ranges) = highlight.as_ref();
19418 (
19419 *style,
19420 ranges
19421 .iter()
19422 .map(|range| range.clone().to_display_points(&snapshot))
19423 .collect(),
19424 )
19425 })
19426 .collect()
19427 })
19428 }
19429
19430 #[cfg(feature = "test-support")]
19431 pub fn all_text_background_highlights(
19432 &self,
19433 window: &mut Window,
19434 cx: &mut Context<Self>,
19435 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19436 let snapshot = self.snapshot(window, cx);
19437 let buffer = &snapshot.buffer_snapshot;
19438 let start = buffer.anchor_before(0);
19439 let end = buffer.anchor_after(buffer.len());
19440 self.background_highlights_in_range(start..end, &snapshot, cx.theme())
19441 }
19442
19443 #[cfg(feature = "test-support")]
19444 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
19445 let snapshot = self.buffer().read(cx).snapshot(cx);
19446
19447 let highlights = self
19448 .background_highlights
19449 .get(&HighlightKey::Type(TypeId::of::<
19450 items::BufferSearchHighlights,
19451 >()));
19452
19453 if let Some((_color, ranges)) = highlights {
19454 ranges
19455 .iter()
19456 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
19457 .collect_vec()
19458 } else {
19459 vec![]
19460 }
19461 }
19462
19463 fn document_highlights_for_position<'a>(
19464 &'a self,
19465 position: Anchor,
19466 buffer: &'a MultiBufferSnapshot,
19467 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
19468 let read_highlights = self
19469 .background_highlights
19470 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
19471 .map(|h| &h.1);
19472 let write_highlights = self
19473 .background_highlights
19474 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
19475 .map(|h| &h.1);
19476 let left_position = position.bias_left(buffer);
19477 let right_position = position.bias_right(buffer);
19478 read_highlights
19479 .into_iter()
19480 .chain(write_highlights)
19481 .flat_map(move |ranges| {
19482 let start_ix = match ranges.binary_search_by(|probe| {
19483 let cmp = probe.end.cmp(&left_position, buffer);
19484 if cmp.is_ge() {
19485 Ordering::Greater
19486 } else {
19487 Ordering::Less
19488 }
19489 }) {
19490 Ok(i) | Err(i) => i,
19491 };
19492
19493 ranges[start_ix..]
19494 .iter()
19495 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
19496 })
19497 }
19498
19499 pub fn has_background_highlights<T: 'static>(&self) -> bool {
19500 self.background_highlights
19501 .get(&HighlightKey::Type(TypeId::of::<T>()))
19502 .map_or(false, |(_, highlights)| !highlights.is_empty())
19503 }
19504
19505 pub fn background_highlights_in_range(
19506 &self,
19507 search_range: Range<Anchor>,
19508 display_snapshot: &DisplaySnapshot,
19509 theme: &Theme,
19510 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19511 let mut results = Vec::new();
19512 for (color_fetcher, ranges) in self.background_highlights.values() {
19513 let color = color_fetcher(theme);
19514 let start_ix = match ranges.binary_search_by(|probe| {
19515 let cmp = probe
19516 .end
19517 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19518 if cmp.is_gt() {
19519 Ordering::Greater
19520 } else {
19521 Ordering::Less
19522 }
19523 }) {
19524 Ok(i) | Err(i) => i,
19525 };
19526 for range in &ranges[start_ix..] {
19527 if range
19528 .start
19529 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19530 .is_ge()
19531 {
19532 break;
19533 }
19534
19535 let start = range.start.to_display_point(display_snapshot);
19536 let end = range.end.to_display_point(display_snapshot);
19537 results.push((start..end, color))
19538 }
19539 }
19540 results
19541 }
19542
19543 pub fn background_highlight_row_ranges<T: 'static>(
19544 &self,
19545 search_range: Range<Anchor>,
19546 display_snapshot: &DisplaySnapshot,
19547 count: usize,
19548 ) -> Vec<RangeInclusive<DisplayPoint>> {
19549 let mut results = Vec::new();
19550 let Some((_, ranges)) = self
19551 .background_highlights
19552 .get(&HighlightKey::Type(TypeId::of::<T>()))
19553 else {
19554 return vec![];
19555 };
19556
19557 let start_ix = match ranges.binary_search_by(|probe| {
19558 let cmp = probe
19559 .end
19560 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19561 if cmp.is_gt() {
19562 Ordering::Greater
19563 } else {
19564 Ordering::Less
19565 }
19566 }) {
19567 Ok(i) | Err(i) => i,
19568 };
19569 let mut push_region = |start: Option<Point>, end: Option<Point>| {
19570 if let (Some(start_display), Some(end_display)) = (start, end) {
19571 results.push(
19572 start_display.to_display_point(display_snapshot)
19573 ..=end_display.to_display_point(display_snapshot),
19574 );
19575 }
19576 };
19577 let mut start_row: Option<Point> = None;
19578 let mut end_row: Option<Point> = None;
19579 if ranges.len() > count {
19580 return Vec::new();
19581 }
19582 for range in &ranges[start_ix..] {
19583 if range
19584 .start
19585 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19586 .is_ge()
19587 {
19588 break;
19589 }
19590 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
19591 if let Some(current_row) = &end_row {
19592 if end.row == current_row.row {
19593 continue;
19594 }
19595 }
19596 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
19597 if start_row.is_none() {
19598 assert_eq!(end_row, None);
19599 start_row = Some(start);
19600 end_row = Some(end);
19601 continue;
19602 }
19603 if let Some(current_end) = end_row.as_mut() {
19604 if start.row > current_end.row + 1 {
19605 push_region(start_row, end_row);
19606 start_row = Some(start);
19607 end_row = Some(end);
19608 } else {
19609 // Merge two hunks.
19610 *current_end = end;
19611 }
19612 } else {
19613 unreachable!();
19614 }
19615 }
19616 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
19617 push_region(start_row, end_row);
19618 results
19619 }
19620
19621 pub fn gutter_highlights_in_range(
19622 &self,
19623 search_range: Range<Anchor>,
19624 display_snapshot: &DisplaySnapshot,
19625 cx: &App,
19626 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19627 let mut results = Vec::new();
19628 for (color_fetcher, ranges) in self.gutter_highlights.values() {
19629 let color = color_fetcher(cx);
19630 let start_ix = match ranges.binary_search_by(|probe| {
19631 let cmp = probe
19632 .end
19633 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19634 if cmp.is_gt() {
19635 Ordering::Greater
19636 } else {
19637 Ordering::Less
19638 }
19639 }) {
19640 Ok(i) | Err(i) => i,
19641 };
19642 for range in &ranges[start_ix..] {
19643 if range
19644 .start
19645 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19646 .is_ge()
19647 {
19648 break;
19649 }
19650
19651 let start = range.start.to_display_point(display_snapshot);
19652 let end = range.end.to_display_point(display_snapshot);
19653 results.push((start..end, color))
19654 }
19655 }
19656 results
19657 }
19658
19659 /// Get the text ranges corresponding to the redaction query
19660 pub fn redacted_ranges(
19661 &self,
19662 search_range: Range<Anchor>,
19663 display_snapshot: &DisplaySnapshot,
19664 cx: &App,
19665 ) -> Vec<Range<DisplayPoint>> {
19666 display_snapshot
19667 .buffer_snapshot
19668 .redacted_ranges(search_range, |file| {
19669 if let Some(file) = file {
19670 file.is_private()
19671 && EditorSettings::get(
19672 Some(SettingsLocation {
19673 worktree_id: file.worktree_id(cx),
19674 path: file.path().as_ref(),
19675 }),
19676 cx,
19677 )
19678 .redact_private_values
19679 } else {
19680 false
19681 }
19682 })
19683 .map(|range| {
19684 range.start.to_display_point(display_snapshot)
19685 ..range.end.to_display_point(display_snapshot)
19686 })
19687 .collect()
19688 }
19689
19690 pub fn highlight_text_key<T: 'static>(
19691 &mut self,
19692 key: usize,
19693 ranges: Vec<Range<Anchor>>,
19694 style: HighlightStyle,
19695 cx: &mut Context<Self>,
19696 ) {
19697 self.display_map.update(cx, |map, _| {
19698 map.highlight_text(
19699 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19700 ranges,
19701 style,
19702 );
19703 });
19704 cx.notify();
19705 }
19706
19707 pub fn highlight_text<T: 'static>(
19708 &mut self,
19709 ranges: Vec<Range<Anchor>>,
19710 style: HighlightStyle,
19711 cx: &mut Context<Self>,
19712 ) {
19713 self.display_map.update(cx, |map, _| {
19714 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
19715 });
19716 cx.notify();
19717 }
19718
19719 pub(crate) fn highlight_inlays<T: 'static>(
19720 &mut self,
19721 highlights: Vec<InlayHighlight>,
19722 style: HighlightStyle,
19723 cx: &mut Context<Self>,
19724 ) {
19725 self.display_map.update(cx, |map, _| {
19726 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
19727 });
19728 cx.notify();
19729 }
19730
19731 pub fn text_highlights<'a, T: 'static>(
19732 &'a self,
19733 cx: &'a App,
19734 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
19735 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
19736 }
19737
19738 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
19739 let cleared = self
19740 .display_map
19741 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
19742 if cleared {
19743 cx.notify();
19744 }
19745 }
19746
19747 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
19748 (self.read_only(cx) || self.blink_manager.read(cx).visible())
19749 && self.focus_handle.is_focused(window)
19750 }
19751
19752 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
19753 self.show_cursor_when_unfocused = is_enabled;
19754 cx.notify();
19755 }
19756
19757 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
19758 cx.notify();
19759 }
19760
19761 fn on_debug_session_event(
19762 &mut self,
19763 _session: Entity<Session>,
19764 event: &SessionEvent,
19765 cx: &mut Context<Self>,
19766 ) {
19767 match event {
19768 SessionEvent::InvalidateInlineValue => {
19769 self.refresh_inline_values(cx);
19770 }
19771 _ => {}
19772 }
19773 }
19774
19775 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
19776 let Some(project) = self.project.clone() else {
19777 return;
19778 };
19779
19780 if !self.inline_value_cache.enabled {
19781 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
19782 self.splice_inlays(&inlays, Vec::new(), cx);
19783 return;
19784 }
19785
19786 let current_execution_position = self
19787 .highlighted_rows
19788 .get(&TypeId::of::<ActiveDebugLine>())
19789 .and_then(|lines| lines.last().map(|line| line.range.end));
19790
19791 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
19792 let inline_values = editor
19793 .update(cx, |editor, cx| {
19794 let Some(current_execution_position) = current_execution_position else {
19795 return Some(Task::ready(Ok(Vec::new())));
19796 };
19797
19798 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
19799 let snapshot = buffer.snapshot(cx);
19800
19801 let excerpt = snapshot.excerpt_containing(
19802 current_execution_position..current_execution_position,
19803 )?;
19804
19805 editor.buffer.read(cx).buffer(excerpt.buffer_id())
19806 })?;
19807
19808 let range =
19809 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
19810
19811 project.inline_values(buffer, range, cx)
19812 })
19813 .ok()
19814 .flatten()?
19815 .await
19816 .context("refreshing debugger inlays")
19817 .log_err()?;
19818
19819 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
19820
19821 for (buffer_id, inline_value) in inline_values
19822 .into_iter()
19823 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
19824 {
19825 buffer_inline_values
19826 .entry(buffer_id)
19827 .or_default()
19828 .push(inline_value);
19829 }
19830
19831 editor
19832 .update(cx, |editor, cx| {
19833 let snapshot = editor.buffer.read(cx).snapshot(cx);
19834 let mut new_inlays = Vec::default();
19835
19836 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
19837 let buffer_id = buffer_snapshot.remote_id();
19838 buffer_inline_values
19839 .get(&buffer_id)
19840 .into_iter()
19841 .flatten()
19842 .for_each(|hint| {
19843 let inlay = Inlay::debugger(
19844 post_inc(&mut editor.next_inlay_id),
19845 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
19846 hint.text(),
19847 );
19848 if !inlay.text.chars().contains(&'\n') {
19849 new_inlays.push(inlay);
19850 }
19851 });
19852 }
19853
19854 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
19855 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
19856
19857 editor.splice_inlays(&inlay_ids, new_inlays, cx);
19858 })
19859 .ok()?;
19860 Some(())
19861 });
19862 }
19863
19864 fn on_buffer_event(
19865 &mut self,
19866 multibuffer: &Entity<MultiBuffer>,
19867 event: &multi_buffer::Event,
19868 window: &mut Window,
19869 cx: &mut Context<Self>,
19870 ) {
19871 match event {
19872 multi_buffer::Event::Edited {
19873 singleton_buffer_edited,
19874 edited_buffer,
19875 } => {
19876 self.scrollbar_marker_state.dirty = true;
19877 self.active_indent_guides_state.dirty = true;
19878 self.refresh_active_diagnostics(cx);
19879 self.refresh_code_actions(window, cx);
19880 self.refresh_selected_text_highlights(true, window, cx);
19881 self.refresh_single_line_folds(window, cx);
19882 refresh_matching_bracket_highlights(self, window, cx);
19883 if self.has_active_edit_prediction() {
19884 self.update_visible_edit_prediction(window, cx);
19885 }
19886 if let Some(project) = self.project.as_ref() {
19887 if let Some(edited_buffer) = edited_buffer {
19888 project.update(cx, |project, cx| {
19889 self.registered_buffers
19890 .entry(edited_buffer.read(cx).remote_id())
19891 .or_insert_with(|| {
19892 project
19893 .register_buffer_with_language_servers(&edited_buffer, cx)
19894 });
19895 });
19896 }
19897 }
19898 cx.emit(EditorEvent::BufferEdited);
19899 cx.emit(SearchEvent::MatchesInvalidated);
19900
19901 if let Some(buffer) = edited_buffer {
19902 self.update_lsp_data(false, Some(buffer.read(cx).remote_id()), window, cx);
19903 }
19904
19905 if *singleton_buffer_edited {
19906 if let Some(buffer) = edited_buffer {
19907 if buffer.read(cx).file().is_none() {
19908 cx.emit(EditorEvent::TitleChanged);
19909 }
19910 }
19911 if let Some(project) = &self.project {
19912 #[allow(clippy::mutable_key_type)]
19913 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
19914 multibuffer
19915 .all_buffers()
19916 .into_iter()
19917 .filter_map(|buffer| {
19918 buffer.update(cx, |buffer, cx| {
19919 let language = buffer.language()?;
19920 let should_discard = project.update(cx, |project, cx| {
19921 project.is_local()
19922 && !project.has_language_servers_for(buffer, cx)
19923 });
19924 should_discard.not().then_some(language.clone())
19925 })
19926 })
19927 .collect::<HashSet<_>>()
19928 });
19929 if !languages_affected.is_empty() {
19930 self.refresh_inlay_hints(
19931 InlayHintRefreshReason::BufferEdited(languages_affected),
19932 cx,
19933 );
19934 }
19935 }
19936 }
19937
19938 let Some(project) = &self.project else { return };
19939 let (telemetry, is_via_ssh) = {
19940 let project = project.read(cx);
19941 let telemetry = project.client().telemetry().clone();
19942 let is_via_ssh = project.is_via_ssh();
19943 (telemetry, is_via_ssh)
19944 };
19945 refresh_linked_ranges(self, window, cx);
19946 telemetry.log_edit_event("editor", is_via_ssh);
19947 }
19948 multi_buffer::Event::ExcerptsAdded {
19949 buffer,
19950 predecessor,
19951 excerpts,
19952 } => {
19953 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19954 let buffer_id = buffer.read(cx).remote_id();
19955 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
19956 if let Some(project) = &self.project {
19957 update_uncommitted_diff_for_buffer(
19958 cx.entity(),
19959 project,
19960 [buffer.clone()],
19961 self.buffer.clone(),
19962 cx,
19963 )
19964 .detach();
19965 }
19966 }
19967 self.update_lsp_data(false, Some(buffer_id), window, cx);
19968 cx.emit(EditorEvent::ExcerptsAdded {
19969 buffer: buffer.clone(),
19970 predecessor: *predecessor,
19971 excerpts: excerpts.clone(),
19972 });
19973 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
19974 }
19975 multi_buffer::Event::ExcerptsRemoved {
19976 ids,
19977 removed_buffer_ids,
19978 } => {
19979 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
19980 let buffer = self.buffer.read(cx);
19981 self.registered_buffers
19982 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
19983 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19984 cx.emit(EditorEvent::ExcerptsRemoved {
19985 ids: ids.clone(),
19986 removed_buffer_ids: removed_buffer_ids.clone(),
19987 });
19988 }
19989 multi_buffer::Event::ExcerptsEdited {
19990 excerpt_ids,
19991 buffer_ids,
19992 } => {
19993 self.display_map.update(cx, |map, cx| {
19994 map.unfold_buffers(buffer_ids.iter().copied(), cx)
19995 });
19996 cx.emit(EditorEvent::ExcerptsEdited {
19997 ids: excerpt_ids.clone(),
19998 });
19999 }
20000 multi_buffer::Event::ExcerptsExpanded { ids } => {
20001 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20002 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
20003 }
20004 multi_buffer::Event::Reparsed(buffer_id) => {
20005 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20006 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20007
20008 cx.emit(EditorEvent::Reparsed(*buffer_id));
20009 }
20010 multi_buffer::Event::DiffHunksToggled => {
20011 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20012 }
20013 multi_buffer::Event::LanguageChanged(buffer_id) => {
20014 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
20015 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20016 cx.emit(EditorEvent::Reparsed(*buffer_id));
20017 cx.notify();
20018 }
20019 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
20020 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
20021 multi_buffer::Event::FileHandleChanged
20022 | multi_buffer::Event::Reloaded
20023 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
20024 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
20025 multi_buffer::Event::DiagnosticsUpdated => {
20026 self.update_diagnostics_state(window, cx);
20027 }
20028 _ => {}
20029 };
20030 }
20031
20032 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
20033 if !self.diagnostics_enabled() {
20034 return;
20035 }
20036 self.refresh_active_diagnostics(cx);
20037 self.refresh_inline_diagnostics(true, window, cx);
20038 self.scrollbar_marker_state.dirty = true;
20039 cx.notify();
20040 }
20041
20042 pub fn start_temporary_diff_override(&mut self) {
20043 self.load_diff_task.take();
20044 self.temporary_diff_override = true;
20045 }
20046
20047 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
20048 self.temporary_diff_override = false;
20049 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
20050 self.buffer.update(cx, |buffer, cx| {
20051 buffer.set_all_diff_hunks_collapsed(cx);
20052 });
20053
20054 if let Some(project) = self.project.clone() {
20055 self.load_diff_task = Some(
20056 update_uncommitted_diff_for_buffer(
20057 cx.entity(),
20058 &project,
20059 self.buffer.read(cx).all_buffers(),
20060 self.buffer.clone(),
20061 cx,
20062 )
20063 .shared(),
20064 );
20065 }
20066 }
20067
20068 fn on_display_map_changed(
20069 &mut self,
20070 _: Entity<DisplayMap>,
20071 _: &mut Window,
20072 cx: &mut Context<Self>,
20073 ) {
20074 cx.notify();
20075 }
20076
20077 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20078 if self.diagnostics_enabled() {
20079 let new_severity = EditorSettings::get_global(cx)
20080 .diagnostics_max_severity
20081 .unwrap_or(DiagnosticSeverity::Hint);
20082 self.set_max_diagnostics_severity(new_severity, cx);
20083 }
20084 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20085 self.update_edit_prediction_settings(cx);
20086 self.refresh_edit_prediction(true, false, window, cx);
20087 self.refresh_inline_values(cx);
20088 self.refresh_inlay_hints(
20089 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
20090 self.selections.newest_anchor().head(),
20091 &self.buffer.read(cx).snapshot(cx),
20092 cx,
20093 )),
20094 cx,
20095 );
20096
20097 let old_cursor_shape = self.cursor_shape;
20098
20099 {
20100 let editor_settings = EditorSettings::get_global(cx);
20101 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
20102 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
20103 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
20104 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
20105 }
20106
20107 if old_cursor_shape != self.cursor_shape {
20108 cx.emit(EditorEvent::CursorShapeChanged);
20109 }
20110
20111 let project_settings = ProjectSettings::get_global(cx);
20112 self.serialize_dirty_buffers =
20113 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
20114
20115 if self.mode.is_full() {
20116 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
20117 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
20118 if self.show_inline_diagnostics != show_inline_diagnostics {
20119 self.show_inline_diagnostics = show_inline_diagnostics;
20120 self.refresh_inline_diagnostics(false, window, cx);
20121 }
20122
20123 if self.git_blame_inline_enabled != inline_blame_enabled {
20124 self.toggle_git_blame_inline_internal(false, window, cx);
20125 }
20126
20127 let minimap_settings = EditorSettings::get_global(cx).minimap;
20128 if self.minimap_visibility != MinimapVisibility::Disabled {
20129 if self.minimap_visibility.settings_visibility()
20130 != minimap_settings.minimap_enabled()
20131 {
20132 self.set_minimap_visibility(
20133 MinimapVisibility::for_mode(self.mode(), cx),
20134 window,
20135 cx,
20136 );
20137 } else if let Some(minimap_entity) = self.minimap.as_ref() {
20138 minimap_entity.update(cx, |minimap_editor, cx| {
20139 minimap_editor.update_minimap_configuration(minimap_settings, cx)
20140 })
20141 }
20142 }
20143 }
20144
20145 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
20146 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
20147 }) {
20148 if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
20149 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
20150 }
20151 self.refresh_colors(false, None, window, cx);
20152 }
20153
20154 cx.notify();
20155 }
20156
20157 pub fn set_searchable(&mut self, searchable: bool) {
20158 self.searchable = searchable;
20159 }
20160
20161 pub fn searchable(&self) -> bool {
20162 self.searchable
20163 }
20164
20165 fn open_proposed_changes_editor(
20166 &mut self,
20167 _: &OpenProposedChangesEditor,
20168 window: &mut Window,
20169 cx: &mut Context<Self>,
20170 ) {
20171 let Some(workspace) = self.workspace() else {
20172 cx.propagate();
20173 return;
20174 };
20175
20176 let selections = self.selections.all::<usize>(cx);
20177 let multi_buffer = self.buffer.read(cx);
20178 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20179 let mut new_selections_by_buffer = HashMap::default();
20180 for selection in selections {
20181 for (buffer, range, _) in
20182 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
20183 {
20184 let mut range = range.to_point(buffer);
20185 range.start.column = 0;
20186 range.end.column = buffer.line_len(range.end.row);
20187 new_selections_by_buffer
20188 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
20189 .or_insert(Vec::new())
20190 .push(range)
20191 }
20192 }
20193
20194 let proposed_changes_buffers = new_selections_by_buffer
20195 .into_iter()
20196 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
20197 .collect::<Vec<_>>();
20198 let proposed_changes_editor = cx.new(|cx| {
20199 ProposedChangesEditor::new(
20200 "Proposed changes",
20201 proposed_changes_buffers,
20202 self.project.clone(),
20203 window,
20204 cx,
20205 )
20206 });
20207
20208 window.defer(cx, move |window, cx| {
20209 workspace.update(cx, |workspace, cx| {
20210 workspace.active_pane().update(cx, |pane, cx| {
20211 pane.add_item(
20212 Box::new(proposed_changes_editor),
20213 true,
20214 true,
20215 None,
20216 window,
20217 cx,
20218 );
20219 });
20220 });
20221 });
20222 }
20223
20224 pub fn open_excerpts_in_split(
20225 &mut self,
20226 _: &OpenExcerptsSplit,
20227 window: &mut Window,
20228 cx: &mut Context<Self>,
20229 ) {
20230 self.open_excerpts_common(None, true, window, cx)
20231 }
20232
20233 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
20234 self.open_excerpts_common(None, false, window, cx)
20235 }
20236
20237 fn open_excerpts_common(
20238 &mut self,
20239 jump_data: Option<JumpData>,
20240 split: bool,
20241 window: &mut Window,
20242 cx: &mut Context<Self>,
20243 ) {
20244 let Some(workspace) = self.workspace() else {
20245 cx.propagate();
20246 return;
20247 };
20248
20249 if self.buffer.read(cx).is_singleton() {
20250 cx.propagate();
20251 return;
20252 }
20253
20254 let mut new_selections_by_buffer = HashMap::default();
20255 match &jump_data {
20256 Some(JumpData::MultiBufferPoint {
20257 excerpt_id,
20258 position,
20259 anchor,
20260 line_offset_from_top,
20261 }) => {
20262 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20263 if let Some(buffer) = multi_buffer_snapshot
20264 .buffer_id_for_excerpt(*excerpt_id)
20265 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
20266 {
20267 let buffer_snapshot = buffer.read(cx).snapshot();
20268 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
20269 language::ToPoint::to_point(anchor, &buffer_snapshot)
20270 } else {
20271 buffer_snapshot.clip_point(*position, Bias::Left)
20272 };
20273 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
20274 new_selections_by_buffer.insert(
20275 buffer,
20276 (
20277 vec![jump_to_offset..jump_to_offset],
20278 Some(*line_offset_from_top),
20279 ),
20280 );
20281 }
20282 }
20283 Some(JumpData::MultiBufferRow {
20284 row,
20285 line_offset_from_top,
20286 }) => {
20287 let point = MultiBufferPoint::new(row.0, 0);
20288 if let Some((buffer, buffer_point, _)) =
20289 self.buffer.read(cx).point_to_buffer_point(point, cx)
20290 {
20291 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
20292 new_selections_by_buffer
20293 .entry(buffer)
20294 .or_insert((Vec::new(), Some(*line_offset_from_top)))
20295 .0
20296 .push(buffer_offset..buffer_offset)
20297 }
20298 }
20299 None => {
20300 let selections = self.selections.all::<usize>(cx);
20301 let multi_buffer = self.buffer.read(cx);
20302 for selection in selections {
20303 for (snapshot, range, _, anchor) in multi_buffer
20304 .snapshot(cx)
20305 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
20306 {
20307 if let Some(anchor) = anchor {
20308 // selection is in a deleted hunk
20309 let Some(buffer_id) = anchor.buffer_id else {
20310 continue;
20311 };
20312 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
20313 continue;
20314 };
20315 let offset = text::ToOffset::to_offset(
20316 &anchor.text_anchor,
20317 &buffer_handle.read(cx).snapshot(),
20318 );
20319 let range = offset..offset;
20320 new_selections_by_buffer
20321 .entry(buffer_handle)
20322 .or_insert((Vec::new(), None))
20323 .0
20324 .push(range)
20325 } else {
20326 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
20327 else {
20328 continue;
20329 };
20330 new_selections_by_buffer
20331 .entry(buffer_handle)
20332 .or_insert((Vec::new(), None))
20333 .0
20334 .push(range)
20335 }
20336 }
20337 }
20338 }
20339 }
20340
20341 new_selections_by_buffer
20342 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
20343
20344 if new_selections_by_buffer.is_empty() {
20345 return;
20346 }
20347
20348 // We defer the pane interaction because we ourselves are a workspace item
20349 // and activating a new item causes the pane to call a method on us reentrantly,
20350 // which panics if we're on the stack.
20351 window.defer(cx, move |window, cx| {
20352 workspace.update(cx, |workspace, cx| {
20353 let pane = if split {
20354 workspace.adjacent_pane(window, cx)
20355 } else {
20356 workspace.active_pane().clone()
20357 };
20358
20359 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
20360 let editor = buffer
20361 .read(cx)
20362 .file()
20363 .is_none()
20364 .then(|| {
20365 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
20366 // so `workspace.open_project_item` will never find them, always opening a new editor.
20367 // Instead, we try to activate the existing editor in the pane first.
20368 let (editor, pane_item_index) =
20369 pane.read(cx).items().enumerate().find_map(|(i, item)| {
20370 let editor = item.downcast::<Editor>()?;
20371 let singleton_buffer =
20372 editor.read(cx).buffer().read(cx).as_singleton()?;
20373 if singleton_buffer == buffer {
20374 Some((editor, i))
20375 } else {
20376 None
20377 }
20378 })?;
20379 pane.update(cx, |pane, cx| {
20380 pane.activate_item(pane_item_index, true, true, window, cx)
20381 });
20382 Some(editor)
20383 })
20384 .flatten()
20385 .unwrap_or_else(|| {
20386 workspace.open_project_item::<Self>(
20387 pane.clone(),
20388 buffer,
20389 true,
20390 true,
20391 window,
20392 cx,
20393 )
20394 });
20395
20396 editor.update(cx, |editor, cx| {
20397 let autoscroll = match scroll_offset {
20398 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
20399 None => Autoscroll::newest(),
20400 };
20401 let nav_history = editor.nav_history.take();
20402 editor.change_selections(
20403 SelectionEffects::scroll(autoscroll),
20404 window,
20405 cx,
20406 |s| {
20407 s.select_ranges(ranges);
20408 },
20409 );
20410 editor.nav_history = nav_history;
20411 });
20412 }
20413 })
20414 });
20415 }
20416
20417 // For now, don't allow opening excerpts in buffers that aren't backed by
20418 // regular project files.
20419 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
20420 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
20421 }
20422
20423 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
20424 let snapshot = self.buffer.read(cx).read(cx);
20425 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
20426 Some(
20427 ranges
20428 .iter()
20429 .map(move |range| {
20430 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
20431 })
20432 .collect(),
20433 )
20434 }
20435
20436 fn selection_replacement_ranges(
20437 &self,
20438 range: Range<OffsetUtf16>,
20439 cx: &mut App,
20440 ) -> Vec<Range<OffsetUtf16>> {
20441 let selections = self.selections.all::<OffsetUtf16>(cx);
20442 let newest_selection = selections
20443 .iter()
20444 .max_by_key(|selection| selection.id)
20445 .unwrap();
20446 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
20447 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
20448 let snapshot = self.buffer.read(cx).read(cx);
20449 selections
20450 .into_iter()
20451 .map(|mut selection| {
20452 selection.start.0 =
20453 (selection.start.0 as isize).saturating_add(start_delta) as usize;
20454 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
20455 snapshot.clip_offset_utf16(selection.start, Bias::Left)
20456 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
20457 })
20458 .collect()
20459 }
20460
20461 fn report_editor_event(
20462 &self,
20463 event_type: &'static str,
20464 file_extension: Option<String>,
20465 cx: &App,
20466 ) {
20467 if cfg!(any(test, feature = "test-support")) {
20468 return;
20469 }
20470
20471 let Some(project) = &self.project else { return };
20472
20473 // If None, we are in a file without an extension
20474 let file = self
20475 .buffer
20476 .read(cx)
20477 .as_singleton()
20478 .and_then(|b| b.read(cx).file());
20479 let file_extension = file_extension.or(file
20480 .as_ref()
20481 .and_then(|file| Path::new(file.file_name(cx)).extension())
20482 .and_then(|e| e.to_str())
20483 .map(|a| a.to_string()));
20484
20485 let vim_mode = vim_enabled(cx);
20486
20487 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
20488 let copilot_enabled = edit_predictions_provider
20489 == language::language_settings::EditPredictionProvider::Copilot;
20490 let copilot_enabled_for_language = self
20491 .buffer
20492 .read(cx)
20493 .language_settings(cx)
20494 .show_edit_predictions;
20495
20496 let project = project.read(cx);
20497 telemetry::event!(
20498 event_type,
20499 file_extension,
20500 vim_mode,
20501 copilot_enabled,
20502 copilot_enabled_for_language,
20503 edit_predictions_provider,
20504 is_via_ssh = project.is_via_ssh(),
20505 );
20506 }
20507
20508 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
20509 /// with each line being an array of {text, highlight} objects.
20510 fn copy_highlight_json(
20511 &mut self,
20512 _: &CopyHighlightJson,
20513 window: &mut Window,
20514 cx: &mut Context<Self>,
20515 ) {
20516 #[derive(Serialize)]
20517 struct Chunk<'a> {
20518 text: String,
20519 highlight: Option<&'a str>,
20520 }
20521
20522 let snapshot = self.buffer.read(cx).snapshot(cx);
20523 let range = self
20524 .selected_text_range(false, window, cx)
20525 .and_then(|selection| {
20526 if selection.range.is_empty() {
20527 None
20528 } else {
20529 Some(selection.range)
20530 }
20531 })
20532 .unwrap_or_else(|| 0..snapshot.len());
20533
20534 let chunks = snapshot.chunks(range, true);
20535 let mut lines = Vec::new();
20536 let mut line: VecDeque<Chunk> = VecDeque::new();
20537
20538 let Some(style) = self.style.as_ref() else {
20539 return;
20540 };
20541
20542 for chunk in chunks {
20543 let highlight = chunk
20544 .syntax_highlight_id
20545 .and_then(|id| id.name(&style.syntax));
20546 let mut chunk_lines = chunk.text.split('\n').peekable();
20547 while let Some(text) = chunk_lines.next() {
20548 let mut merged_with_last_token = false;
20549 if let Some(last_token) = line.back_mut() {
20550 if last_token.highlight == highlight {
20551 last_token.text.push_str(text);
20552 merged_with_last_token = true;
20553 }
20554 }
20555
20556 if !merged_with_last_token {
20557 line.push_back(Chunk {
20558 text: text.into(),
20559 highlight,
20560 });
20561 }
20562
20563 if chunk_lines.peek().is_some() {
20564 if line.len() > 1 && line.front().unwrap().text.is_empty() {
20565 line.pop_front();
20566 }
20567 if line.len() > 1 && line.back().unwrap().text.is_empty() {
20568 line.pop_back();
20569 }
20570
20571 lines.push(mem::take(&mut line));
20572 }
20573 }
20574 }
20575
20576 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
20577 return;
20578 };
20579 cx.write_to_clipboard(ClipboardItem::new_string(lines));
20580 }
20581
20582 pub fn open_context_menu(
20583 &mut self,
20584 _: &OpenContextMenu,
20585 window: &mut Window,
20586 cx: &mut Context<Self>,
20587 ) {
20588 self.request_autoscroll(Autoscroll::newest(), cx);
20589 let position = self.selections.newest_display(cx).start;
20590 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
20591 }
20592
20593 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
20594 &self.inlay_hint_cache
20595 }
20596
20597 pub fn replay_insert_event(
20598 &mut self,
20599 text: &str,
20600 relative_utf16_range: Option<Range<isize>>,
20601 window: &mut Window,
20602 cx: &mut Context<Self>,
20603 ) {
20604 if !self.input_enabled {
20605 cx.emit(EditorEvent::InputIgnored { text: text.into() });
20606 return;
20607 }
20608 if let Some(relative_utf16_range) = relative_utf16_range {
20609 let selections = self.selections.all::<OffsetUtf16>(cx);
20610 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20611 let new_ranges = selections.into_iter().map(|range| {
20612 let start = OffsetUtf16(
20613 range
20614 .head()
20615 .0
20616 .saturating_add_signed(relative_utf16_range.start),
20617 );
20618 let end = OffsetUtf16(
20619 range
20620 .head()
20621 .0
20622 .saturating_add_signed(relative_utf16_range.end),
20623 );
20624 start..end
20625 });
20626 s.select_ranges(new_ranges);
20627 });
20628 }
20629
20630 self.handle_input(text, window, cx);
20631 }
20632
20633 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
20634 let Some(provider) = self.semantics_provider.as_ref() else {
20635 return false;
20636 };
20637
20638 let mut supports = false;
20639 self.buffer().update(cx, |this, cx| {
20640 this.for_each_buffer(|buffer| {
20641 supports |= provider.supports_inlay_hints(buffer, cx);
20642 });
20643 });
20644
20645 supports
20646 }
20647
20648 pub fn is_focused(&self, window: &Window) -> bool {
20649 self.focus_handle.is_focused(window)
20650 }
20651
20652 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20653 cx.emit(EditorEvent::Focused);
20654
20655 if let Some(descendant) = self
20656 .last_focused_descendant
20657 .take()
20658 .and_then(|descendant| descendant.upgrade())
20659 {
20660 window.focus(&descendant);
20661 } else {
20662 if let Some(blame) = self.blame.as_ref() {
20663 blame.update(cx, GitBlame::focus)
20664 }
20665
20666 self.blink_manager.update(cx, BlinkManager::enable);
20667 self.show_cursor_names(window, cx);
20668 self.buffer.update(cx, |buffer, cx| {
20669 buffer.finalize_last_transaction(cx);
20670 if self.leader_id.is_none() {
20671 buffer.set_active_selections(
20672 &self.selections.disjoint_anchors(),
20673 self.selections.line_mode,
20674 self.cursor_shape,
20675 cx,
20676 );
20677 }
20678 });
20679 }
20680 }
20681
20682 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
20683 cx.emit(EditorEvent::FocusedIn)
20684 }
20685
20686 fn handle_focus_out(
20687 &mut self,
20688 event: FocusOutEvent,
20689 _window: &mut Window,
20690 cx: &mut Context<Self>,
20691 ) {
20692 if event.blurred != self.focus_handle {
20693 self.last_focused_descendant = Some(event.blurred);
20694 }
20695 self.selection_drag_state = SelectionDragState::None;
20696 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
20697 }
20698
20699 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20700 self.blink_manager.update(cx, BlinkManager::disable);
20701 self.buffer
20702 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
20703
20704 if let Some(blame) = self.blame.as_ref() {
20705 blame.update(cx, GitBlame::blur)
20706 }
20707 if !self.hover_state.focused(window, cx) {
20708 hide_hover(self, cx);
20709 }
20710 if !self
20711 .context_menu
20712 .borrow()
20713 .as_ref()
20714 .is_some_and(|context_menu| context_menu.focused(window, cx))
20715 {
20716 self.hide_context_menu(window, cx);
20717 }
20718 self.discard_edit_prediction(false, cx);
20719 cx.emit(EditorEvent::Blurred);
20720 cx.notify();
20721 }
20722
20723 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20724 let mut pending: String = window
20725 .pending_input_keystrokes()
20726 .into_iter()
20727 .flatten()
20728 .filter_map(|keystroke| {
20729 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
20730 keystroke.key_char.clone()
20731 } else {
20732 None
20733 }
20734 })
20735 .collect();
20736
20737 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
20738 pending = "".to_string();
20739 }
20740
20741 let existing_pending = self
20742 .text_highlights::<PendingInput>(cx)
20743 .map(|(_, ranges)| ranges.iter().cloned().collect::<Vec<_>>());
20744 if existing_pending.is_none() && pending.is_empty() {
20745 return;
20746 }
20747 let transaction =
20748 self.transact(window, cx, |this, window, cx| {
20749 let selections = this.selections.all::<usize>(cx);
20750 let edits = selections
20751 .iter()
20752 .map(|selection| (selection.end..selection.end, pending.clone()));
20753 this.edit(edits, cx);
20754 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20755 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
20756 sel.start + ix * pending.len()..sel.end + ix * pending.len()
20757 }));
20758 });
20759 if let Some(existing_ranges) = existing_pending {
20760 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
20761 this.edit(edits, cx);
20762 }
20763 });
20764
20765 let snapshot = self.snapshot(window, cx);
20766 let ranges = self
20767 .selections
20768 .all::<usize>(cx)
20769 .into_iter()
20770 .map(|selection| {
20771 snapshot.buffer_snapshot.anchor_after(selection.end)
20772 ..snapshot
20773 .buffer_snapshot
20774 .anchor_before(selection.end + pending.len())
20775 })
20776 .collect();
20777
20778 if pending.is_empty() {
20779 self.clear_highlights::<PendingInput>(cx);
20780 } else {
20781 self.highlight_text::<PendingInput>(
20782 ranges,
20783 HighlightStyle {
20784 underline: Some(UnderlineStyle {
20785 thickness: px(1.),
20786 color: None,
20787 wavy: false,
20788 }),
20789 ..Default::default()
20790 },
20791 cx,
20792 );
20793 }
20794
20795 self.ime_transaction = self.ime_transaction.or(transaction);
20796 if let Some(transaction) = self.ime_transaction {
20797 self.buffer.update(cx, |buffer, cx| {
20798 buffer.group_until_transaction(transaction, cx);
20799 });
20800 }
20801
20802 if self.text_highlights::<PendingInput>(cx).is_none() {
20803 self.ime_transaction.take();
20804 }
20805 }
20806
20807 pub fn register_action_renderer(
20808 &mut self,
20809 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
20810 ) -> Subscription {
20811 let id = self.next_editor_action_id.post_inc();
20812 self.editor_actions
20813 .borrow_mut()
20814 .insert(id, Box::new(listener));
20815
20816 let editor_actions = self.editor_actions.clone();
20817 Subscription::new(move || {
20818 editor_actions.borrow_mut().remove(&id);
20819 })
20820 }
20821
20822 pub fn register_action<A: Action>(
20823 &mut self,
20824 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
20825 ) -> Subscription {
20826 let id = self.next_editor_action_id.post_inc();
20827 let listener = Arc::new(listener);
20828 self.editor_actions.borrow_mut().insert(
20829 id,
20830 Box::new(move |_, window, _| {
20831 let listener = listener.clone();
20832 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
20833 let action = action.downcast_ref().unwrap();
20834 if phase == DispatchPhase::Bubble {
20835 listener(action, window, cx)
20836 }
20837 })
20838 }),
20839 );
20840
20841 let editor_actions = self.editor_actions.clone();
20842 Subscription::new(move || {
20843 editor_actions.borrow_mut().remove(&id);
20844 })
20845 }
20846
20847 pub fn file_header_size(&self) -> u32 {
20848 FILE_HEADER_HEIGHT
20849 }
20850
20851 pub fn restore(
20852 &mut self,
20853 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
20854 window: &mut Window,
20855 cx: &mut Context<Self>,
20856 ) {
20857 let workspace = self.workspace();
20858 let project = self.project.as_ref();
20859 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
20860 let mut tasks = Vec::new();
20861 for (buffer_id, changes) in revert_changes {
20862 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
20863 buffer.update(cx, |buffer, cx| {
20864 buffer.edit(
20865 changes
20866 .into_iter()
20867 .map(|(range, text)| (range, text.to_string())),
20868 None,
20869 cx,
20870 );
20871 });
20872
20873 if let Some(project) =
20874 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
20875 {
20876 project.update(cx, |project, cx| {
20877 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
20878 })
20879 }
20880 }
20881 }
20882 tasks
20883 });
20884 cx.spawn_in(window, async move |_, cx| {
20885 for (buffer, task) in save_tasks {
20886 let result = task.await;
20887 if result.is_err() {
20888 let Some(path) = buffer
20889 .read_with(cx, |buffer, cx| buffer.project_path(cx))
20890 .ok()
20891 else {
20892 continue;
20893 };
20894 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
20895 let Some(task) = cx
20896 .update_window_entity(&workspace, |workspace, window, cx| {
20897 workspace
20898 .open_path_preview(path, None, false, false, false, window, cx)
20899 })
20900 .ok()
20901 else {
20902 continue;
20903 };
20904 task.await.log_err();
20905 }
20906 }
20907 }
20908 })
20909 .detach();
20910 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
20911 selections.refresh()
20912 });
20913 }
20914
20915 pub fn to_pixel_point(
20916 &self,
20917 source: multi_buffer::Anchor,
20918 editor_snapshot: &EditorSnapshot,
20919 window: &mut Window,
20920 ) -> Option<gpui::Point<Pixels>> {
20921 let source_point = source.to_display_point(editor_snapshot);
20922 self.display_to_pixel_point(source_point, editor_snapshot, window)
20923 }
20924
20925 pub fn display_to_pixel_point(
20926 &self,
20927 source: DisplayPoint,
20928 editor_snapshot: &EditorSnapshot,
20929 window: &mut Window,
20930 ) -> Option<gpui::Point<Pixels>> {
20931 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
20932 let text_layout_details = self.text_layout_details(window);
20933 let scroll_top = text_layout_details
20934 .scroll_anchor
20935 .scroll_position(editor_snapshot)
20936 .y;
20937
20938 if source.row().as_f32() < scroll_top.floor() {
20939 return None;
20940 }
20941 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
20942 let source_y = line_height * (source.row().as_f32() - scroll_top);
20943 Some(gpui::Point::new(source_x, source_y))
20944 }
20945
20946 pub fn has_visible_completions_menu(&self) -> bool {
20947 !self.edit_prediction_preview_is_active()
20948 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
20949 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
20950 })
20951 }
20952
20953 pub fn register_addon<T: Addon>(&mut self, instance: T) {
20954 if self.mode.is_minimap() {
20955 return;
20956 }
20957 self.addons
20958 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
20959 }
20960
20961 pub fn unregister_addon<T: Addon>(&mut self) {
20962 self.addons.remove(&std::any::TypeId::of::<T>());
20963 }
20964
20965 pub fn addon<T: Addon>(&self) -> Option<&T> {
20966 let type_id = std::any::TypeId::of::<T>();
20967 self.addons
20968 .get(&type_id)
20969 .and_then(|item| item.to_any().downcast_ref::<T>())
20970 }
20971
20972 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
20973 let type_id = std::any::TypeId::of::<T>();
20974 self.addons
20975 .get_mut(&type_id)
20976 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
20977 }
20978
20979 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
20980 let text_layout_details = self.text_layout_details(window);
20981 let style = &text_layout_details.editor_style;
20982 let font_id = window.text_system().resolve_font(&style.text.font());
20983 let font_size = style.text.font_size.to_pixels(window.rem_size());
20984 let line_height = style.text.line_height_in_pixels(window.rem_size());
20985 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
20986 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
20987
20988 CharacterDimensions {
20989 em_width,
20990 em_advance,
20991 line_height,
20992 }
20993 }
20994
20995 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
20996 self.load_diff_task.clone()
20997 }
20998
20999 fn read_metadata_from_db(
21000 &mut self,
21001 item_id: u64,
21002 workspace_id: WorkspaceId,
21003 window: &mut Window,
21004 cx: &mut Context<Editor>,
21005 ) {
21006 if self.is_singleton(cx)
21007 && !self.mode.is_minimap()
21008 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
21009 {
21010 let buffer_snapshot = OnceCell::new();
21011
21012 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
21013 if !folds.is_empty() {
21014 let snapshot =
21015 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21016 self.fold_ranges(
21017 folds
21018 .into_iter()
21019 .map(|(start, end)| {
21020 snapshot.clip_offset(start, Bias::Left)
21021 ..snapshot.clip_offset(end, Bias::Right)
21022 })
21023 .collect(),
21024 false,
21025 window,
21026 cx,
21027 );
21028 }
21029 }
21030
21031 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
21032 if !selections.is_empty() {
21033 let snapshot =
21034 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21035 // skip adding the initial selection to selection history
21036 self.selection_history.mode = SelectionHistoryMode::Skipping;
21037 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21038 s.select_ranges(selections.into_iter().map(|(start, end)| {
21039 snapshot.clip_offset(start, Bias::Left)
21040 ..snapshot.clip_offset(end, Bias::Right)
21041 }));
21042 });
21043 self.selection_history.mode = SelectionHistoryMode::Normal;
21044 }
21045 };
21046 }
21047
21048 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
21049 }
21050
21051 fn update_lsp_data(
21052 &mut self,
21053 ignore_cache: bool,
21054 for_buffer: Option<BufferId>,
21055 window: &mut Window,
21056 cx: &mut Context<'_, Self>,
21057 ) {
21058 self.pull_diagnostics(for_buffer, window, cx);
21059 self.refresh_colors(ignore_cache, for_buffer, window, cx);
21060 }
21061}
21062
21063fn vim_enabled(cx: &App) -> bool {
21064 cx.global::<SettingsStore>()
21065 .raw_user_settings()
21066 .get("vim_mode")
21067 == Some(&serde_json::Value::Bool(true))
21068}
21069
21070fn process_completion_for_edit(
21071 completion: &Completion,
21072 intent: CompletionIntent,
21073 buffer: &Entity<Buffer>,
21074 cursor_position: &text::Anchor,
21075 cx: &mut Context<Editor>,
21076) -> CompletionEdit {
21077 let buffer = buffer.read(cx);
21078 let buffer_snapshot = buffer.snapshot();
21079 let (snippet, new_text) = if completion.is_snippet() {
21080 // Workaround for typescript language server issues so that methods don't expand within
21081 // strings and functions with type expressions. The previous point is used because the query
21082 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
21083 let mut snippet_source = completion.new_text.clone();
21084 let mut previous_point = text::ToPoint::to_point(cursor_position, buffer);
21085 previous_point.column = previous_point.column.saturating_sub(1);
21086 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point) {
21087 if scope.prefers_label_for_snippet_in_completion() {
21088 if let Some(label) = completion.label() {
21089 if matches!(
21090 completion.kind(),
21091 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
21092 ) {
21093 snippet_source = label;
21094 }
21095 }
21096 }
21097 }
21098 match Snippet::parse(&snippet_source).log_err() {
21099 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
21100 None => (None, completion.new_text.clone()),
21101 }
21102 } else {
21103 (None, completion.new_text.clone())
21104 };
21105
21106 let mut range_to_replace = {
21107 let replace_range = &completion.replace_range;
21108 if let CompletionSource::Lsp {
21109 insert_range: Some(insert_range),
21110 ..
21111 } = &completion.source
21112 {
21113 debug_assert_eq!(
21114 insert_range.start, replace_range.start,
21115 "insert_range and replace_range should start at the same position"
21116 );
21117 debug_assert!(
21118 insert_range
21119 .start
21120 .cmp(&cursor_position, &buffer_snapshot)
21121 .is_le(),
21122 "insert_range should start before or at cursor position"
21123 );
21124 debug_assert!(
21125 replace_range
21126 .start
21127 .cmp(&cursor_position, &buffer_snapshot)
21128 .is_le(),
21129 "replace_range should start before or at cursor position"
21130 );
21131
21132 let should_replace = match intent {
21133 CompletionIntent::CompleteWithInsert => false,
21134 CompletionIntent::CompleteWithReplace => true,
21135 CompletionIntent::Complete | CompletionIntent::Compose => {
21136 let insert_mode =
21137 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
21138 .completions
21139 .lsp_insert_mode;
21140 match insert_mode {
21141 LspInsertMode::Insert => false,
21142 LspInsertMode::Replace => true,
21143 LspInsertMode::ReplaceSubsequence => {
21144 let mut text_to_replace = buffer.chars_for_range(
21145 buffer.anchor_before(replace_range.start)
21146 ..buffer.anchor_after(replace_range.end),
21147 );
21148 let mut current_needle = text_to_replace.next();
21149 for haystack_ch in completion.label.text.chars() {
21150 if let Some(needle_ch) = current_needle {
21151 if haystack_ch.eq_ignore_ascii_case(&needle_ch) {
21152 current_needle = text_to_replace.next();
21153 }
21154 }
21155 }
21156 current_needle.is_none()
21157 }
21158 LspInsertMode::ReplaceSuffix => {
21159 if replace_range
21160 .end
21161 .cmp(&cursor_position, &buffer_snapshot)
21162 .is_gt()
21163 {
21164 let range_after_cursor = *cursor_position..replace_range.end;
21165 let text_after_cursor = buffer
21166 .text_for_range(
21167 buffer.anchor_before(range_after_cursor.start)
21168 ..buffer.anchor_after(range_after_cursor.end),
21169 )
21170 .collect::<String>()
21171 .to_ascii_lowercase();
21172 completion
21173 .label
21174 .text
21175 .to_ascii_lowercase()
21176 .ends_with(&text_after_cursor)
21177 } else {
21178 true
21179 }
21180 }
21181 }
21182 }
21183 };
21184
21185 if should_replace {
21186 replace_range.clone()
21187 } else {
21188 insert_range.clone()
21189 }
21190 } else {
21191 replace_range.clone()
21192 }
21193 };
21194
21195 if range_to_replace
21196 .end
21197 .cmp(&cursor_position, &buffer_snapshot)
21198 .is_lt()
21199 {
21200 range_to_replace.end = *cursor_position;
21201 }
21202
21203 CompletionEdit {
21204 new_text,
21205 replace_range: range_to_replace.to_offset(&buffer),
21206 snippet,
21207 }
21208}
21209
21210struct CompletionEdit {
21211 new_text: String,
21212 replace_range: Range<usize>,
21213 snippet: Option<Snippet>,
21214}
21215
21216fn insert_extra_newline_brackets(
21217 buffer: &MultiBufferSnapshot,
21218 range: Range<usize>,
21219 language: &language::LanguageScope,
21220) -> bool {
21221 let leading_whitespace_len = buffer
21222 .reversed_chars_at(range.start)
21223 .take_while(|c| c.is_whitespace() && *c != '\n')
21224 .map(|c| c.len_utf8())
21225 .sum::<usize>();
21226 let trailing_whitespace_len = buffer
21227 .chars_at(range.end)
21228 .take_while(|c| c.is_whitespace() && *c != '\n')
21229 .map(|c| c.len_utf8())
21230 .sum::<usize>();
21231 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
21232
21233 language.brackets().any(|(pair, enabled)| {
21234 let pair_start = pair.start.trim_end();
21235 let pair_end = pair.end.trim_start();
21236
21237 enabled
21238 && pair.newline
21239 && buffer.contains_str_at(range.end, pair_end)
21240 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
21241 })
21242}
21243
21244fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
21245 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
21246 [(buffer, range, _)] => (*buffer, range.clone()),
21247 _ => return false,
21248 };
21249 let pair = {
21250 let mut result: Option<BracketMatch> = None;
21251
21252 for pair in buffer
21253 .all_bracket_ranges(range.clone())
21254 .filter(move |pair| {
21255 pair.open_range.start <= range.start && pair.close_range.end >= range.end
21256 })
21257 {
21258 let len = pair.close_range.end - pair.open_range.start;
21259
21260 if let Some(existing) = &result {
21261 let existing_len = existing.close_range.end - existing.open_range.start;
21262 if len > existing_len {
21263 continue;
21264 }
21265 }
21266
21267 result = Some(pair);
21268 }
21269
21270 result
21271 };
21272 let Some(pair) = pair else {
21273 return false;
21274 };
21275 pair.newline_only
21276 && buffer
21277 .chars_for_range(pair.open_range.end..range.start)
21278 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
21279 .all(|c| c.is_whitespace() && c != '\n')
21280}
21281
21282fn update_uncommitted_diff_for_buffer(
21283 editor: Entity<Editor>,
21284 project: &Entity<Project>,
21285 buffers: impl IntoIterator<Item = Entity<Buffer>>,
21286 buffer: Entity<MultiBuffer>,
21287 cx: &mut App,
21288) -> Task<()> {
21289 let mut tasks = Vec::new();
21290 project.update(cx, |project, cx| {
21291 for buffer in buffers {
21292 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
21293 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
21294 }
21295 }
21296 });
21297 cx.spawn(async move |cx| {
21298 let diffs = future::join_all(tasks).await;
21299 if editor
21300 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
21301 .unwrap_or(false)
21302 {
21303 return;
21304 }
21305
21306 buffer
21307 .update(cx, |buffer, cx| {
21308 for diff in diffs.into_iter().flatten() {
21309 buffer.add_diff(diff, cx);
21310 }
21311 })
21312 .ok();
21313 })
21314}
21315
21316fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
21317 let tab_size = tab_size.get() as usize;
21318 let mut width = offset;
21319
21320 for ch in text.chars() {
21321 width += if ch == '\t' {
21322 tab_size - (width % tab_size)
21323 } else {
21324 1
21325 };
21326 }
21327
21328 width - offset
21329}
21330
21331#[cfg(test)]
21332mod tests {
21333 use super::*;
21334
21335 #[test]
21336 fn test_string_size_with_expanded_tabs() {
21337 let nz = |val| NonZeroU32::new(val).unwrap();
21338 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
21339 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
21340 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
21341 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
21342 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
21343 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
21344 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
21345 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
21346 }
21347}
21348
21349/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
21350struct WordBreakingTokenizer<'a> {
21351 input: &'a str,
21352}
21353
21354impl<'a> WordBreakingTokenizer<'a> {
21355 fn new(input: &'a str) -> Self {
21356 Self { input }
21357 }
21358}
21359
21360fn is_char_ideographic(ch: char) -> bool {
21361 use unicode_script::Script::*;
21362 use unicode_script::UnicodeScript;
21363 matches!(ch.script(), Han | Tangut | Yi)
21364}
21365
21366fn is_grapheme_ideographic(text: &str) -> bool {
21367 text.chars().any(is_char_ideographic)
21368}
21369
21370fn is_grapheme_whitespace(text: &str) -> bool {
21371 text.chars().any(|x| x.is_whitespace())
21372}
21373
21374fn should_stay_with_preceding_ideograph(text: &str) -> bool {
21375 text.chars().next().map_or(false, |ch| {
21376 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
21377 })
21378}
21379
21380#[derive(PartialEq, Eq, Debug, Clone, Copy)]
21381enum WordBreakToken<'a> {
21382 Word { token: &'a str, grapheme_len: usize },
21383 InlineWhitespace { token: &'a str, grapheme_len: usize },
21384 Newline,
21385}
21386
21387impl<'a> Iterator for WordBreakingTokenizer<'a> {
21388 /// Yields a span, the count of graphemes in the token, and whether it was
21389 /// whitespace. Note that it also breaks at word boundaries.
21390 type Item = WordBreakToken<'a>;
21391
21392 fn next(&mut self) -> Option<Self::Item> {
21393 use unicode_segmentation::UnicodeSegmentation;
21394 if self.input.is_empty() {
21395 return None;
21396 }
21397
21398 let mut iter = self.input.graphemes(true).peekable();
21399 let mut offset = 0;
21400 let mut grapheme_len = 0;
21401 if let Some(first_grapheme) = iter.next() {
21402 let is_newline = first_grapheme == "\n";
21403 let is_whitespace = is_grapheme_whitespace(first_grapheme);
21404 offset += first_grapheme.len();
21405 grapheme_len += 1;
21406 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
21407 if let Some(grapheme) = iter.peek().copied() {
21408 if should_stay_with_preceding_ideograph(grapheme) {
21409 offset += grapheme.len();
21410 grapheme_len += 1;
21411 }
21412 }
21413 } else {
21414 let mut words = self.input[offset..].split_word_bound_indices().peekable();
21415 let mut next_word_bound = words.peek().copied();
21416 if next_word_bound.map_or(false, |(i, _)| i == 0) {
21417 next_word_bound = words.next();
21418 }
21419 while let Some(grapheme) = iter.peek().copied() {
21420 if next_word_bound.map_or(false, |(i, _)| i == offset) {
21421 break;
21422 };
21423 if is_grapheme_whitespace(grapheme) != is_whitespace
21424 || (grapheme == "\n") != is_newline
21425 {
21426 break;
21427 };
21428 offset += grapheme.len();
21429 grapheme_len += 1;
21430 iter.next();
21431 }
21432 }
21433 let token = &self.input[..offset];
21434 self.input = &self.input[offset..];
21435 if token == "\n" {
21436 Some(WordBreakToken::Newline)
21437 } else if is_whitespace {
21438 Some(WordBreakToken::InlineWhitespace {
21439 token,
21440 grapheme_len,
21441 })
21442 } else {
21443 Some(WordBreakToken::Word {
21444 token,
21445 grapheme_len,
21446 })
21447 }
21448 } else {
21449 None
21450 }
21451 }
21452}
21453
21454#[test]
21455fn test_word_breaking_tokenizer() {
21456 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
21457 ("", &[]),
21458 (" ", &[whitespace(" ", 2)]),
21459 ("Ʒ", &[word("Ʒ", 1)]),
21460 ("Ǽ", &[word("Ǽ", 1)]),
21461 ("⋑", &[word("⋑", 1)]),
21462 ("⋑⋑", &[word("⋑⋑", 2)]),
21463 (
21464 "原理,进而",
21465 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
21466 ),
21467 (
21468 "hello world",
21469 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
21470 ),
21471 (
21472 "hello, world",
21473 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
21474 ),
21475 (
21476 " hello world",
21477 &[
21478 whitespace(" ", 2),
21479 word("hello", 5),
21480 whitespace(" ", 1),
21481 word("world", 5),
21482 ],
21483 ),
21484 (
21485 "这是什么 \n 钢笔",
21486 &[
21487 word("这", 1),
21488 word("是", 1),
21489 word("什", 1),
21490 word("么", 1),
21491 whitespace(" ", 1),
21492 newline(),
21493 whitespace(" ", 1),
21494 word("钢", 1),
21495 word("笔", 1),
21496 ],
21497 ),
21498 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
21499 ];
21500
21501 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21502 WordBreakToken::Word {
21503 token,
21504 grapheme_len,
21505 }
21506 }
21507
21508 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21509 WordBreakToken::InlineWhitespace {
21510 token,
21511 grapheme_len,
21512 }
21513 }
21514
21515 fn newline() -> WordBreakToken<'static> {
21516 WordBreakToken::Newline
21517 }
21518
21519 for (input, result) in tests {
21520 assert_eq!(
21521 WordBreakingTokenizer::new(input)
21522 .collect::<Vec<_>>()
21523 .as_slice(),
21524 *result,
21525 );
21526 }
21527}
21528
21529fn wrap_with_prefix(
21530 first_line_prefix: String,
21531 subsequent_lines_prefix: String,
21532 unwrapped_text: String,
21533 wrap_column: usize,
21534 tab_size: NonZeroU32,
21535 preserve_existing_whitespace: bool,
21536) -> String {
21537 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
21538 let subsequent_lines_prefix_len =
21539 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
21540 let mut wrapped_text = String::new();
21541 let mut current_line = first_line_prefix.clone();
21542 let mut is_first_line = true;
21543
21544 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
21545 let mut current_line_len = first_line_prefix_len;
21546 let mut in_whitespace = false;
21547 for token in tokenizer {
21548 let have_preceding_whitespace = in_whitespace;
21549 match token {
21550 WordBreakToken::Word {
21551 token,
21552 grapheme_len,
21553 } => {
21554 in_whitespace = false;
21555 let current_prefix_len = if is_first_line {
21556 first_line_prefix_len
21557 } else {
21558 subsequent_lines_prefix_len
21559 };
21560 if current_line_len + grapheme_len > wrap_column
21561 && current_line_len != current_prefix_len
21562 {
21563 wrapped_text.push_str(current_line.trim_end());
21564 wrapped_text.push('\n');
21565 is_first_line = false;
21566 current_line = subsequent_lines_prefix.clone();
21567 current_line_len = subsequent_lines_prefix_len;
21568 }
21569 current_line.push_str(token);
21570 current_line_len += grapheme_len;
21571 }
21572 WordBreakToken::InlineWhitespace {
21573 mut token,
21574 mut grapheme_len,
21575 } => {
21576 in_whitespace = true;
21577 if have_preceding_whitespace && !preserve_existing_whitespace {
21578 continue;
21579 }
21580 if !preserve_existing_whitespace {
21581 token = " ";
21582 grapheme_len = 1;
21583 }
21584 let current_prefix_len = if is_first_line {
21585 first_line_prefix_len
21586 } else {
21587 subsequent_lines_prefix_len
21588 };
21589 if current_line_len + grapheme_len > wrap_column {
21590 wrapped_text.push_str(current_line.trim_end());
21591 wrapped_text.push('\n');
21592 is_first_line = false;
21593 current_line = subsequent_lines_prefix.clone();
21594 current_line_len = subsequent_lines_prefix_len;
21595 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
21596 current_line.push_str(token);
21597 current_line_len += grapheme_len;
21598 }
21599 }
21600 WordBreakToken::Newline => {
21601 in_whitespace = true;
21602 let current_prefix_len = if is_first_line {
21603 first_line_prefix_len
21604 } else {
21605 subsequent_lines_prefix_len
21606 };
21607 if preserve_existing_whitespace {
21608 wrapped_text.push_str(current_line.trim_end());
21609 wrapped_text.push('\n');
21610 is_first_line = false;
21611 current_line = subsequent_lines_prefix.clone();
21612 current_line_len = subsequent_lines_prefix_len;
21613 } else if have_preceding_whitespace {
21614 continue;
21615 } else if current_line_len + 1 > wrap_column
21616 && current_line_len != current_prefix_len
21617 {
21618 wrapped_text.push_str(current_line.trim_end());
21619 wrapped_text.push('\n');
21620 is_first_line = false;
21621 current_line = subsequent_lines_prefix.clone();
21622 current_line_len = subsequent_lines_prefix_len;
21623 } else if current_line_len != current_prefix_len {
21624 current_line.push(' ');
21625 current_line_len += 1;
21626 }
21627 }
21628 }
21629 }
21630
21631 if !current_line.is_empty() {
21632 wrapped_text.push_str(¤t_line);
21633 }
21634 wrapped_text
21635}
21636
21637#[test]
21638fn test_wrap_with_prefix() {
21639 assert_eq!(
21640 wrap_with_prefix(
21641 "# ".to_string(),
21642 "# ".to_string(),
21643 "abcdefg".to_string(),
21644 4,
21645 NonZeroU32::new(4).unwrap(),
21646 false,
21647 ),
21648 "# abcdefg"
21649 );
21650 assert_eq!(
21651 wrap_with_prefix(
21652 "".to_string(),
21653 "".to_string(),
21654 "\thello world".to_string(),
21655 8,
21656 NonZeroU32::new(4).unwrap(),
21657 false,
21658 ),
21659 "hello\nworld"
21660 );
21661 assert_eq!(
21662 wrap_with_prefix(
21663 "// ".to_string(),
21664 "// ".to_string(),
21665 "xx \nyy zz aa bb cc".to_string(),
21666 12,
21667 NonZeroU32::new(4).unwrap(),
21668 false,
21669 ),
21670 "// xx yy zz\n// aa bb cc"
21671 );
21672 assert_eq!(
21673 wrap_with_prefix(
21674 String::new(),
21675 String::new(),
21676 "这是什么 \n 钢笔".to_string(),
21677 3,
21678 NonZeroU32::new(4).unwrap(),
21679 false,
21680 ),
21681 "这是什\n么 钢\n笔"
21682 );
21683}
21684
21685pub trait CollaborationHub {
21686 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
21687 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
21688 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
21689}
21690
21691impl CollaborationHub for Entity<Project> {
21692 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
21693 self.read(cx).collaborators()
21694 }
21695
21696 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
21697 self.read(cx).user_store().read(cx).participant_indices()
21698 }
21699
21700 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
21701 let this = self.read(cx);
21702 let user_ids = this.collaborators().values().map(|c| c.user_id);
21703 this.user_store().read(cx).participant_names(user_ids, cx)
21704 }
21705}
21706
21707pub trait SemanticsProvider {
21708 fn hover(
21709 &self,
21710 buffer: &Entity<Buffer>,
21711 position: text::Anchor,
21712 cx: &mut App,
21713 ) -> Option<Task<Vec<project::Hover>>>;
21714
21715 fn inline_values(
21716 &self,
21717 buffer_handle: Entity<Buffer>,
21718 range: Range<text::Anchor>,
21719 cx: &mut App,
21720 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21721
21722 fn inlay_hints(
21723 &self,
21724 buffer_handle: Entity<Buffer>,
21725 range: Range<text::Anchor>,
21726 cx: &mut App,
21727 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21728
21729 fn resolve_inlay_hint(
21730 &self,
21731 hint: InlayHint,
21732 buffer_handle: Entity<Buffer>,
21733 server_id: LanguageServerId,
21734 cx: &mut App,
21735 ) -> Option<Task<anyhow::Result<InlayHint>>>;
21736
21737 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
21738
21739 fn document_highlights(
21740 &self,
21741 buffer: &Entity<Buffer>,
21742 position: text::Anchor,
21743 cx: &mut App,
21744 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
21745
21746 fn definitions(
21747 &self,
21748 buffer: &Entity<Buffer>,
21749 position: text::Anchor,
21750 kind: GotoDefinitionKind,
21751 cx: &mut App,
21752 ) -> Option<Task<Result<Vec<LocationLink>>>>;
21753
21754 fn range_for_rename(
21755 &self,
21756 buffer: &Entity<Buffer>,
21757 position: text::Anchor,
21758 cx: &mut App,
21759 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
21760
21761 fn perform_rename(
21762 &self,
21763 buffer: &Entity<Buffer>,
21764 position: text::Anchor,
21765 new_name: String,
21766 cx: &mut App,
21767 ) -> Option<Task<Result<ProjectTransaction>>>;
21768}
21769
21770pub trait CompletionProvider {
21771 fn completions(
21772 &self,
21773 excerpt_id: ExcerptId,
21774 buffer: &Entity<Buffer>,
21775 buffer_position: text::Anchor,
21776 trigger: CompletionContext,
21777 window: &mut Window,
21778 cx: &mut Context<Editor>,
21779 ) -> Task<Result<Vec<CompletionResponse>>>;
21780
21781 fn resolve_completions(
21782 &self,
21783 _buffer: Entity<Buffer>,
21784 _completion_indices: Vec<usize>,
21785 _completions: Rc<RefCell<Box<[Completion]>>>,
21786 _cx: &mut Context<Editor>,
21787 ) -> Task<Result<bool>> {
21788 Task::ready(Ok(false))
21789 }
21790
21791 fn apply_additional_edits_for_completion(
21792 &self,
21793 _buffer: Entity<Buffer>,
21794 _completions: Rc<RefCell<Box<[Completion]>>>,
21795 _completion_index: usize,
21796 _push_to_history: bool,
21797 _cx: &mut Context<Editor>,
21798 ) -> Task<Result<Option<language::Transaction>>> {
21799 Task::ready(Ok(None))
21800 }
21801
21802 fn is_completion_trigger(
21803 &self,
21804 buffer: &Entity<Buffer>,
21805 position: language::Anchor,
21806 text: &str,
21807 trigger_in_words: bool,
21808 menu_is_open: bool,
21809 cx: &mut Context<Editor>,
21810 ) -> bool;
21811
21812 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
21813
21814 fn sort_completions(&self) -> bool {
21815 true
21816 }
21817
21818 fn filter_completions(&self) -> bool {
21819 true
21820 }
21821}
21822
21823pub trait CodeActionProvider {
21824 fn id(&self) -> Arc<str>;
21825
21826 fn code_actions(
21827 &self,
21828 buffer: &Entity<Buffer>,
21829 range: Range<text::Anchor>,
21830 window: &mut Window,
21831 cx: &mut App,
21832 ) -> Task<Result<Vec<CodeAction>>>;
21833
21834 fn apply_code_action(
21835 &self,
21836 buffer_handle: Entity<Buffer>,
21837 action: CodeAction,
21838 excerpt_id: ExcerptId,
21839 push_to_history: bool,
21840 window: &mut Window,
21841 cx: &mut App,
21842 ) -> Task<Result<ProjectTransaction>>;
21843}
21844
21845impl CodeActionProvider for Entity<Project> {
21846 fn id(&self) -> Arc<str> {
21847 "project".into()
21848 }
21849
21850 fn code_actions(
21851 &self,
21852 buffer: &Entity<Buffer>,
21853 range: Range<text::Anchor>,
21854 _window: &mut Window,
21855 cx: &mut App,
21856 ) -> Task<Result<Vec<CodeAction>>> {
21857 self.update(cx, |project, cx| {
21858 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
21859 let code_actions = project.code_actions(buffer, range, None, cx);
21860 cx.background_spawn(async move {
21861 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
21862 Ok(code_lens_actions
21863 .context("code lens fetch")?
21864 .into_iter()
21865 .chain(code_actions.context("code action fetch")?)
21866 .collect())
21867 })
21868 })
21869 }
21870
21871 fn apply_code_action(
21872 &self,
21873 buffer_handle: Entity<Buffer>,
21874 action: CodeAction,
21875 _excerpt_id: ExcerptId,
21876 push_to_history: bool,
21877 _window: &mut Window,
21878 cx: &mut App,
21879 ) -> Task<Result<ProjectTransaction>> {
21880 self.update(cx, |project, cx| {
21881 project.apply_code_action(buffer_handle, action, push_to_history, cx)
21882 })
21883 }
21884}
21885
21886fn snippet_completions(
21887 project: &Project,
21888 buffer: &Entity<Buffer>,
21889 buffer_position: text::Anchor,
21890 cx: &mut App,
21891) -> Task<Result<CompletionResponse>> {
21892 let languages = buffer.read(cx).languages_at(buffer_position);
21893 let snippet_store = project.snippets().read(cx);
21894
21895 let scopes: Vec<_> = languages
21896 .iter()
21897 .filter_map(|language| {
21898 let language_name = language.lsp_id();
21899 let snippets = snippet_store.snippets_for(Some(language_name), cx);
21900
21901 if snippets.is_empty() {
21902 None
21903 } else {
21904 Some((language.default_scope(), snippets))
21905 }
21906 })
21907 .collect();
21908
21909 if scopes.is_empty() {
21910 return Task::ready(Ok(CompletionResponse {
21911 completions: vec![],
21912 is_incomplete: false,
21913 }));
21914 }
21915
21916 let snapshot = buffer.read(cx).text_snapshot();
21917 let chars: String = snapshot
21918 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
21919 .collect();
21920 let executor = cx.background_executor().clone();
21921
21922 cx.background_spawn(async move {
21923 let mut is_incomplete = false;
21924 let mut completions: Vec<Completion> = Vec::new();
21925 for (scope, snippets) in scopes.into_iter() {
21926 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
21927 let mut last_word = chars
21928 .chars()
21929 .take_while(|c| classifier.is_word(*c))
21930 .collect::<String>();
21931 last_word = last_word.chars().rev().collect();
21932
21933 if last_word.is_empty() {
21934 return Ok(CompletionResponse {
21935 completions: vec![],
21936 is_incomplete: true,
21937 });
21938 }
21939
21940 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
21941 let to_lsp = |point: &text::Anchor| {
21942 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
21943 point_to_lsp(end)
21944 };
21945 let lsp_end = to_lsp(&buffer_position);
21946
21947 let candidates = snippets
21948 .iter()
21949 .enumerate()
21950 .flat_map(|(ix, snippet)| {
21951 snippet
21952 .prefix
21953 .iter()
21954 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
21955 })
21956 .collect::<Vec<StringMatchCandidate>>();
21957
21958 const MAX_RESULTS: usize = 100;
21959 let mut matches = fuzzy::match_strings(
21960 &candidates,
21961 &last_word,
21962 last_word.chars().any(|c| c.is_uppercase()),
21963 true,
21964 MAX_RESULTS,
21965 &Default::default(),
21966 executor.clone(),
21967 )
21968 .await;
21969
21970 if matches.len() >= MAX_RESULTS {
21971 is_incomplete = true;
21972 }
21973
21974 // Remove all candidates where the query's start does not match the start of any word in the candidate
21975 if let Some(query_start) = last_word.chars().next() {
21976 matches.retain(|string_match| {
21977 split_words(&string_match.string).any(|word| {
21978 // Check that the first codepoint of the word as lowercase matches the first
21979 // codepoint of the query as lowercase
21980 word.chars()
21981 .flat_map(|codepoint| codepoint.to_lowercase())
21982 .zip(query_start.to_lowercase())
21983 .all(|(word_cp, query_cp)| word_cp == query_cp)
21984 })
21985 });
21986 }
21987
21988 let matched_strings = matches
21989 .into_iter()
21990 .map(|m| m.string)
21991 .collect::<HashSet<_>>();
21992
21993 completions.extend(snippets.iter().filter_map(|snippet| {
21994 let matching_prefix = snippet
21995 .prefix
21996 .iter()
21997 .find(|prefix| matched_strings.contains(*prefix))?;
21998 let start = as_offset - last_word.len();
21999 let start = snapshot.anchor_before(start);
22000 let range = start..buffer_position;
22001 let lsp_start = to_lsp(&start);
22002 let lsp_range = lsp::Range {
22003 start: lsp_start,
22004 end: lsp_end,
22005 };
22006 Some(Completion {
22007 replace_range: range,
22008 new_text: snippet.body.clone(),
22009 source: CompletionSource::Lsp {
22010 insert_range: None,
22011 server_id: LanguageServerId(usize::MAX),
22012 resolved: true,
22013 lsp_completion: Box::new(lsp::CompletionItem {
22014 label: snippet.prefix.first().unwrap().clone(),
22015 kind: Some(CompletionItemKind::SNIPPET),
22016 label_details: snippet.description.as_ref().map(|description| {
22017 lsp::CompletionItemLabelDetails {
22018 detail: Some(description.clone()),
22019 description: None,
22020 }
22021 }),
22022 insert_text_format: Some(InsertTextFormat::SNIPPET),
22023 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22024 lsp::InsertReplaceEdit {
22025 new_text: snippet.body.clone(),
22026 insert: lsp_range,
22027 replace: lsp_range,
22028 },
22029 )),
22030 filter_text: Some(snippet.body.clone()),
22031 sort_text: Some(char::MAX.to_string()),
22032 ..lsp::CompletionItem::default()
22033 }),
22034 lsp_defaults: None,
22035 },
22036 label: CodeLabel {
22037 text: matching_prefix.clone(),
22038 runs: Vec::new(),
22039 filter_range: 0..matching_prefix.len(),
22040 },
22041 icon_path: None,
22042 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
22043 single_line: snippet.name.clone().into(),
22044 plain_text: snippet
22045 .description
22046 .clone()
22047 .map(|description| description.into()),
22048 }),
22049 insert_text_mode: None,
22050 confirm: None,
22051 })
22052 }))
22053 }
22054
22055 Ok(CompletionResponse {
22056 completions,
22057 is_incomplete,
22058 })
22059 })
22060}
22061
22062impl CompletionProvider for Entity<Project> {
22063 fn completions(
22064 &self,
22065 _excerpt_id: ExcerptId,
22066 buffer: &Entity<Buffer>,
22067 buffer_position: text::Anchor,
22068 options: CompletionContext,
22069 _window: &mut Window,
22070 cx: &mut Context<Editor>,
22071 ) -> Task<Result<Vec<CompletionResponse>>> {
22072 self.update(cx, |project, cx| {
22073 let snippets = snippet_completions(project, buffer, buffer_position, cx);
22074 let project_completions = project.completions(buffer, buffer_position, options, cx);
22075 cx.background_spawn(async move {
22076 let mut responses = project_completions.await?;
22077 let snippets = snippets.await?;
22078 if !snippets.completions.is_empty() {
22079 responses.push(snippets);
22080 }
22081 Ok(responses)
22082 })
22083 })
22084 }
22085
22086 fn resolve_completions(
22087 &self,
22088 buffer: Entity<Buffer>,
22089 completion_indices: Vec<usize>,
22090 completions: Rc<RefCell<Box<[Completion]>>>,
22091 cx: &mut Context<Editor>,
22092 ) -> Task<Result<bool>> {
22093 self.update(cx, |project, cx| {
22094 project.lsp_store().update(cx, |lsp_store, cx| {
22095 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
22096 })
22097 })
22098 }
22099
22100 fn apply_additional_edits_for_completion(
22101 &self,
22102 buffer: Entity<Buffer>,
22103 completions: Rc<RefCell<Box<[Completion]>>>,
22104 completion_index: usize,
22105 push_to_history: bool,
22106 cx: &mut Context<Editor>,
22107 ) -> Task<Result<Option<language::Transaction>>> {
22108 self.update(cx, |project, cx| {
22109 project.lsp_store().update(cx, |lsp_store, cx| {
22110 lsp_store.apply_additional_edits_for_completion(
22111 buffer,
22112 completions,
22113 completion_index,
22114 push_to_history,
22115 cx,
22116 )
22117 })
22118 })
22119 }
22120
22121 fn is_completion_trigger(
22122 &self,
22123 buffer: &Entity<Buffer>,
22124 position: language::Anchor,
22125 text: &str,
22126 trigger_in_words: bool,
22127 menu_is_open: bool,
22128 cx: &mut Context<Editor>,
22129 ) -> bool {
22130 let mut chars = text.chars();
22131 let char = if let Some(char) = chars.next() {
22132 char
22133 } else {
22134 return false;
22135 };
22136 if chars.next().is_some() {
22137 return false;
22138 }
22139
22140 let buffer = buffer.read(cx);
22141 let snapshot = buffer.snapshot();
22142 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
22143 return false;
22144 }
22145 let classifier = snapshot.char_classifier_at(position).for_completion(true);
22146 if trigger_in_words && classifier.is_word(char) {
22147 return true;
22148 }
22149
22150 buffer.completion_triggers().contains(text)
22151 }
22152}
22153
22154impl SemanticsProvider for Entity<Project> {
22155 fn hover(
22156 &self,
22157 buffer: &Entity<Buffer>,
22158 position: text::Anchor,
22159 cx: &mut App,
22160 ) -> Option<Task<Vec<project::Hover>>> {
22161 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
22162 }
22163
22164 fn document_highlights(
22165 &self,
22166 buffer: &Entity<Buffer>,
22167 position: text::Anchor,
22168 cx: &mut App,
22169 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
22170 Some(self.update(cx, |project, cx| {
22171 project.document_highlights(buffer, position, cx)
22172 }))
22173 }
22174
22175 fn definitions(
22176 &self,
22177 buffer: &Entity<Buffer>,
22178 position: text::Anchor,
22179 kind: GotoDefinitionKind,
22180 cx: &mut App,
22181 ) -> Option<Task<Result<Vec<LocationLink>>>> {
22182 Some(self.update(cx, |project, cx| match kind {
22183 GotoDefinitionKind::Symbol => project.definitions(&buffer, position, cx),
22184 GotoDefinitionKind::Declaration => project.declarations(&buffer, position, cx),
22185 GotoDefinitionKind::Type => project.type_definitions(&buffer, position, cx),
22186 GotoDefinitionKind::Implementation => project.implementations(&buffer, position, cx),
22187 }))
22188 }
22189
22190 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
22191 // TODO: make this work for remote projects
22192 self.update(cx, |project, cx| {
22193 if project
22194 .active_debug_session(cx)
22195 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
22196 {
22197 return true;
22198 }
22199
22200 buffer.update(cx, |buffer, cx| {
22201 project.any_language_server_supports_inlay_hints(buffer, cx)
22202 })
22203 })
22204 }
22205
22206 fn inline_values(
22207 &self,
22208 buffer_handle: Entity<Buffer>,
22209 range: Range<text::Anchor>,
22210 cx: &mut App,
22211 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22212 self.update(cx, |project, cx| {
22213 let (session, active_stack_frame) = project.active_debug_session(cx)?;
22214
22215 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
22216 })
22217 }
22218
22219 fn inlay_hints(
22220 &self,
22221 buffer_handle: Entity<Buffer>,
22222 range: Range<text::Anchor>,
22223 cx: &mut App,
22224 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22225 Some(self.update(cx, |project, cx| {
22226 project.inlay_hints(buffer_handle, range, cx)
22227 }))
22228 }
22229
22230 fn resolve_inlay_hint(
22231 &self,
22232 hint: InlayHint,
22233 buffer_handle: Entity<Buffer>,
22234 server_id: LanguageServerId,
22235 cx: &mut App,
22236 ) -> Option<Task<anyhow::Result<InlayHint>>> {
22237 Some(self.update(cx, |project, cx| {
22238 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
22239 }))
22240 }
22241
22242 fn range_for_rename(
22243 &self,
22244 buffer: &Entity<Buffer>,
22245 position: text::Anchor,
22246 cx: &mut App,
22247 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
22248 Some(self.update(cx, |project, cx| {
22249 let buffer = buffer.clone();
22250 let task = project.prepare_rename(buffer.clone(), position, cx);
22251 cx.spawn(async move |_, cx| {
22252 Ok(match task.await? {
22253 PrepareRenameResponse::Success(range) => Some(range),
22254 PrepareRenameResponse::InvalidPosition => None,
22255 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
22256 // Fallback on using TreeSitter info to determine identifier range
22257 buffer.read_with(cx, |buffer, _| {
22258 let snapshot = buffer.snapshot();
22259 let (range, kind) = snapshot.surrounding_word(position, false);
22260 if kind != Some(CharKind::Word) {
22261 return None;
22262 }
22263 Some(
22264 snapshot.anchor_before(range.start)
22265 ..snapshot.anchor_after(range.end),
22266 )
22267 })?
22268 }
22269 })
22270 })
22271 }))
22272 }
22273
22274 fn perform_rename(
22275 &self,
22276 buffer: &Entity<Buffer>,
22277 position: text::Anchor,
22278 new_name: String,
22279 cx: &mut App,
22280 ) -> Option<Task<Result<ProjectTransaction>>> {
22281 Some(self.update(cx, |project, cx| {
22282 project.perform_rename(buffer.clone(), position, new_name, cx)
22283 }))
22284 }
22285}
22286
22287fn inlay_hint_settings(
22288 location: Anchor,
22289 snapshot: &MultiBufferSnapshot,
22290 cx: &mut Context<Editor>,
22291) -> InlayHintSettings {
22292 let file = snapshot.file_at(location);
22293 let language = snapshot.language_at(location).map(|l| l.name());
22294 language_settings(language, file, cx).inlay_hints
22295}
22296
22297fn consume_contiguous_rows(
22298 contiguous_row_selections: &mut Vec<Selection<Point>>,
22299 selection: &Selection<Point>,
22300 display_map: &DisplaySnapshot,
22301 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
22302) -> (MultiBufferRow, MultiBufferRow) {
22303 contiguous_row_selections.push(selection.clone());
22304 let start_row = starting_row(selection, display_map);
22305 let mut end_row = ending_row(selection, display_map);
22306
22307 while let Some(next_selection) = selections.peek() {
22308 if next_selection.start.row <= end_row.0 {
22309 end_row = ending_row(next_selection, display_map);
22310 contiguous_row_selections.push(selections.next().unwrap().clone());
22311 } else {
22312 break;
22313 }
22314 }
22315 (start_row, end_row)
22316}
22317
22318fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22319 if selection.start.column > 0 {
22320 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
22321 } else {
22322 MultiBufferRow(selection.start.row)
22323 }
22324}
22325
22326fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22327 if next_selection.end.column > 0 || next_selection.is_empty() {
22328 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
22329 } else {
22330 MultiBufferRow(next_selection.end.row)
22331 }
22332}
22333
22334impl EditorSnapshot {
22335 pub fn remote_selections_in_range<'a>(
22336 &'a self,
22337 range: &'a Range<Anchor>,
22338 collaboration_hub: &dyn CollaborationHub,
22339 cx: &'a App,
22340 ) -> impl 'a + Iterator<Item = RemoteSelection> {
22341 let participant_names = collaboration_hub.user_names(cx);
22342 let participant_indices = collaboration_hub.user_participant_indices(cx);
22343 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
22344 let collaborators_by_replica_id = collaborators_by_peer_id
22345 .values()
22346 .map(|collaborator| (collaborator.replica_id, collaborator))
22347 .collect::<HashMap<_, _>>();
22348 self.buffer_snapshot
22349 .selections_in_range(range, false)
22350 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
22351 if replica_id == AGENT_REPLICA_ID {
22352 Some(RemoteSelection {
22353 replica_id,
22354 selection,
22355 cursor_shape,
22356 line_mode,
22357 collaborator_id: CollaboratorId::Agent,
22358 user_name: Some("Agent".into()),
22359 color: cx.theme().players().agent(),
22360 })
22361 } else {
22362 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
22363 let participant_index = participant_indices.get(&collaborator.user_id).copied();
22364 let user_name = participant_names.get(&collaborator.user_id).cloned();
22365 Some(RemoteSelection {
22366 replica_id,
22367 selection,
22368 cursor_shape,
22369 line_mode,
22370 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
22371 user_name,
22372 color: if let Some(index) = participant_index {
22373 cx.theme().players().color_for_participant(index.0)
22374 } else {
22375 cx.theme().players().absent()
22376 },
22377 })
22378 }
22379 })
22380 }
22381
22382 pub fn hunks_for_ranges(
22383 &self,
22384 ranges: impl IntoIterator<Item = Range<Point>>,
22385 ) -> Vec<MultiBufferDiffHunk> {
22386 let mut hunks = Vec::new();
22387 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
22388 HashMap::default();
22389 for query_range in ranges {
22390 let query_rows =
22391 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
22392 for hunk in self.buffer_snapshot.diff_hunks_in_range(
22393 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
22394 ) {
22395 // Include deleted hunks that are adjacent to the query range, because
22396 // otherwise they would be missed.
22397 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
22398 if hunk.status().is_deleted() {
22399 intersects_range |= hunk.row_range.start == query_rows.end;
22400 intersects_range |= hunk.row_range.end == query_rows.start;
22401 }
22402 if intersects_range {
22403 if !processed_buffer_rows
22404 .entry(hunk.buffer_id)
22405 .or_default()
22406 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
22407 {
22408 continue;
22409 }
22410 hunks.push(hunk);
22411 }
22412 }
22413 }
22414
22415 hunks
22416 }
22417
22418 fn display_diff_hunks_for_rows<'a>(
22419 &'a self,
22420 display_rows: Range<DisplayRow>,
22421 folded_buffers: &'a HashSet<BufferId>,
22422 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
22423 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
22424 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
22425
22426 self.buffer_snapshot
22427 .diff_hunks_in_range(buffer_start..buffer_end)
22428 .filter_map(|hunk| {
22429 if folded_buffers.contains(&hunk.buffer_id) {
22430 return None;
22431 }
22432
22433 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
22434 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
22435
22436 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
22437 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
22438
22439 let display_hunk = if hunk_display_start.column() != 0 {
22440 DisplayDiffHunk::Folded {
22441 display_row: hunk_display_start.row(),
22442 }
22443 } else {
22444 let mut end_row = hunk_display_end.row();
22445 if hunk_display_end.column() > 0 {
22446 end_row.0 += 1;
22447 }
22448 let is_created_file = hunk.is_created_file();
22449 DisplayDiffHunk::Unfolded {
22450 status: hunk.status(),
22451 diff_base_byte_range: hunk.diff_base_byte_range,
22452 display_row_range: hunk_display_start.row()..end_row,
22453 multi_buffer_range: Anchor::range_in_buffer(
22454 hunk.excerpt_id,
22455 hunk.buffer_id,
22456 hunk.buffer_range,
22457 ),
22458 is_created_file,
22459 }
22460 };
22461
22462 Some(display_hunk)
22463 })
22464 }
22465
22466 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
22467 self.display_snapshot.buffer_snapshot.language_at(position)
22468 }
22469
22470 pub fn is_focused(&self) -> bool {
22471 self.is_focused
22472 }
22473
22474 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
22475 self.placeholder_text.as_ref()
22476 }
22477
22478 pub fn scroll_position(&self) -> gpui::Point<f32> {
22479 self.scroll_anchor.scroll_position(&self.display_snapshot)
22480 }
22481
22482 fn gutter_dimensions(
22483 &self,
22484 font_id: FontId,
22485 font_size: Pixels,
22486 max_line_number_width: Pixels,
22487 cx: &App,
22488 ) -> Option<GutterDimensions> {
22489 if !self.show_gutter {
22490 return None;
22491 }
22492
22493 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
22494 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
22495
22496 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
22497 matches!(
22498 ProjectSettings::get_global(cx).git.git_gutter,
22499 Some(GitGutterSetting::TrackedFiles)
22500 )
22501 });
22502 let gutter_settings = EditorSettings::get_global(cx).gutter;
22503 let show_line_numbers = self
22504 .show_line_numbers
22505 .unwrap_or(gutter_settings.line_numbers);
22506 let line_gutter_width = if show_line_numbers {
22507 // Avoid flicker-like gutter resizes when the line number gains another digit by
22508 // only resizing the gutter on files with > 10**min_line_number_digits lines.
22509 let min_width_for_number_on_gutter =
22510 ch_advance * gutter_settings.min_line_number_digits as f32;
22511 max_line_number_width.max(min_width_for_number_on_gutter)
22512 } else {
22513 0.0.into()
22514 };
22515
22516 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
22517 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
22518
22519 let git_blame_entries_width =
22520 self.git_blame_gutter_max_author_length
22521 .map(|max_author_length| {
22522 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
22523 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
22524
22525 /// The number of characters to dedicate to gaps and margins.
22526 const SPACING_WIDTH: usize = 4;
22527
22528 let max_char_count = max_author_length.min(renderer.max_author_length())
22529 + ::git::SHORT_SHA_LENGTH
22530 + MAX_RELATIVE_TIMESTAMP.len()
22531 + SPACING_WIDTH;
22532
22533 ch_advance * max_char_count
22534 });
22535
22536 let is_singleton = self.buffer_snapshot.is_singleton();
22537
22538 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
22539 left_padding += if !is_singleton {
22540 ch_width * 4.0
22541 } else if show_runnables || show_breakpoints {
22542 ch_width * 3.0
22543 } else if show_git_gutter && show_line_numbers {
22544 ch_width * 2.0
22545 } else if show_git_gutter || show_line_numbers {
22546 ch_width
22547 } else {
22548 px(0.)
22549 };
22550
22551 let shows_folds = is_singleton && gutter_settings.folds;
22552
22553 let right_padding = if shows_folds && show_line_numbers {
22554 ch_width * 4.0
22555 } else if shows_folds || (!is_singleton && show_line_numbers) {
22556 ch_width * 3.0
22557 } else if show_line_numbers {
22558 ch_width
22559 } else {
22560 px(0.)
22561 };
22562
22563 Some(GutterDimensions {
22564 left_padding,
22565 right_padding,
22566 width: line_gutter_width + left_padding + right_padding,
22567 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
22568 git_blame_entries_width,
22569 })
22570 }
22571
22572 pub fn render_crease_toggle(
22573 &self,
22574 buffer_row: MultiBufferRow,
22575 row_contains_cursor: bool,
22576 editor: Entity<Editor>,
22577 window: &mut Window,
22578 cx: &mut App,
22579 ) -> Option<AnyElement> {
22580 let folded = self.is_line_folded(buffer_row);
22581 let mut is_foldable = false;
22582
22583 if let Some(crease) = self
22584 .crease_snapshot
22585 .query_row(buffer_row, &self.buffer_snapshot)
22586 {
22587 is_foldable = true;
22588 match crease {
22589 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
22590 if let Some(render_toggle) = render_toggle {
22591 let toggle_callback =
22592 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
22593 if folded {
22594 editor.update(cx, |editor, cx| {
22595 editor.fold_at(buffer_row, window, cx)
22596 });
22597 } else {
22598 editor.update(cx, |editor, cx| {
22599 editor.unfold_at(buffer_row, window, cx)
22600 });
22601 }
22602 });
22603 return Some((render_toggle)(
22604 buffer_row,
22605 folded,
22606 toggle_callback,
22607 window,
22608 cx,
22609 ));
22610 }
22611 }
22612 }
22613 }
22614
22615 is_foldable |= self.starts_indent(buffer_row);
22616
22617 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
22618 Some(
22619 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
22620 .toggle_state(folded)
22621 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
22622 if folded {
22623 this.unfold_at(buffer_row, window, cx);
22624 } else {
22625 this.fold_at(buffer_row, window, cx);
22626 }
22627 }))
22628 .into_any_element(),
22629 )
22630 } else {
22631 None
22632 }
22633 }
22634
22635 pub fn render_crease_trailer(
22636 &self,
22637 buffer_row: MultiBufferRow,
22638 window: &mut Window,
22639 cx: &mut App,
22640 ) -> Option<AnyElement> {
22641 let folded = self.is_line_folded(buffer_row);
22642 if let Crease::Inline { render_trailer, .. } = self
22643 .crease_snapshot
22644 .query_row(buffer_row, &self.buffer_snapshot)?
22645 {
22646 let render_trailer = render_trailer.as_ref()?;
22647 Some(render_trailer(buffer_row, folded, window, cx))
22648 } else {
22649 None
22650 }
22651 }
22652}
22653
22654impl Deref for EditorSnapshot {
22655 type Target = DisplaySnapshot;
22656
22657 fn deref(&self) -> &Self::Target {
22658 &self.display_snapshot
22659 }
22660}
22661
22662#[derive(Clone, Debug, PartialEq, Eq)]
22663pub enum EditorEvent {
22664 InputIgnored {
22665 text: Arc<str>,
22666 },
22667 InputHandled {
22668 utf16_range_to_replace: Option<Range<isize>>,
22669 text: Arc<str>,
22670 },
22671 ExcerptsAdded {
22672 buffer: Entity<Buffer>,
22673 predecessor: ExcerptId,
22674 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
22675 },
22676 ExcerptsRemoved {
22677 ids: Vec<ExcerptId>,
22678 removed_buffer_ids: Vec<BufferId>,
22679 },
22680 BufferFoldToggled {
22681 ids: Vec<ExcerptId>,
22682 folded: bool,
22683 },
22684 ExcerptsEdited {
22685 ids: Vec<ExcerptId>,
22686 },
22687 ExcerptsExpanded {
22688 ids: Vec<ExcerptId>,
22689 },
22690 BufferEdited,
22691 Edited {
22692 transaction_id: clock::Lamport,
22693 },
22694 Reparsed(BufferId),
22695 Focused,
22696 FocusedIn,
22697 Blurred,
22698 DirtyChanged,
22699 Saved,
22700 TitleChanged,
22701 DiffBaseChanged,
22702 SelectionsChanged {
22703 local: bool,
22704 },
22705 ScrollPositionChanged {
22706 local: bool,
22707 autoscroll: bool,
22708 },
22709 Closed,
22710 TransactionUndone {
22711 transaction_id: clock::Lamport,
22712 },
22713 TransactionBegun {
22714 transaction_id: clock::Lamport,
22715 },
22716 Reloaded,
22717 CursorShapeChanged,
22718 PushedToNavHistory {
22719 anchor: Anchor,
22720 is_deactivate: bool,
22721 },
22722}
22723
22724impl EventEmitter<EditorEvent> for Editor {}
22725
22726impl Focusable for Editor {
22727 fn focus_handle(&self, _cx: &App) -> FocusHandle {
22728 self.focus_handle.clone()
22729 }
22730}
22731
22732impl Render for Editor {
22733 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22734 let settings = ThemeSettings::get_global(cx);
22735
22736 let mut text_style = match self.mode {
22737 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
22738 color: cx.theme().colors().editor_foreground,
22739 font_family: settings.ui_font.family.clone(),
22740 font_features: settings.ui_font.features.clone(),
22741 font_fallbacks: settings.ui_font.fallbacks.clone(),
22742 font_size: rems(0.875).into(),
22743 font_weight: settings.ui_font.weight,
22744 line_height: relative(settings.buffer_line_height.value()),
22745 ..Default::default()
22746 },
22747 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
22748 color: cx.theme().colors().editor_foreground,
22749 font_family: settings.buffer_font.family.clone(),
22750 font_features: settings.buffer_font.features.clone(),
22751 font_fallbacks: settings.buffer_font.fallbacks.clone(),
22752 font_size: settings.buffer_font_size(cx).into(),
22753 font_weight: settings.buffer_font.weight,
22754 line_height: relative(settings.buffer_line_height.value()),
22755 ..Default::default()
22756 },
22757 };
22758 if let Some(text_style_refinement) = &self.text_style_refinement {
22759 text_style.refine(text_style_refinement)
22760 }
22761
22762 let background = match self.mode {
22763 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
22764 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
22765 EditorMode::Full { .. } => cx.theme().colors().editor_background,
22766 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
22767 };
22768
22769 EditorElement::new(
22770 &cx.entity(),
22771 EditorStyle {
22772 background,
22773 border: cx.theme().colors().border,
22774 local_player: cx.theme().players().local(),
22775 text: text_style,
22776 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
22777 syntax: cx.theme().syntax().clone(),
22778 status: cx.theme().status().clone(),
22779 inlay_hints_style: make_inlay_hints_style(cx),
22780 edit_prediction_styles: make_suggestion_styles(cx),
22781 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
22782 show_underlines: self.diagnostics_enabled(),
22783 },
22784 )
22785 }
22786}
22787
22788impl EntityInputHandler for Editor {
22789 fn text_for_range(
22790 &mut self,
22791 range_utf16: Range<usize>,
22792 adjusted_range: &mut Option<Range<usize>>,
22793 _: &mut Window,
22794 cx: &mut Context<Self>,
22795 ) -> Option<String> {
22796 let snapshot = self.buffer.read(cx).read(cx);
22797 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
22798 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
22799 if (start.0..end.0) != range_utf16 {
22800 adjusted_range.replace(start.0..end.0);
22801 }
22802 Some(snapshot.text_for_range(start..end).collect())
22803 }
22804
22805 fn selected_text_range(
22806 &mut self,
22807 ignore_disabled_input: bool,
22808 _: &mut Window,
22809 cx: &mut Context<Self>,
22810 ) -> Option<UTF16Selection> {
22811 // Prevent the IME menu from appearing when holding down an alphabetic key
22812 // while input is disabled.
22813 if !ignore_disabled_input && !self.input_enabled {
22814 return None;
22815 }
22816
22817 let selection = self.selections.newest::<OffsetUtf16>(cx);
22818 let range = selection.range();
22819
22820 Some(UTF16Selection {
22821 range: range.start.0..range.end.0,
22822 reversed: selection.reversed,
22823 })
22824 }
22825
22826 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
22827 let snapshot = self.buffer.read(cx).read(cx);
22828 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
22829 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
22830 }
22831
22832 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
22833 self.clear_highlights::<InputComposition>(cx);
22834 self.ime_transaction.take();
22835 }
22836
22837 fn replace_text_in_range(
22838 &mut self,
22839 range_utf16: Option<Range<usize>>,
22840 text: &str,
22841 window: &mut Window,
22842 cx: &mut Context<Self>,
22843 ) {
22844 if !self.input_enabled {
22845 cx.emit(EditorEvent::InputIgnored { text: text.into() });
22846 return;
22847 }
22848
22849 self.transact(window, cx, |this, window, cx| {
22850 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
22851 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
22852 Some(this.selection_replacement_ranges(range_utf16, cx))
22853 } else {
22854 this.marked_text_ranges(cx)
22855 };
22856
22857 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
22858 let newest_selection_id = this.selections.newest_anchor().id;
22859 this.selections
22860 .all::<OffsetUtf16>(cx)
22861 .iter()
22862 .zip(ranges_to_replace.iter())
22863 .find_map(|(selection, range)| {
22864 if selection.id == newest_selection_id {
22865 Some(
22866 (range.start.0 as isize - selection.head().0 as isize)
22867 ..(range.end.0 as isize - selection.head().0 as isize),
22868 )
22869 } else {
22870 None
22871 }
22872 })
22873 });
22874
22875 cx.emit(EditorEvent::InputHandled {
22876 utf16_range_to_replace: range_to_replace,
22877 text: text.into(),
22878 });
22879
22880 if let Some(new_selected_ranges) = new_selected_ranges {
22881 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22882 selections.select_ranges(new_selected_ranges)
22883 });
22884 this.backspace(&Default::default(), window, cx);
22885 }
22886
22887 this.handle_input(text, window, cx);
22888 });
22889
22890 if let Some(transaction) = self.ime_transaction {
22891 self.buffer.update(cx, |buffer, cx| {
22892 buffer.group_until_transaction(transaction, cx);
22893 });
22894 }
22895
22896 self.unmark_text(window, cx);
22897 }
22898
22899 fn replace_and_mark_text_in_range(
22900 &mut self,
22901 range_utf16: Option<Range<usize>>,
22902 text: &str,
22903 new_selected_range_utf16: Option<Range<usize>>,
22904 window: &mut Window,
22905 cx: &mut Context<Self>,
22906 ) {
22907 if !self.input_enabled {
22908 return;
22909 }
22910
22911 let transaction = self.transact(window, cx, |this, window, cx| {
22912 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
22913 let snapshot = this.buffer.read(cx).read(cx);
22914 if let Some(relative_range_utf16) = range_utf16.as_ref() {
22915 for marked_range in &mut marked_ranges {
22916 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
22917 marked_range.start.0 += relative_range_utf16.start;
22918 marked_range.start =
22919 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
22920 marked_range.end =
22921 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
22922 }
22923 }
22924 Some(marked_ranges)
22925 } else if let Some(range_utf16) = range_utf16 {
22926 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
22927 Some(this.selection_replacement_ranges(range_utf16, cx))
22928 } else {
22929 None
22930 };
22931
22932 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
22933 let newest_selection_id = this.selections.newest_anchor().id;
22934 this.selections
22935 .all::<OffsetUtf16>(cx)
22936 .iter()
22937 .zip(ranges_to_replace.iter())
22938 .find_map(|(selection, range)| {
22939 if selection.id == newest_selection_id {
22940 Some(
22941 (range.start.0 as isize - selection.head().0 as isize)
22942 ..(range.end.0 as isize - selection.head().0 as isize),
22943 )
22944 } else {
22945 None
22946 }
22947 })
22948 });
22949
22950 cx.emit(EditorEvent::InputHandled {
22951 utf16_range_to_replace: range_to_replace,
22952 text: text.into(),
22953 });
22954
22955 if let Some(ranges) = ranges_to_replace {
22956 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22957 s.select_ranges(ranges)
22958 });
22959 }
22960
22961 let marked_ranges = {
22962 let snapshot = this.buffer.read(cx).read(cx);
22963 this.selections
22964 .disjoint_anchors()
22965 .iter()
22966 .map(|selection| {
22967 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
22968 })
22969 .collect::<Vec<_>>()
22970 };
22971
22972 if text.is_empty() {
22973 this.unmark_text(window, cx);
22974 } else {
22975 this.highlight_text::<InputComposition>(
22976 marked_ranges.clone(),
22977 HighlightStyle {
22978 underline: Some(UnderlineStyle {
22979 thickness: px(1.),
22980 color: None,
22981 wavy: false,
22982 }),
22983 ..Default::default()
22984 },
22985 cx,
22986 );
22987 }
22988
22989 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
22990 let use_autoclose = this.use_autoclose;
22991 let use_auto_surround = this.use_auto_surround;
22992 this.set_use_autoclose(false);
22993 this.set_use_auto_surround(false);
22994 this.handle_input(text, window, cx);
22995 this.set_use_autoclose(use_autoclose);
22996 this.set_use_auto_surround(use_auto_surround);
22997
22998 if let Some(new_selected_range) = new_selected_range_utf16 {
22999 let snapshot = this.buffer.read(cx).read(cx);
23000 let new_selected_ranges = marked_ranges
23001 .into_iter()
23002 .map(|marked_range| {
23003 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
23004 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
23005 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
23006 snapshot.clip_offset_utf16(new_start, Bias::Left)
23007 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
23008 })
23009 .collect::<Vec<_>>();
23010
23011 drop(snapshot);
23012 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23013 selections.select_ranges(new_selected_ranges)
23014 });
23015 }
23016 });
23017
23018 self.ime_transaction = self.ime_transaction.or(transaction);
23019 if let Some(transaction) = self.ime_transaction {
23020 self.buffer.update(cx, |buffer, cx| {
23021 buffer.group_until_transaction(transaction, cx);
23022 });
23023 }
23024
23025 if self.text_highlights::<InputComposition>(cx).is_none() {
23026 self.ime_transaction.take();
23027 }
23028 }
23029
23030 fn bounds_for_range(
23031 &mut self,
23032 range_utf16: Range<usize>,
23033 element_bounds: gpui::Bounds<Pixels>,
23034 window: &mut Window,
23035 cx: &mut Context<Self>,
23036 ) -> Option<gpui::Bounds<Pixels>> {
23037 let text_layout_details = self.text_layout_details(window);
23038 let CharacterDimensions {
23039 em_width,
23040 em_advance,
23041 line_height,
23042 } = self.character_dimensions(window);
23043
23044 let snapshot = self.snapshot(window, cx);
23045 let scroll_position = snapshot.scroll_position();
23046 let scroll_left = scroll_position.x * em_advance;
23047
23048 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
23049 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
23050 + self.gutter_dimensions.full_width();
23051 let y = line_height * (start.row().as_f32() - scroll_position.y);
23052
23053 Some(Bounds {
23054 origin: element_bounds.origin + point(x, y),
23055 size: size(em_width, line_height),
23056 })
23057 }
23058
23059 fn character_index_for_point(
23060 &mut self,
23061 point: gpui::Point<Pixels>,
23062 _window: &mut Window,
23063 _cx: &mut Context<Self>,
23064 ) -> Option<usize> {
23065 let position_map = self.last_position_map.as_ref()?;
23066 if !position_map.text_hitbox.contains(&point) {
23067 return None;
23068 }
23069 let display_point = position_map.point_for_position(point).previous_valid;
23070 let anchor = position_map
23071 .snapshot
23072 .display_point_to_anchor(display_point, Bias::Left);
23073 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
23074 Some(utf16_offset.0)
23075 }
23076}
23077
23078trait SelectionExt {
23079 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
23080 fn spanned_rows(
23081 &self,
23082 include_end_if_at_line_start: bool,
23083 map: &DisplaySnapshot,
23084 ) -> Range<MultiBufferRow>;
23085}
23086
23087impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
23088 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
23089 let start = self
23090 .start
23091 .to_point(&map.buffer_snapshot)
23092 .to_display_point(map);
23093 let end = self
23094 .end
23095 .to_point(&map.buffer_snapshot)
23096 .to_display_point(map);
23097 if self.reversed {
23098 end..start
23099 } else {
23100 start..end
23101 }
23102 }
23103
23104 fn spanned_rows(
23105 &self,
23106 include_end_if_at_line_start: bool,
23107 map: &DisplaySnapshot,
23108 ) -> Range<MultiBufferRow> {
23109 let start = self.start.to_point(&map.buffer_snapshot);
23110 let mut end = self.end.to_point(&map.buffer_snapshot);
23111 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
23112 end.row -= 1;
23113 }
23114
23115 let buffer_start = map.prev_line_boundary(start).0;
23116 let buffer_end = map.next_line_boundary(end).0;
23117 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
23118 }
23119}
23120
23121impl<T: InvalidationRegion> InvalidationStack<T> {
23122 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
23123 where
23124 S: Clone + ToOffset,
23125 {
23126 while let Some(region) = self.last() {
23127 let all_selections_inside_invalidation_ranges =
23128 if selections.len() == region.ranges().len() {
23129 selections
23130 .iter()
23131 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
23132 .all(|(selection, invalidation_range)| {
23133 let head = selection.head().to_offset(buffer);
23134 invalidation_range.start <= head && invalidation_range.end >= head
23135 })
23136 } else {
23137 false
23138 };
23139
23140 if all_selections_inside_invalidation_ranges {
23141 break;
23142 } else {
23143 self.pop();
23144 }
23145 }
23146 }
23147}
23148
23149impl<T> Default for InvalidationStack<T> {
23150 fn default() -> Self {
23151 Self(Default::default())
23152 }
23153}
23154
23155impl<T> Deref for InvalidationStack<T> {
23156 type Target = Vec<T>;
23157
23158 fn deref(&self) -> &Self::Target {
23159 &self.0
23160 }
23161}
23162
23163impl<T> DerefMut for InvalidationStack<T> {
23164 fn deref_mut(&mut self) -> &mut Self::Target {
23165 &mut self.0
23166 }
23167}
23168
23169impl InvalidationRegion for SnippetState {
23170 fn ranges(&self) -> &[Range<Anchor>] {
23171 &self.ranges[self.active_index]
23172 }
23173}
23174
23175fn edit_prediction_edit_text(
23176 current_snapshot: &BufferSnapshot,
23177 edits: &[(Range<Anchor>, String)],
23178 edit_preview: &EditPreview,
23179 include_deletions: bool,
23180 cx: &App,
23181) -> HighlightedText {
23182 let edits = edits
23183 .iter()
23184 .map(|(anchor, text)| {
23185 (
23186 anchor.start.text_anchor..anchor.end.text_anchor,
23187 text.clone(),
23188 )
23189 })
23190 .collect::<Vec<_>>();
23191
23192 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
23193}
23194
23195pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
23196 match severity {
23197 lsp::DiagnosticSeverity::ERROR => colors.error,
23198 lsp::DiagnosticSeverity::WARNING => colors.warning,
23199 lsp::DiagnosticSeverity::INFORMATION => colors.info,
23200 lsp::DiagnosticSeverity::HINT => colors.info,
23201 _ => colors.ignored,
23202 }
23203}
23204
23205pub fn styled_runs_for_code_label<'a>(
23206 label: &'a CodeLabel,
23207 syntax_theme: &'a theme::SyntaxTheme,
23208) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
23209 let fade_out = HighlightStyle {
23210 fade_out: Some(0.35),
23211 ..Default::default()
23212 };
23213
23214 let mut prev_end = label.filter_range.end;
23215 label
23216 .runs
23217 .iter()
23218 .enumerate()
23219 .flat_map(move |(ix, (range, highlight_id))| {
23220 let style = if let Some(style) = highlight_id.style(syntax_theme) {
23221 style
23222 } else {
23223 return Default::default();
23224 };
23225 let mut muted_style = style;
23226 muted_style.highlight(fade_out);
23227
23228 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
23229 if range.start >= label.filter_range.end {
23230 if range.start > prev_end {
23231 runs.push((prev_end..range.start, fade_out));
23232 }
23233 runs.push((range.clone(), muted_style));
23234 } else if range.end <= label.filter_range.end {
23235 runs.push((range.clone(), style));
23236 } else {
23237 runs.push((range.start..label.filter_range.end, style));
23238 runs.push((label.filter_range.end..range.end, muted_style));
23239 }
23240 prev_end = cmp::max(prev_end, range.end);
23241
23242 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
23243 runs.push((prev_end..label.text.len(), fade_out));
23244 }
23245
23246 runs
23247 })
23248}
23249
23250pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
23251 let mut prev_index = 0;
23252 let mut prev_codepoint: Option<char> = None;
23253 text.char_indices()
23254 .chain([(text.len(), '\0')])
23255 .filter_map(move |(index, codepoint)| {
23256 let prev_codepoint = prev_codepoint.replace(codepoint)?;
23257 let is_boundary = index == text.len()
23258 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
23259 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
23260 if is_boundary {
23261 let chunk = &text[prev_index..index];
23262 prev_index = index;
23263 Some(chunk)
23264 } else {
23265 None
23266 }
23267 })
23268}
23269
23270pub trait RangeToAnchorExt: Sized {
23271 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
23272
23273 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
23274 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
23275 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
23276 }
23277}
23278
23279impl<T: ToOffset> RangeToAnchorExt for Range<T> {
23280 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
23281 let start_offset = self.start.to_offset(snapshot);
23282 let end_offset = self.end.to_offset(snapshot);
23283 if start_offset == end_offset {
23284 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
23285 } else {
23286 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
23287 }
23288 }
23289}
23290
23291pub trait RowExt {
23292 fn as_f32(&self) -> f32;
23293
23294 fn next_row(&self) -> Self;
23295
23296 fn previous_row(&self) -> Self;
23297
23298 fn minus(&self, other: Self) -> u32;
23299}
23300
23301impl RowExt for DisplayRow {
23302 fn as_f32(&self) -> f32 {
23303 self.0 as f32
23304 }
23305
23306 fn next_row(&self) -> Self {
23307 Self(self.0 + 1)
23308 }
23309
23310 fn previous_row(&self) -> Self {
23311 Self(self.0.saturating_sub(1))
23312 }
23313
23314 fn minus(&self, other: Self) -> u32 {
23315 self.0 - other.0
23316 }
23317}
23318
23319impl RowExt for MultiBufferRow {
23320 fn as_f32(&self) -> f32 {
23321 self.0 as f32
23322 }
23323
23324 fn next_row(&self) -> Self {
23325 Self(self.0 + 1)
23326 }
23327
23328 fn previous_row(&self) -> Self {
23329 Self(self.0.saturating_sub(1))
23330 }
23331
23332 fn minus(&self, other: Self) -> u32 {
23333 self.0 - other.0
23334 }
23335}
23336
23337trait RowRangeExt {
23338 type Row;
23339
23340 fn len(&self) -> usize;
23341
23342 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
23343}
23344
23345impl RowRangeExt for Range<MultiBufferRow> {
23346 type Row = MultiBufferRow;
23347
23348 fn len(&self) -> usize {
23349 (self.end.0 - self.start.0) as usize
23350 }
23351
23352 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
23353 (self.start.0..self.end.0).map(MultiBufferRow)
23354 }
23355}
23356
23357impl RowRangeExt for Range<DisplayRow> {
23358 type Row = DisplayRow;
23359
23360 fn len(&self) -> usize {
23361 (self.end.0 - self.start.0) as usize
23362 }
23363
23364 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
23365 (self.start.0..self.end.0).map(DisplayRow)
23366 }
23367}
23368
23369/// If select range has more than one line, we
23370/// just point the cursor to range.start.
23371fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
23372 if range.start.row == range.end.row {
23373 range
23374 } else {
23375 range.start..range.start
23376 }
23377}
23378pub struct KillRing(ClipboardItem);
23379impl Global for KillRing {}
23380
23381const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
23382
23383enum BreakpointPromptEditAction {
23384 Log,
23385 Condition,
23386 HitCondition,
23387}
23388
23389struct BreakpointPromptEditor {
23390 pub(crate) prompt: Entity<Editor>,
23391 editor: WeakEntity<Editor>,
23392 breakpoint_anchor: Anchor,
23393 breakpoint: Breakpoint,
23394 edit_action: BreakpointPromptEditAction,
23395 block_ids: HashSet<CustomBlockId>,
23396 editor_margins: Arc<Mutex<EditorMargins>>,
23397 _subscriptions: Vec<Subscription>,
23398}
23399
23400impl BreakpointPromptEditor {
23401 const MAX_LINES: u8 = 4;
23402
23403 fn new(
23404 editor: WeakEntity<Editor>,
23405 breakpoint_anchor: Anchor,
23406 breakpoint: Breakpoint,
23407 edit_action: BreakpointPromptEditAction,
23408 window: &mut Window,
23409 cx: &mut Context<Self>,
23410 ) -> Self {
23411 let base_text = match edit_action {
23412 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
23413 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
23414 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
23415 }
23416 .map(|msg| msg.to_string())
23417 .unwrap_or_default();
23418
23419 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
23420 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
23421
23422 let prompt = cx.new(|cx| {
23423 let mut prompt = Editor::new(
23424 EditorMode::AutoHeight {
23425 min_lines: 1,
23426 max_lines: Some(Self::MAX_LINES as usize),
23427 },
23428 buffer,
23429 None,
23430 window,
23431 cx,
23432 );
23433 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
23434 prompt.set_show_cursor_when_unfocused(false, cx);
23435 prompt.set_placeholder_text(
23436 match edit_action {
23437 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
23438 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
23439 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
23440 },
23441 cx,
23442 );
23443
23444 prompt
23445 });
23446
23447 Self {
23448 prompt,
23449 editor,
23450 breakpoint_anchor,
23451 breakpoint,
23452 edit_action,
23453 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
23454 block_ids: Default::default(),
23455 _subscriptions: vec![],
23456 }
23457 }
23458
23459 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
23460 self.block_ids.extend(block_ids)
23461 }
23462
23463 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
23464 if let Some(editor) = self.editor.upgrade() {
23465 let message = self
23466 .prompt
23467 .read(cx)
23468 .buffer
23469 .read(cx)
23470 .as_singleton()
23471 .expect("A multi buffer in breakpoint prompt isn't possible")
23472 .read(cx)
23473 .as_rope()
23474 .to_string();
23475
23476 editor.update(cx, |editor, cx| {
23477 editor.edit_breakpoint_at_anchor(
23478 self.breakpoint_anchor,
23479 self.breakpoint.clone(),
23480 match self.edit_action {
23481 BreakpointPromptEditAction::Log => {
23482 BreakpointEditAction::EditLogMessage(message.into())
23483 }
23484 BreakpointPromptEditAction::Condition => {
23485 BreakpointEditAction::EditCondition(message.into())
23486 }
23487 BreakpointPromptEditAction::HitCondition => {
23488 BreakpointEditAction::EditHitCondition(message.into())
23489 }
23490 },
23491 cx,
23492 );
23493
23494 editor.remove_blocks(self.block_ids.clone(), None, cx);
23495 cx.focus_self(window);
23496 });
23497 }
23498 }
23499
23500 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
23501 self.editor
23502 .update(cx, |editor, cx| {
23503 editor.remove_blocks(self.block_ids.clone(), None, cx);
23504 window.focus(&editor.focus_handle);
23505 })
23506 .log_err();
23507 }
23508
23509 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
23510 let settings = ThemeSettings::get_global(cx);
23511 let text_style = TextStyle {
23512 color: if self.prompt.read(cx).read_only(cx) {
23513 cx.theme().colors().text_disabled
23514 } else {
23515 cx.theme().colors().text
23516 },
23517 font_family: settings.buffer_font.family.clone(),
23518 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23519 font_size: settings.buffer_font_size(cx).into(),
23520 font_weight: settings.buffer_font.weight,
23521 line_height: relative(settings.buffer_line_height.value()),
23522 ..Default::default()
23523 };
23524 EditorElement::new(
23525 &self.prompt,
23526 EditorStyle {
23527 background: cx.theme().colors().editor_background,
23528 local_player: cx.theme().players().local(),
23529 text: text_style,
23530 ..Default::default()
23531 },
23532 )
23533 }
23534}
23535
23536impl Render for BreakpointPromptEditor {
23537 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23538 let editor_margins = *self.editor_margins.lock();
23539 let gutter_dimensions = editor_margins.gutter;
23540 h_flex()
23541 .key_context("Editor")
23542 .bg(cx.theme().colors().editor_background)
23543 .border_y_1()
23544 .border_color(cx.theme().status().info_border)
23545 .size_full()
23546 .py(window.line_height() / 2.5)
23547 .on_action(cx.listener(Self::confirm))
23548 .on_action(cx.listener(Self::cancel))
23549 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
23550 .child(div().flex_1().child(self.render_prompt_editor(cx)))
23551 }
23552}
23553
23554impl Focusable for BreakpointPromptEditor {
23555 fn focus_handle(&self, cx: &App) -> FocusHandle {
23556 self.prompt.focus_handle(cx)
23557 }
23558}
23559
23560fn all_edits_insertions_or_deletions(
23561 edits: &Vec<(Range<Anchor>, String)>,
23562 snapshot: &MultiBufferSnapshot,
23563) -> bool {
23564 let mut all_insertions = true;
23565 let mut all_deletions = true;
23566
23567 for (range, new_text) in edits.iter() {
23568 let range_is_empty = range.to_offset(&snapshot).is_empty();
23569 let text_is_empty = new_text.is_empty();
23570
23571 if range_is_empty != text_is_empty {
23572 if range_is_empty {
23573 all_deletions = false;
23574 } else {
23575 all_insertions = false;
23576 }
23577 } else {
23578 return false;
23579 }
23580
23581 if !all_insertions && !all_deletions {
23582 return false;
23583 }
23584 }
23585 all_insertions || all_deletions
23586}
23587
23588struct MissingEditPredictionKeybindingTooltip;
23589
23590impl Render for MissingEditPredictionKeybindingTooltip {
23591 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23592 ui::tooltip_container(window, cx, |container, _, cx| {
23593 container
23594 .flex_shrink_0()
23595 .max_w_80()
23596 .min_h(rems_from_px(124.))
23597 .justify_between()
23598 .child(
23599 v_flex()
23600 .flex_1()
23601 .text_ui_sm(cx)
23602 .child(Label::new("Conflict with Accept Keybinding"))
23603 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
23604 )
23605 .child(
23606 h_flex()
23607 .pb_1()
23608 .gap_1()
23609 .items_end()
23610 .w_full()
23611 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
23612 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
23613 }))
23614 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
23615 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
23616 })),
23617 )
23618 })
23619 }
23620}
23621
23622#[derive(Debug, Clone, Copy, PartialEq)]
23623pub struct LineHighlight {
23624 pub background: Background,
23625 pub border: Option<gpui::Hsla>,
23626 pub include_gutter: bool,
23627 pub type_id: Option<TypeId>,
23628}
23629
23630struct LineManipulationResult {
23631 pub new_text: String,
23632 pub line_count_before: usize,
23633 pub line_count_after: usize,
23634}
23635
23636fn render_diff_hunk_controls(
23637 row: u32,
23638 status: &DiffHunkStatus,
23639 hunk_range: Range<Anchor>,
23640 is_created_file: bool,
23641 line_height: Pixels,
23642 editor: &Entity<Editor>,
23643 _window: &mut Window,
23644 cx: &mut App,
23645) -> AnyElement {
23646 h_flex()
23647 .h(line_height)
23648 .mr_1()
23649 .gap_1()
23650 .px_0p5()
23651 .pb_1()
23652 .border_x_1()
23653 .border_b_1()
23654 .border_color(cx.theme().colors().border_variant)
23655 .rounded_b_lg()
23656 .bg(cx.theme().colors().editor_background)
23657 .gap_1()
23658 .block_mouse_except_scroll()
23659 .shadow_md()
23660 .child(if status.has_secondary_hunk() {
23661 Button::new(("stage", row as u64), "Stage")
23662 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
23663 .tooltip({
23664 let focus_handle = editor.focus_handle(cx);
23665 move |window, cx| {
23666 Tooltip::for_action_in(
23667 "Stage Hunk",
23668 &::git::ToggleStaged,
23669 &focus_handle,
23670 window,
23671 cx,
23672 )
23673 }
23674 })
23675 .on_click({
23676 let editor = editor.clone();
23677 move |_event, _window, cx| {
23678 editor.update(cx, |editor, cx| {
23679 editor.stage_or_unstage_diff_hunks(
23680 true,
23681 vec![hunk_range.start..hunk_range.start],
23682 cx,
23683 );
23684 });
23685 }
23686 })
23687 } else {
23688 Button::new(("unstage", row as u64), "Unstage")
23689 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
23690 .tooltip({
23691 let focus_handle = editor.focus_handle(cx);
23692 move |window, cx| {
23693 Tooltip::for_action_in(
23694 "Unstage Hunk",
23695 &::git::ToggleStaged,
23696 &focus_handle,
23697 window,
23698 cx,
23699 )
23700 }
23701 })
23702 .on_click({
23703 let editor = editor.clone();
23704 move |_event, _window, cx| {
23705 editor.update(cx, |editor, cx| {
23706 editor.stage_or_unstage_diff_hunks(
23707 false,
23708 vec![hunk_range.start..hunk_range.start],
23709 cx,
23710 );
23711 });
23712 }
23713 })
23714 })
23715 .child(
23716 Button::new(("restore", row as u64), "Restore")
23717 .tooltip({
23718 let focus_handle = editor.focus_handle(cx);
23719 move |window, cx| {
23720 Tooltip::for_action_in(
23721 "Restore Hunk",
23722 &::git::Restore,
23723 &focus_handle,
23724 window,
23725 cx,
23726 )
23727 }
23728 })
23729 .on_click({
23730 let editor = editor.clone();
23731 move |_event, window, cx| {
23732 editor.update(cx, |editor, cx| {
23733 let snapshot = editor.snapshot(window, cx);
23734 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
23735 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
23736 });
23737 }
23738 })
23739 .disabled(is_created_file),
23740 )
23741 .when(
23742 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
23743 |el| {
23744 el.child(
23745 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
23746 .shape(IconButtonShape::Square)
23747 .icon_size(IconSize::Small)
23748 // .disabled(!has_multiple_hunks)
23749 .tooltip({
23750 let focus_handle = editor.focus_handle(cx);
23751 move |window, cx| {
23752 Tooltip::for_action_in(
23753 "Next Hunk",
23754 &GoToHunk,
23755 &focus_handle,
23756 window,
23757 cx,
23758 )
23759 }
23760 })
23761 .on_click({
23762 let editor = editor.clone();
23763 move |_event, window, cx| {
23764 editor.update(cx, |editor, cx| {
23765 let snapshot = editor.snapshot(window, cx);
23766 let position =
23767 hunk_range.end.to_point(&snapshot.buffer_snapshot);
23768 editor.go_to_hunk_before_or_after_position(
23769 &snapshot,
23770 position,
23771 Direction::Next,
23772 window,
23773 cx,
23774 );
23775 editor.expand_selected_diff_hunks(cx);
23776 });
23777 }
23778 }),
23779 )
23780 .child(
23781 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
23782 .shape(IconButtonShape::Square)
23783 .icon_size(IconSize::Small)
23784 // .disabled(!has_multiple_hunks)
23785 .tooltip({
23786 let focus_handle = editor.focus_handle(cx);
23787 move |window, cx| {
23788 Tooltip::for_action_in(
23789 "Previous Hunk",
23790 &GoToPreviousHunk,
23791 &focus_handle,
23792 window,
23793 cx,
23794 )
23795 }
23796 })
23797 .on_click({
23798 let editor = editor.clone();
23799 move |_event, window, cx| {
23800 editor.update(cx, |editor, cx| {
23801 let snapshot = editor.snapshot(window, cx);
23802 let point =
23803 hunk_range.start.to_point(&snapshot.buffer_snapshot);
23804 editor.go_to_hunk_before_or_after_position(
23805 &snapshot,
23806 point,
23807 Direction::Prev,
23808 window,
23809 cx,
23810 );
23811 editor.expand_selected_diff_hunks(cx);
23812 });
23813 }
23814 }),
23815 )
23816 },
23817 )
23818 .into_any_element()
23819}