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.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 = match e {
8354 ClickEvent::Keyboard(_) => true,
8355 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8356 };
8357
8358 window.focus(&editor.focus_handle(cx));
8359 editor.toggle_code_actions(
8360 &ToggleCodeActions {
8361 deployed_from: Some(CodeActionSource::RunMenu(row)),
8362 quick_launch,
8363 },
8364 window,
8365 cx,
8366 );
8367 }))
8368 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8369 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8370 }))
8371 }
8372
8373 pub fn context_menu_visible(&self) -> bool {
8374 !self.edit_prediction_preview_is_active()
8375 && self
8376 .context_menu
8377 .borrow()
8378 .as_ref()
8379 .map_or(false, |menu| menu.visible())
8380 }
8381
8382 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8383 self.context_menu
8384 .borrow()
8385 .as_ref()
8386 .map(|menu| menu.origin())
8387 }
8388
8389 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8390 self.context_menu_options = Some(options);
8391 }
8392
8393 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
8394 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
8395
8396 fn render_edit_prediction_popover(
8397 &mut self,
8398 text_bounds: &Bounds<Pixels>,
8399 content_origin: gpui::Point<Pixels>,
8400 right_margin: Pixels,
8401 editor_snapshot: &EditorSnapshot,
8402 visible_row_range: Range<DisplayRow>,
8403 scroll_top: f32,
8404 scroll_bottom: f32,
8405 line_layouts: &[LineWithInvisibles],
8406 line_height: Pixels,
8407 scroll_pixel_position: gpui::Point<Pixels>,
8408 newest_selection_head: Option<DisplayPoint>,
8409 editor_width: Pixels,
8410 style: &EditorStyle,
8411 window: &mut Window,
8412 cx: &mut App,
8413 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8414 if self.mode().is_minimap() {
8415 return None;
8416 }
8417 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8418
8419 if self.edit_prediction_visible_in_cursor_popover(true) {
8420 return None;
8421 }
8422
8423 match &active_edit_prediction.completion {
8424 EditPrediction::Move { target, .. } => {
8425 let target_display_point = target.to_display_point(editor_snapshot);
8426
8427 if self.edit_prediction_requires_modifier() {
8428 if !self.edit_prediction_preview_is_active() {
8429 return None;
8430 }
8431
8432 self.render_edit_prediction_modifier_jump_popover(
8433 text_bounds,
8434 content_origin,
8435 visible_row_range,
8436 line_layouts,
8437 line_height,
8438 scroll_pixel_position,
8439 newest_selection_head,
8440 target_display_point,
8441 window,
8442 cx,
8443 )
8444 } else {
8445 self.render_edit_prediction_eager_jump_popover(
8446 text_bounds,
8447 content_origin,
8448 editor_snapshot,
8449 visible_row_range,
8450 scroll_top,
8451 scroll_bottom,
8452 line_height,
8453 scroll_pixel_position,
8454 target_display_point,
8455 editor_width,
8456 window,
8457 cx,
8458 )
8459 }
8460 }
8461 EditPrediction::Edit {
8462 display_mode: EditDisplayMode::Inline,
8463 ..
8464 } => None,
8465 EditPrediction::Edit {
8466 display_mode: EditDisplayMode::TabAccept,
8467 edits,
8468 ..
8469 } => {
8470 let range = &edits.first()?.0;
8471 let target_display_point = range.end.to_display_point(editor_snapshot);
8472
8473 self.render_edit_prediction_end_of_line_popover(
8474 "Accept",
8475 editor_snapshot,
8476 visible_row_range,
8477 target_display_point,
8478 line_height,
8479 scroll_pixel_position,
8480 content_origin,
8481 editor_width,
8482 window,
8483 cx,
8484 )
8485 }
8486 EditPrediction::Edit {
8487 edits,
8488 edit_preview,
8489 display_mode: EditDisplayMode::DiffPopover,
8490 snapshot,
8491 } => self.render_edit_prediction_diff_popover(
8492 text_bounds,
8493 content_origin,
8494 right_margin,
8495 editor_snapshot,
8496 visible_row_range,
8497 line_layouts,
8498 line_height,
8499 scroll_pixel_position,
8500 newest_selection_head,
8501 editor_width,
8502 style,
8503 edits,
8504 edit_preview,
8505 snapshot,
8506 window,
8507 cx,
8508 ),
8509 }
8510 }
8511
8512 fn render_edit_prediction_modifier_jump_popover(
8513 &mut self,
8514 text_bounds: &Bounds<Pixels>,
8515 content_origin: gpui::Point<Pixels>,
8516 visible_row_range: Range<DisplayRow>,
8517 line_layouts: &[LineWithInvisibles],
8518 line_height: Pixels,
8519 scroll_pixel_position: gpui::Point<Pixels>,
8520 newest_selection_head: Option<DisplayPoint>,
8521 target_display_point: DisplayPoint,
8522 window: &mut Window,
8523 cx: &mut App,
8524 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8525 let scrolled_content_origin =
8526 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
8527
8528 const SCROLL_PADDING_Y: Pixels = px(12.);
8529
8530 if target_display_point.row() < visible_row_range.start {
8531 return self.render_edit_prediction_scroll_popover(
8532 |_| SCROLL_PADDING_Y,
8533 IconName::ArrowUp,
8534 visible_row_range,
8535 line_layouts,
8536 newest_selection_head,
8537 scrolled_content_origin,
8538 window,
8539 cx,
8540 );
8541 } else if target_display_point.row() >= visible_row_range.end {
8542 return self.render_edit_prediction_scroll_popover(
8543 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8544 IconName::ArrowDown,
8545 visible_row_range,
8546 line_layouts,
8547 newest_selection_head,
8548 scrolled_content_origin,
8549 window,
8550 cx,
8551 );
8552 }
8553
8554 const POLE_WIDTH: Pixels = px(2.);
8555
8556 let line_layout =
8557 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8558 let target_column = target_display_point.column() as usize;
8559
8560 let target_x = line_layout.x_for_index(target_column);
8561 let target_y =
8562 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
8563
8564 let flag_on_right = target_x < text_bounds.size.width / 2.;
8565
8566 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8567 border_color.l += 0.001;
8568
8569 let mut element = v_flex()
8570 .items_end()
8571 .when(flag_on_right, |el| el.items_start())
8572 .child(if flag_on_right {
8573 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8574 .rounded_bl(px(0.))
8575 .rounded_tl(px(0.))
8576 .border_l_2()
8577 .border_color(border_color)
8578 } else {
8579 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8580 .rounded_br(px(0.))
8581 .rounded_tr(px(0.))
8582 .border_r_2()
8583 .border_color(border_color)
8584 })
8585 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8586 .into_any();
8587
8588 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8589
8590 let mut origin = scrolled_content_origin + point(target_x, target_y)
8591 - point(
8592 if flag_on_right {
8593 POLE_WIDTH
8594 } else {
8595 size.width - POLE_WIDTH
8596 },
8597 size.height - line_height,
8598 );
8599
8600 origin.x = origin.x.max(content_origin.x);
8601
8602 element.prepaint_at(origin, window, cx);
8603
8604 Some((element, origin))
8605 }
8606
8607 fn render_edit_prediction_scroll_popover(
8608 &mut self,
8609 to_y: impl Fn(Size<Pixels>) -> Pixels,
8610 scroll_icon: IconName,
8611 visible_row_range: Range<DisplayRow>,
8612 line_layouts: &[LineWithInvisibles],
8613 newest_selection_head: Option<DisplayPoint>,
8614 scrolled_content_origin: gpui::Point<Pixels>,
8615 window: &mut Window,
8616 cx: &mut App,
8617 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8618 let mut element = self
8619 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
8620 .into_any();
8621
8622 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8623
8624 let cursor = newest_selection_head?;
8625 let cursor_row_layout =
8626 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8627 let cursor_column = cursor.column() as usize;
8628
8629 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8630
8631 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8632
8633 element.prepaint_at(origin, window, cx);
8634 Some((element, origin))
8635 }
8636
8637 fn render_edit_prediction_eager_jump_popover(
8638 &mut self,
8639 text_bounds: &Bounds<Pixels>,
8640 content_origin: gpui::Point<Pixels>,
8641 editor_snapshot: &EditorSnapshot,
8642 visible_row_range: Range<DisplayRow>,
8643 scroll_top: f32,
8644 scroll_bottom: f32,
8645 line_height: Pixels,
8646 scroll_pixel_position: gpui::Point<Pixels>,
8647 target_display_point: DisplayPoint,
8648 editor_width: Pixels,
8649 window: &mut Window,
8650 cx: &mut App,
8651 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8652 if target_display_point.row().as_f32() < scroll_top {
8653 let mut element = self
8654 .render_edit_prediction_line_popover(
8655 "Jump to Edit",
8656 Some(IconName::ArrowUp),
8657 window,
8658 cx,
8659 )?
8660 .into_any();
8661
8662 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8663 let offset = point(
8664 (text_bounds.size.width - size.width) / 2.,
8665 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8666 );
8667
8668 let origin = text_bounds.origin + offset;
8669 element.prepaint_at(origin, window, cx);
8670 Some((element, origin))
8671 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
8672 let mut element = self
8673 .render_edit_prediction_line_popover(
8674 "Jump to Edit",
8675 Some(IconName::ArrowDown),
8676 window,
8677 cx,
8678 )?
8679 .into_any();
8680
8681 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8682 let offset = point(
8683 (text_bounds.size.width - size.width) / 2.,
8684 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8685 );
8686
8687 let origin = text_bounds.origin + offset;
8688 element.prepaint_at(origin, window, cx);
8689 Some((element, origin))
8690 } else {
8691 self.render_edit_prediction_end_of_line_popover(
8692 "Jump to Edit",
8693 editor_snapshot,
8694 visible_row_range,
8695 target_display_point,
8696 line_height,
8697 scroll_pixel_position,
8698 content_origin,
8699 editor_width,
8700 window,
8701 cx,
8702 )
8703 }
8704 }
8705
8706 fn render_edit_prediction_end_of_line_popover(
8707 self: &mut Editor,
8708 label: &'static str,
8709 editor_snapshot: &EditorSnapshot,
8710 visible_row_range: Range<DisplayRow>,
8711 target_display_point: DisplayPoint,
8712 line_height: Pixels,
8713 scroll_pixel_position: gpui::Point<Pixels>,
8714 content_origin: gpui::Point<Pixels>,
8715 editor_width: Pixels,
8716 window: &mut Window,
8717 cx: &mut App,
8718 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8719 let target_line_end = DisplayPoint::new(
8720 target_display_point.row(),
8721 editor_snapshot.line_len(target_display_point.row()),
8722 );
8723
8724 let mut element = self
8725 .render_edit_prediction_line_popover(label, None, window, cx)?
8726 .into_any();
8727
8728 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8729
8730 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8731
8732 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
8733 let mut origin = start_point
8734 + line_origin
8735 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8736 origin.x = origin.x.max(content_origin.x);
8737
8738 let max_x = content_origin.x + editor_width - size.width;
8739
8740 if origin.x > max_x {
8741 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8742
8743 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8744 origin.y += offset;
8745 IconName::ArrowUp
8746 } else {
8747 origin.y -= offset;
8748 IconName::ArrowDown
8749 };
8750
8751 element = self
8752 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
8753 .into_any();
8754
8755 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8756
8757 origin.x = content_origin.x + editor_width - size.width - px(2.);
8758 }
8759
8760 element.prepaint_at(origin, window, cx);
8761 Some((element, origin))
8762 }
8763
8764 fn render_edit_prediction_diff_popover(
8765 self: &Editor,
8766 text_bounds: &Bounds<Pixels>,
8767 content_origin: gpui::Point<Pixels>,
8768 right_margin: Pixels,
8769 editor_snapshot: &EditorSnapshot,
8770 visible_row_range: Range<DisplayRow>,
8771 line_layouts: &[LineWithInvisibles],
8772 line_height: Pixels,
8773 scroll_pixel_position: gpui::Point<Pixels>,
8774 newest_selection_head: Option<DisplayPoint>,
8775 editor_width: Pixels,
8776 style: &EditorStyle,
8777 edits: &Vec<(Range<Anchor>, String)>,
8778 edit_preview: &Option<language::EditPreview>,
8779 snapshot: &language::BufferSnapshot,
8780 window: &mut Window,
8781 cx: &mut App,
8782 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8783 let edit_start = edits
8784 .first()
8785 .unwrap()
8786 .0
8787 .start
8788 .to_display_point(editor_snapshot);
8789 let edit_end = edits
8790 .last()
8791 .unwrap()
8792 .0
8793 .end
8794 .to_display_point(editor_snapshot);
8795
8796 let is_visible = visible_row_range.contains(&edit_start.row())
8797 || visible_row_range.contains(&edit_end.row());
8798 if !is_visible {
8799 return None;
8800 }
8801
8802 let highlighted_edits =
8803 crate::edit_prediction_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
8804
8805 let styled_text = highlighted_edits.to_styled_text(&style.text);
8806 let line_count = highlighted_edits.text.lines().count();
8807
8808 const BORDER_WIDTH: Pixels = px(1.);
8809
8810 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8811 let has_keybind = keybind.is_some();
8812
8813 let mut element = h_flex()
8814 .items_start()
8815 .child(
8816 h_flex()
8817 .bg(cx.theme().colors().editor_background)
8818 .border(BORDER_WIDTH)
8819 .shadow_xs()
8820 .border_color(cx.theme().colors().border)
8821 .rounded_l_lg()
8822 .when(line_count > 1, |el| el.rounded_br_lg())
8823 .pr_1()
8824 .child(styled_text),
8825 )
8826 .child(
8827 h_flex()
8828 .h(line_height + BORDER_WIDTH * 2.)
8829 .px_1p5()
8830 .gap_1()
8831 // Workaround: For some reason, there's a gap if we don't do this
8832 .ml(-BORDER_WIDTH)
8833 .shadow(vec![gpui::BoxShadow {
8834 color: gpui::black().opacity(0.05),
8835 offset: point(px(1.), px(1.)),
8836 blur_radius: px(2.),
8837 spread_radius: px(0.),
8838 }])
8839 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8840 .border(BORDER_WIDTH)
8841 .border_color(cx.theme().colors().border)
8842 .rounded_r_lg()
8843 .id("edit_prediction_diff_popover_keybind")
8844 .when(!has_keybind, |el| {
8845 let status_colors = cx.theme().status();
8846
8847 el.bg(status_colors.error_background)
8848 .border_color(status_colors.error.opacity(0.6))
8849 .child(Icon::new(IconName::Info).color(Color::Error))
8850 .cursor_default()
8851 .hoverable_tooltip(move |_window, cx| {
8852 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8853 })
8854 })
8855 .children(keybind),
8856 )
8857 .into_any();
8858
8859 let longest_row =
8860 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8861 let longest_line_width = if visible_row_range.contains(&longest_row) {
8862 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8863 } else {
8864 layout_line(
8865 longest_row,
8866 editor_snapshot,
8867 style,
8868 editor_width,
8869 |_| false,
8870 window,
8871 cx,
8872 )
8873 .width
8874 };
8875
8876 let viewport_bounds =
8877 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8878 right: -right_margin,
8879 ..Default::default()
8880 });
8881
8882 let x_after_longest =
8883 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
8884 - scroll_pixel_position.x;
8885
8886 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8887
8888 // Fully visible if it can be displayed within the window (allow overlapping other
8889 // panes). However, this is only allowed if the popover starts within text_bounds.
8890 let can_position_to_the_right = x_after_longest < text_bounds.right()
8891 && x_after_longest + element_bounds.width < viewport_bounds.right();
8892
8893 let mut origin = if can_position_to_the_right {
8894 point(
8895 x_after_longest,
8896 text_bounds.origin.y + edit_start.row().as_f32() * line_height
8897 - scroll_pixel_position.y,
8898 )
8899 } else {
8900 let cursor_row = newest_selection_head.map(|head| head.row());
8901 let above_edit = edit_start
8902 .row()
8903 .0
8904 .checked_sub(line_count as u32)
8905 .map(DisplayRow);
8906 let below_edit = Some(edit_end.row() + 1);
8907 let above_cursor =
8908 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
8909 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
8910
8911 // Place the edit popover adjacent to the edit if there is a location
8912 // available that is onscreen and does not obscure the cursor. Otherwise,
8913 // place it adjacent to the cursor.
8914 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
8915 .into_iter()
8916 .flatten()
8917 .find(|&start_row| {
8918 let end_row = start_row + line_count as u32;
8919 visible_row_range.contains(&start_row)
8920 && visible_row_range.contains(&end_row)
8921 && cursor_row.map_or(true, |cursor_row| {
8922 !((start_row..end_row).contains(&cursor_row))
8923 })
8924 })?;
8925
8926 content_origin
8927 + point(
8928 -scroll_pixel_position.x,
8929 row_target.as_f32() * line_height - scroll_pixel_position.y,
8930 )
8931 };
8932
8933 origin.x -= BORDER_WIDTH;
8934
8935 window.defer_draw(element, origin, 1);
8936
8937 // Do not return an element, since it will already be drawn due to defer_draw.
8938 None
8939 }
8940
8941 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
8942 px(30.)
8943 }
8944
8945 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
8946 if self.read_only(cx) {
8947 cx.theme().players().read_only()
8948 } else {
8949 self.style.as_ref().unwrap().local_player
8950 }
8951 }
8952
8953 fn render_edit_prediction_accept_keybind(
8954 &self,
8955 window: &mut Window,
8956 cx: &App,
8957 ) -> Option<AnyElement> {
8958 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
8959 let accept_keystroke = accept_binding.keystroke()?;
8960
8961 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8962
8963 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
8964 Color::Accent
8965 } else {
8966 Color::Muted
8967 };
8968
8969 h_flex()
8970 .px_0p5()
8971 .when(is_platform_style_mac, |parent| parent.gap_0p5())
8972 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8973 .text_size(TextSize::XSmall.rems(cx))
8974 .child(h_flex().children(ui::render_modifiers(
8975 &accept_keystroke.modifiers,
8976 PlatformStyle::platform(),
8977 Some(modifiers_color),
8978 Some(IconSize::XSmall.rems().into()),
8979 true,
8980 )))
8981 .when(is_platform_style_mac, |parent| {
8982 parent.child(accept_keystroke.key.clone())
8983 })
8984 .when(!is_platform_style_mac, |parent| {
8985 parent.child(
8986 Key::new(
8987 util::capitalize(&accept_keystroke.key),
8988 Some(Color::Default),
8989 )
8990 .size(Some(IconSize::XSmall.rems().into())),
8991 )
8992 })
8993 .into_any()
8994 .into()
8995 }
8996
8997 fn render_edit_prediction_line_popover(
8998 &self,
8999 label: impl Into<SharedString>,
9000 icon: Option<IconName>,
9001 window: &mut Window,
9002 cx: &App,
9003 ) -> Option<Stateful<Div>> {
9004 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9005
9006 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9007 let has_keybind = keybind.is_some();
9008
9009 let result = h_flex()
9010 .id("ep-line-popover")
9011 .py_0p5()
9012 .pl_1()
9013 .pr(padding_right)
9014 .gap_1()
9015 .rounded_md()
9016 .border_1()
9017 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9018 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9019 .shadow_xs()
9020 .when(!has_keybind, |el| {
9021 let status_colors = cx.theme().status();
9022
9023 el.bg(status_colors.error_background)
9024 .border_color(status_colors.error.opacity(0.6))
9025 .pl_2()
9026 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9027 .cursor_default()
9028 .hoverable_tooltip(move |_window, cx| {
9029 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9030 })
9031 })
9032 .children(keybind)
9033 .child(
9034 Label::new(label)
9035 .size(LabelSize::Small)
9036 .when(!has_keybind, |el| {
9037 el.color(cx.theme().status().error.into()).strikethrough()
9038 }),
9039 )
9040 .when(!has_keybind, |el| {
9041 el.child(
9042 h_flex().ml_1().child(
9043 Icon::new(IconName::Info)
9044 .size(IconSize::Small)
9045 .color(cx.theme().status().error.into()),
9046 ),
9047 )
9048 })
9049 .when_some(icon, |element, icon| {
9050 element.child(
9051 div()
9052 .mt(px(1.5))
9053 .child(Icon::new(icon).size(IconSize::Small)),
9054 )
9055 });
9056
9057 Some(result)
9058 }
9059
9060 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9061 let accent_color = cx.theme().colors().text_accent;
9062 let editor_bg_color = cx.theme().colors().editor_background;
9063 editor_bg_color.blend(accent_color.opacity(0.1))
9064 }
9065
9066 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9067 let accent_color = cx.theme().colors().text_accent;
9068 let editor_bg_color = cx.theme().colors().editor_background;
9069 editor_bg_color.blend(accent_color.opacity(0.6))
9070 }
9071
9072 fn render_edit_prediction_cursor_popover(
9073 &self,
9074 min_width: Pixels,
9075 max_width: Pixels,
9076 cursor_point: Point,
9077 style: &EditorStyle,
9078 accept_keystroke: Option<&gpui::Keystroke>,
9079 _window: &Window,
9080 cx: &mut Context<Editor>,
9081 ) -> Option<AnyElement> {
9082 let provider = self.edit_prediction_provider.as_ref()?;
9083
9084 if provider.provider.needs_terms_acceptance(cx) {
9085 return Some(
9086 h_flex()
9087 .min_w(min_width)
9088 .flex_1()
9089 .px_2()
9090 .py_1()
9091 .gap_3()
9092 .elevation_2(cx)
9093 .hover(|style| style.bg(cx.theme().colors().element_hover))
9094 .id("accept-terms")
9095 .cursor_pointer()
9096 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
9097 .on_click(cx.listener(|this, _event, window, cx| {
9098 cx.stop_propagation();
9099 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
9100 window.dispatch_action(
9101 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
9102 cx,
9103 );
9104 }))
9105 .child(
9106 h_flex()
9107 .flex_1()
9108 .gap_2()
9109 .child(Icon::new(IconName::ZedPredict))
9110 .child(Label::new("Accept Terms of Service"))
9111 .child(div().w_full())
9112 .child(
9113 Icon::new(IconName::ArrowUpRight)
9114 .color(Color::Muted)
9115 .size(IconSize::Small),
9116 )
9117 .into_any_element(),
9118 )
9119 .into_any(),
9120 );
9121 }
9122
9123 let is_refreshing = provider.provider.is_refreshing(cx);
9124
9125 fn pending_completion_container() -> Div {
9126 h_flex()
9127 .h_full()
9128 .flex_1()
9129 .gap_2()
9130 .child(Icon::new(IconName::ZedPredict))
9131 }
9132
9133 let completion = match &self.active_edit_prediction {
9134 Some(prediction) => {
9135 if !self.has_visible_completions_menu() {
9136 const RADIUS: Pixels = px(6.);
9137 const BORDER_WIDTH: Pixels = px(1.);
9138
9139 return Some(
9140 h_flex()
9141 .elevation_2(cx)
9142 .border(BORDER_WIDTH)
9143 .border_color(cx.theme().colors().border)
9144 .when(accept_keystroke.is_none(), |el| {
9145 el.border_color(cx.theme().status().error)
9146 })
9147 .rounded(RADIUS)
9148 .rounded_tl(px(0.))
9149 .overflow_hidden()
9150 .child(div().px_1p5().child(match &prediction.completion {
9151 EditPrediction::Move { target, snapshot } => {
9152 use text::ToPoint as _;
9153 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
9154 {
9155 Icon::new(IconName::ZedPredictDown)
9156 } else {
9157 Icon::new(IconName::ZedPredictUp)
9158 }
9159 }
9160 EditPrediction::Edit { .. } => Icon::new(IconName::ZedPredict),
9161 }))
9162 .child(
9163 h_flex()
9164 .gap_1()
9165 .py_1()
9166 .px_2()
9167 .rounded_r(RADIUS - BORDER_WIDTH)
9168 .border_l_1()
9169 .border_color(cx.theme().colors().border)
9170 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9171 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9172 el.child(
9173 Label::new("Hold")
9174 .size(LabelSize::Small)
9175 .when(accept_keystroke.is_none(), |el| {
9176 el.strikethrough()
9177 })
9178 .line_height_style(LineHeightStyle::UiLabel),
9179 )
9180 })
9181 .id("edit_prediction_cursor_popover_keybind")
9182 .when(accept_keystroke.is_none(), |el| {
9183 let status_colors = cx.theme().status();
9184
9185 el.bg(status_colors.error_background)
9186 .border_color(status_colors.error.opacity(0.6))
9187 .child(Icon::new(IconName::Info).color(Color::Error))
9188 .cursor_default()
9189 .hoverable_tooltip(move |_window, cx| {
9190 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9191 .into()
9192 })
9193 })
9194 .when_some(
9195 accept_keystroke.as_ref(),
9196 |el, accept_keystroke| {
9197 el.child(h_flex().children(ui::render_modifiers(
9198 &accept_keystroke.modifiers,
9199 PlatformStyle::platform(),
9200 Some(Color::Default),
9201 Some(IconSize::XSmall.rems().into()),
9202 false,
9203 )))
9204 },
9205 ),
9206 )
9207 .into_any(),
9208 );
9209 }
9210
9211 self.render_edit_prediction_cursor_popover_preview(
9212 prediction,
9213 cursor_point,
9214 style,
9215 cx,
9216 )?
9217 }
9218
9219 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9220 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9221 stale_completion,
9222 cursor_point,
9223 style,
9224 cx,
9225 )?,
9226
9227 None => {
9228 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
9229 }
9230 },
9231
9232 None => pending_completion_container().child(Label::new("No Prediction")),
9233 };
9234
9235 let completion = if is_refreshing {
9236 completion
9237 .with_animation(
9238 "loading-completion",
9239 Animation::new(Duration::from_secs(2))
9240 .repeat()
9241 .with_easing(pulsating_between(0.4, 0.8)),
9242 |label, delta| label.opacity(delta),
9243 )
9244 .into_any_element()
9245 } else {
9246 completion.into_any_element()
9247 };
9248
9249 let has_completion = self.active_edit_prediction.is_some();
9250
9251 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9252 Some(
9253 h_flex()
9254 .min_w(min_width)
9255 .max_w(max_width)
9256 .flex_1()
9257 .elevation_2(cx)
9258 .border_color(cx.theme().colors().border)
9259 .child(
9260 div()
9261 .flex_1()
9262 .py_1()
9263 .px_2()
9264 .overflow_hidden()
9265 .child(completion),
9266 )
9267 .when_some(accept_keystroke, |el, accept_keystroke| {
9268 if !accept_keystroke.modifiers.modified() {
9269 return el;
9270 }
9271
9272 el.child(
9273 h_flex()
9274 .h_full()
9275 .border_l_1()
9276 .rounded_r_lg()
9277 .border_color(cx.theme().colors().border)
9278 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9279 .gap_1()
9280 .py_1()
9281 .px_2()
9282 .child(
9283 h_flex()
9284 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9285 .when(is_platform_style_mac, |parent| parent.gap_1())
9286 .child(h_flex().children(ui::render_modifiers(
9287 &accept_keystroke.modifiers,
9288 PlatformStyle::platform(),
9289 Some(if !has_completion {
9290 Color::Muted
9291 } else {
9292 Color::Default
9293 }),
9294 None,
9295 false,
9296 ))),
9297 )
9298 .child(Label::new("Preview").into_any_element())
9299 .opacity(if has_completion { 1.0 } else { 0.4 }),
9300 )
9301 })
9302 .into_any(),
9303 )
9304 }
9305
9306 fn render_edit_prediction_cursor_popover_preview(
9307 &self,
9308 completion: &EditPredictionState,
9309 cursor_point: Point,
9310 style: &EditorStyle,
9311 cx: &mut Context<Editor>,
9312 ) -> Option<Div> {
9313 use text::ToPoint as _;
9314
9315 fn render_relative_row_jump(
9316 prefix: impl Into<String>,
9317 current_row: u32,
9318 target_row: u32,
9319 ) -> Div {
9320 let (row_diff, arrow) = if target_row < current_row {
9321 (current_row - target_row, IconName::ArrowUp)
9322 } else {
9323 (target_row - current_row, IconName::ArrowDown)
9324 };
9325
9326 h_flex()
9327 .child(
9328 Label::new(format!("{}{}", prefix.into(), row_diff))
9329 .color(Color::Muted)
9330 .size(LabelSize::Small),
9331 )
9332 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9333 }
9334
9335 match &completion.completion {
9336 EditPrediction::Move {
9337 target, snapshot, ..
9338 } => Some(
9339 h_flex()
9340 .px_2()
9341 .gap_2()
9342 .flex_1()
9343 .child(
9344 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
9345 Icon::new(IconName::ZedPredictDown)
9346 } else {
9347 Icon::new(IconName::ZedPredictUp)
9348 },
9349 )
9350 .child(Label::new("Jump to Edit")),
9351 ),
9352
9353 EditPrediction::Edit {
9354 edits,
9355 edit_preview,
9356 snapshot,
9357 display_mode: _,
9358 } => {
9359 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
9360
9361 let (highlighted_edits, has_more_lines) = crate::edit_prediction_edit_text(
9362 &snapshot,
9363 &edits,
9364 edit_preview.as_ref()?,
9365 true,
9366 cx,
9367 )
9368 .first_line_preview();
9369
9370 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9371 .with_default_highlights(&style.text, highlighted_edits.highlights);
9372
9373 let preview = h_flex()
9374 .gap_1()
9375 .min_w_16()
9376 .child(styled_text)
9377 .when(has_more_lines, |parent| parent.child("…"));
9378
9379 let left = if first_edit_row != cursor_point.row {
9380 render_relative_row_jump("", cursor_point.row, first_edit_row)
9381 .into_any_element()
9382 } else {
9383 Icon::new(IconName::ZedPredict).into_any_element()
9384 };
9385
9386 Some(
9387 h_flex()
9388 .h_full()
9389 .flex_1()
9390 .gap_2()
9391 .pr_1()
9392 .overflow_x_hidden()
9393 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9394 .child(left)
9395 .child(preview),
9396 )
9397 }
9398 }
9399 }
9400
9401 pub fn render_context_menu(
9402 &self,
9403 style: &EditorStyle,
9404 max_height_in_lines: u32,
9405 window: &mut Window,
9406 cx: &mut Context<Editor>,
9407 ) -> Option<AnyElement> {
9408 let menu = self.context_menu.borrow();
9409 let menu = menu.as_ref()?;
9410 if !menu.visible() {
9411 return None;
9412 };
9413 Some(menu.render(style, max_height_in_lines, window, cx))
9414 }
9415
9416 fn render_context_menu_aside(
9417 &mut self,
9418 max_size: Size<Pixels>,
9419 window: &mut Window,
9420 cx: &mut Context<Editor>,
9421 ) -> Option<AnyElement> {
9422 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9423 if menu.visible() {
9424 menu.render_aside(max_size, window, cx)
9425 } else {
9426 None
9427 }
9428 })
9429 }
9430
9431 fn hide_context_menu(
9432 &mut self,
9433 window: &mut Window,
9434 cx: &mut Context<Self>,
9435 ) -> Option<CodeContextMenu> {
9436 cx.notify();
9437 self.completion_tasks.clear();
9438 let context_menu = self.context_menu.borrow_mut().take();
9439 self.stale_edit_prediction_in_menu.take();
9440 self.update_visible_edit_prediction(window, cx);
9441 if let Some(CodeContextMenu::Completions(_)) = &context_menu {
9442 if let Some(completion_provider) = &self.completion_provider {
9443 completion_provider.selection_changed(None, window, cx);
9444 }
9445 }
9446 context_menu
9447 }
9448
9449 fn show_snippet_choices(
9450 &mut self,
9451 choices: &Vec<String>,
9452 selection: Range<Anchor>,
9453 cx: &mut Context<Self>,
9454 ) {
9455 let buffer_id = match (&selection.start.buffer_id, &selection.end.buffer_id) {
9456 (Some(a), Some(b)) if a == b => a,
9457 _ => {
9458 log::error!("expected anchor range to have matching buffer IDs");
9459 return;
9460 }
9461 };
9462 let multi_buffer = self.buffer().read(cx);
9463 let Some(buffer) = multi_buffer.buffer(*buffer_id) else {
9464 return;
9465 };
9466
9467 let id = post_inc(&mut self.next_completion_id);
9468 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9469 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9470 CompletionsMenu::new_snippet_choices(
9471 id,
9472 true,
9473 choices,
9474 selection,
9475 buffer,
9476 snippet_sort_order,
9477 ),
9478 ));
9479 }
9480
9481 pub fn insert_snippet(
9482 &mut self,
9483 insertion_ranges: &[Range<usize>],
9484 snippet: Snippet,
9485 window: &mut Window,
9486 cx: &mut Context<Self>,
9487 ) -> Result<()> {
9488 struct Tabstop<T> {
9489 is_end_tabstop: bool,
9490 ranges: Vec<Range<T>>,
9491 choices: Option<Vec<String>>,
9492 }
9493
9494 let tabstops = self.buffer.update(cx, |buffer, cx| {
9495 let snippet_text: Arc<str> = snippet.text.clone().into();
9496 let edits = insertion_ranges
9497 .iter()
9498 .cloned()
9499 .map(|range| (range, snippet_text.clone()));
9500 let autoindent_mode = AutoindentMode::Block {
9501 original_indent_columns: Vec::new(),
9502 };
9503 buffer.edit(edits, Some(autoindent_mode), cx);
9504
9505 let snapshot = &*buffer.read(cx);
9506 let snippet = &snippet;
9507 snippet
9508 .tabstops
9509 .iter()
9510 .map(|tabstop| {
9511 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
9512 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9513 });
9514 let mut tabstop_ranges = tabstop
9515 .ranges
9516 .iter()
9517 .flat_map(|tabstop_range| {
9518 let mut delta = 0_isize;
9519 insertion_ranges.iter().map(move |insertion_range| {
9520 let insertion_start = insertion_range.start as isize + delta;
9521 delta +=
9522 snippet.text.len() as isize - insertion_range.len() as isize;
9523
9524 let start = ((insertion_start + tabstop_range.start) as usize)
9525 .min(snapshot.len());
9526 let end = ((insertion_start + tabstop_range.end) as usize)
9527 .min(snapshot.len());
9528 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9529 })
9530 })
9531 .collect::<Vec<_>>();
9532 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9533
9534 Tabstop {
9535 is_end_tabstop,
9536 ranges: tabstop_ranges,
9537 choices: tabstop.choices.clone(),
9538 }
9539 })
9540 .collect::<Vec<_>>()
9541 });
9542 if let Some(tabstop) = tabstops.first() {
9543 self.change_selections(Default::default(), window, cx, |s| {
9544 // Reverse order so that the first range is the newest created selection.
9545 // Completions will use it and autoscroll will prioritize it.
9546 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9547 });
9548
9549 if let Some(choices) = &tabstop.choices {
9550 if let Some(selection) = tabstop.ranges.first() {
9551 self.show_snippet_choices(choices, selection.clone(), cx)
9552 }
9553 }
9554
9555 // If we're already at the last tabstop and it's at the end of the snippet,
9556 // we're done, we don't need to keep the state around.
9557 if !tabstop.is_end_tabstop {
9558 let choices = tabstops
9559 .iter()
9560 .map(|tabstop| tabstop.choices.clone())
9561 .collect();
9562
9563 let ranges = tabstops
9564 .into_iter()
9565 .map(|tabstop| tabstop.ranges)
9566 .collect::<Vec<_>>();
9567
9568 self.snippet_stack.push(SnippetState {
9569 active_index: 0,
9570 ranges,
9571 choices,
9572 });
9573 }
9574
9575 // Check whether the just-entered snippet ends with an auto-closable bracket.
9576 if self.autoclose_regions.is_empty() {
9577 let snapshot = self.buffer.read(cx).snapshot(cx);
9578 let mut all_selections = self.selections.all::<Point>(cx);
9579 for selection in &mut all_selections {
9580 let selection_head = selection.head();
9581 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9582 continue;
9583 };
9584
9585 let mut bracket_pair = None;
9586 let max_lookup_length = scope
9587 .brackets()
9588 .map(|(pair, _)| {
9589 pair.start
9590 .as_str()
9591 .chars()
9592 .count()
9593 .max(pair.end.as_str().chars().count())
9594 })
9595 .max();
9596 if let Some(max_lookup_length) = max_lookup_length {
9597 let next_text = snapshot
9598 .chars_at(selection_head)
9599 .take(max_lookup_length)
9600 .collect::<String>();
9601 let prev_text = snapshot
9602 .reversed_chars_at(selection_head)
9603 .take(max_lookup_length)
9604 .collect::<String>();
9605
9606 for (pair, enabled) in scope.brackets() {
9607 if enabled
9608 && pair.close
9609 && prev_text.starts_with(pair.start.as_str())
9610 && next_text.starts_with(pair.end.as_str())
9611 {
9612 bracket_pair = Some(pair.clone());
9613 break;
9614 }
9615 }
9616 }
9617
9618 if let Some(pair) = bracket_pair {
9619 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9620 let autoclose_enabled =
9621 self.use_autoclose && snapshot_settings.use_autoclose;
9622 if autoclose_enabled {
9623 let start = snapshot.anchor_after(selection_head);
9624 let end = snapshot.anchor_after(selection_head);
9625 self.autoclose_regions.push(AutocloseRegion {
9626 selection_id: selection.id,
9627 range: start..end,
9628 pair,
9629 });
9630 }
9631 }
9632 }
9633 }
9634 }
9635 Ok(())
9636 }
9637
9638 pub fn move_to_next_snippet_tabstop(
9639 &mut self,
9640 window: &mut Window,
9641 cx: &mut Context<Self>,
9642 ) -> bool {
9643 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9644 }
9645
9646 pub fn move_to_prev_snippet_tabstop(
9647 &mut self,
9648 window: &mut Window,
9649 cx: &mut Context<Self>,
9650 ) -> bool {
9651 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9652 }
9653
9654 pub fn move_to_snippet_tabstop(
9655 &mut self,
9656 bias: Bias,
9657 window: &mut Window,
9658 cx: &mut Context<Self>,
9659 ) -> bool {
9660 if let Some(mut snippet) = self.snippet_stack.pop() {
9661 match bias {
9662 Bias::Left => {
9663 if snippet.active_index > 0 {
9664 snippet.active_index -= 1;
9665 } else {
9666 self.snippet_stack.push(snippet);
9667 return false;
9668 }
9669 }
9670 Bias::Right => {
9671 if snippet.active_index + 1 < snippet.ranges.len() {
9672 snippet.active_index += 1;
9673 } else {
9674 self.snippet_stack.push(snippet);
9675 return false;
9676 }
9677 }
9678 }
9679 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9680 self.change_selections(Default::default(), window, cx, |s| {
9681 // Reverse order so that the first range is the newest created selection.
9682 // Completions will use it and autoscroll will prioritize it.
9683 s.select_ranges(current_ranges.iter().rev().cloned())
9684 });
9685
9686 if let Some(choices) = &snippet.choices[snippet.active_index] {
9687 if let Some(selection) = current_ranges.first() {
9688 self.show_snippet_choices(&choices, selection.clone(), cx);
9689 }
9690 }
9691
9692 // If snippet state is not at the last tabstop, push it back on the stack
9693 if snippet.active_index + 1 < snippet.ranges.len() {
9694 self.snippet_stack.push(snippet);
9695 }
9696 return true;
9697 }
9698 }
9699
9700 false
9701 }
9702
9703 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9704 self.transact(window, cx, |this, window, cx| {
9705 this.select_all(&SelectAll, window, cx);
9706 this.insert("", window, cx);
9707 });
9708 }
9709
9710 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9711 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9712 self.transact(window, cx, |this, window, cx| {
9713 this.select_autoclose_pair(window, cx);
9714 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9715 if !this.linked_edit_ranges.is_empty() {
9716 let selections = this.selections.all::<MultiBufferPoint>(cx);
9717 let snapshot = this.buffer.read(cx).snapshot(cx);
9718
9719 for selection in selections.iter() {
9720 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9721 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9722 if selection_start.buffer_id != selection_end.buffer_id {
9723 continue;
9724 }
9725 if let Some(ranges) =
9726 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9727 {
9728 for (buffer, entries) in ranges {
9729 linked_ranges.entry(buffer).or_default().extend(entries);
9730 }
9731 }
9732 }
9733 }
9734
9735 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
9736 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9737 for selection in &mut selections {
9738 if selection.is_empty() {
9739 let old_head = selection.head();
9740 let mut new_head =
9741 movement::left(&display_map, old_head.to_display_point(&display_map))
9742 .to_point(&display_map);
9743 if let Some((buffer, line_buffer_range)) = display_map
9744 .buffer_snapshot
9745 .buffer_line_for_row(MultiBufferRow(old_head.row))
9746 {
9747 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9748 let indent_len = match indent_size.kind {
9749 IndentKind::Space => {
9750 buffer.settings_at(line_buffer_range.start, cx).tab_size
9751 }
9752 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9753 };
9754 if old_head.column <= indent_size.len && old_head.column > 0 {
9755 let indent_len = indent_len.get();
9756 new_head = cmp::min(
9757 new_head,
9758 MultiBufferPoint::new(
9759 old_head.row,
9760 ((old_head.column - 1) / indent_len) * indent_len,
9761 ),
9762 );
9763 }
9764 }
9765
9766 selection.set_head(new_head, SelectionGoal::None);
9767 }
9768 }
9769
9770 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9771 this.insert("", window, cx);
9772 let empty_str: Arc<str> = Arc::from("");
9773 for (buffer, edits) in linked_ranges {
9774 let snapshot = buffer.read(cx).snapshot();
9775 use text::ToPoint as TP;
9776
9777 let edits = edits
9778 .into_iter()
9779 .map(|range| {
9780 let end_point = TP::to_point(&range.end, &snapshot);
9781 let mut start_point = TP::to_point(&range.start, &snapshot);
9782
9783 if end_point == start_point {
9784 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9785 .saturating_sub(1);
9786 start_point =
9787 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9788 };
9789
9790 (start_point..end_point, empty_str.clone())
9791 })
9792 .sorted_by_key(|(range, _)| range.start)
9793 .collect::<Vec<_>>();
9794 buffer.update(cx, |this, cx| {
9795 this.edit(edits, None, cx);
9796 })
9797 }
9798 this.refresh_edit_prediction(true, false, window, cx);
9799 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
9800 });
9801 }
9802
9803 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9804 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9805 self.transact(window, cx, |this, window, cx| {
9806 this.change_selections(Default::default(), window, cx, |s| {
9807 s.move_with(|map, selection| {
9808 if selection.is_empty() {
9809 let cursor = movement::right(map, selection.head());
9810 selection.end = cursor;
9811 selection.reversed = true;
9812 selection.goal = SelectionGoal::None;
9813 }
9814 })
9815 });
9816 this.insert("", window, cx);
9817 this.refresh_edit_prediction(true, false, window, cx);
9818 });
9819 }
9820
9821 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9822 if self.mode.is_single_line() {
9823 cx.propagate();
9824 return;
9825 }
9826
9827 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9828 if self.move_to_prev_snippet_tabstop(window, cx) {
9829 return;
9830 }
9831 self.outdent(&Outdent, window, cx);
9832 }
9833
9834 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9835 if self.mode.is_single_line() {
9836 cx.propagate();
9837 return;
9838 }
9839
9840 if self.move_to_next_snippet_tabstop(window, cx) {
9841 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9842 return;
9843 }
9844 if self.read_only(cx) {
9845 return;
9846 }
9847 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9848 let mut selections = self.selections.all_adjusted(cx);
9849 let buffer = self.buffer.read(cx);
9850 let snapshot = buffer.snapshot(cx);
9851 let rows_iter = selections.iter().map(|s| s.head().row);
9852 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9853
9854 let has_some_cursor_in_whitespace = selections
9855 .iter()
9856 .filter(|selection| selection.is_empty())
9857 .any(|selection| {
9858 let cursor = selection.head();
9859 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9860 cursor.column < current_indent.len
9861 });
9862
9863 let mut edits = Vec::new();
9864 let mut prev_edited_row = 0;
9865 let mut row_delta = 0;
9866 for selection in &mut selections {
9867 if selection.start.row != prev_edited_row {
9868 row_delta = 0;
9869 }
9870 prev_edited_row = selection.end.row;
9871
9872 // If the selection is non-empty, then increase the indentation of the selected lines.
9873 if !selection.is_empty() {
9874 row_delta =
9875 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9876 continue;
9877 }
9878
9879 let cursor = selection.head();
9880 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9881 if let Some(suggested_indent) =
9882 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
9883 {
9884 // Don't do anything if already at suggested indent
9885 // and there is any other cursor which is not
9886 if has_some_cursor_in_whitespace
9887 && cursor.column == current_indent.len
9888 && current_indent.len == suggested_indent.len
9889 {
9890 continue;
9891 }
9892
9893 // Adjust line and move cursor to suggested indent
9894 // if cursor is not at suggested indent
9895 if cursor.column < suggested_indent.len
9896 && cursor.column <= current_indent.len
9897 && current_indent.len <= suggested_indent.len
9898 {
9899 selection.start = Point::new(cursor.row, suggested_indent.len);
9900 selection.end = selection.start;
9901 if row_delta == 0 {
9902 edits.extend(Buffer::edit_for_indent_size_adjustment(
9903 cursor.row,
9904 current_indent,
9905 suggested_indent,
9906 ));
9907 row_delta = suggested_indent.len - current_indent.len;
9908 }
9909 continue;
9910 }
9911
9912 // If current indent is more than suggested indent
9913 // only move cursor to current indent and skip indent
9914 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
9915 selection.start = Point::new(cursor.row, current_indent.len);
9916 selection.end = selection.start;
9917 continue;
9918 }
9919 }
9920
9921 // Otherwise, insert a hard or soft tab.
9922 let settings = buffer.language_settings_at(cursor, cx);
9923 let tab_size = if settings.hard_tabs {
9924 IndentSize::tab()
9925 } else {
9926 let tab_size = settings.tab_size.get();
9927 let indent_remainder = snapshot
9928 .text_for_range(Point::new(cursor.row, 0)..cursor)
9929 .flat_map(str::chars)
9930 .fold(row_delta % tab_size, |counter: u32, c| {
9931 if c == '\t' {
9932 0
9933 } else {
9934 (counter + 1) % tab_size
9935 }
9936 });
9937
9938 let chars_to_next_tab_stop = tab_size - indent_remainder;
9939 IndentSize::spaces(chars_to_next_tab_stop)
9940 };
9941 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
9942 selection.end = selection.start;
9943 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
9944 row_delta += tab_size.len;
9945 }
9946
9947 self.transact(window, cx, |this, window, cx| {
9948 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9949 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9950 this.refresh_edit_prediction(true, false, window, cx);
9951 });
9952 }
9953
9954 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
9955 if self.read_only(cx) {
9956 return;
9957 }
9958 if self.mode.is_single_line() {
9959 cx.propagate();
9960 return;
9961 }
9962
9963 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9964 let mut selections = self.selections.all::<Point>(cx);
9965 let mut prev_edited_row = 0;
9966 let mut row_delta = 0;
9967 let mut edits = Vec::new();
9968 let buffer = self.buffer.read(cx);
9969 let snapshot = buffer.snapshot(cx);
9970 for selection in &mut selections {
9971 if selection.start.row != prev_edited_row {
9972 row_delta = 0;
9973 }
9974 prev_edited_row = selection.end.row;
9975
9976 row_delta =
9977 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9978 }
9979
9980 self.transact(window, cx, |this, window, cx| {
9981 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9982 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9983 });
9984 }
9985
9986 fn indent_selection(
9987 buffer: &MultiBuffer,
9988 snapshot: &MultiBufferSnapshot,
9989 selection: &mut Selection<Point>,
9990 edits: &mut Vec<(Range<Point>, String)>,
9991 delta_for_start_row: u32,
9992 cx: &App,
9993 ) -> u32 {
9994 let settings = buffer.language_settings_at(selection.start, cx);
9995 let tab_size = settings.tab_size.get();
9996 let indent_kind = if settings.hard_tabs {
9997 IndentKind::Tab
9998 } else {
9999 IndentKind::Space
10000 };
10001 let mut start_row = selection.start.row;
10002 let mut end_row = selection.end.row + 1;
10003
10004 // If a selection ends at the beginning of a line, don't indent
10005 // that last line.
10006 if selection.end.column == 0 && selection.end.row > selection.start.row {
10007 end_row -= 1;
10008 }
10009
10010 // Avoid re-indenting a row that has already been indented by a
10011 // previous selection, but still update this selection's column
10012 // to reflect that indentation.
10013 if delta_for_start_row > 0 {
10014 start_row += 1;
10015 selection.start.column += delta_for_start_row;
10016 if selection.end.row == selection.start.row {
10017 selection.end.column += delta_for_start_row;
10018 }
10019 }
10020
10021 let mut delta_for_end_row = 0;
10022 let has_multiple_rows = start_row + 1 != end_row;
10023 for row in start_row..end_row {
10024 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10025 let indent_delta = match (current_indent.kind, indent_kind) {
10026 (IndentKind::Space, IndentKind::Space) => {
10027 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10028 IndentSize::spaces(columns_to_next_tab_stop)
10029 }
10030 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10031 (_, IndentKind::Tab) => IndentSize::tab(),
10032 };
10033
10034 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10035 0
10036 } else {
10037 selection.start.column
10038 };
10039 let row_start = Point::new(row, start);
10040 edits.push((
10041 row_start..row_start,
10042 indent_delta.chars().collect::<String>(),
10043 ));
10044
10045 // Update this selection's endpoints to reflect the indentation.
10046 if row == selection.start.row {
10047 selection.start.column += indent_delta.len;
10048 }
10049 if row == selection.end.row {
10050 selection.end.column += indent_delta.len;
10051 delta_for_end_row = indent_delta.len;
10052 }
10053 }
10054
10055 if selection.start.row == selection.end.row {
10056 delta_for_start_row + delta_for_end_row
10057 } else {
10058 delta_for_end_row
10059 }
10060 }
10061
10062 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10063 if self.read_only(cx) {
10064 return;
10065 }
10066 if self.mode.is_single_line() {
10067 cx.propagate();
10068 return;
10069 }
10070
10071 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10072 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10073 let selections = self.selections.all::<Point>(cx);
10074 let mut deletion_ranges = Vec::new();
10075 let mut last_outdent = None;
10076 {
10077 let buffer = self.buffer.read(cx);
10078 let snapshot = buffer.snapshot(cx);
10079 for selection in &selections {
10080 let settings = buffer.language_settings_at(selection.start, cx);
10081 let tab_size = settings.tab_size.get();
10082 let mut rows = selection.spanned_rows(false, &display_map);
10083
10084 // Avoid re-outdenting a row that has already been outdented by a
10085 // previous selection.
10086 if let Some(last_row) = last_outdent {
10087 if last_row == rows.start {
10088 rows.start = rows.start.next_row();
10089 }
10090 }
10091 let has_multiple_rows = rows.len() > 1;
10092 for row in rows.iter_rows() {
10093 let indent_size = snapshot.indent_size_for_line(row);
10094 if indent_size.len > 0 {
10095 let deletion_len = match indent_size.kind {
10096 IndentKind::Space => {
10097 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10098 if columns_to_prev_tab_stop == 0 {
10099 tab_size
10100 } else {
10101 columns_to_prev_tab_stop
10102 }
10103 }
10104 IndentKind::Tab => 1,
10105 };
10106 let start = if has_multiple_rows
10107 || deletion_len > selection.start.column
10108 || indent_size.len < selection.start.column
10109 {
10110 0
10111 } else {
10112 selection.start.column - deletion_len
10113 };
10114 deletion_ranges.push(
10115 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10116 );
10117 last_outdent = Some(row);
10118 }
10119 }
10120 }
10121 }
10122
10123 self.transact(window, cx, |this, window, cx| {
10124 this.buffer.update(cx, |buffer, cx| {
10125 let empty_str: Arc<str> = Arc::default();
10126 buffer.edit(
10127 deletion_ranges
10128 .into_iter()
10129 .map(|range| (range, empty_str.clone())),
10130 None,
10131 cx,
10132 );
10133 });
10134 let selections = this.selections.all::<usize>(cx);
10135 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10136 });
10137 }
10138
10139 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10140 if self.read_only(cx) {
10141 return;
10142 }
10143 if self.mode.is_single_line() {
10144 cx.propagate();
10145 return;
10146 }
10147
10148 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10149 let selections = self
10150 .selections
10151 .all::<usize>(cx)
10152 .into_iter()
10153 .map(|s| s.range());
10154
10155 self.transact(window, cx, |this, window, cx| {
10156 this.buffer.update(cx, |buffer, cx| {
10157 buffer.autoindent_ranges(selections, cx);
10158 });
10159 let selections = this.selections.all::<usize>(cx);
10160 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10161 });
10162 }
10163
10164 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10165 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10166 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10167 let selections = self.selections.all::<Point>(cx);
10168
10169 let mut new_cursors = Vec::new();
10170 let mut edit_ranges = Vec::new();
10171 let mut selections = selections.iter().peekable();
10172 while let Some(selection) = selections.next() {
10173 let mut rows = selection.spanned_rows(false, &display_map);
10174 let goal_display_column = selection.head().to_display_point(&display_map).column();
10175
10176 // Accumulate contiguous regions of rows that we want to delete.
10177 while let Some(next_selection) = selections.peek() {
10178 let next_rows = next_selection.spanned_rows(false, &display_map);
10179 if next_rows.start <= rows.end {
10180 rows.end = next_rows.end;
10181 selections.next().unwrap();
10182 } else {
10183 break;
10184 }
10185 }
10186
10187 let buffer = &display_map.buffer_snapshot;
10188 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
10189 let edit_end;
10190 let cursor_buffer_row;
10191 if buffer.max_point().row >= rows.end.0 {
10192 // If there's a line after the range, delete the \n from the end of the row range
10193 // and position the cursor on the next line.
10194 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
10195 cursor_buffer_row = rows.end;
10196 } else {
10197 // If there isn't a line after the range, delete the \n from the line before the
10198 // start of the row range and position the cursor there.
10199 edit_start = edit_start.saturating_sub(1);
10200 edit_end = buffer.len();
10201 cursor_buffer_row = rows.start.previous_row();
10202 }
10203
10204 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
10205 *cursor.column_mut() =
10206 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
10207
10208 new_cursors.push((
10209 selection.id,
10210 buffer.anchor_after(cursor.to_point(&display_map)),
10211 ));
10212 edit_ranges.push(edit_start..edit_end);
10213 }
10214
10215 self.transact(window, cx, |this, window, cx| {
10216 let buffer = this.buffer.update(cx, |buffer, cx| {
10217 let empty_str: Arc<str> = Arc::default();
10218 buffer.edit(
10219 edit_ranges
10220 .into_iter()
10221 .map(|range| (range, empty_str.clone())),
10222 None,
10223 cx,
10224 );
10225 buffer.snapshot(cx)
10226 });
10227 let new_selections = new_cursors
10228 .into_iter()
10229 .map(|(id, cursor)| {
10230 let cursor = cursor.to_point(&buffer);
10231 Selection {
10232 id,
10233 start: cursor,
10234 end: cursor,
10235 reversed: false,
10236 goal: SelectionGoal::None,
10237 }
10238 })
10239 .collect();
10240
10241 this.change_selections(Default::default(), window, cx, |s| {
10242 s.select(new_selections);
10243 });
10244 });
10245 }
10246
10247 pub fn join_lines_impl(
10248 &mut self,
10249 insert_whitespace: bool,
10250 window: &mut Window,
10251 cx: &mut Context<Self>,
10252 ) {
10253 if self.read_only(cx) {
10254 return;
10255 }
10256 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10257 for selection in self.selections.all::<Point>(cx) {
10258 let start = MultiBufferRow(selection.start.row);
10259 // Treat single line selections as if they include the next line. Otherwise this action
10260 // would do nothing for single line selections individual cursors.
10261 let end = if selection.start.row == selection.end.row {
10262 MultiBufferRow(selection.start.row + 1)
10263 } else {
10264 MultiBufferRow(selection.end.row)
10265 };
10266
10267 if let Some(last_row_range) = row_ranges.last_mut() {
10268 if start <= last_row_range.end {
10269 last_row_range.end = end;
10270 continue;
10271 }
10272 }
10273 row_ranges.push(start..end);
10274 }
10275
10276 let snapshot = self.buffer.read(cx).snapshot(cx);
10277 let mut cursor_positions = Vec::new();
10278 for row_range in &row_ranges {
10279 let anchor = snapshot.anchor_before(Point::new(
10280 row_range.end.previous_row().0,
10281 snapshot.line_len(row_range.end.previous_row()),
10282 ));
10283 cursor_positions.push(anchor..anchor);
10284 }
10285
10286 self.transact(window, cx, |this, window, cx| {
10287 for row_range in row_ranges.into_iter().rev() {
10288 for row in row_range.iter_rows().rev() {
10289 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10290 let next_line_row = row.next_row();
10291 let indent = snapshot.indent_size_for_line(next_line_row);
10292 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10293
10294 let replace =
10295 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10296 " "
10297 } else {
10298 ""
10299 };
10300
10301 this.buffer.update(cx, |buffer, cx| {
10302 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10303 });
10304 }
10305 }
10306
10307 this.change_selections(Default::default(), window, cx, |s| {
10308 s.select_anchor_ranges(cursor_positions)
10309 });
10310 });
10311 }
10312
10313 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10314 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10315 self.join_lines_impl(true, window, cx);
10316 }
10317
10318 pub fn sort_lines_case_sensitive(
10319 &mut self,
10320 _: &SortLinesCaseSensitive,
10321 window: &mut Window,
10322 cx: &mut Context<Self>,
10323 ) {
10324 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10325 }
10326
10327 pub fn sort_lines_by_length(
10328 &mut self,
10329 _: &SortLinesByLength,
10330 window: &mut Window,
10331 cx: &mut Context<Self>,
10332 ) {
10333 self.manipulate_immutable_lines(window, cx, |lines| {
10334 lines.sort_by_key(|&line| line.chars().count())
10335 })
10336 }
10337
10338 pub fn sort_lines_case_insensitive(
10339 &mut self,
10340 _: &SortLinesCaseInsensitive,
10341 window: &mut Window,
10342 cx: &mut Context<Self>,
10343 ) {
10344 self.manipulate_immutable_lines(window, cx, |lines| {
10345 lines.sort_by_key(|line| line.to_lowercase())
10346 })
10347 }
10348
10349 pub fn unique_lines_case_insensitive(
10350 &mut self,
10351 _: &UniqueLinesCaseInsensitive,
10352 window: &mut Window,
10353 cx: &mut Context<Self>,
10354 ) {
10355 self.manipulate_immutable_lines(window, cx, |lines| {
10356 let mut seen = HashSet::default();
10357 lines.retain(|line| seen.insert(line.to_lowercase()));
10358 })
10359 }
10360
10361 pub fn unique_lines_case_sensitive(
10362 &mut self,
10363 _: &UniqueLinesCaseSensitive,
10364 window: &mut Window,
10365 cx: &mut Context<Self>,
10366 ) {
10367 self.manipulate_immutable_lines(window, cx, |lines| {
10368 let mut seen = HashSet::default();
10369 lines.retain(|line| seen.insert(*line));
10370 })
10371 }
10372
10373 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10374 let Some(project) = self.project.clone() else {
10375 return;
10376 };
10377 self.reload(project, window, cx)
10378 .detach_and_notify_err(window, cx);
10379 }
10380
10381 pub fn restore_file(
10382 &mut self,
10383 _: &::git::RestoreFile,
10384 window: &mut Window,
10385 cx: &mut Context<Self>,
10386 ) {
10387 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10388 let mut buffer_ids = HashSet::default();
10389 let snapshot = self.buffer().read(cx).snapshot(cx);
10390 for selection in self.selections.all::<usize>(cx) {
10391 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10392 }
10393
10394 let buffer = self.buffer().read(cx);
10395 let ranges = buffer_ids
10396 .into_iter()
10397 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10398 .collect::<Vec<_>>();
10399
10400 self.restore_hunks_in_ranges(ranges, window, cx);
10401 }
10402
10403 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10404 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10405 let selections = self
10406 .selections
10407 .all(cx)
10408 .into_iter()
10409 .map(|s| s.range())
10410 .collect();
10411 self.restore_hunks_in_ranges(selections, window, cx);
10412 }
10413
10414 pub fn restore_hunks_in_ranges(
10415 &mut self,
10416 ranges: Vec<Range<Point>>,
10417 window: &mut Window,
10418 cx: &mut Context<Editor>,
10419 ) {
10420 let mut revert_changes = HashMap::default();
10421 let chunk_by = self
10422 .snapshot(window, cx)
10423 .hunks_for_ranges(ranges)
10424 .into_iter()
10425 .chunk_by(|hunk| hunk.buffer_id);
10426 for (buffer_id, hunks) in &chunk_by {
10427 let hunks = hunks.collect::<Vec<_>>();
10428 for hunk in &hunks {
10429 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10430 }
10431 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10432 }
10433 drop(chunk_by);
10434 if !revert_changes.is_empty() {
10435 self.transact(window, cx, |editor, window, cx| {
10436 editor.restore(revert_changes, window, cx);
10437 });
10438 }
10439 }
10440
10441 pub fn open_active_item_in_terminal(
10442 &mut self,
10443 _: &OpenInTerminal,
10444 window: &mut Window,
10445 cx: &mut Context<Self>,
10446 ) {
10447 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10448 let project_path = buffer.read(cx).project_path(cx)?;
10449 let project = self.project.as_ref()?.read(cx);
10450 let entry = project.entry_for_path(&project_path, cx)?;
10451 let parent = match &entry.canonical_path {
10452 Some(canonical_path) => canonical_path.to_path_buf(),
10453 None => project.absolute_path(&project_path, cx)?,
10454 }
10455 .parent()?
10456 .to_path_buf();
10457 Some(parent)
10458 }) {
10459 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10460 }
10461 }
10462
10463 fn set_breakpoint_context_menu(
10464 &mut self,
10465 display_row: DisplayRow,
10466 position: Option<Anchor>,
10467 clicked_point: gpui::Point<Pixels>,
10468 window: &mut Window,
10469 cx: &mut Context<Self>,
10470 ) {
10471 let source = self
10472 .buffer
10473 .read(cx)
10474 .snapshot(cx)
10475 .anchor_before(Point::new(display_row.0, 0u32));
10476
10477 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10478
10479 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10480 self,
10481 source,
10482 clicked_point,
10483 context_menu,
10484 window,
10485 cx,
10486 );
10487 }
10488
10489 fn add_edit_breakpoint_block(
10490 &mut self,
10491 anchor: Anchor,
10492 breakpoint: &Breakpoint,
10493 edit_action: BreakpointPromptEditAction,
10494 window: &mut Window,
10495 cx: &mut Context<Self>,
10496 ) {
10497 let weak_editor = cx.weak_entity();
10498 let bp_prompt = cx.new(|cx| {
10499 BreakpointPromptEditor::new(
10500 weak_editor,
10501 anchor,
10502 breakpoint.clone(),
10503 edit_action,
10504 window,
10505 cx,
10506 )
10507 });
10508
10509 let height = bp_prompt.update(cx, |this, cx| {
10510 this.prompt
10511 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10512 });
10513 let cloned_prompt = bp_prompt.clone();
10514 let blocks = vec![BlockProperties {
10515 style: BlockStyle::Sticky,
10516 placement: BlockPlacement::Above(anchor),
10517 height: Some(height),
10518 render: Arc::new(move |cx| {
10519 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10520 cloned_prompt.clone().into_any_element()
10521 }),
10522 priority: 0,
10523 }];
10524
10525 let focus_handle = bp_prompt.focus_handle(cx);
10526 window.focus(&focus_handle);
10527
10528 let block_ids = self.insert_blocks(blocks, None, cx);
10529 bp_prompt.update(cx, |prompt, _| {
10530 prompt.add_block_ids(block_ids);
10531 });
10532 }
10533
10534 pub(crate) fn breakpoint_at_row(
10535 &self,
10536 row: u32,
10537 window: &mut Window,
10538 cx: &mut Context<Self>,
10539 ) -> Option<(Anchor, Breakpoint)> {
10540 let snapshot = self.snapshot(window, cx);
10541 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
10542
10543 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10544 }
10545
10546 pub(crate) fn breakpoint_at_anchor(
10547 &self,
10548 breakpoint_position: Anchor,
10549 snapshot: &EditorSnapshot,
10550 cx: &mut Context<Self>,
10551 ) -> Option<(Anchor, Breakpoint)> {
10552 let project = self.project.clone()?;
10553
10554 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
10555 snapshot
10556 .buffer_snapshot
10557 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
10558 })?;
10559
10560 let enclosing_excerpt = breakpoint_position.excerpt_id;
10561 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
10562 let buffer_snapshot = buffer.read(cx).snapshot();
10563
10564 let row = buffer_snapshot
10565 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10566 .row;
10567
10568 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
10569 let anchor_end = snapshot
10570 .buffer_snapshot
10571 .anchor_after(Point::new(row, line_len));
10572
10573 let bp = self
10574 .breakpoint_store
10575 .as_ref()?
10576 .read_with(cx, |breakpoint_store, cx| {
10577 breakpoint_store
10578 .breakpoints(
10579 &buffer,
10580 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10581 &buffer_snapshot,
10582 cx,
10583 )
10584 .next()
10585 .and_then(|(bp, _)| {
10586 let breakpoint_row = buffer_snapshot
10587 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10588 .row;
10589
10590 if breakpoint_row == row {
10591 snapshot
10592 .buffer_snapshot
10593 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10594 .map(|position| (position, bp.bp.clone()))
10595 } else {
10596 None
10597 }
10598 })
10599 });
10600 bp
10601 }
10602
10603 pub fn edit_log_breakpoint(
10604 &mut self,
10605 _: &EditLogBreakpoint,
10606 window: &mut Window,
10607 cx: &mut Context<Self>,
10608 ) {
10609 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10610 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10611 message: None,
10612 state: BreakpointState::Enabled,
10613 condition: None,
10614 hit_condition: None,
10615 });
10616
10617 self.add_edit_breakpoint_block(
10618 anchor,
10619 &breakpoint,
10620 BreakpointPromptEditAction::Log,
10621 window,
10622 cx,
10623 );
10624 }
10625 }
10626
10627 fn breakpoints_at_cursors(
10628 &self,
10629 window: &mut Window,
10630 cx: &mut Context<Self>,
10631 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10632 let snapshot = self.snapshot(window, cx);
10633 let cursors = self
10634 .selections
10635 .disjoint_anchors()
10636 .into_iter()
10637 .map(|selection| {
10638 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
10639
10640 let breakpoint_position = self
10641 .breakpoint_at_row(cursor_position.row, window, cx)
10642 .map(|bp| bp.0)
10643 .unwrap_or_else(|| {
10644 snapshot
10645 .display_snapshot
10646 .buffer_snapshot
10647 .anchor_after(Point::new(cursor_position.row, 0))
10648 });
10649
10650 let breakpoint = self
10651 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10652 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10653
10654 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10655 })
10656 // 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.
10657 .collect::<HashMap<Anchor, _>>();
10658
10659 cursors.into_iter().collect()
10660 }
10661
10662 pub fn enable_breakpoint(
10663 &mut self,
10664 _: &crate::actions::EnableBreakpoint,
10665 window: &mut Window,
10666 cx: &mut Context<Self>,
10667 ) {
10668 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10669 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10670 continue;
10671 };
10672 self.edit_breakpoint_at_anchor(
10673 anchor,
10674 breakpoint,
10675 BreakpointEditAction::InvertState,
10676 cx,
10677 );
10678 }
10679 }
10680
10681 pub fn disable_breakpoint(
10682 &mut self,
10683 _: &crate::actions::DisableBreakpoint,
10684 window: &mut Window,
10685 cx: &mut Context<Self>,
10686 ) {
10687 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10688 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10689 continue;
10690 };
10691 self.edit_breakpoint_at_anchor(
10692 anchor,
10693 breakpoint,
10694 BreakpointEditAction::InvertState,
10695 cx,
10696 );
10697 }
10698 }
10699
10700 pub fn toggle_breakpoint(
10701 &mut self,
10702 _: &crate::actions::ToggleBreakpoint,
10703 window: &mut Window,
10704 cx: &mut Context<Self>,
10705 ) {
10706 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10707 if let Some(breakpoint) = breakpoint {
10708 self.edit_breakpoint_at_anchor(
10709 anchor,
10710 breakpoint,
10711 BreakpointEditAction::Toggle,
10712 cx,
10713 );
10714 } else {
10715 self.edit_breakpoint_at_anchor(
10716 anchor,
10717 Breakpoint::new_standard(),
10718 BreakpointEditAction::Toggle,
10719 cx,
10720 );
10721 }
10722 }
10723 }
10724
10725 pub fn edit_breakpoint_at_anchor(
10726 &mut self,
10727 breakpoint_position: Anchor,
10728 breakpoint: Breakpoint,
10729 edit_action: BreakpointEditAction,
10730 cx: &mut Context<Self>,
10731 ) {
10732 let Some(breakpoint_store) = &self.breakpoint_store else {
10733 return;
10734 };
10735
10736 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
10737 if breakpoint_position == Anchor::min() {
10738 self.buffer()
10739 .read(cx)
10740 .excerpt_buffer_ids()
10741 .into_iter()
10742 .next()
10743 } else {
10744 None
10745 }
10746 }) else {
10747 return;
10748 };
10749
10750 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
10751 return;
10752 };
10753
10754 breakpoint_store.update(cx, |breakpoint_store, cx| {
10755 breakpoint_store.toggle_breakpoint(
10756 buffer,
10757 BreakpointWithPosition {
10758 position: breakpoint_position.text_anchor,
10759 bp: breakpoint,
10760 },
10761 edit_action,
10762 cx,
10763 );
10764 });
10765
10766 cx.notify();
10767 }
10768
10769 #[cfg(any(test, feature = "test-support"))]
10770 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10771 self.breakpoint_store.clone()
10772 }
10773
10774 pub fn prepare_restore_change(
10775 &self,
10776 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10777 hunk: &MultiBufferDiffHunk,
10778 cx: &mut App,
10779 ) -> Option<()> {
10780 if hunk.is_created_file() {
10781 return None;
10782 }
10783 let buffer = self.buffer.read(cx);
10784 let diff = buffer.diff_for(hunk.buffer_id)?;
10785 let buffer = buffer.buffer(hunk.buffer_id)?;
10786 let buffer = buffer.read(cx);
10787 let original_text = diff
10788 .read(cx)
10789 .base_text()
10790 .as_rope()
10791 .slice(hunk.diff_base_byte_range.clone());
10792 let buffer_snapshot = buffer.snapshot();
10793 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
10794 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
10795 probe
10796 .0
10797 .start
10798 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
10799 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
10800 }) {
10801 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
10802 Some(())
10803 } else {
10804 None
10805 }
10806 }
10807
10808 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
10809 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
10810 }
10811
10812 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
10813 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
10814 }
10815
10816 fn manipulate_lines<M>(
10817 &mut self,
10818 window: &mut Window,
10819 cx: &mut Context<Self>,
10820 mut manipulate: M,
10821 ) where
10822 M: FnMut(&str) -> LineManipulationResult,
10823 {
10824 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10825
10826 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10827 let buffer = self.buffer.read(cx).snapshot(cx);
10828
10829 let mut edits = Vec::new();
10830
10831 let selections = self.selections.all::<Point>(cx);
10832 let mut selections = selections.iter().peekable();
10833 let mut contiguous_row_selections = Vec::new();
10834 let mut new_selections = Vec::new();
10835 let mut added_lines = 0;
10836 let mut removed_lines = 0;
10837
10838 while let Some(selection) = selections.next() {
10839 let (start_row, end_row) = consume_contiguous_rows(
10840 &mut contiguous_row_selections,
10841 selection,
10842 &display_map,
10843 &mut selections,
10844 );
10845
10846 let start_point = Point::new(start_row.0, 0);
10847 let end_point = Point::new(
10848 end_row.previous_row().0,
10849 buffer.line_len(end_row.previous_row()),
10850 );
10851 let text = buffer
10852 .text_for_range(start_point..end_point)
10853 .collect::<String>();
10854
10855 let LineManipulationResult {
10856 new_text,
10857 line_count_before,
10858 line_count_after,
10859 } = manipulate(&text);
10860
10861 edits.push((start_point..end_point, new_text));
10862
10863 // Selections must change based on added and removed line count
10864 let start_row =
10865 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
10866 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
10867 new_selections.push(Selection {
10868 id: selection.id,
10869 start: start_row,
10870 end: end_row,
10871 goal: SelectionGoal::None,
10872 reversed: selection.reversed,
10873 });
10874
10875 if line_count_after > line_count_before {
10876 added_lines += line_count_after - line_count_before;
10877 } else if line_count_before > line_count_after {
10878 removed_lines += line_count_before - line_count_after;
10879 }
10880 }
10881
10882 self.transact(window, cx, |this, window, cx| {
10883 let buffer = this.buffer.update(cx, |buffer, cx| {
10884 buffer.edit(edits, None, cx);
10885 buffer.snapshot(cx)
10886 });
10887
10888 // Recalculate offsets on newly edited buffer
10889 let new_selections = new_selections
10890 .iter()
10891 .map(|s| {
10892 let start_point = Point::new(s.start.0, 0);
10893 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
10894 Selection {
10895 id: s.id,
10896 start: buffer.point_to_offset(start_point),
10897 end: buffer.point_to_offset(end_point),
10898 goal: s.goal,
10899 reversed: s.reversed,
10900 }
10901 })
10902 .collect();
10903
10904 this.change_selections(Default::default(), window, cx, |s| {
10905 s.select(new_selections);
10906 });
10907
10908 this.request_autoscroll(Autoscroll::fit(), cx);
10909 });
10910 }
10911
10912 fn manipulate_immutable_lines<Fn>(
10913 &mut self,
10914 window: &mut Window,
10915 cx: &mut Context<Self>,
10916 mut callback: Fn,
10917 ) where
10918 Fn: FnMut(&mut Vec<&str>),
10919 {
10920 self.manipulate_lines(window, cx, |text| {
10921 let mut lines: Vec<&str> = text.split('\n').collect();
10922 let line_count_before = lines.len();
10923
10924 callback(&mut lines);
10925
10926 LineManipulationResult {
10927 new_text: lines.join("\n"),
10928 line_count_before,
10929 line_count_after: lines.len(),
10930 }
10931 });
10932 }
10933
10934 fn manipulate_mutable_lines<Fn>(
10935 &mut self,
10936 window: &mut Window,
10937 cx: &mut Context<Self>,
10938 mut callback: Fn,
10939 ) where
10940 Fn: FnMut(&mut Vec<Cow<'_, str>>),
10941 {
10942 self.manipulate_lines(window, cx, |text| {
10943 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
10944 let line_count_before = lines.len();
10945
10946 callback(&mut lines);
10947
10948 LineManipulationResult {
10949 new_text: lines.join("\n"),
10950 line_count_before,
10951 line_count_after: lines.len(),
10952 }
10953 });
10954 }
10955
10956 pub fn convert_indentation_to_spaces(
10957 &mut self,
10958 _: &ConvertIndentationToSpaces,
10959 window: &mut Window,
10960 cx: &mut Context<Self>,
10961 ) {
10962 let settings = self.buffer.read(cx).language_settings(cx);
10963 let tab_size = settings.tab_size.get() as usize;
10964
10965 self.manipulate_mutable_lines(window, cx, |lines| {
10966 // Allocates a reasonably sized scratch buffer once for the whole loop
10967 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
10968 // Avoids recomputing spaces that could be inserted many times
10969 let space_cache: Vec<Vec<char>> = (1..=tab_size)
10970 .map(|n| IndentSize::spaces(n as u32).chars().collect())
10971 .collect();
10972
10973 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
10974 let mut chars = line.as_ref().chars();
10975 let mut col = 0;
10976 let mut changed = false;
10977
10978 while let Some(ch) = chars.next() {
10979 match ch {
10980 ' ' => {
10981 reindented_line.push(' ');
10982 col += 1;
10983 }
10984 '\t' => {
10985 // \t are converted to spaces depending on the current column
10986 let spaces_len = tab_size - (col % tab_size);
10987 reindented_line.extend(&space_cache[spaces_len - 1]);
10988 col += spaces_len;
10989 changed = true;
10990 }
10991 _ => {
10992 // If we dont append before break, the character is consumed
10993 reindented_line.push(ch);
10994 break;
10995 }
10996 }
10997 }
10998
10999 if !changed {
11000 reindented_line.clear();
11001 continue;
11002 }
11003 // Append the rest of the line and replace old reference with new one
11004 reindented_line.extend(chars);
11005 *line = Cow::Owned(reindented_line.clone());
11006 reindented_line.clear();
11007 }
11008 });
11009 }
11010
11011 pub fn convert_indentation_to_tabs(
11012 &mut self,
11013 _: &ConvertIndentationToTabs,
11014 window: &mut Window,
11015 cx: &mut Context<Self>,
11016 ) {
11017 let settings = self.buffer.read(cx).language_settings(cx);
11018 let tab_size = settings.tab_size.get() as usize;
11019
11020 self.manipulate_mutable_lines(window, cx, |lines| {
11021 // Allocates a reasonably sized buffer once for the whole loop
11022 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11023 // Avoids recomputing spaces that could be inserted many times
11024 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11025 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11026 .collect();
11027
11028 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11029 let mut chars = line.chars();
11030 let mut spaces_count = 0;
11031 let mut first_non_indent_char = None;
11032 let mut changed = false;
11033
11034 while let Some(ch) = chars.next() {
11035 match ch {
11036 ' ' => {
11037 // Keep track of spaces. Append \t when we reach tab_size
11038 spaces_count += 1;
11039 changed = true;
11040 if spaces_count == tab_size {
11041 reindented_line.push('\t');
11042 spaces_count = 0;
11043 }
11044 }
11045 '\t' => {
11046 reindented_line.push('\t');
11047 spaces_count = 0;
11048 }
11049 _ => {
11050 // Dont append it yet, we might have remaining spaces
11051 first_non_indent_char = Some(ch);
11052 break;
11053 }
11054 }
11055 }
11056
11057 if !changed {
11058 reindented_line.clear();
11059 continue;
11060 }
11061 // Remaining spaces that didn't make a full tab stop
11062 if spaces_count > 0 {
11063 reindented_line.extend(&space_cache[spaces_count - 1]);
11064 }
11065 // If we consume an extra character that was not indentation, add it back
11066 if let Some(extra_char) = first_non_indent_char {
11067 reindented_line.push(extra_char);
11068 }
11069 // Append the rest of the line and replace old reference with new one
11070 reindented_line.extend(chars);
11071 *line = Cow::Owned(reindented_line.clone());
11072 reindented_line.clear();
11073 }
11074 });
11075 }
11076
11077 pub fn convert_to_upper_case(
11078 &mut self,
11079 _: &ConvertToUpperCase,
11080 window: &mut Window,
11081 cx: &mut Context<Self>,
11082 ) {
11083 self.manipulate_text(window, cx, |text| text.to_uppercase())
11084 }
11085
11086 pub fn convert_to_lower_case(
11087 &mut self,
11088 _: &ConvertToLowerCase,
11089 window: &mut Window,
11090 cx: &mut Context<Self>,
11091 ) {
11092 self.manipulate_text(window, cx, |text| text.to_lowercase())
11093 }
11094
11095 pub fn convert_to_title_case(
11096 &mut self,
11097 _: &ConvertToTitleCase,
11098 window: &mut Window,
11099 cx: &mut Context<Self>,
11100 ) {
11101 self.manipulate_text(window, cx, |text| {
11102 text.split('\n')
11103 .map(|line| line.to_case(Case::Title))
11104 .join("\n")
11105 })
11106 }
11107
11108 pub fn convert_to_snake_case(
11109 &mut self,
11110 _: &ConvertToSnakeCase,
11111 window: &mut Window,
11112 cx: &mut Context<Self>,
11113 ) {
11114 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11115 }
11116
11117 pub fn convert_to_kebab_case(
11118 &mut self,
11119 _: &ConvertToKebabCase,
11120 window: &mut Window,
11121 cx: &mut Context<Self>,
11122 ) {
11123 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11124 }
11125
11126 pub fn convert_to_upper_camel_case(
11127 &mut self,
11128 _: &ConvertToUpperCamelCase,
11129 window: &mut Window,
11130 cx: &mut Context<Self>,
11131 ) {
11132 self.manipulate_text(window, cx, |text| {
11133 text.split('\n')
11134 .map(|line| line.to_case(Case::UpperCamel))
11135 .join("\n")
11136 })
11137 }
11138
11139 pub fn convert_to_lower_camel_case(
11140 &mut self,
11141 _: &ConvertToLowerCamelCase,
11142 window: &mut Window,
11143 cx: &mut Context<Self>,
11144 ) {
11145 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11146 }
11147
11148 pub fn convert_to_opposite_case(
11149 &mut self,
11150 _: &ConvertToOppositeCase,
11151 window: &mut Window,
11152 cx: &mut Context<Self>,
11153 ) {
11154 self.manipulate_text(window, cx, |text| {
11155 text.chars()
11156 .fold(String::with_capacity(text.len()), |mut t, c| {
11157 if c.is_uppercase() {
11158 t.extend(c.to_lowercase());
11159 } else {
11160 t.extend(c.to_uppercase());
11161 }
11162 t
11163 })
11164 })
11165 }
11166
11167 pub fn convert_to_sentence_case(
11168 &mut self,
11169 _: &ConvertToSentenceCase,
11170 window: &mut Window,
11171 cx: &mut Context<Self>,
11172 ) {
11173 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11174 }
11175
11176 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11177 self.manipulate_text(window, cx, |text| {
11178 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11179 if has_upper_case_characters {
11180 text.to_lowercase()
11181 } else {
11182 text.to_uppercase()
11183 }
11184 })
11185 }
11186
11187 pub fn convert_to_rot13(
11188 &mut self,
11189 _: &ConvertToRot13,
11190 window: &mut Window,
11191 cx: &mut Context<Self>,
11192 ) {
11193 self.manipulate_text(window, cx, |text| {
11194 text.chars()
11195 .map(|c| match c {
11196 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11197 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11198 _ => c,
11199 })
11200 .collect()
11201 })
11202 }
11203
11204 pub fn convert_to_rot47(
11205 &mut self,
11206 _: &ConvertToRot47,
11207 window: &mut Window,
11208 cx: &mut Context<Self>,
11209 ) {
11210 self.manipulate_text(window, cx, |text| {
11211 text.chars()
11212 .map(|c| {
11213 let code_point = c as u32;
11214 if code_point >= 33 && code_point <= 126 {
11215 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11216 }
11217 c
11218 })
11219 .collect()
11220 })
11221 }
11222
11223 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11224 where
11225 Fn: FnMut(&str) -> String,
11226 {
11227 let buffer = self.buffer.read(cx).snapshot(cx);
11228
11229 let mut new_selections = Vec::new();
11230 let mut edits = Vec::new();
11231 let mut selection_adjustment = 0i32;
11232
11233 for selection in self.selections.all::<usize>(cx) {
11234 let selection_is_empty = selection.is_empty();
11235
11236 let (start, end) = if selection_is_empty {
11237 let (word_range, _) = buffer.surrounding_word(selection.start, false);
11238 (word_range.start, word_range.end)
11239 } else {
11240 (selection.start, selection.end)
11241 };
11242
11243 let text = buffer.text_for_range(start..end).collect::<String>();
11244 let old_length = text.len() as i32;
11245 let text = callback(&text);
11246
11247 new_selections.push(Selection {
11248 start: (start as i32 - selection_adjustment) as usize,
11249 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11250 goal: SelectionGoal::None,
11251 ..selection
11252 });
11253
11254 selection_adjustment += old_length - text.len() as i32;
11255
11256 edits.push((start..end, text));
11257 }
11258
11259 self.transact(window, cx, |this, window, cx| {
11260 this.buffer.update(cx, |buffer, cx| {
11261 buffer.edit(edits, None, cx);
11262 });
11263
11264 this.change_selections(Default::default(), window, cx, |s| {
11265 s.select(new_selections);
11266 });
11267
11268 this.request_autoscroll(Autoscroll::fit(), cx);
11269 });
11270 }
11271
11272 pub fn move_selection_on_drop(
11273 &mut self,
11274 selection: &Selection<Anchor>,
11275 target: DisplayPoint,
11276 is_cut: bool,
11277 window: &mut Window,
11278 cx: &mut Context<Self>,
11279 ) {
11280 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11281 let buffer = &display_map.buffer_snapshot;
11282 let mut edits = Vec::new();
11283 let insert_point = display_map
11284 .clip_point(target, Bias::Left)
11285 .to_point(&display_map);
11286 let text = buffer
11287 .text_for_range(selection.start..selection.end)
11288 .collect::<String>();
11289 if is_cut {
11290 edits.push(((selection.start..selection.end), String::new()));
11291 }
11292 let insert_anchor = buffer.anchor_before(insert_point);
11293 edits.push(((insert_anchor..insert_anchor), text));
11294 let last_edit_start = insert_anchor.bias_left(buffer);
11295 let last_edit_end = insert_anchor.bias_right(buffer);
11296 self.transact(window, cx, |this, window, cx| {
11297 this.buffer.update(cx, |buffer, cx| {
11298 buffer.edit(edits, None, cx);
11299 });
11300 this.change_selections(Default::default(), window, cx, |s| {
11301 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11302 });
11303 });
11304 }
11305
11306 pub fn clear_selection_drag_state(&mut self) {
11307 self.selection_drag_state = SelectionDragState::None;
11308 }
11309
11310 pub fn duplicate(
11311 &mut self,
11312 upwards: bool,
11313 whole_lines: bool,
11314 window: &mut Window,
11315 cx: &mut Context<Self>,
11316 ) {
11317 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11318
11319 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11320 let buffer = &display_map.buffer_snapshot;
11321 let selections = self.selections.all::<Point>(cx);
11322
11323 let mut edits = Vec::new();
11324 let mut selections_iter = selections.iter().peekable();
11325 while let Some(selection) = selections_iter.next() {
11326 let mut rows = selection.spanned_rows(false, &display_map);
11327 // duplicate line-wise
11328 if whole_lines || selection.start == selection.end {
11329 // Avoid duplicating the same lines twice.
11330 while let Some(next_selection) = selections_iter.peek() {
11331 let next_rows = next_selection.spanned_rows(false, &display_map);
11332 if next_rows.start < rows.end {
11333 rows.end = next_rows.end;
11334 selections_iter.next().unwrap();
11335 } else {
11336 break;
11337 }
11338 }
11339
11340 // Copy the text from the selected row region and splice it either at the start
11341 // or end of the region.
11342 let start = Point::new(rows.start.0, 0);
11343 let end = Point::new(
11344 rows.end.previous_row().0,
11345 buffer.line_len(rows.end.previous_row()),
11346 );
11347 let text = buffer
11348 .text_for_range(start..end)
11349 .chain(Some("\n"))
11350 .collect::<String>();
11351 let insert_location = if upwards {
11352 Point::new(rows.end.0, 0)
11353 } else {
11354 start
11355 };
11356 edits.push((insert_location..insert_location, text));
11357 } else {
11358 // duplicate character-wise
11359 let start = selection.start;
11360 let end = selection.end;
11361 let text = buffer.text_for_range(start..end).collect::<String>();
11362 edits.push((selection.end..selection.end, text));
11363 }
11364 }
11365
11366 self.transact(window, cx, |this, _, cx| {
11367 this.buffer.update(cx, |buffer, cx| {
11368 buffer.edit(edits, None, cx);
11369 });
11370
11371 this.request_autoscroll(Autoscroll::fit(), cx);
11372 });
11373 }
11374
11375 pub fn duplicate_line_up(
11376 &mut self,
11377 _: &DuplicateLineUp,
11378 window: &mut Window,
11379 cx: &mut Context<Self>,
11380 ) {
11381 self.duplicate(true, true, window, cx);
11382 }
11383
11384 pub fn duplicate_line_down(
11385 &mut self,
11386 _: &DuplicateLineDown,
11387 window: &mut Window,
11388 cx: &mut Context<Self>,
11389 ) {
11390 self.duplicate(false, true, window, cx);
11391 }
11392
11393 pub fn duplicate_selection(
11394 &mut self,
11395 _: &DuplicateSelection,
11396 window: &mut Window,
11397 cx: &mut Context<Self>,
11398 ) {
11399 self.duplicate(false, false, window, cx);
11400 }
11401
11402 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11403 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11404 if self.mode.is_single_line() {
11405 cx.propagate();
11406 return;
11407 }
11408
11409 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11410 let buffer = self.buffer.read(cx).snapshot(cx);
11411
11412 let mut edits = Vec::new();
11413 let mut unfold_ranges = Vec::new();
11414 let mut refold_creases = Vec::new();
11415
11416 let selections = self.selections.all::<Point>(cx);
11417 let mut selections = selections.iter().peekable();
11418 let mut contiguous_row_selections = Vec::new();
11419 let mut new_selections = Vec::new();
11420
11421 while let Some(selection) = selections.next() {
11422 // Find all the selections that span a contiguous row range
11423 let (start_row, end_row) = consume_contiguous_rows(
11424 &mut contiguous_row_selections,
11425 selection,
11426 &display_map,
11427 &mut selections,
11428 );
11429
11430 // Move the text spanned by the row range to be before the line preceding the row range
11431 if start_row.0 > 0 {
11432 let range_to_move = Point::new(
11433 start_row.previous_row().0,
11434 buffer.line_len(start_row.previous_row()),
11435 )
11436 ..Point::new(
11437 end_row.previous_row().0,
11438 buffer.line_len(end_row.previous_row()),
11439 );
11440 let insertion_point = display_map
11441 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11442 .0;
11443
11444 // Don't move lines across excerpts
11445 if buffer
11446 .excerpt_containing(insertion_point..range_to_move.end)
11447 .is_some()
11448 {
11449 let text = buffer
11450 .text_for_range(range_to_move.clone())
11451 .flat_map(|s| s.chars())
11452 .skip(1)
11453 .chain(['\n'])
11454 .collect::<String>();
11455
11456 edits.push((
11457 buffer.anchor_after(range_to_move.start)
11458 ..buffer.anchor_before(range_to_move.end),
11459 String::new(),
11460 ));
11461 let insertion_anchor = buffer.anchor_after(insertion_point);
11462 edits.push((insertion_anchor..insertion_anchor, text));
11463
11464 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11465
11466 // Move selections up
11467 new_selections.extend(contiguous_row_selections.drain(..).map(
11468 |mut selection| {
11469 selection.start.row -= row_delta;
11470 selection.end.row -= row_delta;
11471 selection
11472 },
11473 ));
11474
11475 // Move folds up
11476 unfold_ranges.push(range_to_move.clone());
11477 for fold in display_map.folds_in_range(
11478 buffer.anchor_before(range_to_move.start)
11479 ..buffer.anchor_after(range_to_move.end),
11480 ) {
11481 let mut start = fold.range.start.to_point(&buffer);
11482 let mut end = fold.range.end.to_point(&buffer);
11483 start.row -= row_delta;
11484 end.row -= row_delta;
11485 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11486 }
11487 }
11488 }
11489
11490 // If we didn't move line(s), preserve the existing selections
11491 new_selections.append(&mut contiguous_row_selections);
11492 }
11493
11494 self.transact(window, cx, |this, window, cx| {
11495 this.unfold_ranges(&unfold_ranges, true, true, cx);
11496 this.buffer.update(cx, |buffer, cx| {
11497 for (range, text) in edits {
11498 buffer.edit([(range, text)], None, cx);
11499 }
11500 });
11501 this.fold_creases(refold_creases, true, window, cx);
11502 this.change_selections(Default::default(), window, cx, |s| {
11503 s.select(new_selections);
11504 })
11505 });
11506 }
11507
11508 pub fn move_line_down(
11509 &mut self,
11510 _: &MoveLineDown,
11511 window: &mut Window,
11512 cx: &mut Context<Self>,
11513 ) {
11514 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11515 if self.mode.is_single_line() {
11516 cx.propagate();
11517 return;
11518 }
11519
11520 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11521 let buffer = self.buffer.read(cx).snapshot(cx);
11522
11523 let mut edits = Vec::new();
11524 let mut unfold_ranges = Vec::new();
11525 let mut refold_creases = Vec::new();
11526
11527 let selections = self.selections.all::<Point>(cx);
11528 let mut selections = selections.iter().peekable();
11529 let mut contiguous_row_selections = Vec::new();
11530 let mut new_selections = Vec::new();
11531
11532 while let Some(selection) = selections.next() {
11533 // Find all the selections that span a contiguous row range
11534 let (start_row, end_row) = consume_contiguous_rows(
11535 &mut contiguous_row_selections,
11536 selection,
11537 &display_map,
11538 &mut selections,
11539 );
11540
11541 // Move the text spanned by the row range to be after the last line of the row range
11542 if end_row.0 <= buffer.max_point().row {
11543 let range_to_move =
11544 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11545 let insertion_point = display_map
11546 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11547 .0;
11548
11549 // Don't move lines across excerpt boundaries
11550 if buffer
11551 .excerpt_containing(range_to_move.start..insertion_point)
11552 .is_some()
11553 {
11554 let mut text = String::from("\n");
11555 text.extend(buffer.text_for_range(range_to_move.clone()));
11556 text.pop(); // Drop trailing newline
11557 edits.push((
11558 buffer.anchor_after(range_to_move.start)
11559 ..buffer.anchor_before(range_to_move.end),
11560 String::new(),
11561 ));
11562 let insertion_anchor = buffer.anchor_after(insertion_point);
11563 edits.push((insertion_anchor..insertion_anchor, text));
11564
11565 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11566
11567 // Move selections down
11568 new_selections.extend(contiguous_row_selections.drain(..).map(
11569 |mut selection| {
11570 selection.start.row += row_delta;
11571 selection.end.row += row_delta;
11572 selection
11573 },
11574 ));
11575
11576 // Move folds down
11577 unfold_ranges.push(range_to_move.clone());
11578 for fold in display_map.folds_in_range(
11579 buffer.anchor_before(range_to_move.start)
11580 ..buffer.anchor_after(range_to_move.end),
11581 ) {
11582 let mut start = fold.range.start.to_point(&buffer);
11583 let mut end = fold.range.end.to_point(&buffer);
11584 start.row += row_delta;
11585 end.row += row_delta;
11586 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11587 }
11588 }
11589 }
11590
11591 // If we didn't move line(s), preserve the existing selections
11592 new_selections.append(&mut contiguous_row_selections);
11593 }
11594
11595 self.transact(window, cx, |this, window, cx| {
11596 this.unfold_ranges(&unfold_ranges, true, true, cx);
11597 this.buffer.update(cx, |buffer, cx| {
11598 for (range, text) in edits {
11599 buffer.edit([(range, text)], None, cx);
11600 }
11601 });
11602 this.fold_creases(refold_creases, true, window, cx);
11603 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
11604 });
11605 }
11606
11607 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11608 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11609 let text_layout_details = &self.text_layout_details(window);
11610 self.transact(window, cx, |this, window, cx| {
11611 let edits = this.change_selections(Default::default(), window, cx, |s| {
11612 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11613 s.move_with(|display_map, selection| {
11614 if !selection.is_empty() {
11615 return;
11616 }
11617
11618 let mut head = selection.head();
11619 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11620 if head.column() == display_map.line_len(head.row()) {
11621 transpose_offset = display_map
11622 .buffer_snapshot
11623 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11624 }
11625
11626 if transpose_offset == 0 {
11627 return;
11628 }
11629
11630 *head.column_mut() += 1;
11631 head = display_map.clip_point(head, Bias::Right);
11632 let goal = SelectionGoal::HorizontalPosition(
11633 display_map
11634 .x_for_display_point(head, text_layout_details)
11635 .into(),
11636 );
11637 selection.collapse_to(head, goal);
11638
11639 let transpose_start = display_map
11640 .buffer_snapshot
11641 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11642 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
11643 let transpose_end = display_map
11644 .buffer_snapshot
11645 .clip_offset(transpose_offset + 1, Bias::Right);
11646 if let Some(ch) =
11647 display_map.buffer_snapshot.chars_at(transpose_start).next()
11648 {
11649 edits.push((transpose_start..transpose_offset, String::new()));
11650 edits.push((transpose_end..transpose_end, ch.to_string()));
11651 }
11652 }
11653 });
11654 edits
11655 });
11656 this.buffer
11657 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11658 let selections = this.selections.all::<usize>(cx);
11659 this.change_selections(Default::default(), window, cx, |s| {
11660 s.select(selections);
11661 });
11662 });
11663 }
11664
11665 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
11666 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11667 if self.mode.is_single_line() {
11668 cx.propagate();
11669 return;
11670 }
11671
11672 self.rewrap_impl(RewrapOptions::default(), cx)
11673 }
11674
11675 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
11676 let buffer = self.buffer.read(cx).snapshot(cx);
11677 let selections = self.selections.all::<Point>(cx);
11678
11679 // Split selections to respect paragraph, indent, and comment prefix boundaries.
11680 let wrap_ranges = selections.into_iter().flat_map(|selection| {
11681 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
11682 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11683 .peekable();
11684
11685 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
11686 row
11687 } else {
11688 return Vec::new();
11689 };
11690
11691 let language_settings = buffer.language_settings_at(selection.head(), cx);
11692 let language_scope = buffer.language_scope_at(selection.head());
11693
11694 let indent_and_prefix_for_row =
11695 |row: u32| -> (IndentSize, Option<String>, Option<String>) {
11696 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
11697 let (comment_prefix, rewrap_prefix) =
11698 if let Some(language_scope) = &language_scope {
11699 let indent_end = Point::new(row, indent.len);
11700 let comment_prefix = language_scope
11701 .line_comment_prefixes()
11702 .iter()
11703 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11704 .map(|prefix| prefix.to_string());
11705 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11706 let line_text_after_indent = buffer
11707 .text_for_range(indent_end..line_end)
11708 .collect::<String>();
11709 let rewrap_prefix = language_scope
11710 .rewrap_prefixes()
11711 .iter()
11712 .find_map(|prefix_regex| {
11713 prefix_regex.find(&line_text_after_indent).map(|mat| {
11714 if mat.start() == 0 {
11715 Some(mat.as_str().to_string())
11716 } else {
11717 None
11718 }
11719 })
11720 })
11721 .flatten();
11722 (comment_prefix, rewrap_prefix)
11723 } else {
11724 (None, None)
11725 };
11726 (indent, comment_prefix, rewrap_prefix)
11727 };
11728
11729 let mut ranges = Vec::new();
11730 let from_empty_selection = selection.is_empty();
11731
11732 let mut current_range_start = first_row;
11733 let mut prev_row = first_row;
11734 let (
11735 mut current_range_indent,
11736 mut current_range_comment_prefix,
11737 mut current_range_rewrap_prefix,
11738 ) = indent_and_prefix_for_row(first_row);
11739
11740 for row in non_blank_rows_iter.skip(1) {
11741 let has_paragraph_break = row > prev_row + 1;
11742
11743 let (row_indent, row_comment_prefix, row_rewrap_prefix) =
11744 indent_and_prefix_for_row(row);
11745
11746 let has_indent_change = row_indent != current_range_indent;
11747 let has_comment_change = row_comment_prefix != current_range_comment_prefix;
11748
11749 let has_boundary_change = has_comment_change
11750 || row_rewrap_prefix.is_some()
11751 || (has_indent_change && current_range_comment_prefix.is_some());
11752
11753 if has_paragraph_break || has_boundary_change {
11754 ranges.push((
11755 language_settings.clone(),
11756 Point::new(current_range_start, 0)
11757 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
11758 current_range_indent,
11759 current_range_comment_prefix.clone(),
11760 current_range_rewrap_prefix.clone(),
11761 from_empty_selection,
11762 ));
11763 current_range_start = row;
11764 current_range_indent = row_indent;
11765 current_range_comment_prefix = row_comment_prefix;
11766 current_range_rewrap_prefix = row_rewrap_prefix;
11767 }
11768 prev_row = row;
11769 }
11770
11771 ranges.push((
11772 language_settings.clone(),
11773 Point::new(current_range_start, 0)
11774 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
11775 current_range_indent,
11776 current_range_comment_prefix,
11777 current_range_rewrap_prefix,
11778 from_empty_selection,
11779 ));
11780
11781 ranges
11782 });
11783
11784 let mut edits = Vec::new();
11785 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
11786
11787 for (
11788 language_settings,
11789 wrap_range,
11790 indent_size,
11791 comment_prefix,
11792 rewrap_prefix,
11793 from_empty_selection,
11794 ) in wrap_ranges
11795 {
11796 let mut start_row = wrap_range.start.row;
11797 let mut end_row = wrap_range.end.row;
11798
11799 // Skip selections that overlap with a range that has already been rewrapped.
11800 let selection_range = start_row..end_row;
11801 if rewrapped_row_ranges
11802 .iter()
11803 .any(|range| range.overlaps(&selection_range))
11804 {
11805 continue;
11806 }
11807
11808 let tab_size = language_settings.tab_size;
11809
11810 let indent_prefix = indent_size.chars().collect::<String>();
11811 let mut line_prefix = indent_prefix.clone();
11812 let mut inside_comment = false;
11813 if let Some(prefix) = &comment_prefix {
11814 line_prefix.push_str(prefix);
11815 inside_comment = true;
11816 }
11817 if let Some(prefix) = &rewrap_prefix {
11818 line_prefix.push_str(prefix);
11819 }
11820
11821 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
11822 RewrapBehavior::InComments => inside_comment,
11823 RewrapBehavior::InSelections => !wrap_range.is_empty(),
11824 RewrapBehavior::Anywhere => true,
11825 };
11826
11827 let should_rewrap = options.override_language_settings
11828 || allow_rewrap_based_on_language
11829 || self.hard_wrap.is_some();
11830 if !should_rewrap {
11831 continue;
11832 }
11833
11834 if from_empty_selection {
11835 'expand_upwards: while start_row > 0 {
11836 let prev_row = start_row - 1;
11837 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
11838 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
11839 && !buffer.is_line_blank(MultiBufferRow(prev_row))
11840 {
11841 start_row = prev_row;
11842 } else {
11843 break 'expand_upwards;
11844 }
11845 }
11846
11847 'expand_downwards: while end_row < buffer.max_point().row {
11848 let next_row = end_row + 1;
11849 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
11850 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
11851 && !buffer.is_line_blank(MultiBufferRow(next_row))
11852 {
11853 end_row = next_row;
11854 } else {
11855 break 'expand_downwards;
11856 }
11857 }
11858 }
11859
11860 let start = Point::new(start_row, 0);
11861 let start_offset = start.to_offset(&buffer);
11862 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
11863 let selection_text = buffer.text_for_range(start..end).collect::<String>();
11864 let Some(lines_without_prefixes) = selection_text
11865 .lines()
11866 .enumerate()
11867 .map(|(ix, line)| {
11868 let line_trimmed = line.trim_start();
11869 if rewrap_prefix.is_some() && ix > 0 {
11870 Ok(line_trimmed)
11871 } else {
11872 line_trimmed
11873 .strip_prefix(&line_prefix.trim_start())
11874 .with_context(|| {
11875 format!("line did not start with prefix {line_prefix:?}: {line:?}")
11876 })
11877 }
11878 })
11879 .collect::<Result<Vec<_>, _>>()
11880 .log_err()
11881 else {
11882 continue;
11883 };
11884
11885 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
11886 buffer
11887 .language_settings_at(Point::new(start_row, 0), cx)
11888 .preferred_line_length as usize
11889 });
11890
11891 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
11892 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
11893 } else {
11894 line_prefix.clone()
11895 };
11896
11897 let wrapped_text = wrap_with_prefix(
11898 line_prefix,
11899 subsequent_lines_prefix,
11900 lines_without_prefixes.join("\n"),
11901 wrap_column,
11902 tab_size,
11903 options.preserve_existing_whitespace,
11904 );
11905
11906 // TODO: should always use char-based diff while still supporting cursor behavior that
11907 // matches vim.
11908 let mut diff_options = DiffOptions::default();
11909 if options.override_language_settings {
11910 diff_options.max_word_diff_len = 0;
11911 diff_options.max_word_diff_line_count = 0;
11912 } else {
11913 diff_options.max_word_diff_len = usize::MAX;
11914 diff_options.max_word_diff_line_count = usize::MAX;
11915 }
11916
11917 for (old_range, new_text) in
11918 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
11919 {
11920 let edit_start = buffer.anchor_after(start_offset + old_range.start);
11921 let edit_end = buffer.anchor_after(start_offset + old_range.end);
11922 edits.push((edit_start..edit_end, new_text));
11923 }
11924
11925 rewrapped_row_ranges.push(start_row..=end_row);
11926 }
11927
11928 self.buffer
11929 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11930 }
11931
11932 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
11933 let mut text = String::new();
11934 let buffer = self.buffer.read(cx).snapshot(cx);
11935 let mut selections = self.selections.all::<Point>(cx);
11936 let mut clipboard_selections = Vec::with_capacity(selections.len());
11937 {
11938 let max_point = buffer.max_point();
11939 let mut is_first = true;
11940 for selection in &mut selections {
11941 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11942 if is_entire_line {
11943 selection.start = Point::new(selection.start.row, 0);
11944 if !selection.is_empty() && selection.end.column == 0 {
11945 selection.end = cmp::min(max_point, selection.end);
11946 } else {
11947 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
11948 }
11949 selection.goal = SelectionGoal::None;
11950 }
11951 if is_first {
11952 is_first = false;
11953 } else {
11954 text += "\n";
11955 }
11956 let mut len = 0;
11957 for chunk in buffer.text_for_range(selection.start..selection.end) {
11958 text.push_str(chunk);
11959 len += chunk.len();
11960 }
11961 clipboard_selections.push(ClipboardSelection {
11962 len,
11963 is_entire_line,
11964 first_line_indent: buffer
11965 .indent_size_for_line(MultiBufferRow(selection.start.row))
11966 .len,
11967 });
11968 }
11969 }
11970
11971 self.transact(window, cx, |this, window, cx| {
11972 this.change_selections(Default::default(), window, cx, |s| {
11973 s.select(selections);
11974 });
11975 this.insert("", window, cx);
11976 });
11977 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
11978 }
11979
11980 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
11981 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11982 let item = self.cut_common(window, cx);
11983 cx.write_to_clipboard(item);
11984 }
11985
11986 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
11987 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11988 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11989 s.move_with(|snapshot, sel| {
11990 if sel.is_empty() {
11991 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
11992 }
11993 });
11994 });
11995 let item = self.cut_common(window, cx);
11996 cx.set_global(KillRing(item))
11997 }
11998
11999 pub fn kill_ring_yank(
12000 &mut self,
12001 _: &KillRingYank,
12002 window: &mut Window,
12003 cx: &mut Context<Self>,
12004 ) {
12005 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12006 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12007 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12008 (kill_ring.text().to_string(), kill_ring.metadata_json())
12009 } else {
12010 return;
12011 }
12012 } else {
12013 return;
12014 };
12015 self.do_paste(&text, metadata, false, window, cx);
12016 }
12017
12018 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12019 self.do_copy(true, cx);
12020 }
12021
12022 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12023 self.do_copy(false, cx);
12024 }
12025
12026 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12027 let selections = self.selections.all::<Point>(cx);
12028 let buffer = self.buffer.read(cx).read(cx);
12029 let mut text = String::new();
12030
12031 let mut clipboard_selections = Vec::with_capacity(selections.len());
12032 {
12033 let max_point = buffer.max_point();
12034 let mut is_first = true;
12035 for selection in &selections {
12036 let mut start = selection.start;
12037 let mut end = selection.end;
12038 let is_entire_line = selection.is_empty() || self.selections.line_mode;
12039 if is_entire_line {
12040 start = Point::new(start.row, 0);
12041 end = cmp::min(max_point, Point::new(end.row + 1, 0));
12042 }
12043
12044 let mut trimmed_selections = Vec::new();
12045 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12046 let row = MultiBufferRow(start.row);
12047 let first_indent = buffer.indent_size_for_line(row);
12048 if first_indent.len == 0 || start.column > first_indent.len {
12049 trimmed_selections.push(start..end);
12050 } else {
12051 trimmed_selections.push(
12052 Point::new(row.0, first_indent.len)
12053 ..Point::new(row.0, buffer.line_len(row)),
12054 );
12055 for row in start.row + 1..=end.row {
12056 let mut line_len = buffer.line_len(MultiBufferRow(row));
12057 if row == end.row {
12058 line_len = end.column;
12059 }
12060 if line_len == 0 {
12061 trimmed_selections
12062 .push(Point::new(row, 0)..Point::new(row, line_len));
12063 continue;
12064 }
12065 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12066 if row_indent_size.len >= first_indent.len {
12067 trimmed_selections.push(
12068 Point::new(row, first_indent.len)..Point::new(row, line_len),
12069 );
12070 } else {
12071 trimmed_selections.clear();
12072 trimmed_selections.push(start..end);
12073 break;
12074 }
12075 }
12076 }
12077 } else {
12078 trimmed_selections.push(start..end);
12079 }
12080
12081 for trimmed_range in trimmed_selections {
12082 if is_first {
12083 is_first = false;
12084 } else {
12085 text += "\n";
12086 }
12087 let mut len = 0;
12088 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12089 text.push_str(chunk);
12090 len += chunk.len();
12091 }
12092 clipboard_selections.push(ClipboardSelection {
12093 len,
12094 is_entire_line,
12095 first_line_indent: buffer
12096 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12097 .len,
12098 });
12099 }
12100 }
12101 }
12102
12103 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12104 text,
12105 clipboard_selections,
12106 ));
12107 }
12108
12109 pub fn do_paste(
12110 &mut self,
12111 text: &String,
12112 clipboard_selections: Option<Vec<ClipboardSelection>>,
12113 handle_entire_lines: bool,
12114 window: &mut Window,
12115 cx: &mut Context<Self>,
12116 ) {
12117 if self.read_only(cx) {
12118 return;
12119 }
12120
12121 let clipboard_text = Cow::Borrowed(text);
12122
12123 self.transact(window, cx, |this, window, cx| {
12124 if let Some(mut clipboard_selections) = clipboard_selections {
12125 let old_selections = this.selections.all::<usize>(cx);
12126 let all_selections_were_entire_line =
12127 clipboard_selections.iter().all(|s| s.is_entire_line);
12128 let first_selection_indent_column =
12129 clipboard_selections.first().map(|s| s.first_line_indent);
12130 if clipboard_selections.len() != old_selections.len() {
12131 clipboard_selections.drain(..);
12132 }
12133 let cursor_offset = this.selections.last::<usize>(cx).head();
12134 let mut auto_indent_on_paste = true;
12135
12136 this.buffer.update(cx, |buffer, cx| {
12137 let snapshot = buffer.read(cx);
12138 auto_indent_on_paste = snapshot
12139 .language_settings_at(cursor_offset, cx)
12140 .auto_indent_on_paste;
12141
12142 let mut start_offset = 0;
12143 let mut edits = Vec::new();
12144 let mut original_indent_columns = Vec::new();
12145 for (ix, selection) in old_selections.iter().enumerate() {
12146 let to_insert;
12147 let entire_line;
12148 let original_indent_column;
12149 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12150 let end_offset = start_offset + clipboard_selection.len;
12151 to_insert = &clipboard_text[start_offset..end_offset];
12152 entire_line = clipboard_selection.is_entire_line;
12153 start_offset = end_offset + 1;
12154 original_indent_column = Some(clipboard_selection.first_line_indent);
12155 } else {
12156 to_insert = clipboard_text.as_str();
12157 entire_line = all_selections_were_entire_line;
12158 original_indent_column = first_selection_indent_column
12159 }
12160
12161 // If the corresponding selection was empty when this slice of the
12162 // clipboard text was written, then the entire line containing the
12163 // selection was copied. If this selection is also currently empty,
12164 // then paste the line before the current line of the buffer.
12165 let range = if selection.is_empty() && handle_entire_lines && entire_line {
12166 let column = selection.start.to_point(&snapshot).column as usize;
12167 let line_start = selection.start - column;
12168 line_start..line_start
12169 } else {
12170 selection.range()
12171 };
12172
12173 edits.push((range, to_insert));
12174 original_indent_columns.push(original_indent_column);
12175 }
12176 drop(snapshot);
12177
12178 buffer.edit(
12179 edits,
12180 if auto_indent_on_paste {
12181 Some(AutoindentMode::Block {
12182 original_indent_columns,
12183 })
12184 } else {
12185 None
12186 },
12187 cx,
12188 );
12189 });
12190
12191 let selections = this.selections.all::<usize>(cx);
12192 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12193 } else {
12194 this.insert(&clipboard_text, window, cx);
12195 }
12196 });
12197 }
12198
12199 pub fn diff_clipboard_with_selection(
12200 &mut self,
12201 _: &DiffClipboardWithSelection,
12202 window: &mut Window,
12203 cx: &mut Context<Self>,
12204 ) {
12205 let selections = self.selections.all::<usize>(cx);
12206
12207 if selections.is_empty() {
12208 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12209 return;
12210 };
12211
12212 let clipboard_text = match cx.read_from_clipboard() {
12213 Some(item) => match item.entries().first() {
12214 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
12215 _ => None,
12216 },
12217 None => None,
12218 };
12219
12220 let Some(clipboard_text) = clipboard_text else {
12221 log::warn!("Clipboard doesn't contain text.");
12222 return;
12223 };
12224
12225 window.dispatch_action(
12226 Box::new(DiffClipboardWithSelectionData {
12227 clipboard_text,
12228 editor: cx.entity(),
12229 }),
12230 cx,
12231 );
12232 }
12233
12234 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12235 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12236 if let Some(item) = cx.read_from_clipboard() {
12237 let entries = item.entries();
12238
12239 match entries.first() {
12240 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12241 // of all the pasted entries.
12242 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12243 .do_paste(
12244 clipboard_string.text(),
12245 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12246 true,
12247 window,
12248 cx,
12249 ),
12250 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12251 }
12252 }
12253 }
12254
12255 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12256 if self.read_only(cx) {
12257 return;
12258 }
12259
12260 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12261
12262 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12263 if let Some((selections, _)) =
12264 self.selection_history.transaction(transaction_id).cloned()
12265 {
12266 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12267 s.select_anchors(selections.to_vec());
12268 });
12269 } else {
12270 log::error!(
12271 "No entry in selection_history found for undo. \
12272 This may correspond to a bug where undo does not update the selection. \
12273 If this is occurring, please add details to \
12274 https://github.com/zed-industries/zed/issues/22692"
12275 );
12276 }
12277 self.request_autoscroll(Autoscroll::fit(), cx);
12278 self.unmark_text(window, cx);
12279 self.refresh_edit_prediction(true, false, window, cx);
12280 cx.emit(EditorEvent::Edited { transaction_id });
12281 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12282 }
12283 }
12284
12285 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12286 if self.read_only(cx) {
12287 return;
12288 }
12289
12290 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12291
12292 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12293 if let Some((_, Some(selections))) =
12294 self.selection_history.transaction(transaction_id).cloned()
12295 {
12296 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12297 s.select_anchors(selections.to_vec());
12298 });
12299 } else {
12300 log::error!(
12301 "No entry in selection_history found for redo. \
12302 This may correspond to a bug where undo does not update the selection. \
12303 If this is occurring, please add details to \
12304 https://github.com/zed-industries/zed/issues/22692"
12305 );
12306 }
12307 self.request_autoscroll(Autoscroll::fit(), cx);
12308 self.unmark_text(window, cx);
12309 self.refresh_edit_prediction(true, false, window, cx);
12310 cx.emit(EditorEvent::Edited { transaction_id });
12311 }
12312 }
12313
12314 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12315 self.buffer
12316 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12317 }
12318
12319 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12320 self.buffer
12321 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12322 }
12323
12324 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
12325 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12326 self.change_selections(Default::default(), window, cx, |s| {
12327 s.move_with(|map, selection| {
12328 let cursor = if selection.is_empty() {
12329 movement::left(map, selection.start)
12330 } else {
12331 selection.start
12332 };
12333 selection.collapse_to(cursor, SelectionGoal::None);
12334 });
12335 })
12336 }
12337
12338 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12339 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12340 self.change_selections(Default::default(), window, cx, |s| {
12341 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12342 })
12343 }
12344
12345 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
12346 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12347 self.change_selections(Default::default(), window, cx, |s| {
12348 s.move_with(|map, selection| {
12349 let cursor = if selection.is_empty() {
12350 movement::right(map, selection.end)
12351 } else {
12352 selection.end
12353 };
12354 selection.collapse_to(cursor, SelectionGoal::None)
12355 });
12356 })
12357 }
12358
12359 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12360 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12361 self.change_selections(Default::default(), window, cx, |s| {
12362 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
12363 })
12364 }
12365
12366 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
12367 if self.take_rename(true, window, cx).is_some() {
12368 return;
12369 }
12370
12371 if self.mode.is_single_line() {
12372 cx.propagate();
12373 return;
12374 }
12375
12376 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12377
12378 let text_layout_details = &self.text_layout_details(window);
12379 let selection_count = self.selections.count();
12380 let first_selection = self.selections.first_anchor();
12381
12382 self.change_selections(Default::default(), window, cx, |s| {
12383 s.move_with(|map, selection| {
12384 if !selection.is_empty() {
12385 selection.goal = SelectionGoal::None;
12386 }
12387 let (cursor, goal) = movement::up(
12388 map,
12389 selection.start,
12390 selection.goal,
12391 false,
12392 text_layout_details,
12393 );
12394 selection.collapse_to(cursor, goal);
12395 });
12396 });
12397
12398 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12399 {
12400 cx.propagate();
12401 }
12402 }
12403
12404 pub fn move_up_by_lines(
12405 &mut self,
12406 action: &MoveUpByLines,
12407 window: &mut Window,
12408 cx: &mut Context<Self>,
12409 ) {
12410 if self.take_rename(true, window, cx).is_some() {
12411 return;
12412 }
12413
12414 if self.mode.is_single_line() {
12415 cx.propagate();
12416 return;
12417 }
12418
12419 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12420
12421 let text_layout_details = &self.text_layout_details(window);
12422
12423 self.change_selections(Default::default(), window, cx, |s| {
12424 s.move_with(|map, selection| {
12425 if !selection.is_empty() {
12426 selection.goal = SelectionGoal::None;
12427 }
12428 let (cursor, goal) = movement::up_by_rows(
12429 map,
12430 selection.start,
12431 action.lines,
12432 selection.goal,
12433 false,
12434 text_layout_details,
12435 );
12436 selection.collapse_to(cursor, goal);
12437 });
12438 })
12439 }
12440
12441 pub fn move_down_by_lines(
12442 &mut self,
12443 action: &MoveDownByLines,
12444 window: &mut Window,
12445 cx: &mut Context<Self>,
12446 ) {
12447 if self.take_rename(true, window, cx).is_some() {
12448 return;
12449 }
12450
12451 if self.mode.is_single_line() {
12452 cx.propagate();
12453 return;
12454 }
12455
12456 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12457
12458 let text_layout_details = &self.text_layout_details(window);
12459
12460 self.change_selections(Default::default(), window, cx, |s| {
12461 s.move_with(|map, selection| {
12462 if !selection.is_empty() {
12463 selection.goal = SelectionGoal::None;
12464 }
12465 let (cursor, goal) = movement::down_by_rows(
12466 map,
12467 selection.start,
12468 action.lines,
12469 selection.goal,
12470 false,
12471 text_layout_details,
12472 );
12473 selection.collapse_to(cursor, goal);
12474 });
12475 })
12476 }
12477
12478 pub fn select_down_by_lines(
12479 &mut self,
12480 action: &SelectDownByLines,
12481 window: &mut Window,
12482 cx: &mut Context<Self>,
12483 ) {
12484 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12485 let text_layout_details = &self.text_layout_details(window);
12486 self.change_selections(Default::default(), window, cx, |s| {
12487 s.move_heads_with(|map, head, goal| {
12488 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
12489 })
12490 })
12491 }
12492
12493 pub fn select_up_by_lines(
12494 &mut self,
12495 action: &SelectUpByLines,
12496 window: &mut Window,
12497 cx: &mut Context<Self>,
12498 ) {
12499 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12500 let text_layout_details = &self.text_layout_details(window);
12501 self.change_selections(Default::default(), window, cx, |s| {
12502 s.move_heads_with(|map, head, goal| {
12503 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
12504 })
12505 })
12506 }
12507
12508 pub fn select_page_up(
12509 &mut self,
12510 _: &SelectPageUp,
12511 window: &mut Window,
12512 cx: &mut Context<Self>,
12513 ) {
12514 let Some(row_count) = self.visible_row_count() else {
12515 return;
12516 };
12517
12518 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12519
12520 let text_layout_details = &self.text_layout_details(window);
12521
12522 self.change_selections(Default::default(), window, cx, |s| {
12523 s.move_heads_with(|map, head, goal| {
12524 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
12525 })
12526 })
12527 }
12528
12529 pub fn move_page_up(
12530 &mut self,
12531 action: &MovePageUp,
12532 window: &mut Window,
12533 cx: &mut Context<Self>,
12534 ) {
12535 if self.take_rename(true, window, cx).is_some() {
12536 return;
12537 }
12538
12539 if self
12540 .context_menu
12541 .borrow_mut()
12542 .as_mut()
12543 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
12544 .unwrap_or(false)
12545 {
12546 return;
12547 }
12548
12549 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12550 cx.propagate();
12551 return;
12552 }
12553
12554 let Some(row_count) = self.visible_row_count() else {
12555 return;
12556 };
12557
12558 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12559
12560 let effects = if action.center_cursor {
12561 SelectionEffects::scroll(Autoscroll::center())
12562 } else {
12563 SelectionEffects::default()
12564 };
12565
12566 let text_layout_details = &self.text_layout_details(window);
12567
12568 self.change_selections(effects, window, cx, |s| {
12569 s.move_with(|map, selection| {
12570 if !selection.is_empty() {
12571 selection.goal = SelectionGoal::None;
12572 }
12573 let (cursor, goal) = movement::up_by_rows(
12574 map,
12575 selection.end,
12576 row_count,
12577 selection.goal,
12578 false,
12579 text_layout_details,
12580 );
12581 selection.collapse_to(cursor, goal);
12582 });
12583 });
12584 }
12585
12586 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
12587 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12588 let text_layout_details = &self.text_layout_details(window);
12589 self.change_selections(Default::default(), window, cx, |s| {
12590 s.move_heads_with(|map, head, goal| {
12591 movement::up(map, head, goal, false, text_layout_details)
12592 })
12593 })
12594 }
12595
12596 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
12597 self.take_rename(true, window, cx);
12598
12599 if self.mode.is_single_line() {
12600 cx.propagate();
12601 return;
12602 }
12603
12604 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12605
12606 let text_layout_details = &self.text_layout_details(window);
12607 let selection_count = self.selections.count();
12608 let first_selection = self.selections.first_anchor();
12609
12610 self.change_selections(Default::default(), window, cx, |s| {
12611 s.move_with(|map, selection| {
12612 if !selection.is_empty() {
12613 selection.goal = SelectionGoal::None;
12614 }
12615 let (cursor, goal) = movement::down(
12616 map,
12617 selection.end,
12618 selection.goal,
12619 false,
12620 text_layout_details,
12621 );
12622 selection.collapse_to(cursor, goal);
12623 });
12624 });
12625
12626 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12627 {
12628 cx.propagate();
12629 }
12630 }
12631
12632 pub fn select_page_down(
12633 &mut self,
12634 _: &SelectPageDown,
12635 window: &mut Window,
12636 cx: &mut Context<Self>,
12637 ) {
12638 let Some(row_count) = self.visible_row_count() else {
12639 return;
12640 };
12641
12642 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12643
12644 let text_layout_details = &self.text_layout_details(window);
12645
12646 self.change_selections(Default::default(), window, cx, |s| {
12647 s.move_heads_with(|map, head, goal| {
12648 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
12649 })
12650 })
12651 }
12652
12653 pub fn move_page_down(
12654 &mut self,
12655 action: &MovePageDown,
12656 window: &mut Window,
12657 cx: &mut Context<Self>,
12658 ) {
12659 if self.take_rename(true, window, cx).is_some() {
12660 return;
12661 }
12662
12663 if self
12664 .context_menu
12665 .borrow_mut()
12666 .as_mut()
12667 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
12668 .unwrap_or(false)
12669 {
12670 return;
12671 }
12672
12673 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12674 cx.propagate();
12675 return;
12676 }
12677
12678 let Some(row_count) = self.visible_row_count() else {
12679 return;
12680 };
12681
12682 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12683
12684 let effects = if action.center_cursor {
12685 SelectionEffects::scroll(Autoscroll::center())
12686 } else {
12687 SelectionEffects::default()
12688 };
12689
12690 let text_layout_details = &self.text_layout_details(window);
12691 self.change_selections(effects, window, cx, |s| {
12692 s.move_with(|map, selection| {
12693 if !selection.is_empty() {
12694 selection.goal = SelectionGoal::None;
12695 }
12696 let (cursor, goal) = movement::down_by_rows(
12697 map,
12698 selection.end,
12699 row_count,
12700 selection.goal,
12701 false,
12702 text_layout_details,
12703 );
12704 selection.collapse_to(cursor, goal);
12705 });
12706 });
12707 }
12708
12709 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
12710 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12711 let text_layout_details = &self.text_layout_details(window);
12712 self.change_selections(Default::default(), window, cx, |s| {
12713 s.move_heads_with(|map, head, goal| {
12714 movement::down(map, head, goal, false, text_layout_details)
12715 })
12716 });
12717 }
12718
12719 pub fn context_menu_first(
12720 &mut self,
12721 _: &ContextMenuFirst,
12722 window: &mut Window,
12723 cx: &mut Context<Self>,
12724 ) {
12725 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12726 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
12727 }
12728 }
12729
12730 pub fn context_menu_prev(
12731 &mut self,
12732 _: &ContextMenuPrevious,
12733 window: &mut Window,
12734 cx: &mut Context<Self>,
12735 ) {
12736 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12737 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
12738 }
12739 }
12740
12741 pub fn context_menu_next(
12742 &mut self,
12743 _: &ContextMenuNext,
12744 window: &mut Window,
12745 cx: &mut Context<Self>,
12746 ) {
12747 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12748 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
12749 }
12750 }
12751
12752 pub fn context_menu_last(
12753 &mut self,
12754 _: &ContextMenuLast,
12755 window: &mut Window,
12756 cx: &mut Context<Self>,
12757 ) {
12758 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12759 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
12760 }
12761 }
12762
12763 pub fn signature_help_prev(
12764 &mut self,
12765 _: &SignatureHelpPrevious,
12766 _: &mut Window,
12767 cx: &mut Context<Self>,
12768 ) {
12769 if let Some(popover) = self.signature_help_state.popover_mut() {
12770 if popover.current_signature == 0 {
12771 popover.current_signature = popover.signatures.len() - 1;
12772 } else {
12773 popover.current_signature -= 1;
12774 }
12775 cx.notify();
12776 }
12777 }
12778
12779 pub fn signature_help_next(
12780 &mut self,
12781 _: &SignatureHelpNext,
12782 _: &mut Window,
12783 cx: &mut Context<Self>,
12784 ) {
12785 if let Some(popover) = self.signature_help_state.popover_mut() {
12786 if popover.current_signature + 1 == popover.signatures.len() {
12787 popover.current_signature = 0;
12788 } else {
12789 popover.current_signature += 1;
12790 }
12791 cx.notify();
12792 }
12793 }
12794
12795 pub fn move_to_previous_word_start(
12796 &mut self,
12797 _: &MoveToPreviousWordStart,
12798 window: &mut Window,
12799 cx: &mut Context<Self>,
12800 ) {
12801 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12802 self.change_selections(Default::default(), window, cx, |s| {
12803 s.move_cursors_with(|map, head, _| {
12804 (
12805 movement::previous_word_start(map, head),
12806 SelectionGoal::None,
12807 )
12808 });
12809 })
12810 }
12811
12812 pub fn move_to_previous_subword_start(
12813 &mut self,
12814 _: &MoveToPreviousSubwordStart,
12815 window: &mut Window,
12816 cx: &mut Context<Self>,
12817 ) {
12818 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12819 self.change_selections(Default::default(), window, cx, |s| {
12820 s.move_cursors_with(|map, head, _| {
12821 (
12822 movement::previous_subword_start(map, head),
12823 SelectionGoal::None,
12824 )
12825 });
12826 })
12827 }
12828
12829 pub fn select_to_previous_word_start(
12830 &mut self,
12831 _: &SelectToPreviousWordStart,
12832 window: &mut Window,
12833 cx: &mut Context<Self>,
12834 ) {
12835 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12836 self.change_selections(Default::default(), window, cx, |s| {
12837 s.move_heads_with(|map, head, _| {
12838 (
12839 movement::previous_word_start(map, head),
12840 SelectionGoal::None,
12841 )
12842 });
12843 })
12844 }
12845
12846 pub fn select_to_previous_subword_start(
12847 &mut self,
12848 _: &SelectToPreviousSubwordStart,
12849 window: &mut Window,
12850 cx: &mut Context<Self>,
12851 ) {
12852 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12853 self.change_selections(Default::default(), window, cx, |s| {
12854 s.move_heads_with(|map, head, _| {
12855 (
12856 movement::previous_subword_start(map, head),
12857 SelectionGoal::None,
12858 )
12859 });
12860 })
12861 }
12862
12863 pub fn delete_to_previous_word_start(
12864 &mut self,
12865 action: &DeleteToPreviousWordStart,
12866 window: &mut Window,
12867 cx: &mut Context<Self>,
12868 ) {
12869 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12870 self.transact(window, cx, |this, window, cx| {
12871 this.select_autoclose_pair(window, cx);
12872 this.change_selections(Default::default(), window, cx, |s| {
12873 s.move_with(|map, selection| {
12874 if selection.is_empty() {
12875 let cursor = if action.ignore_newlines {
12876 movement::previous_word_start(map, selection.head())
12877 } else {
12878 movement::previous_word_start_or_newline(map, selection.head())
12879 };
12880 selection.set_head(cursor, SelectionGoal::None);
12881 }
12882 });
12883 });
12884 this.insert("", window, cx);
12885 });
12886 }
12887
12888 pub fn delete_to_previous_subword_start(
12889 &mut self,
12890 _: &DeleteToPreviousSubwordStart,
12891 window: &mut Window,
12892 cx: &mut Context<Self>,
12893 ) {
12894 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12895 self.transact(window, cx, |this, window, cx| {
12896 this.select_autoclose_pair(window, cx);
12897 this.change_selections(Default::default(), window, cx, |s| {
12898 s.move_with(|map, selection| {
12899 if selection.is_empty() {
12900 let cursor = movement::previous_subword_start(map, selection.head());
12901 selection.set_head(cursor, SelectionGoal::None);
12902 }
12903 });
12904 });
12905 this.insert("", window, cx);
12906 });
12907 }
12908
12909 pub fn move_to_next_word_end(
12910 &mut self,
12911 _: &MoveToNextWordEnd,
12912 window: &mut Window,
12913 cx: &mut Context<Self>,
12914 ) {
12915 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12916 self.change_selections(Default::default(), window, cx, |s| {
12917 s.move_cursors_with(|map, head, _| {
12918 (movement::next_word_end(map, head), SelectionGoal::None)
12919 });
12920 })
12921 }
12922
12923 pub fn move_to_next_subword_end(
12924 &mut self,
12925 _: &MoveToNextSubwordEnd,
12926 window: &mut Window,
12927 cx: &mut Context<Self>,
12928 ) {
12929 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12930 self.change_selections(Default::default(), window, cx, |s| {
12931 s.move_cursors_with(|map, head, _| {
12932 (movement::next_subword_end(map, head), SelectionGoal::None)
12933 });
12934 })
12935 }
12936
12937 pub fn select_to_next_word_end(
12938 &mut self,
12939 _: &SelectToNextWordEnd,
12940 window: &mut Window,
12941 cx: &mut Context<Self>,
12942 ) {
12943 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12944 self.change_selections(Default::default(), window, cx, |s| {
12945 s.move_heads_with(|map, head, _| {
12946 (movement::next_word_end(map, head), SelectionGoal::None)
12947 });
12948 })
12949 }
12950
12951 pub fn select_to_next_subword_end(
12952 &mut self,
12953 _: &SelectToNextSubwordEnd,
12954 window: &mut Window,
12955 cx: &mut Context<Self>,
12956 ) {
12957 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12958 self.change_selections(Default::default(), window, cx, |s| {
12959 s.move_heads_with(|map, head, _| {
12960 (movement::next_subword_end(map, head), SelectionGoal::None)
12961 });
12962 })
12963 }
12964
12965 pub fn delete_to_next_word_end(
12966 &mut self,
12967 action: &DeleteToNextWordEnd,
12968 window: &mut Window,
12969 cx: &mut Context<Self>,
12970 ) {
12971 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12972 self.transact(window, cx, |this, window, cx| {
12973 this.change_selections(Default::default(), window, cx, |s| {
12974 s.move_with(|map, selection| {
12975 if selection.is_empty() {
12976 let cursor = if action.ignore_newlines {
12977 movement::next_word_end(map, selection.head())
12978 } else {
12979 movement::next_word_end_or_newline(map, selection.head())
12980 };
12981 selection.set_head(cursor, SelectionGoal::None);
12982 }
12983 });
12984 });
12985 this.insert("", window, cx);
12986 });
12987 }
12988
12989 pub fn delete_to_next_subword_end(
12990 &mut self,
12991 _: &DeleteToNextSubwordEnd,
12992 window: &mut Window,
12993 cx: &mut Context<Self>,
12994 ) {
12995 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12996 self.transact(window, cx, |this, window, cx| {
12997 this.change_selections(Default::default(), window, cx, |s| {
12998 s.move_with(|map, selection| {
12999 if selection.is_empty() {
13000 let cursor = movement::next_subword_end(map, selection.head());
13001 selection.set_head(cursor, SelectionGoal::None);
13002 }
13003 });
13004 });
13005 this.insert("", window, cx);
13006 });
13007 }
13008
13009 pub fn move_to_beginning_of_line(
13010 &mut self,
13011 action: &MoveToBeginningOfLine,
13012 window: &mut Window,
13013 cx: &mut Context<Self>,
13014 ) {
13015 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13016 self.change_selections(Default::default(), window, cx, |s| {
13017 s.move_cursors_with(|map, head, _| {
13018 (
13019 movement::indented_line_beginning(
13020 map,
13021 head,
13022 action.stop_at_soft_wraps,
13023 action.stop_at_indent,
13024 ),
13025 SelectionGoal::None,
13026 )
13027 });
13028 })
13029 }
13030
13031 pub fn select_to_beginning_of_line(
13032 &mut self,
13033 action: &SelectToBeginningOfLine,
13034 window: &mut Window,
13035 cx: &mut Context<Self>,
13036 ) {
13037 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13038 self.change_selections(Default::default(), window, cx, |s| {
13039 s.move_heads_with(|map, head, _| {
13040 (
13041 movement::indented_line_beginning(
13042 map,
13043 head,
13044 action.stop_at_soft_wraps,
13045 action.stop_at_indent,
13046 ),
13047 SelectionGoal::None,
13048 )
13049 });
13050 });
13051 }
13052
13053 pub fn delete_to_beginning_of_line(
13054 &mut self,
13055 action: &DeleteToBeginningOfLine,
13056 window: &mut Window,
13057 cx: &mut Context<Self>,
13058 ) {
13059 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13060 self.transact(window, cx, |this, window, cx| {
13061 this.change_selections(Default::default(), window, cx, |s| {
13062 s.move_with(|_, selection| {
13063 selection.reversed = true;
13064 });
13065 });
13066
13067 this.select_to_beginning_of_line(
13068 &SelectToBeginningOfLine {
13069 stop_at_soft_wraps: false,
13070 stop_at_indent: action.stop_at_indent,
13071 },
13072 window,
13073 cx,
13074 );
13075 this.backspace(&Backspace, window, cx);
13076 });
13077 }
13078
13079 pub fn move_to_end_of_line(
13080 &mut self,
13081 action: &MoveToEndOfLine,
13082 window: &mut Window,
13083 cx: &mut Context<Self>,
13084 ) {
13085 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13086 self.change_selections(Default::default(), window, cx, |s| {
13087 s.move_cursors_with(|map, head, _| {
13088 (
13089 movement::line_end(map, head, action.stop_at_soft_wraps),
13090 SelectionGoal::None,
13091 )
13092 });
13093 })
13094 }
13095
13096 pub fn select_to_end_of_line(
13097 &mut self,
13098 action: &SelectToEndOfLine,
13099 window: &mut Window,
13100 cx: &mut Context<Self>,
13101 ) {
13102 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13103 self.change_selections(Default::default(), window, cx, |s| {
13104 s.move_heads_with(|map, head, _| {
13105 (
13106 movement::line_end(map, head, action.stop_at_soft_wraps),
13107 SelectionGoal::None,
13108 )
13109 });
13110 })
13111 }
13112
13113 pub fn delete_to_end_of_line(
13114 &mut self,
13115 _: &DeleteToEndOfLine,
13116 window: &mut Window,
13117 cx: &mut Context<Self>,
13118 ) {
13119 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13120 self.transact(window, cx, |this, window, cx| {
13121 this.select_to_end_of_line(
13122 &SelectToEndOfLine {
13123 stop_at_soft_wraps: false,
13124 },
13125 window,
13126 cx,
13127 );
13128 this.delete(&Delete, window, cx);
13129 });
13130 }
13131
13132 pub fn cut_to_end_of_line(
13133 &mut self,
13134 _: &CutToEndOfLine,
13135 window: &mut Window,
13136 cx: &mut Context<Self>,
13137 ) {
13138 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13139 self.transact(window, cx, |this, window, cx| {
13140 this.select_to_end_of_line(
13141 &SelectToEndOfLine {
13142 stop_at_soft_wraps: false,
13143 },
13144 window,
13145 cx,
13146 );
13147 this.cut(&Cut, window, cx);
13148 });
13149 }
13150
13151 pub fn move_to_start_of_paragraph(
13152 &mut self,
13153 _: &MoveToStartOfParagraph,
13154 window: &mut Window,
13155 cx: &mut Context<Self>,
13156 ) {
13157 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13158 cx.propagate();
13159 return;
13160 }
13161 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13162 self.change_selections(Default::default(), window, cx, |s| {
13163 s.move_with(|map, selection| {
13164 selection.collapse_to(
13165 movement::start_of_paragraph(map, selection.head(), 1),
13166 SelectionGoal::None,
13167 )
13168 });
13169 })
13170 }
13171
13172 pub fn move_to_end_of_paragraph(
13173 &mut self,
13174 _: &MoveToEndOfParagraph,
13175 window: &mut Window,
13176 cx: &mut Context<Self>,
13177 ) {
13178 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13179 cx.propagate();
13180 return;
13181 }
13182 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13183 self.change_selections(Default::default(), window, cx, |s| {
13184 s.move_with(|map, selection| {
13185 selection.collapse_to(
13186 movement::end_of_paragraph(map, selection.head(), 1),
13187 SelectionGoal::None,
13188 )
13189 });
13190 })
13191 }
13192
13193 pub fn select_to_start_of_paragraph(
13194 &mut self,
13195 _: &SelectToStartOfParagraph,
13196 window: &mut Window,
13197 cx: &mut Context<Self>,
13198 ) {
13199 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13200 cx.propagate();
13201 return;
13202 }
13203 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13204 self.change_selections(Default::default(), window, cx, |s| {
13205 s.move_heads_with(|map, head, _| {
13206 (
13207 movement::start_of_paragraph(map, head, 1),
13208 SelectionGoal::None,
13209 )
13210 });
13211 })
13212 }
13213
13214 pub fn select_to_end_of_paragraph(
13215 &mut self,
13216 _: &SelectToEndOfParagraph,
13217 window: &mut Window,
13218 cx: &mut Context<Self>,
13219 ) {
13220 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13221 cx.propagate();
13222 return;
13223 }
13224 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13225 self.change_selections(Default::default(), window, cx, |s| {
13226 s.move_heads_with(|map, head, _| {
13227 (
13228 movement::end_of_paragraph(map, head, 1),
13229 SelectionGoal::None,
13230 )
13231 });
13232 })
13233 }
13234
13235 pub fn move_to_start_of_excerpt(
13236 &mut self,
13237 _: &MoveToStartOfExcerpt,
13238 window: &mut Window,
13239 cx: &mut Context<Self>,
13240 ) {
13241 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13242 cx.propagate();
13243 return;
13244 }
13245 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13246 self.change_selections(Default::default(), window, cx, |s| {
13247 s.move_with(|map, selection| {
13248 selection.collapse_to(
13249 movement::start_of_excerpt(
13250 map,
13251 selection.head(),
13252 workspace::searchable::Direction::Prev,
13253 ),
13254 SelectionGoal::None,
13255 )
13256 });
13257 })
13258 }
13259
13260 pub fn move_to_start_of_next_excerpt(
13261 &mut self,
13262 _: &MoveToStartOfNextExcerpt,
13263 window: &mut Window,
13264 cx: &mut Context<Self>,
13265 ) {
13266 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13267 cx.propagate();
13268 return;
13269 }
13270
13271 self.change_selections(Default::default(), window, cx, |s| {
13272 s.move_with(|map, selection| {
13273 selection.collapse_to(
13274 movement::start_of_excerpt(
13275 map,
13276 selection.head(),
13277 workspace::searchable::Direction::Next,
13278 ),
13279 SelectionGoal::None,
13280 )
13281 });
13282 })
13283 }
13284
13285 pub fn move_to_end_of_excerpt(
13286 &mut self,
13287 _: &MoveToEndOfExcerpt,
13288 window: &mut Window,
13289 cx: &mut Context<Self>,
13290 ) {
13291 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13292 cx.propagate();
13293 return;
13294 }
13295 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13296 self.change_selections(Default::default(), window, cx, |s| {
13297 s.move_with(|map, selection| {
13298 selection.collapse_to(
13299 movement::end_of_excerpt(
13300 map,
13301 selection.head(),
13302 workspace::searchable::Direction::Next,
13303 ),
13304 SelectionGoal::None,
13305 )
13306 });
13307 })
13308 }
13309
13310 pub fn move_to_end_of_previous_excerpt(
13311 &mut self,
13312 _: &MoveToEndOfPreviousExcerpt,
13313 window: &mut Window,
13314 cx: &mut Context<Self>,
13315 ) {
13316 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13317 cx.propagate();
13318 return;
13319 }
13320 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13321 self.change_selections(Default::default(), window, cx, |s| {
13322 s.move_with(|map, selection| {
13323 selection.collapse_to(
13324 movement::end_of_excerpt(
13325 map,
13326 selection.head(),
13327 workspace::searchable::Direction::Prev,
13328 ),
13329 SelectionGoal::None,
13330 )
13331 });
13332 })
13333 }
13334
13335 pub fn select_to_start_of_excerpt(
13336 &mut self,
13337 _: &SelectToStartOfExcerpt,
13338 window: &mut Window,
13339 cx: &mut Context<Self>,
13340 ) {
13341 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13342 cx.propagate();
13343 return;
13344 }
13345 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13346 self.change_selections(Default::default(), window, cx, |s| {
13347 s.move_heads_with(|map, head, _| {
13348 (
13349 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13350 SelectionGoal::None,
13351 )
13352 });
13353 })
13354 }
13355
13356 pub fn select_to_start_of_next_excerpt(
13357 &mut self,
13358 _: &SelectToStartOfNextExcerpt,
13359 window: &mut Window,
13360 cx: &mut Context<Self>,
13361 ) {
13362 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13363 cx.propagate();
13364 return;
13365 }
13366 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13367 self.change_selections(Default::default(), window, cx, |s| {
13368 s.move_heads_with(|map, head, _| {
13369 (
13370 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
13371 SelectionGoal::None,
13372 )
13373 });
13374 })
13375 }
13376
13377 pub fn select_to_end_of_excerpt(
13378 &mut self,
13379 _: &SelectToEndOfExcerpt,
13380 window: &mut Window,
13381 cx: &mut Context<Self>,
13382 ) {
13383 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13384 cx.propagate();
13385 return;
13386 }
13387 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13388 self.change_selections(Default::default(), window, cx, |s| {
13389 s.move_heads_with(|map, head, _| {
13390 (
13391 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
13392 SelectionGoal::None,
13393 )
13394 });
13395 })
13396 }
13397
13398 pub fn select_to_end_of_previous_excerpt(
13399 &mut self,
13400 _: &SelectToEndOfPreviousExcerpt,
13401 window: &mut Window,
13402 cx: &mut Context<Self>,
13403 ) {
13404 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13405 cx.propagate();
13406 return;
13407 }
13408 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13409 self.change_selections(Default::default(), window, cx, |s| {
13410 s.move_heads_with(|map, head, _| {
13411 (
13412 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13413 SelectionGoal::None,
13414 )
13415 });
13416 })
13417 }
13418
13419 pub fn move_to_beginning(
13420 &mut self,
13421 _: &MoveToBeginning,
13422 window: &mut Window,
13423 cx: &mut Context<Self>,
13424 ) {
13425 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13426 cx.propagate();
13427 return;
13428 }
13429 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13430 self.change_selections(Default::default(), window, cx, |s| {
13431 s.select_ranges(vec![0..0]);
13432 });
13433 }
13434
13435 pub fn select_to_beginning(
13436 &mut self,
13437 _: &SelectToBeginning,
13438 window: &mut Window,
13439 cx: &mut Context<Self>,
13440 ) {
13441 let mut selection = self.selections.last::<Point>(cx);
13442 selection.set_head(Point::zero(), SelectionGoal::None);
13443 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13444 self.change_selections(Default::default(), window, cx, |s| {
13445 s.select(vec![selection]);
13446 });
13447 }
13448
13449 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
13450 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13451 cx.propagate();
13452 return;
13453 }
13454 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13455 let cursor = self.buffer.read(cx).read(cx).len();
13456 self.change_selections(Default::default(), window, cx, |s| {
13457 s.select_ranges(vec![cursor..cursor])
13458 });
13459 }
13460
13461 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
13462 self.nav_history = nav_history;
13463 }
13464
13465 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
13466 self.nav_history.as_ref()
13467 }
13468
13469 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
13470 self.push_to_nav_history(
13471 self.selections.newest_anchor().head(),
13472 None,
13473 false,
13474 true,
13475 cx,
13476 );
13477 }
13478
13479 fn push_to_nav_history(
13480 &mut self,
13481 cursor_anchor: Anchor,
13482 new_position: Option<Point>,
13483 is_deactivate: bool,
13484 always: bool,
13485 cx: &mut Context<Self>,
13486 ) {
13487 if let Some(nav_history) = self.nav_history.as_mut() {
13488 let buffer = self.buffer.read(cx).read(cx);
13489 let cursor_position = cursor_anchor.to_point(&buffer);
13490 let scroll_state = self.scroll_manager.anchor();
13491 let scroll_top_row = scroll_state.top_row(&buffer);
13492 drop(buffer);
13493
13494 if let Some(new_position) = new_position {
13495 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
13496 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
13497 return;
13498 }
13499 }
13500
13501 nav_history.push(
13502 Some(NavigationData {
13503 cursor_anchor,
13504 cursor_position,
13505 scroll_anchor: scroll_state,
13506 scroll_top_row,
13507 }),
13508 cx,
13509 );
13510 cx.emit(EditorEvent::PushedToNavHistory {
13511 anchor: cursor_anchor,
13512 is_deactivate,
13513 })
13514 }
13515 }
13516
13517 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
13518 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13519 let buffer = self.buffer.read(cx).snapshot(cx);
13520 let mut selection = self.selections.first::<usize>(cx);
13521 selection.set_head(buffer.len(), SelectionGoal::None);
13522 self.change_selections(Default::default(), window, cx, |s| {
13523 s.select(vec![selection]);
13524 });
13525 }
13526
13527 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
13528 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13529 let end = self.buffer.read(cx).read(cx).len();
13530 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13531 s.select_ranges(vec![0..end]);
13532 });
13533 }
13534
13535 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
13536 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13537 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13538 let mut selections = self.selections.all::<Point>(cx);
13539 let max_point = display_map.buffer_snapshot.max_point();
13540 for selection in &mut selections {
13541 let rows = selection.spanned_rows(true, &display_map);
13542 selection.start = Point::new(rows.start.0, 0);
13543 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
13544 selection.reversed = false;
13545 }
13546 self.change_selections(Default::default(), window, cx, |s| {
13547 s.select(selections);
13548 });
13549 }
13550
13551 pub fn split_selection_into_lines(
13552 &mut self,
13553 _: &SplitSelectionIntoLines,
13554 window: &mut Window,
13555 cx: &mut Context<Self>,
13556 ) {
13557 let selections = self
13558 .selections
13559 .all::<Point>(cx)
13560 .into_iter()
13561 .map(|selection| selection.start..selection.end)
13562 .collect::<Vec<_>>();
13563 self.unfold_ranges(&selections, true, true, cx);
13564
13565 let mut new_selection_ranges = Vec::new();
13566 {
13567 let buffer = self.buffer.read(cx).read(cx);
13568 for selection in selections {
13569 for row in selection.start.row..selection.end.row {
13570 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
13571 new_selection_ranges.push(cursor..cursor);
13572 }
13573
13574 let is_multiline_selection = selection.start.row != selection.end.row;
13575 // Don't insert last one if it's a multi-line selection ending at the start of a line,
13576 // so this action feels more ergonomic when paired with other selection operations
13577 let should_skip_last = is_multiline_selection && selection.end.column == 0;
13578 if !should_skip_last {
13579 new_selection_ranges.push(selection.end..selection.end);
13580 }
13581 }
13582 }
13583 self.change_selections(Default::default(), window, cx, |s| {
13584 s.select_ranges(new_selection_ranges);
13585 });
13586 }
13587
13588 pub fn add_selection_above(
13589 &mut self,
13590 _: &AddSelectionAbove,
13591 window: &mut Window,
13592 cx: &mut Context<Self>,
13593 ) {
13594 self.add_selection(true, window, cx);
13595 }
13596
13597 pub fn add_selection_below(
13598 &mut self,
13599 _: &AddSelectionBelow,
13600 window: &mut Window,
13601 cx: &mut Context<Self>,
13602 ) {
13603 self.add_selection(false, window, cx);
13604 }
13605
13606 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
13607 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13608
13609 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13610 let all_selections = self.selections.all::<Point>(cx);
13611 let text_layout_details = self.text_layout_details(window);
13612
13613 let (mut columnar_selections, new_selections_to_columnarize) = {
13614 if let Some(state) = self.add_selections_state.as_ref() {
13615 let columnar_selection_ids: HashSet<_> = state
13616 .groups
13617 .iter()
13618 .flat_map(|group| group.stack.iter())
13619 .copied()
13620 .collect();
13621
13622 all_selections
13623 .into_iter()
13624 .partition(|s| columnar_selection_ids.contains(&s.id))
13625 } else {
13626 (Vec::new(), all_selections)
13627 }
13628 };
13629
13630 let mut state = self
13631 .add_selections_state
13632 .take()
13633 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
13634
13635 for selection in new_selections_to_columnarize {
13636 let range = selection.display_range(&display_map).sorted();
13637 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
13638 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
13639 let positions = start_x.min(end_x)..start_x.max(end_x);
13640 let mut stack = Vec::new();
13641 for row in range.start.row().0..=range.end.row().0 {
13642 if let Some(selection) = self.selections.build_columnar_selection(
13643 &display_map,
13644 DisplayRow(row),
13645 &positions,
13646 selection.reversed,
13647 &text_layout_details,
13648 ) {
13649 stack.push(selection.id);
13650 columnar_selections.push(selection);
13651 }
13652 }
13653 if !stack.is_empty() {
13654 if above {
13655 stack.reverse();
13656 }
13657 state.groups.push(AddSelectionsGroup { above, stack });
13658 }
13659 }
13660
13661 let mut final_selections = Vec::new();
13662 let end_row = if above {
13663 DisplayRow(0)
13664 } else {
13665 display_map.max_point().row()
13666 };
13667
13668 let mut last_added_item_per_group = HashMap::default();
13669 for group in state.groups.iter_mut() {
13670 if let Some(last_id) = group.stack.last() {
13671 last_added_item_per_group.insert(*last_id, group);
13672 }
13673 }
13674
13675 for selection in columnar_selections {
13676 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
13677 if above == group.above {
13678 let range = selection.display_range(&display_map).sorted();
13679 debug_assert_eq!(range.start.row(), range.end.row());
13680 let mut row = range.start.row();
13681 let positions =
13682 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
13683 px(start)..px(end)
13684 } else {
13685 let start_x =
13686 display_map.x_for_display_point(range.start, &text_layout_details);
13687 let end_x =
13688 display_map.x_for_display_point(range.end, &text_layout_details);
13689 start_x.min(end_x)..start_x.max(end_x)
13690 };
13691
13692 let mut maybe_new_selection = None;
13693 while row != end_row {
13694 if above {
13695 row.0 -= 1;
13696 } else {
13697 row.0 += 1;
13698 }
13699 if let Some(new_selection) = self.selections.build_columnar_selection(
13700 &display_map,
13701 row,
13702 &positions,
13703 selection.reversed,
13704 &text_layout_details,
13705 ) {
13706 maybe_new_selection = Some(new_selection);
13707 break;
13708 }
13709 }
13710
13711 if let Some(new_selection) = maybe_new_selection {
13712 group.stack.push(new_selection.id);
13713 if above {
13714 final_selections.push(new_selection);
13715 final_selections.push(selection);
13716 } else {
13717 final_selections.push(selection);
13718 final_selections.push(new_selection);
13719 }
13720 } else {
13721 final_selections.push(selection);
13722 }
13723 } else {
13724 group.stack.pop();
13725 }
13726 } else {
13727 final_selections.push(selection);
13728 }
13729 }
13730
13731 self.change_selections(Default::default(), window, cx, |s| {
13732 s.select(final_selections);
13733 });
13734
13735 let final_selection_ids: HashSet<_> = self
13736 .selections
13737 .all::<Point>(cx)
13738 .iter()
13739 .map(|s| s.id)
13740 .collect();
13741 state.groups.retain_mut(|group| {
13742 // selections might get merged above so we remove invalid items from stacks
13743 group.stack.retain(|id| final_selection_ids.contains(id));
13744
13745 // single selection in stack can be treated as initial state
13746 group.stack.len() > 1
13747 });
13748
13749 if !state.groups.is_empty() {
13750 self.add_selections_state = Some(state);
13751 }
13752 }
13753
13754 fn select_match_ranges(
13755 &mut self,
13756 range: Range<usize>,
13757 reversed: bool,
13758 replace_newest: bool,
13759 auto_scroll: Option<Autoscroll>,
13760 window: &mut Window,
13761 cx: &mut Context<Editor>,
13762 ) {
13763 self.unfold_ranges(
13764 std::slice::from_ref(&range),
13765 false,
13766 auto_scroll.is_some(),
13767 cx,
13768 );
13769 let effects = if let Some(scroll) = auto_scroll {
13770 SelectionEffects::scroll(scroll)
13771 } else {
13772 SelectionEffects::no_scroll()
13773 };
13774 self.change_selections(effects, window, cx, |s| {
13775 if replace_newest {
13776 s.delete(s.newest_anchor().id);
13777 }
13778 if reversed {
13779 s.insert_range(range.end..range.start);
13780 } else {
13781 s.insert_range(range);
13782 }
13783 });
13784 }
13785
13786 pub fn select_next_match_internal(
13787 &mut self,
13788 display_map: &DisplaySnapshot,
13789 replace_newest: bool,
13790 autoscroll: Option<Autoscroll>,
13791 window: &mut Window,
13792 cx: &mut Context<Self>,
13793 ) -> Result<()> {
13794 let buffer = &display_map.buffer_snapshot;
13795 let mut selections = self.selections.all::<usize>(cx);
13796 if let Some(mut select_next_state) = self.select_next_state.take() {
13797 let query = &select_next_state.query;
13798 if !select_next_state.done {
13799 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13800 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13801 let mut next_selected_range = None;
13802
13803 let bytes_after_last_selection =
13804 buffer.bytes_in_range(last_selection.end..buffer.len());
13805 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
13806 let query_matches = query
13807 .stream_find_iter(bytes_after_last_selection)
13808 .map(|result| (last_selection.end, result))
13809 .chain(
13810 query
13811 .stream_find_iter(bytes_before_first_selection)
13812 .map(|result| (0, result)),
13813 );
13814
13815 for (start_offset, query_match) in query_matches {
13816 let query_match = query_match.unwrap(); // can only fail due to I/O
13817 let offset_range =
13818 start_offset + query_match.start()..start_offset + query_match.end();
13819
13820 if !select_next_state.wordwise
13821 || (!buffer.is_inside_word(offset_range.start, false)
13822 && !buffer.is_inside_word(offset_range.end, false))
13823 {
13824 // TODO: This is n^2, because we might check all the selections
13825 if !selections
13826 .iter()
13827 .any(|selection| selection.range().overlaps(&offset_range))
13828 {
13829 next_selected_range = Some(offset_range);
13830 break;
13831 }
13832 }
13833 }
13834
13835 if let Some(next_selected_range) = next_selected_range {
13836 self.select_match_ranges(
13837 next_selected_range,
13838 last_selection.reversed,
13839 replace_newest,
13840 autoscroll,
13841 window,
13842 cx,
13843 );
13844 } else {
13845 select_next_state.done = true;
13846 }
13847 }
13848
13849 self.select_next_state = Some(select_next_state);
13850 } else {
13851 let mut only_carets = true;
13852 let mut same_text_selected = true;
13853 let mut selected_text = None;
13854
13855 let mut selections_iter = selections.iter().peekable();
13856 while let Some(selection) = selections_iter.next() {
13857 if selection.start != selection.end {
13858 only_carets = false;
13859 }
13860
13861 if same_text_selected {
13862 if selected_text.is_none() {
13863 selected_text =
13864 Some(buffer.text_for_range(selection.range()).collect::<String>());
13865 }
13866
13867 if let Some(next_selection) = selections_iter.peek() {
13868 if next_selection.range().len() == selection.range().len() {
13869 let next_selected_text = buffer
13870 .text_for_range(next_selection.range())
13871 .collect::<String>();
13872 if Some(next_selected_text) != selected_text {
13873 same_text_selected = false;
13874 selected_text = None;
13875 }
13876 } else {
13877 same_text_selected = false;
13878 selected_text = None;
13879 }
13880 }
13881 }
13882 }
13883
13884 if only_carets {
13885 for selection in &mut selections {
13886 let (word_range, _) = buffer.surrounding_word(selection.start, false);
13887 selection.start = word_range.start;
13888 selection.end = word_range.end;
13889 selection.goal = SelectionGoal::None;
13890 selection.reversed = false;
13891 self.select_match_ranges(
13892 selection.start..selection.end,
13893 selection.reversed,
13894 replace_newest,
13895 autoscroll,
13896 window,
13897 cx,
13898 );
13899 }
13900
13901 if selections.len() == 1 {
13902 let selection = selections
13903 .last()
13904 .expect("ensured that there's only one selection");
13905 let query = buffer
13906 .text_for_range(selection.start..selection.end)
13907 .collect::<String>();
13908 let is_empty = query.is_empty();
13909 let select_state = SelectNextState {
13910 query: AhoCorasick::new(&[query])?,
13911 wordwise: true,
13912 done: is_empty,
13913 };
13914 self.select_next_state = Some(select_state);
13915 } else {
13916 self.select_next_state = None;
13917 }
13918 } else if let Some(selected_text) = selected_text {
13919 self.select_next_state = Some(SelectNextState {
13920 query: AhoCorasick::new(&[selected_text])?,
13921 wordwise: false,
13922 done: false,
13923 });
13924 self.select_next_match_internal(
13925 display_map,
13926 replace_newest,
13927 autoscroll,
13928 window,
13929 cx,
13930 )?;
13931 }
13932 }
13933 Ok(())
13934 }
13935
13936 pub fn select_all_matches(
13937 &mut self,
13938 _action: &SelectAllMatches,
13939 window: &mut Window,
13940 cx: &mut Context<Self>,
13941 ) -> Result<()> {
13942 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13943
13944 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13945
13946 self.select_next_match_internal(&display_map, false, None, window, cx)?;
13947 let Some(select_next_state) = self.select_next_state.as_mut() else {
13948 return Ok(());
13949 };
13950 if select_next_state.done {
13951 return Ok(());
13952 }
13953
13954 let mut new_selections = Vec::new();
13955
13956 let reversed = self.selections.oldest::<usize>(cx).reversed;
13957 let buffer = &display_map.buffer_snapshot;
13958 let query_matches = select_next_state
13959 .query
13960 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
13961
13962 for query_match in query_matches.into_iter() {
13963 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
13964 let offset_range = if reversed {
13965 query_match.end()..query_match.start()
13966 } else {
13967 query_match.start()..query_match.end()
13968 };
13969
13970 if !select_next_state.wordwise
13971 || (!buffer.is_inside_word(offset_range.start, false)
13972 && !buffer.is_inside_word(offset_range.end, false))
13973 {
13974 new_selections.push(offset_range.start..offset_range.end);
13975 }
13976 }
13977
13978 select_next_state.done = true;
13979
13980 if new_selections.is_empty() {
13981 log::error!("bug: new_selections is empty in select_all_matches");
13982 return Ok(());
13983 }
13984
13985 self.unfold_ranges(&new_selections.clone(), false, false, cx);
13986 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
13987 selections.select_ranges(new_selections)
13988 });
13989
13990 Ok(())
13991 }
13992
13993 pub fn select_next(
13994 &mut self,
13995 action: &SelectNext,
13996 window: &mut Window,
13997 cx: &mut Context<Self>,
13998 ) -> Result<()> {
13999 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14000 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14001 self.select_next_match_internal(
14002 &display_map,
14003 action.replace_newest,
14004 Some(Autoscroll::newest()),
14005 window,
14006 cx,
14007 )?;
14008 Ok(())
14009 }
14010
14011 pub fn select_previous(
14012 &mut self,
14013 action: &SelectPrevious,
14014 window: &mut Window,
14015 cx: &mut Context<Self>,
14016 ) -> Result<()> {
14017 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14018 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14019 let buffer = &display_map.buffer_snapshot;
14020 let mut selections = self.selections.all::<usize>(cx);
14021 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14022 let query = &select_prev_state.query;
14023 if !select_prev_state.done {
14024 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14025 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14026 let mut next_selected_range = None;
14027 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14028 let bytes_before_last_selection =
14029 buffer.reversed_bytes_in_range(0..last_selection.start);
14030 let bytes_after_first_selection =
14031 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14032 let query_matches = query
14033 .stream_find_iter(bytes_before_last_selection)
14034 .map(|result| (last_selection.start, result))
14035 .chain(
14036 query
14037 .stream_find_iter(bytes_after_first_selection)
14038 .map(|result| (buffer.len(), result)),
14039 );
14040 for (end_offset, query_match) in query_matches {
14041 let query_match = query_match.unwrap(); // can only fail due to I/O
14042 let offset_range =
14043 end_offset - query_match.end()..end_offset - query_match.start();
14044
14045 if !select_prev_state.wordwise
14046 || (!buffer.is_inside_word(offset_range.start, false)
14047 && !buffer.is_inside_word(offset_range.end, false))
14048 {
14049 next_selected_range = Some(offset_range);
14050 break;
14051 }
14052 }
14053
14054 if let Some(next_selected_range) = next_selected_range {
14055 self.select_match_ranges(
14056 next_selected_range,
14057 last_selection.reversed,
14058 action.replace_newest,
14059 Some(Autoscroll::newest()),
14060 window,
14061 cx,
14062 );
14063 } else {
14064 select_prev_state.done = true;
14065 }
14066 }
14067
14068 self.select_prev_state = Some(select_prev_state);
14069 } else {
14070 let mut only_carets = true;
14071 let mut same_text_selected = true;
14072 let mut selected_text = None;
14073
14074 let mut selections_iter = selections.iter().peekable();
14075 while let Some(selection) = selections_iter.next() {
14076 if selection.start != selection.end {
14077 only_carets = false;
14078 }
14079
14080 if same_text_selected {
14081 if selected_text.is_none() {
14082 selected_text =
14083 Some(buffer.text_for_range(selection.range()).collect::<String>());
14084 }
14085
14086 if let Some(next_selection) = selections_iter.peek() {
14087 if next_selection.range().len() == selection.range().len() {
14088 let next_selected_text = buffer
14089 .text_for_range(next_selection.range())
14090 .collect::<String>();
14091 if Some(next_selected_text) != selected_text {
14092 same_text_selected = false;
14093 selected_text = None;
14094 }
14095 } else {
14096 same_text_selected = false;
14097 selected_text = None;
14098 }
14099 }
14100 }
14101 }
14102
14103 if only_carets {
14104 for selection in &mut selections {
14105 let (word_range, _) = buffer.surrounding_word(selection.start, false);
14106 selection.start = word_range.start;
14107 selection.end = word_range.end;
14108 selection.goal = SelectionGoal::None;
14109 selection.reversed = false;
14110 self.select_match_ranges(
14111 selection.start..selection.end,
14112 selection.reversed,
14113 action.replace_newest,
14114 Some(Autoscroll::newest()),
14115 window,
14116 cx,
14117 );
14118 }
14119 if selections.len() == 1 {
14120 let selection = selections
14121 .last()
14122 .expect("ensured that there's only one selection");
14123 let query = buffer
14124 .text_for_range(selection.start..selection.end)
14125 .collect::<String>();
14126 let is_empty = query.is_empty();
14127 let select_state = SelectNextState {
14128 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
14129 wordwise: true,
14130 done: is_empty,
14131 };
14132 self.select_prev_state = Some(select_state);
14133 } else {
14134 self.select_prev_state = None;
14135 }
14136 } else if let Some(selected_text) = selected_text {
14137 self.select_prev_state = Some(SelectNextState {
14138 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
14139 wordwise: false,
14140 done: false,
14141 });
14142 self.select_previous(action, window, cx)?;
14143 }
14144 }
14145 Ok(())
14146 }
14147
14148 pub fn find_next_match(
14149 &mut self,
14150 _: &FindNextMatch,
14151 window: &mut Window,
14152 cx: &mut Context<Self>,
14153 ) -> Result<()> {
14154 let selections = self.selections.disjoint_anchors();
14155 match selections.first() {
14156 Some(first) if selections.len() >= 2 => {
14157 self.change_selections(Default::default(), window, cx, |s| {
14158 s.select_ranges([first.range()]);
14159 });
14160 }
14161 _ => self.select_next(
14162 &SelectNext {
14163 replace_newest: true,
14164 },
14165 window,
14166 cx,
14167 )?,
14168 }
14169 Ok(())
14170 }
14171
14172 pub fn find_previous_match(
14173 &mut self,
14174 _: &FindPreviousMatch,
14175 window: &mut Window,
14176 cx: &mut Context<Self>,
14177 ) -> Result<()> {
14178 let selections = self.selections.disjoint_anchors();
14179 match selections.last() {
14180 Some(last) if selections.len() >= 2 => {
14181 self.change_selections(Default::default(), window, cx, |s| {
14182 s.select_ranges([last.range()]);
14183 });
14184 }
14185 _ => self.select_previous(
14186 &SelectPrevious {
14187 replace_newest: true,
14188 },
14189 window,
14190 cx,
14191 )?,
14192 }
14193 Ok(())
14194 }
14195
14196 pub fn toggle_comments(
14197 &mut self,
14198 action: &ToggleComments,
14199 window: &mut Window,
14200 cx: &mut Context<Self>,
14201 ) {
14202 if self.read_only(cx) {
14203 return;
14204 }
14205 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14206 let text_layout_details = &self.text_layout_details(window);
14207 self.transact(window, cx, |this, window, cx| {
14208 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
14209 let mut edits = Vec::new();
14210 let mut selection_edit_ranges = Vec::new();
14211 let mut last_toggled_row = None;
14212 let snapshot = this.buffer.read(cx).read(cx);
14213 let empty_str: Arc<str> = Arc::default();
14214 let mut suffixes_inserted = Vec::new();
14215 let ignore_indent = action.ignore_indent;
14216
14217 fn comment_prefix_range(
14218 snapshot: &MultiBufferSnapshot,
14219 row: MultiBufferRow,
14220 comment_prefix: &str,
14221 comment_prefix_whitespace: &str,
14222 ignore_indent: bool,
14223 ) -> Range<Point> {
14224 let indent_size = if ignore_indent {
14225 0
14226 } else {
14227 snapshot.indent_size_for_line(row).len
14228 };
14229
14230 let start = Point::new(row.0, indent_size);
14231
14232 let mut line_bytes = snapshot
14233 .bytes_in_range(start..snapshot.max_point())
14234 .flatten()
14235 .copied();
14236
14237 // If this line currently begins with the line comment prefix, then record
14238 // the range containing the prefix.
14239 if line_bytes
14240 .by_ref()
14241 .take(comment_prefix.len())
14242 .eq(comment_prefix.bytes())
14243 {
14244 // Include any whitespace that matches the comment prefix.
14245 let matching_whitespace_len = line_bytes
14246 .zip(comment_prefix_whitespace.bytes())
14247 .take_while(|(a, b)| a == b)
14248 .count() as u32;
14249 let end = Point::new(
14250 start.row,
14251 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14252 );
14253 start..end
14254 } else {
14255 start..start
14256 }
14257 }
14258
14259 fn comment_suffix_range(
14260 snapshot: &MultiBufferSnapshot,
14261 row: MultiBufferRow,
14262 comment_suffix: &str,
14263 comment_suffix_has_leading_space: bool,
14264 ) -> Range<Point> {
14265 let end = Point::new(row.0, snapshot.line_len(row));
14266 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14267
14268 let mut line_end_bytes = snapshot
14269 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14270 .flatten()
14271 .copied();
14272
14273 let leading_space_len = if suffix_start_column > 0
14274 && line_end_bytes.next() == Some(b' ')
14275 && comment_suffix_has_leading_space
14276 {
14277 1
14278 } else {
14279 0
14280 };
14281
14282 // If this line currently begins with the line comment prefix, then record
14283 // the range containing the prefix.
14284 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
14285 let start = Point::new(end.row, suffix_start_column - leading_space_len);
14286 start..end
14287 } else {
14288 end..end
14289 }
14290 }
14291
14292 // TODO: Handle selections that cross excerpts
14293 for selection in &mut selections {
14294 let start_column = snapshot
14295 .indent_size_for_line(MultiBufferRow(selection.start.row))
14296 .len;
14297 let language = if let Some(language) =
14298 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
14299 {
14300 language
14301 } else {
14302 continue;
14303 };
14304
14305 selection_edit_ranges.clear();
14306
14307 // If multiple selections contain a given row, avoid processing that
14308 // row more than once.
14309 let mut start_row = MultiBufferRow(selection.start.row);
14310 if last_toggled_row == Some(start_row) {
14311 start_row = start_row.next_row();
14312 }
14313 let end_row =
14314 if selection.end.row > selection.start.row && selection.end.column == 0 {
14315 MultiBufferRow(selection.end.row - 1)
14316 } else {
14317 MultiBufferRow(selection.end.row)
14318 };
14319 last_toggled_row = Some(end_row);
14320
14321 if start_row > end_row {
14322 continue;
14323 }
14324
14325 // If the language has line comments, toggle those.
14326 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
14327
14328 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
14329 if ignore_indent {
14330 full_comment_prefixes = full_comment_prefixes
14331 .into_iter()
14332 .map(|s| Arc::from(s.trim_end()))
14333 .collect();
14334 }
14335
14336 if !full_comment_prefixes.is_empty() {
14337 let first_prefix = full_comment_prefixes
14338 .first()
14339 .expect("prefixes is non-empty");
14340 let prefix_trimmed_lengths = full_comment_prefixes
14341 .iter()
14342 .map(|p| p.trim_end_matches(' ').len())
14343 .collect::<SmallVec<[usize; 4]>>();
14344
14345 let mut all_selection_lines_are_comments = true;
14346
14347 for row in start_row.0..=end_row.0 {
14348 let row = MultiBufferRow(row);
14349 if start_row < end_row && snapshot.is_line_blank(row) {
14350 continue;
14351 }
14352
14353 let prefix_range = full_comment_prefixes
14354 .iter()
14355 .zip(prefix_trimmed_lengths.iter().copied())
14356 .map(|(prefix, trimmed_prefix_len)| {
14357 comment_prefix_range(
14358 snapshot.deref(),
14359 row,
14360 &prefix[..trimmed_prefix_len],
14361 &prefix[trimmed_prefix_len..],
14362 ignore_indent,
14363 )
14364 })
14365 .max_by_key(|range| range.end.column - range.start.column)
14366 .expect("prefixes is non-empty");
14367
14368 if prefix_range.is_empty() {
14369 all_selection_lines_are_comments = false;
14370 }
14371
14372 selection_edit_ranges.push(prefix_range);
14373 }
14374
14375 if all_selection_lines_are_comments {
14376 edits.extend(
14377 selection_edit_ranges
14378 .iter()
14379 .cloned()
14380 .map(|range| (range, empty_str.clone())),
14381 );
14382 } else {
14383 let min_column = selection_edit_ranges
14384 .iter()
14385 .map(|range| range.start.column)
14386 .min()
14387 .unwrap_or(0);
14388 edits.extend(selection_edit_ranges.iter().map(|range| {
14389 let position = Point::new(range.start.row, min_column);
14390 (position..position, first_prefix.clone())
14391 }));
14392 }
14393 } else if let Some(BlockCommentConfig {
14394 start: full_comment_prefix,
14395 end: comment_suffix,
14396 ..
14397 }) = language.block_comment()
14398 {
14399 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
14400 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
14401 let prefix_range = comment_prefix_range(
14402 snapshot.deref(),
14403 start_row,
14404 comment_prefix,
14405 comment_prefix_whitespace,
14406 ignore_indent,
14407 );
14408 let suffix_range = comment_suffix_range(
14409 snapshot.deref(),
14410 end_row,
14411 comment_suffix.trim_start_matches(' '),
14412 comment_suffix.starts_with(' '),
14413 );
14414
14415 if prefix_range.is_empty() || suffix_range.is_empty() {
14416 edits.push((
14417 prefix_range.start..prefix_range.start,
14418 full_comment_prefix.clone(),
14419 ));
14420 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
14421 suffixes_inserted.push((end_row, comment_suffix.len()));
14422 } else {
14423 edits.push((prefix_range, empty_str.clone()));
14424 edits.push((suffix_range, empty_str.clone()));
14425 }
14426 } else {
14427 continue;
14428 }
14429 }
14430
14431 drop(snapshot);
14432 this.buffer.update(cx, |buffer, cx| {
14433 buffer.edit(edits, None, cx);
14434 });
14435
14436 // Adjust selections so that they end before any comment suffixes that
14437 // were inserted.
14438 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
14439 let mut selections = this.selections.all::<Point>(cx);
14440 let snapshot = this.buffer.read(cx).read(cx);
14441 for selection in &mut selections {
14442 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
14443 match row.cmp(&MultiBufferRow(selection.end.row)) {
14444 Ordering::Less => {
14445 suffixes_inserted.next();
14446 continue;
14447 }
14448 Ordering::Greater => break,
14449 Ordering::Equal => {
14450 if selection.end.column == snapshot.line_len(row) {
14451 if selection.is_empty() {
14452 selection.start.column -= suffix_len as u32;
14453 }
14454 selection.end.column -= suffix_len as u32;
14455 }
14456 break;
14457 }
14458 }
14459 }
14460 }
14461
14462 drop(snapshot);
14463 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
14464
14465 let selections = this.selections.all::<Point>(cx);
14466 let selections_on_single_row = selections.windows(2).all(|selections| {
14467 selections[0].start.row == selections[1].start.row
14468 && selections[0].end.row == selections[1].end.row
14469 && selections[0].start.row == selections[0].end.row
14470 });
14471 let selections_selecting = selections
14472 .iter()
14473 .any(|selection| selection.start != selection.end);
14474 let advance_downwards = action.advance_downwards
14475 && selections_on_single_row
14476 && !selections_selecting
14477 && !matches!(this.mode, EditorMode::SingleLine { .. });
14478
14479 if advance_downwards {
14480 let snapshot = this.buffer.read(cx).snapshot(cx);
14481
14482 this.change_selections(Default::default(), window, cx, |s| {
14483 s.move_cursors_with(|display_snapshot, display_point, _| {
14484 let mut point = display_point.to_point(display_snapshot);
14485 point.row += 1;
14486 point = snapshot.clip_point(point, Bias::Left);
14487 let display_point = point.to_display_point(display_snapshot);
14488 let goal = SelectionGoal::HorizontalPosition(
14489 display_snapshot
14490 .x_for_display_point(display_point, text_layout_details)
14491 .into(),
14492 );
14493 (display_point, goal)
14494 })
14495 });
14496 }
14497 });
14498 }
14499
14500 pub fn select_enclosing_symbol(
14501 &mut self,
14502 _: &SelectEnclosingSymbol,
14503 window: &mut Window,
14504 cx: &mut Context<Self>,
14505 ) {
14506 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14507
14508 let buffer = self.buffer.read(cx).snapshot(cx);
14509 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
14510
14511 fn update_selection(
14512 selection: &Selection<usize>,
14513 buffer_snap: &MultiBufferSnapshot,
14514 ) -> Option<Selection<usize>> {
14515 let cursor = selection.head();
14516 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
14517 for symbol in symbols.iter().rev() {
14518 let start = symbol.range.start.to_offset(buffer_snap);
14519 let end = symbol.range.end.to_offset(buffer_snap);
14520 let new_range = start..end;
14521 if start < selection.start || end > selection.end {
14522 return Some(Selection {
14523 id: selection.id,
14524 start: new_range.start,
14525 end: new_range.end,
14526 goal: SelectionGoal::None,
14527 reversed: selection.reversed,
14528 });
14529 }
14530 }
14531 None
14532 }
14533
14534 let mut selected_larger_symbol = false;
14535 let new_selections = old_selections
14536 .iter()
14537 .map(|selection| match update_selection(selection, &buffer) {
14538 Some(new_selection) => {
14539 if new_selection.range() != selection.range() {
14540 selected_larger_symbol = true;
14541 }
14542 new_selection
14543 }
14544 None => selection.clone(),
14545 })
14546 .collect::<Vec<_>>();
14547
14548 if selected_larger_symbol {
14549 self.change_selections(Default::default(), window, cx, |s| {
14550 s.select(new_selections);
14551 });
14552 }
14553 }
14554
14555 pub fn select_larger_syntax_node(
14556 &mut self,
14557 _: &SelectLargerSyntaxNode,
14558 window: &mut Window,
14559 cx: &mut Context<Self>,
14560 ) {
14561 let Some(visible_row_count) = self.visible_row_count() else {
14562 return;
14563 };
14564 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
14565 if old_selections.is_empty() {
14566 return;
14567 }
14568
14569 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14570
14571 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14572 let buffer = self.buffer.read(cx).snapshot(cx);
14573
14574 let mut selected_larger_node = false;
14575 let mut new_selections = old_selections
14576 .iter()
14577 .map(|selection| {
14578 let old_range = selection.start..selection.end;
14579
14580 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
14581 // manually select word at selection
14582 if ["string_content", "inline"].contains(&node.kind()) {
14583 let (word_range, _) = buffer.surrounding_word(old_range.start, false);
14584 // ignore if word is already selected
14585 if !word_range.is_empty() && old_range != word_range {
14586 let (last_word_range, _) =
14587 buffer.surrounding_word(old_range.end, false);
14588 // only select word if start and end point belongs to same word
14589 if word_range == last_word_range {
14590 selected_larger_node = true;
14591 return Selection {
14592 id: selection.id,
14593 start: word_range.start,
14594 end: word_range.end,
14595 goal: SelectionGoal::None,
14596 reversed: selection.reversed,
14597 };
14598 }
14599 }
14600 }
14601 }
14602
14603 let mut new_range = old_range.clone();
14604 while let Some((_node, containing_range)) =
14605 buffer.syntax_ancestor(new_range.clone())
14606 {
14607 new_range = match containing_range {
14608 MultiOrSingleBufferOffsetRange::Single(_) => break,
14609 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14610 };
14611 if !display_map.intersects_fold(new_range.start)
14612 && !display_map.intersects_fold(new_range.end)
14613 {
14614 break;
14615 }
14616 }
14617
14618 selected_larger_node |= new_range != old_range;
14619 Selection {
14620 id: selection.id,
14621 start: new_range.start,
14622 end: new_range.end,
14623 goal: SelectionGoal::None,
14624 reversed: selection.reversed,
14625 }
14626 })
14627 .collect::<Vec<_>>();
14628
14629 if !selected_larger_node {
14630 return; // don't put this call in the history
14631 }
14632
14633 // scroll based on transformation done to the last selection created by the user
14634 let (last_old, last_new) = old_selections
14635 .last()
14636 .zip(new_selections.last().cloned())
14637 .expect("old_selections isn't empty");
14638
14639 // revert selection
14640 let is_selection_reversed = {
14641 let should_newest_selection_be_reversed = last_old.start != last_new.start;
14642 new_selections.last_mut().expect("checked above").reversed =
14643 should_newest_selection_be_reversed;
14644 should_newest_selection_be_reversed
14645 };
14646
14647 if selected_larger_node {
14648 self.select_syntax_node_history.disable_clearing = true;
14649 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14650 s.select(new_selections.clone());
14651 });
14652 self.select_syntax_node_history.disable_clearing = false;
14653 }
14654
14655 let start_row = last_new.start.to_display_point(&display_map).row().0;
14656 let end_row = last_new.end.to_display_point(&display_map).row().0;
14657 let selection_height = end_row - start_row + 1;
14658 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
14659
14660 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
14661 let scroll_behavior = if fits_on_the_screen {
14662 self.request_autoscroll(Autoscroll::fit(), cx);
14663 SelectSyntaxNodeScrollBehavior::FitSelection
14664 } else if is_selection_reversed {
14665 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14666 SelectSyntaxNodeScrollBehavior::CursorTop
14667 } else {
14668 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14669 SelectSyntaxNodeScrollBehavior::CursorBottom
14670 };
14671
14672 self.select_syntax_node_history.push((
14673 old_selections,
14674 scroll_behavior,
14675 is_selection_reversed,
14676 ));
14677 }
14678
14679 pub fn select_smaller_syntax_node(
14680 &mut self,
14681 _: &SelectSmallerSyntaxNode,
14682 window: &mut Window,
14683 cx: &mut Context<Self>,
14684 ) {
14685 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14686
14687 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
14688 self.select_syntax_node_history.pop()
14689 {
14690 if let Some(selection) = selections.last_mut() {
14691 selection.reversed = is_selection_reversed;
14692 }
14693
14694 self.select_syntax_node_history.disable_clearing = true;
14695 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14696 s.select(selections.to_vec());
14697 });
14698 self.select_syntax_node_history.disable_clearing = false;
14699
14700 match scroll_behavior {
14701 SelectSyntaxNodeScrollBehavior::CursorTop => {
14702 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14703 }
14704 SelectSyntaxNodeScrollBehavior::FitSelection => {
14705 self.request_autoscroll(Autoscroll::fit(), cx);
14706 }
14707 SelectSyntaxNodeScrollBehavior::CursorBottom => {
14708 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14709 }
14710 }
14711 }
14712 }
14713
14714 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
14715 if !EditorSettings::get_global(cx).gutter.runnables {
14716 self.clear_tasks();
14717 return Task::ready(());
14718 }
14719 let project = self.project.as_ref().map(Entity::downgrade);
14720 let task_sources = self.lsp_task_sources(cx);
14721 let multi_buffer = self.buffer.downgrade();
14722 cx.spawn_in(window, async move |editor, cx| {
14723 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
14724 let Some(project) = project.and_then(|p| p.upgrade()) else {
14725 return;
14726 };
14727 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
14728 this.display_map.update(cx, |map, cx| map.snapshot(cx))
14729 }) else {
14730 return;
14731 };
14732
14733 let hide_runnables = project
14734 .update(cx, |project, cx| {
14735 // Do not display any test indicators in non-dev server remote projects.
14736 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
14737 })
14738 .unwrap_or(true);
14739 if hide_runnables {
14740 return;
14741 }
14742 let new_rows =
14743 cx.background_spawn({
14744 let snapshot = display_snapshot.clone();
14745 async move {
14746 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
14747 }
14748 })
14749 .await;
14750 let Ok(lsp_tasks) =
14751 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
14752 else {
14753 return;
14754 };
14755 let lsp_tasks = lsp_tasks.await;
14756
14757 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
14758 lsp_tasks
14759 .into_iter()
14760 .flat_map(|(kind, tasks)| {
14761 tasks.into_iter().filter_map(move |(location, task)| {
14762 Some((kind.clone(), location?, task))
14763 })
14764 })
14765 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
14766 let buffer = location.target.buffer;
14767 let buffer_snapshot = buffer.read(cx).snapshot();
14768 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
14769 |(excerpt_id, snapshot, _)| {
14770 if snapshot.remote_id() == buffer_snapshot.remote_id() {
14771 display_snapshot
14772 .buffer_snapshot
14773 .anchor_in_excerpt(excerpt_id, location.target.range.start)
14774 } else {
14775 None
14776 }
14777 },
14778 );
14779 if let Some(offset) = offset {
14780 let task_buffer_range =
14781 location.target.range.to_point(&buffer_snapshot);
14782 let context_buffer_range =
14783 task_buffer_range.to_offset(&buffer_snapshot);
14784 let context_range = BufferOffset(context_buffer_range.start)
14785 ..BufferOffset(context_buffer_range.end);
14786
14787 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
14788 .or_insert_with(|| RunnableTasks {
14789 templates: Vec::new(),
14790 offset,
14791 column: task_buffer_range.start.column,
14792 extra_variables: HashMap::default(),
14793 context_range,
14794 })
14795 .templates
14796 .push((kind, task.original_task().clone()));
14797 }
14798
14799 acc
14800 })
14801 }) else {
14802 return;
14803 };
14804
14805 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
14806 buffer.language_settings(cx).tasks.prefer_lsp
14807 }) else {
14808 return;
14809 };
14810
14811 let rows = Self::runnable_rows(
14812 project,
14813 display_snapshot,
14814 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
14815 new_rows,
14816 cx.clone(),
14817 )
14818 .await;
14819 editor
14820 .update(cx, |editor, _| {
14821 editor.clear_tasks();
14822 for (key, mut value) in rows {
14823 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
14824 value.templates.extend(lsp_tasks.templates);
14825 }
14826
14827 editor.insert_tasks(key, value);
14828 }
14829 for (key, value) in lsp_tasks_by_rows {
14830 editor.insert_tasks(key, value);
14831 }
14832 })
14833 .ok();
14834 })
14835 }
14836 fn fetch_runnable_ranges(
14837 snapshot: &DisplaySnapshot,
14838 range: Range<Anchor>,
14839 ) -> Vec<language::RunnableRange> {
14840 snapshot.buffer_snapshot.runnable_ranges(range).collect()
14841 }
14842
14843 fn runnable_rows(
14844 project: Entity<Project>,
14845 snapshot: DisplaySnapshot,
14846 prefer_lsp: bool,
14847 runnable_ranges: Vec<RunnableRange>,
14848 cx: AsyncWindowContext,
14849 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
14850 cx.spawn(async move |cx| {
14851 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
14852 for mut runnable in runnable_ranges {
14853 let Some(tasks) = cx
14854 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
14855 .ok()
14856 else {
14857 continue;
14858 };
14859 let mut tasks = tasks.await;
14860
14861 if prefer_lsp {
14862 tasks.retain(|(task_kind, _)| {
14863 !matches!(task_kind, TaskSourceKind::Language { .. })
14864 });
14865 }
14866 if tasks.is_empty() {
14867 continue;
14868 }
14869
14870 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
14871 let Some(row) = snapshot
14872 .buffer_snapshot
14873 .buffer_line_for_row(MultiBufferRow(point.row))
14874 .map(|(_, range)| range.start.row)
14875 else {
14876 continue;
14877 };
14878
14879 let context_range =
14880 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
14881 runnable_rows.push((
14882 (runnable.buffer_id, row),
14883 RunnableTasks {
14884 templates: tasks,
14885 offset: snapshot
14886 .buffer_snapshot
14887 .anchor_before(runnable.run_range.start),
14888 context_range,
14889 column: point.column,
14890 extra_variables: runnable.extra_captures,
14891 },
14892 ));
14893 }
14894 runnable_rows
14895 })
14896 }
14897
14898 fn templates_with_tags(
14899 project: &Entity<Project>,
14900 runnable: &mut Runnable,
14901 cx: &mut App,
14902 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
14903 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
14904 let (worktree_id, file) = project
14905 .buffer_for_id(runnable.buffer, cx)
14906 .and_then(|buffer| buffer.read(cx).file())
14907 .map(|file| (file.worktree_id(cx), file.clone()))
14908 .unzip();
14909
14910 (
14911 project.task_store().read(cx).task_inventory().cloned(),
14912 worktree_id,
14913 file,
14914 )
14915 });
14916
14917 let tags = mem::take(&mut runnable.tags);
14918 let language = runnable.language.clone();
14919 cx.spawn(async move |cx| {
14920 let mut templates_with_tags = Vec::new();
14921 if let Some(inventory) = inventory {
14922 for RunnableTag(tag) in tags {
14923 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
14924 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
14925 }) else {
14926 return templates_with_tags;
14927 };
14928 templates_with_tags.extend(new_tasks.await.into_iter().filter(
14929 move |(_, template)| {
14930 template.tags.iter().any(|source_tag| source_tag == &tag)
14931 },
14932 ));
14933 }
14934 }
14935 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
14936
14937 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
14938 // Strongest source wins; if we have worktree tag binding, prefer that to
14939 // global and language bindings;
14940 // if we have a global binding, prefer that to language binding.
14941 let first_mismatch = templates_with_tags
14942 .iter()
14943 .position(|(tag_source, _)| tag_source != leading_tag_source);
14944 if let Some(index) = first_mismatch {
14945 templates_with_tags.truncate(index);
14946 }
14947 }
14948
14949 templates_with_tags
14950 })
14951 }
14952
14953 pub fn move_to_enclosing_bracket(
14954 &mut self,
14955 _: &MoveToEnclosingBracket,
14956 window: &mut Window,
14957 cx: &mut Context<Self>,
14958 ) {
14959 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14960 self.change_selections(Default::default(), window, cx, |s| {
14961 s.move_offsets_with(|snapshot, selection| {
14962 let Some(enclosing_bracket_ranges) =
14963 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
14964 else {
14965 return;
14966 };
14967
14968 let mut best_length = usize::MAX;
14969 let mut best_inside = false;
14970 let mut best_in_bracket_range = false;
14971 let mut best_destination = None;
14972 for (open, close) in enclosing_bracket_ranges {
14973 let close = close.to_inclusive();
14974 let length = close.end() - open.start;
14975 let inside = selection.start >= open.end && selection.end <= *close.start();
14976 let in_bracket_range = open.to_inclusive().contains(&selection.head())
14977 || close.contains(&selection.head());
14978
14979 // If best is next to a bracket and current isn't, skip
14980 if !in_bracket_range && best_in_bracket_range {
14981 continue;
14982 }
14983
14984 // Prefer smaller lengths unless best is inside and current isn't
14985 if length > best_length && (best_inside || !inside) {
14986 continue;
14987 }
14988
14989 best_length = length;
14990 best_inside = inside;
14991 best_in_bracket_range = in_bracket_range;
14992 best_destination = Some(
14993 if close.contains(&selection.start) && close.contains(&selection.end) {
14994 if inside { open.end } else { open.start }
14995 } else if inside {
14996 *close.start()
14997 } else {
14998 *close.end()
14999 },
15000 );
15001 }
15002
15003 if let Some(destination) = best_destination {
15004 selection.collapse_to(destination, SelectionGoal::None);
15005 }
15006 })
15007 });
15008 }
15009
15010 pub fn undo_selection(
15011 &mut self,
15012 _: &UndoSelection,
15013 window: &mut Window,
15014 cx: &mut Context<Self>,
15015 ) {
15016 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15017 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
15018 self.selection_history.mode = SelectionHistoryMode::Undoing;
15019 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15020 this.end_selection(window, cx);
15021 this.change_selections(
15022 SelectionEffects::scroll(Autoscroll::newest()),
15023 window,
15024 cx,
15025 |s| s.select_anchors(entry.selections.to_vec()),
15026 );
15027 });
15028 self.selection_history.mode = SelectionHistoryMode::Normal;
15029
15030 self.select_next_state = entry.select_next_state;
15031 self.select_prev_state = entry.select_prev_state;
15032 self.add_selections_state = entry.add_selections_state;
15033 }
15034 }
15035
15036 pub fn redo_selection(
15037 &mut self,
15038 _: &RedoSelection,
15039 window: &mut Window,
15040 cx: &mut Context<Self>,
15041 ) {
15042 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15043 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
15044 self.selection_history.mode = SelectionHistoryMode::Redoing;
15045 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15046 this.end_selection(window, cx);
15047 this.change_selections(
15048 SelectionEffects::scroll(Autoscroll::newest()),
15049 window,
15050 cx,
15051 |s| s.select_anchors(entry.selections.to_vec()),
15052 );
15053 });
15054 self.selection_history.mode = SelectionHistoryMode::Normal;
15055
15056 self.select_next_state = entry.select_next_state;
15057 self.select_prev_state = entry.select_prev_state;
15058 self.add_selections_state = entry.add_selections_state;
15059 }
15060 }
15061
15062 pub fn expand_excerpts(
15063 &mut self,
15064 action: &ExpandExcerpts,
15065 _: &mut Window,
15066 cx: &mut Context<Self>,
15067 ) {
15068 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
15069 }
15070
15071 pub fn expand_excerpts_down(
15072 &mut self,
15073 action: &ExpandExcerptsDown,
15074 _: &mut Window,
15075 cx: &mut Context<Self>,
15076 ) {
15077 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
15078 }
15079
15080 pub fn expand_excerpts_up(
15081 &mut self,
15082 action: &ExpandExcerptsUp,
15083 _: &mut Window,
15084 cx: &mut Context<Self>,
15085 ) {
15086 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
15087 }
15088
15089 pub fn expand_excerpts_for_direction(
15090 &mut self,
15091 lines: u32,
15092 direction: ExpandExcerptDirection,
15093
15094 cx: &mut Context<Self>,
15095 ) {
15096 let selections = self.selections.disjoint_anchors();
15097
15098 let lines = if lines == 0 {
15099 EditorSettings::get_global(cx).expand_excerpt_lines
15100 } else {
15101 lines
15102 };
15103
15104 self.buffer.update(cx, |buffer, cx| {
15105 let snapshot = buffer.snapshot(cx);
15106 let mut excerpt_ids = selections
15107 .iter()
15108 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
15109 .collect::<Vec<_>>();
15110 excerpt_ids.sort();
15111 excerpt_ids.dedup();
15112 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
15113 })
15114 }
15115
15116 pub fn expand_excerpt(
15117 &mut self,
15118 excerpt: ExcerptId,
15119 direction: ExpandExcerptDirection,
15120 window: &mut Window,
15121 cx: &mut Context<Self>,
15122 ) {
15123 let current_scroll_position = self.scroll_position(cx);
15124 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
15125 let mut should_scroll_up = false;
15126
15127 if direction == ExpandExcerptDirection::Down {
15128 let multi_buffer = self.buffer.read(cx);
15129 let snapshot = multi_buffer.snapshot(cx);
15130 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
15131 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
15132 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
15133 let buffer_snapshot = buffer.read(cx).snapshot();
15134 let excerpt_end_row =
15135 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
15136 let last_row = buffer_snapshot.max_point().row;
15137 let lines_below = last_row.saturating_sub(excerpt_end_row);
15138 should_scroll_up = lines_below >= lines_to_expand;
15139 }
15140 }
15141 }
15142 }
15143
15144 self.buffer.update(cx, |buffer, cx| {
15145 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
15146 });
15147
15148 if should_scroll_up {
15149 let new_scroll_position =
15150 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
15151 self.set_scroll_position(new_scroll_position, window, cx);
15152 }
15153 }
15154
15155 pub fn go_to_singleton_buffer_point(
15156 &mut self,
15157 point: Point,
15158 window: &mut Window,
15159 cx: &mut Context<Self>,
15160 ) {
15161 self.go_to_singleton_buffer_range(point..point, window, cx);
15162 }
15163
15164 pub fn go_to_singleton_buffer_range(
15165 &mut self,
15166 range: Range<Point>,
15167 window: &mut Window,
15168 cx: &mut Context<Self>,
15169 ) {
15170 let multibuffer = self.buffer().read(cx);
15171 let Some(buffer) = multibuffer.as_singleton() else {
15172 return;
15173 };
15174 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
15175 return;
15176 };
15177 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
15178 return;
15179 };
15180 self.change_selections(
15181 SelectionEffects::default().nav_history(true),
15182 window,
15183 cx,
15184 |s| s.select_anchor_ranges([start..end]),
15185 );
15186 }
15187
15188 pub fn go_to_diagnostic(
15189 &mut self,
15190 action: &GoToDiagnostic,
15191 window: &mut Window,
15192 cx: &mut Context<Self>,
15193 ) {
15194 if !self.diagnostics_enabled() {
15195 return;
15196 }
15197 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15198 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
15199 }
15200
15201 pub fn go_to_prev_diagnostic(
15202 &mut self,
15203 action: &GoToPreviousDiagnostic,
15204 window: &mut Window,
15205 cx: &mut Context<Self>,
15206 ) {
15207 if !self.diagnostics_enabled() {
15208 return;
15209 }
15210 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15211 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
15212 }
15213
15214 pub fn go_to_diagnostic_impl(
15215 &mut self,
15216 direction: Direction,
15217 severity: GoToDiagnosticSeverityFilter,
15218 window: &mut Window,
15219 cx: &mut Context<Self>,
15220 ) {
15221 let buffer = self.buffer.read(cx).snapshot(cx);
15222 let selection = self.selections.newest::<usize>(cx);
15223
15224 let mut active_group_id = None;
15225 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
15226 if active_group.active_range.start.to_offset(&buffer) == selection.start {
15227 active_group_id = Some(active_group.group_id);
15228 }
15229 }
15230
15231 fn filtered(
15232 snapshot: EditorSnapshot,
15233 severity: GoToDiagnosticSeverityFilter,
15234 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
15235 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
15236 diagnostics
15237 .filter(move |entry| severity.matches(entry.diagnostic.severity))
15238 .filter(|entry| entry.range.start != entry.range.end)
15239 .filter(|entry| !entry.diagnostic.is_unnecessary)
15240 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
15241 }
15242
15243 let snapshot = self.snapshot(window, cx);
15244 let before = filtered(
15245 snapshot.clone(),
15246 severity,
15247 buffer
15248 .diagnostics_in_range(0..selection.start)
15249 .filter(|entry| entry.range.start <= selection.start),
15250 );
15251 let after = filtered(
15252 snapshot,
15253 severity,
15254 buffer
15255 .diagnostics_in_range(selection.start..buffer.len())
15256 .filter(|entry| entry.range.start >= selection.start),
15257 );
15258
15259 let mut found: Option<DiagnosticEntry<usize>> = None;
15260 if direction == Direction::Prev {
15261 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
15262 {
15263 for diagnostic in prev_diagnostics.into_iter().rev() {
15264 if diagnostic.range.start != selection.start
15265 || active_group_id
15266 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
15267 {
15268 found = Some(diagnostic);
15269 break 'outer;
15270 }
15271 }
15272 }
15273 } else {
15274 for diagnostic in after.chain(before) {
15275 if diagnostic.range.start != selection.start
15276 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
15277 {
15278 found = Some(diagnostic);
15279 break;
15280 }
15281 }
15282 }
15283 let Some(next_diagnostic) = found else {
15284 return;
15285 };
15286
15287 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
15288 return;
15289 };
15290 self.change_selections(Default::default(), window, cx, |s| {
15291 s.select_ranges(vec![
15292 next_diagnostic.range.start..next_diagnostic.range.start,
15293 ])
15294 });
15295 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
15296 self.refresh_edit_prediction(false, true, window, cx);
15297 }
15298
15299 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
15300 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15301 let snapshot = self.snapshot(window, cx);
15302 let selection = self.selections.newest::<Point>(cx);
15303 self.go_to_hunk_before_or_after_position(
15304 &snapshot,
15305 selection.head(),
15306 Direction::Next,
15307 window,
15308 cx,
15309 );
15310 }
15311
15312 pub fn go_to_hunk_before_or_after_position(
15313 &mut self,
15314 snapshot: &EditorSnapshot,
15315 position: Point,
15316 direction: Direction,
15317 window: &mut Window,
15318 cx: &mut Context<Editor>,
15319 ) {
15320 let row = if direction == Direction::Next {
15321 self.hunk_after_position(snapshot, position)
15322 .map(|hunk| hunk.row_range.start)
15323 } else {
15324 self.hunk_before_position(snapshot, position)
15325 };
15326
15327 if let Some(row) = row {
15328 let destination = Point::new(row.0, 0);
15329 let autoscroll = Autoscroll::center();
15330
15331 self.unfold_ranges(&[destination..destination], false, false, cx);
15332 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
15333 s.select_ranges([destination..destination]);
15334 });
15335 }
15336 }
15337
15338 fn hunk_after_position(
15339 &mut self,
15340 snapshot: &EditorSnapshot,
15341 position: Point,
15342 ) -> Option<MultiBufferDiffHunk> {
15343 snapshot
15344 .buffer_snapshot
15345 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
15346 .find(|hunk| hunk.row_range.start.0 > position.row)
15347 .or_else(|| {
15348 snapshot
15349 .buffer_snapshot
15350 .diff_hunks_in_range(Point::zero()..position)
15351 .find(|hunk| hunk.row_range.end.0 < position.row)
15352 })
15353 }
15354
15355 fn go_to_prev_hunk(
15356 &mut self,
15357 _: &GoToPreviousHunk,
15358 window: &mut Window,
15359 cx: &mut Context<Self>,
15360 ) {
15361 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15362 let snapshot = self.snapshot(window, cx);
15363 let selection = self.selections.newest::<Point>(cx);
15364 self.go_to_hunk_before_or_after_position(
15365 &snapshot,
15366 selection.head(),
15367 Direction::Prev,
15368 window,
15369 cx,
15370 );
15371 }
15372
15373 fn hunk_before_position(
15374 &mut self,
15375 snapshot: &EditorSnapshot,
15376 position: Point,
15377 ) -> Option<MultiBufferRow> {
15378 snapshot
15379 .buffer_snapshot
15380 .diff_hunk_before(position)
15381 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
15382 }
15383
15384 fn go_to_next_change(
15385 &mut self,
15386 _: &GoToNextChange,
15387 window: &mut Window,
15388 cx: &mut Context<Self>,
15389 ) {
15390 if let Some(selections) = self
15391 .change_list
15392 .next_change(1, Direction::Next)
15393 .map(|s| s.to_vec())
15394 {
15395 self.change_selections(Default::default(), window, cx, |s| {
15396 let map = s.display_map();
15397 s.select_display_ranges(selections.iter().map(|a| {
15398 let point = a.to_display_point(&map);
15399 point..point
15400 }))
15401 })
15402 }
15403 }
15404
15405 fn go_to_previous_change(
15406 &mut self,
15407 _: &GoToPreviousChange,
15408 window: &mut Window,
15409 cx: &mut Context<Self>,
15410 ) {
15411 if let Some(selections) = self
15412 .change_list
15413 .next_change(1, Direction::Prev)
15414 .map(|s| s.to_vec())
15415 {
15416 self.change_selections(Default::default(), window, cx, |s| {
15417 let map = s.display_map();
15418 s.select_display_ranges(selections.iter().map(|a| {
15419 let point = a.to_display_point(&map);
15420 point..point
15421 }))
15422 })
15423 }
15424 }
15425
15426 fn go_to_line<T: 'static>(
15427 &mut self,
15428 position: Anchor,
15429 highlight_color: Option<Hsla>,
15430 window: &mut Window,
15431 cx: &mut Context<Self>,
15432 ) {
15433 let snapshot = self.snapshot(window, cx).display_snapshot;
15434 let position = position.to_point(&snapshot.buffer_snapshot);
15435 let start = snapshot
15436 .buffer_snapshot
15437 .clip_point(Point::new(position.row, 0), Bias::Left);
15438 let end = start + Point::new(1, 0);
15439 let start = snapshot.buffer_snapshot.anchor_before(start);
15440 let end = snapshot.buffer_snapshot.anchor_before(end);
15441
15442 self.highlight_rows::<T>(
15443 start..end,
15444 highlight_color
15445 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
15446 Default::default(),
15447 cx,
15448 );
15449
15450 if self.buffer.read(cx).is_singleton() {
15451 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
15452 }
15453 }
15454
15455 pub fn go_to_definition(
15456 &mut self,
15457 _: &GoToDefinition,
15458 window: &mut Window,
15459 cx: &mut Context<Self>,
15460 ) -> Task<Result<Navigated>> {
15461 let definition =
15462 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
15463 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
15464 cx.spawn_in(window, async move |editor, cx| {
15465 if definition.await? == Navigated::Yes {
15466 return Ok(Navigated::Yes);
15467 }
15468 match fallback_strategy {
15469 GoToDefinitionFallback::None => Ok(Navigated::No),
15470 GoToDefinitionFallback::FindAllReferences => {
15471 match editor.update_in(cx, |editor, window, cx| {
15472 editor.find_all_references(&FindAllReferences, window, cx)
15473 })? {
15474 Some(references) => references.await,
15475 None => Ok(Navigated::No),
15476 }
15477 }
15478 }
15479 })
15480 }
15481
15482 pub fn go_to_declaration(
15483 &mut self,
15484 _: &GoToDeclaration,
15485 window: &mut Window,
15486 cx: &mut Context<Self>,
15487 ) -> Task<Result<Navigated>> {
15488 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
15489 }
15490
15491 pub fn go_to_declaration_split(
15492 &mut self,
15493 _: &GoToDeclaration,
15494 window: &mut Window,
15495 cx: &mut Context<Self>,
15496 ) -> Task<Result<Navigated>> {
15497 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
15498 }
15499
15500 pub fn go_to_implementation(
15501 &mut self,
15502 _: &GoToImplementation,
15503 window: &mut Window,
15504 cx: &mut Context<Self>,
15505 ) -> Task<Result<Navigated>> {
15506 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
15507 }
15508
15509 pub fn go_to_implementation_split(
15510 &mut self,
15511 _: &GoToImplementationSplit,
15512 window: &mut Window,
15513 cx: &mut Context<Self>,
15514 ) -> Task<Result<Navigated>> {
15515 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
15516 }
15517
15518 pub fn go_to_type_definition(
15519 &mut self,
15520 _: &GoToTypeDefinition,
15521 window: &mut Window,
15522 cx: &mut Context<Self>,
15523 ) -> Task<Result<Navigated>> {
15524 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
15525 }
15526
15527 pub fn go_to_definition_split(
15528 &mut self,
15529 _: &GoToDefinitionSplit,
15530 window: &mut Window,
15531 cx: &mut Context<Self>,
15532 ) -> Task<Result<Navigated>> {
15533 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
15534 }
15535
15536 pub fn go_to_type_definition_split(
15537 &mut self,
15538 _: &GoToTypeDefinitionSplit,
15539 window: &mut Window,
15540 cx: &mut Context<Self>,
15541 ) -> Task<Result<Navigated>> {
15542 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
15543 }
15544
15545 fn go_to_definition_of_kind(
15546 &mut self,
15547 kind: GotoDefinitionKind,
15548 split: bool,
15549 window: &mut Window,
15550 cx: &mut Context<Self>,
15551 ) -> Task<Result<Navigated>> {
15552 let Some(provider) = self.semantics_provider.clone() else {
15553 return Task::ready(Ok(Navigated::No));
15554 };
15555 let head = self.selections.newest::<usize>(cx).head();
15556 let buffer = self.buffer.read(cx);
15557 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
15558 text_anchor
15559 } else {
15560 return Task::ready(Ok(Navigated::No));
15561 };
15562
15563 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
15564 return Task::ready(Ok(Navigated::No));
15565 };
15566
15567 cx.spawn_in(window, async move |editor, cx| {
15568 let definitions = definitions.await?;
15569 let navigated = editor
15570 .update_in(cx, |editor, window, cx| {
15571 editor.navigate_to_hover_links(
15572 Some(kind),
15573 definitions
15574 .into_iter()
15575 .filter(|location| {
15576 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
15577 })
15578 .map(HoverLink::Text)
15579 .collect::<Vec<_>>(),
15580 split,
15581 window,
15582 cx,
15583 )
15584 })?
15585 .await?;
15586 anyhow::Ok(navigated)
15587 })
15588 }
15589
15590 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
15591 let selection = self.selections.newest_anchor();
15592 let head = selection.head();
15593 let tail = selection.tail();
15594
15595 let Some((buffer, start_position)) =
15596 self.buffer.read(cx).text_anchor_for_position(head, cx)
15597 else {
15598 return;
15599 };
15600
15601 let end_position = if head != tail {
15602 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
15603 return;
15604 };
15605 Some(pos)
15606 } else {
15607 None
15608 };
15609
15610 let url_finder = cx.spawn_in(window, async move |editor, cx| {
15611 let url = if let Some(end_pos) = end_position {
15612 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
15613 } else {
15614 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
15615 };
15616
15617 if let Some(url) = url {
15618 editor.update(cx, |_, cx| {
15619 cx.open_url(&url);
15620 })
15621 } else {
15622 Ok(())
15623 }
15624 });
15625
15626 url_finder.detach();
15627 }
15628
15629 pub fn open_selected_filename(
15630 &mut self,
15631 _: &OpenSelectedFilename,
15632 window: &mut Window,
15633 cx: &mut Context<Self>,
15634 ) {
15635 let Some(workspace) = self.workspace() else {
15636 return;
15637 };
15638
15639 let position = self.selections.newest_anchor().head();
15640
15641 let Some((buffer, buffer_position)) =
15642 self.buffer.read(cx).text_anchor_for_position(position, cx)
15643 else {
15644 return;
15645 };
15646
15647 let project = self.project.clone();
15648
15649 cx.spawn_in(window, async move |_, cx| {
15650 let result = find_file(&buffer, project, buffer_position, cx).await;
15651
15652 if let Some((_, path)) = result {
15653 workspace
15654 .update_in(cx, |workspace, window, cx| {
15655 workspace.open_resolved_path(path, window, cx)
15656 })?
15657 .await?;
15658 }
15659 anyhow::Ok(())
15660 })
15661 .detach();
15662 }
15663
15664 pub(crate) fn navigate_to_hover_links(
15665 &mut self,
15666 kind: Option<GotoDefinitionKind>,
15667 mut definitions: Vec<HoverLink>,
15668 split: bool,
15669 window: &mut Window,
15670 cx: &mut Context<Editor>,
15671 ) -> Task<Result<Navigated>> {
15672 // If there is one definition, just open it directly
15673 if definitions.len() == 1 {
15674 let definition = definitions.pop().unwrap();
15675
15676 enum TargetTaskResult {
15677 Location(Option<Location>),
15678 AlreadyNavigated,
15679 }
15680
15681 let target_task = match definition {
15682 HoverLink::Text(link) => {
15683 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
15684 }
15685 HoverLink::InlayHint(lsp_location, server_id) => {
15686 let computation =
15687 self.compute_target_location(lsp_location, server_id, window, cx);
15688 cx.background_spawn(async move {
15689 let location = computation.await?;
15690 Ok(TargetTaskResult::Location(location))
15691 })
15692 }
15693 HoverLink::Url(url) => {
15694 cx.open_url(&url);
15695 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
15696 }
15697 HoverLink::File(path) => {
15698 if let Some(workspace) = self.workspace() {
15699 cx.spawn_in(window, async move |_, cx| {
15700 workspace
15701 .update_in(cx, |workspace, window, cx| {
15702 workspace.open_resolved_path(path, window, cx)
15703 })?
15704 .await
15705 .map(|_| TargetTaskResult::AlreadyNavigated)
15706 })
15707 } else {
15708 Task::ready(Ok(TargetTaskResult::Location(None)))
15709 }
15710 }
15711 };
15712 cx.spawn_in(window, async move |editor, cx| {
15713 let target = match target_task.await.context("target resolution task")? {
15714 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
15715 TargetTaskResult::Location(None) => return Ok(Navigated::No),
15716 TargetTaskResult::Location(Some(target)) => target,
15717 };
15718
15719 editor.update_in(cx, |editor, window, cx| {
15720 let Some(workspace) = editor.workspace() else {
15721 return Navigated::No;
15722 };
15723 let pane = workspace.read(cx).active_pane().clone();
15724
15725 let range = target.range.to_point(target.buffer.read(cx));
15726 let range = editor.range_for_match(&range);
15727 let range = collapse_multiline_range(range);
15728
15729 if !split
15730 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
15731 {
15732 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
15733 } else {
15734 window.defer(cx, move |window, cx| {
15735 let target_editor: Entity<Self> =
15736 workspace.update(cx, |workspace, cx| {
15737 let pane = if split {
15738 workspace.adjacent_pane(window, cx)
15739 } else {
15740 workspace.active_pane().clone()
15741 };
15742
15743 workspace.open_project_item(
15744 pane,
15745 target.buffer.clone(),
15746 true,
15747 true,
15748 window,
15749 cx,
15750 )
15751 });
15752 target_editor.update(cx, |target_editor, cx| {
15753 // When selecting a definition in a different buffer, disable the nav history
15754 // to avoid creating a history entry at the previous cursor location.
15755 pane.update(cx, |pane, _| pane.disable_history());
15756 target_editor.go_to_singleton_buffer_range(range, window, cx);
15757 pane.update(cx, |pane, _| pane.enable_history());
15758 });
15759 });
15760 }
15761 Navigated::Yes
15762 })
15763 })
15764 } else if !definitions.is_empty() {
15765 cx.spawn_in(window, async move |editor, cx| {
15766 let (title, location_tasks, workspace) = editor
15767 .update_in(cx, |editor, window, cx| {
15768 let tab_kind = match kind {
15769 Some(GotoDefinitionKind::Implementation) => "Implementations",
15770 _ => "Definitions",
15771 };
15772 let title = definitions
15773 .iter()
15774 .find_map(|definition| match definition {
15775 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
15776 let buffer = origin.buffer.read(cx);
15777 format!(
15778 "{} for {}",
15779 tab_kind,
15780 buffer
15781 .text_for_range(origin.range.clone())
15782 .collect::<String>()
15783 )
15784 }),
15785 HoverLink::InlayHint(_, _) => None,
15786 HoverLink::Url(_) => None,
15787 HoverLink::File(_) => None,
15788 })
15789 .unwrap_or(tab_kind.to_string());
15790 let location_tasks = definitions
15791 .into_iter()
15792 .map(|definition| match definition {
15793 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
15794 HoverLink::InlayHint(lsp_location, server_id) => editor
15795 .compute_target_location(lsp_location, server_id, window, cx),
15796 HoverLink::Url(_) => Task::ready(Ok(None)),
15797 HoverLink::File(_) => Task::ready(Ok(None)),
15798 })
15799 .collect::<Vec<_>>();
15800 (title, location_tasks, editor.workspace().clone())
15801 })
15802 .context("location tasks preparation")?;
15803
15804 let locations: Vec<Location> = future::join_all(location_tasks)
15805 .await
15806 .into_iter()
15807 .filter_map(|location| location.transpose())
15808 .collect::<Result<_>>()
15809 .context("location tasks")?;
15810
15811 if locations.is_empty() {
15812 return Ok(Navigated::No);
15813 }
15814
15815 let Some(workspace) = workspace else {
15816 return Ok(Navigated::No);
15817 };
15818
15819 let opened = workspace
15820 .update_in(cx, |workspace, window, cx| {
15821 Self::open_locations_in_multibuffer(
15822 workspace,
15823 locations,
15824 title,
15825 split,
15826 MultibufferSelectionMode::First,
15827 window,
15828 cx,
15829 )
15830 })
15831 .ok();
15832
15833 anyhow::Ok(Navigated::from_bool(opened.is_some()))
15834 })
15835 } else {
15836 Task::ready(Ok(Navigated::No))
15837 }
15838 }
15839
15840 fn compute_target_location(
15841 &self,
15842 lsp_location: lsp::Location,
15843 server_id: LanguageServerId,
15844 window: &mut Window,
15845 cx: &mut Context<Self>,
15846 ) -> Task<anyhow::Result<Option<Location>>> {
15847 let Some(project) = self.project.clone() else {
15848 return Task::ready(Ok(None));
15849 };
15850
15851 cx.spawn_in(window, async move |editor, cx| {
15852 let location_task = editor.update(cx, |_, cx| {
15853 project.update(cx, |project, cx| {
15854 let language_server_name = project
15855 .language_server_statuses(cx)
15856 .find(|(id, _)| server_id == *id)
15857 .map(|(_, status)| status.name.clone());
15858 language_server_name.map(|language_server_name| {
15859 project.open_local_buffer_via_lsp(
15860 lsp_location.uri.clone(),
15861 server_id,
15862 language_server_name,
15863 cx,
15864 )
15865 })
15866 })
15867 })?;
15868 let location = match location_task {
15869 Some(task) => Some({
15870 let target_buffer_handle = task.await.context("open local buffer")?;
15871 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
15872 let target_start = target_buffer
15873 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
15874 let target_end = target_buffer
15875 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
15876 target_buffer.anchor_after(target_start)
15877 ..target_buffer.anchor_before(target_end)
15878 })?;
15879 Location {
15880 buffer: target_buffer_handle,
15881 range,
15882 }
15883 }),
15884 None => None,
15885 };
15886 Ok(location)
15887 })
15888 }
15889
15890 pub fn find_all_references(
15891 &mut self,
15892 _: &FindAllReferences,
15893 window: &mut Window,
15894 cx: &mut Context<Self>,
15895 ) -> Option<Task<Result<Navigated>>> {
15896 let selection = self.selections.newest::<usize>(cx);
15897 let multi_buffer = self.buffer.read(cx);
15898 let head = selection.head();
15899
15900 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
15901 let head_anchor = multi_buffer_snapshot.anchor_at(
15902 head,
15903 if head < selection.tail() {
15904 Bias::Right
15905 } else {
15906 Bias::Left
15907 },
15908 );
15909
15910 match self
15911 .find_all_references_task_sources
15912 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15913 {
15914 Ok(_) => {
15915 log::info!(
15916 "Ignoring repeated FindAllReferences invocation with the position of already running task"
15917 );
15918 return None;
15919 }
15920 Err(i) => {
15921 self.find_all_references_task_sources.insert(i, head_anchor);
15922 }
15923 }
15924
15925 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
15926 let workspace = self.workspace()?;
15927 let project = workspace.read(cx).project().clone();
15928 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
15929 Some(cx.spawn_in(window, async move |editor, cx| {
15930 let _cleanup = cx.on_drop(&editor, move |editor, _| {
15931 if let Ok(i) = editor
15932 .find_all_references_task_sources
15933 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15934 {
15935 editor.find_all_references_task_sources.remove(i);
15936 }
15937 });
15938
15939 let locations = references.await?;
15940 if locations.is_empty() {
15941 return anyhow::Ok(Navigated::No);
15942 }
15943
15944 workspace.update_in(cx, |workspace, window, cx| {
15945 let title = locations
15946 .first()
15947 .as_ref()
15948 .map(|location| {
15949 let buffer = location.buffer.read(cx);
15950 format!(
15951 "References to `{}`",
15952 buffer
15953 .text_for_range(location.range.clone())
15954 .collect::<String>()
15955 )
15956 })
15957 .unwrap();
15958 Self::open_locations_in_multibuffer(
15959 workspace,
15960 locations,
15961 title,
15962 false,
15963 MultibufferSelectionMode::First,
15964 window,
15965 cx,
15966 );
15967 Navigated::Yes
15968 })
15969 }))
15970 }
15971
15972 /// Opens a multibuffer with the given project locations in it
15973 pub fn open_locations_in_multibuffer(
15974 workspace: &mut Workspace,
15975 mut locations: Vec<Location>,
15976 title: String,
15977 split: bool,
15978 multibuffer_selection_mode: MultibufferSelectionMode,
15979 window: &mut Window,
15980 cx: &mut Context<Workspace>,
15981 ) {
15982 if locations.is_empty() {
15983 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
15984 return;
15985 }
15986
15987 // If there are multiple definitions, open them in a multibuffer
15988 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
15989 let mut locations = locations.into_iter().peekable();
15990 let mut ranges: Vec<Range<Anchor>> = Vec::new();
15991 let capability = workspace.project().read(cx).capability();
15992
15993 let excerpt_buffer = cx.new(|cx| {
15994 let mut multibuffer = MultiBuffer::new(capability);
15995 while let Some(location) = locations.next() {
15996 let buffer = location.buffer.read(cx);
15997 let mut ranges_for_buffer = Vec::new();
15998 let range = location.range.to_point(buffer);
15999 ranges_for_buffer.push(range.clone());
16000
16001 while let Some(next_location) = locations.peek() {
16002 if next_location.buffer == location.buffer {
16003 ranges_for_buffer.push(next_location.range.to_point(buffer));
16004 locations.next();
16005 } else {
16006 break;
16007 }
16008 }
16009
16010 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
16011 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
16012 PathKey::for_buffer(&location.buffer, cx),
16013 location.buffer.clone(),
16014 ranges_for_buffer,
16015 DEFAULT_MULTIBUFFER_CONTEXT,
16016 cx,
16017 );
16018 ranges.extend(new_ranges)
16019 }
16020
16021 multibuffer.with_title(title)
16022 });
16023
16024 let editor = cx.new(|cx| {
16025 Editor::for_multibuffer(
16026 excerpt_buffer,
16027 Some(workspace.project().clone()),
16028 window,
16029 cx,
16030 )
16031 });
16032 editor.update(cx, |editor, cx| {
16033 match multibuffer_selection_mode {
16034 MultibufferSelectionMode::First => {
16035 if let Some(first_range) = ranges.first() {
16036 editor.change_selections(
16037 SelectionEffects::no_scroll(),
16038 window,
16039 cx,
16040 |selections| {
16041 selections.clear_disjoint();
16042 selections
16043 .select_anchor_ranges(std::iter::once(first_range.clone()));
16044 },
16045 );
16046 }
16047 editor.highlight_background::<Self>(
16048 &ranges,
16049 |theme| theme.colors().editor_highlighted_line_background,
16050 cx,
16051 );
16052 }
16053 MultibufferSelectionMode::All => {
16054 editor.change_selections(
16055 SelectionEffects::no_scroll(),
16056 window,
16057 cx,
16058 |selections| {
16059 selections.clear_disjoint();
16060 selections.select_anchor_ranges(ranges);
16061 },
16062 );
16063 }
16064 }
16065 editor.register_buffers_with_language_servers(cx);
16066 });
16067
16068 let item = Box::new(editor);
16069 let item_id = item.item_id();
16070
16071 if split {
16072 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
16073 } else {
16074 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
16075 let (preview_item_id, preview_item_idx) =
16076 workspace.active_pane().read_with(cx, |pane, _| {
16077 (pane.preview_item_id(), pane.preview_item_idx())
16078 });
16079
16080 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
16081
16082 if let Some(preview_item_id) = preview_item_id {
16083 workspace.active_pane().update(cx, |pane, cx| {
16084 pane.remove_item(preview_item_id, false, false, window, cx);
16085 });
16086 }
16087 } else {
16088 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
16089 }
16090 }
16091 workspace.active_pane().update(cx, |pane, cx| {
16092 pane.set_preview_item_id(Some(item_id), cx);
16093 });
16094 }
16095
16096 pub fn rename(
16097 &mut self,
16098 _: &Rename,
16099 window: &mut Window,
16100 cx: &mut Context<Self>,
16101 ) -> Option<Task<Result<()>>> {
16102 use language::ToOffset as _;
16103
16104 let provider = self.semantics_provider.clone()?;
16105 let selection = self.selections.newest_anchor().clone();
16106 let (cursor_buffer, cursor_buffer_position) = self
16107 .buffer
16108 .read(cx)
16109 .text_anchor_for_position(selection.head(), cx)?;
16110 let (tail_buffer, cursor_buffer_position_end) = self
16111 .buffer
16112 .read(cx)
16113 .text_anchor_for_position(selection.tail(), cx)?;
16114 if tail_buffer != cursor_buffer {
16115 return None;
16116 }
16117
16118 let snapshot = cursor_buffer.read(cx).snapshot();
16119 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
16120 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
16121 let prepare_rename = provider
16122 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
16123 .unwrap_or_else(|| Task::ready(Ok(None)));
16124 drop(snapshot);
16125
16126 Some(cx.spawn_in(window, async move |this, cx| {
16127 let rename_range = if let Some(range) = prepare_rename.await? {
16128 Some(range)
16129 } else {
16130 this.update(cx, |this, cx| {
16131 let buffer = this.buffer.read(cx).snapshot(cx);
16132 let mut buffer_highlights = this
16133 .document_highlights_for_position(selection.head(), &buffer)
16134 .filter(|highlight| {
16135 highlight.start.excerpt_id == selection.head().excerpt_id
16136 && highlight.end.excerpt_id == selection.head().excerpt_id
16137 });
16138 buffer_highlights
16139 .next()
16140 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
16141 })?
16142 };
16143 if let Some(rename_range) = rename_range {
16144 this.update_in(cx, |this, window, cx| {
16145 let snapshot = cursor_buffer.read(cx).snapshot();
16146 let rename_buffer_range = rename_range.to_offset(&snapshot);
16147 let cursor_offset_in_rename_range =
16148 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
16149 let cursor_offset_in_rename_range_end =
16150 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
16151
16152 this.take_rename(false, window, cx);
16153 let buffer = this.buffer.read(cx).read(cx);
16154 let cursor_offset = selection.head().to_offset(&buffer);
16155 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
16156 let rename_end = rename_start + rename_buffer_range.len();
16157 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
16158 let mut old_highlight_id = None;
16159 let old_name: Arc<str> = buffer
16160 .chunks(rename_start..rename_end, true)
16161 .map(|chunk| {
16162 if old_highlight_id.is_none() {
16163 old_highlight_id = chunk.syntax_highlight_id;
16164 }
16165 chunk.text
16166 })
16167 .collect::<String>()
16168 .into();
16169
16170 drop(buffer);
16171
16172 // Position the selection in the rename editor so that it matches the current selection.
16173 this.show_local_selections = false;
16174 let rename_editor = cx.new(|cx| {
16175 let mut editor = Editor::single_line(window, cx);
16176 editor.buffer.update(cx, |buffer, cx| {
16177 buffer.edit([(0..0, old_name.clone())], None, cx)
16178 });
16179 let rename_selection_range = match cursor_offset_in_rename_range
16180 .cmp(&cursor_offset_in_rename_range_end)
16181 {
16182 Ordering::Equal => {
16183 editor.select_all(&SelectAll, window, cx);
16184 return editor;
16185 }
16186 Ordering::Less => {
16187 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
16188 }
16189 Ordering::Greater => {
16190 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
16191 }
16192 };
16193 if rename_selection_range.end > old_name.len() {
16194 editor.select_all(&SelectAll, window, cx);
16195 } else {
16196 editor.change_selections(Default::default(), window, cx, |s| {
16197 s.select_ranges([rename_selection_range]);
16198 });
16199 }
16200 editor
16201 });
16202 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
16203 if e == &EditorEvent::Focused {
16204 cx.emit(EditorEvent::FocusedIn)
16205 }
16206 })
16207 .detach();
16208
16209 let write_highlights =
16210 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
16211 let read_highlights =
16212 this.clear_background_highlights::<DocumentHighlightRead>(cx);
16213 let ranges = write_highlights
16214 .iter()
16215 .flat_map(|(_, ranges)| ranges.iter())
16216 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
16217 .cloned()
16218 .collect();
16219
16220 this.highlight_text::<Rename>(
16221 ranges,
16222 HighlightStyle {
16223 fade_out: Some(0.6),
16224 ..Default::default()
16225 },
16226 cx,
16227 );
16228 let rename_focus_handle = rename_editor.focus_handle(cx);
16229 window.focus(&rename_focus_handle);
16230 let block_id = this.insert_blocks(
16231 [BlockProperties {
16232 style: BlockStyle::Flex,
16233 placement: BlockPlacement::Below(range.start),
16234 height: Some(1),
16235 render: Arc::new({
16236 let rename_editor = rename_editor.clone();
16237 move |cx: &mut BlockContext| {
16238 let mut text_style = cx.editor_style.text.clone();
16239 if let Some(highlight_style) = old_highlight_id
16240 .and_then(|h| h.style(&cx.editor_style.syntax))
16241 {
16242 text_style = text_style.highlight(highlight_style);
16243 }
16244 div()
16245 .block_mouse_except_scroll()
16246 .pl(cx.anchor_x)
16247 .child(EditorElement::new(
16248 &rename_editor,
16249 EditorStyle {
16250 background: cx.theme().system().transparent,
16251 local_player: cx.editor_style.local_player,
16252 text: text_style,
16253 scrollbar_width: cx.editor_style.scrollbar_width,
16254 syntax: cx.editor_style.syntax.clone(),
16255 status: cx.editor_style.status.clone(),
16256 inlay_hints_style: HighlightStyle {
16257 font_weight: Some(FontWeight::BOLD),
16258 ..make_inlay_hints_style(cx.app)
16259 },
16260 edit_prediction_styles: make_suggestion_styles(
16261 cx.app,
16262 ),
16263 ..EditorStyle::default()
16264 },
16265 ))
16266 .into_any_element()
16267 }
16268 }),
16269 priority: 0,
16270 }],
16271 Some(Autoscroll::fit()),
16272 cx,
16273 )[0];
16274 this.pending_rename = Some(RenameState {
16275 range,
16276 old_name,
16277 editor: rename_editor,
16278 block_id,
16279 });
16280 })?;
16281 }
16282
16283 Ok(())
16284 }))
16285 }
16286
16287 pub fn confirm_rename(
16288 &mut self,
16289 _: &ConfirmRename,
16290 window: &mut Window,
16291 cx: &mut Context<Self>,
16292 ) -> Option<Task<Result<()>>> {
16293 let rename = self.take_rename(false, window, cx)?;
16294 let workspace = self.workspace()?.downgrade();
16295 let (buffer, start) = self
16296 .buffer
16297 .read(cx)
16298 .text_anchor_for_position(rename.range.start, cx)?;
16299 let (end_buffer, _) = self
16300 .buffer
16301 .read(cx)
16302 .text_anchor_for_position(rename.range.end, cx)?;
16303 if buffer != end_buffer {
16304 return None;
16305 }
16306
16307 let old_name = rename.old_name;
16308 let new_name = rename.editor.read(cx).text(cx);
16309
16310 let rename = self.semantics_provider.as_ref()?.perform_rename(
16311 &buffer,
16312 start,
16313 new_name.clone(),
16314 cx,
16315 )?;
16316
16317 Some(cx.spawn_in(window, async move |editor, cx| {
16318 let project_transaction = rename.await?;
16319 Self::open_project_transaction(
16320 &editor,
16321 workspace,
16322 project_transaction,
16323 format!("Rename: {} → {}", old_name, new_name),
16324 cx,
16325 )
16326 .await?;
16327
16328 editor.update(cx, |editor, cx| {
16329 editor.refresh_document_highlights(cx);
16330 })?;
16331 Ok(())
16332 }))
16333 }
16334
16335 fn take_rename(
16336 &mut self,
16337 moving_cursor: bool,
16338 window: &mut Window,
16339 cx: &mut Context<Self>,
16340 ) -> Option<RenameState> {
16341 let rename = self.pending_rename.take()?;
16342 if rename.editor.focus_handle(cx).is_focused(window) {
16343 window.focus(&self.focus_handle);
16344 }
16345
16346 self.remove_blocks(
16347 [rename.block_id].into_iter().collect(),
16348 Some(Autoscroll::fit()),
16349 cx,
16350 );
16351 self.clear_highlights::<Rename>(cx);
16352 self.show_local_selections = true;
16353
16354 if moving_cursor {
16355 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
16356 editor.selections.newest::<usize>(cx).head()
16357 });
16358
16359 // Update the selection to match the position of the selection inside
16360 // the rename editor.
16361 let snapshot = self.buffer.read(cx).read(cx);
16362 let rename_range = rename.range.to_offset(&snapshot);
16363 let cursor_in_editor = snapshot
16364 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
16365 .min(rename_range.end);
16366 drop(snapshot);
16367
16368 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16369 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
16370 });
16371 } else {
16372 self.refresh_document_highlights(cx);
16373 }
16374
16375 Some(rename)
16376 }
16377
16378 pub fn pending_rename(&self) -> Option<&RenameState> {
16379 self.pending_rename.as_ref()
16380 }
16381
16382 fn format(
16383 &mut self,
16384 _: &Format,
16385 window: &mut Window,
16386 cx: &mut Context<Self>,
16387 ) -> Option<Task<Result<()>>> {
16388 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16389
16390 let project = match &self.project {
16391 Some(project) => project.clone(),
16392 None => return None,
16393 };
16394
16395 Some(self.perform_format(
16396 project,
16397 FormatTrigger::Manual,
16398 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
16399 window,
16400 cx,
16401 ))
16402 }
16403
16404 fn format_selections(
16405 &mut self,
16406 _: &FormatSelections,
16407 window: &mut Window,
16408 cx: &mut Context<Self>,
16409 ) -> Option<Task<Result<()>>> {
16410 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16411
16412 let project = match &self.project {
16413 Some(project) => project.clone(),
16414 None => return None,
16415 };
16416
16417 let ranges = self
16418 .selections
16419 .all_adjusted(cx)
16420 .into_iter()
16421 .map(|selection| selection.range())
16422 .collect_vec();
16423
16424 Some(self.perform_format(
16425 project,
16426 FormatTrigger::Manual,
16427 FormatTarget::Ranges(ranges),
16428 window,
16429 cx,
16430 ))
16431 }
16432
16433 fn perform_format(
16434 &mut self,
16435 project: Entity<Project>,
16436 trigger: FormatTrigger,
16437 target: FormatTarget,
16438 window: &mut Window,
16439 cx: &mut Context<Self>,
16440 ) -> Task<Result<()>> {
16441 let buffer = self.buffer.clone();
16442 let (buffers, target) = match target {
16443 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
16444 FormatTarget::Ranges(selection_ranges) => {
16445 let multi_buffer = buffer.read(cx);
16446 let snapshot = multi_buffer.read(cx);
16447 let mut buffers = HashSet::default();
16448 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
16449 BTreeMap::new();
16450 for selection_range in selection_ranges {
16451 for (buffer, buffer_range, _) in
16452 snapshot.range_to_buffer_ranges(selection_range)
16453 {
16454 let buffer_id = buffer.remote_id();
16455 let start = buffer.anchor_before(buffer_range.start);
16456 let end = buffer.anchor_after(buffer_range.end);
16457 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
16458 buffer_id_to_ranges
16459 .entry(buffer_id)
16460 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
16461 .or_insert_with(|| vec![start..end]);
16462 }
16463 }
16464 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
16465 }
16466 };
16467
16468 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
16469 let selections_prev = transaction_id_prev
16470 .and_then(|transaction_id_prev| {
16471 // default to selections as they were after the last edit, if we have them,
16472 // instead of how they are now.
16473 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
16474 // will take you back to where you made the last edit, instead of staying where you scrolled
16475 self.selection_history
16476 .transaction(transaction_id_prev)
16477 .map(|t| t.0.clone())
16478 })
16479 .unwrap_or_else(|| {
16480 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
16481 self.selections.disjoint_anchors()
16482 });
16483
16484 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
16485 let format = project.update(cx, |project, cx| {
16486 project.format(buffers, target, true, trigger, cx)
16487 });
16488
16489 cx.spawn_in(window, async move |editor, cx| {
16490 let transaction = futures::select_biased! {
16491 transaction = format.log_err().fuse() => transaction,
16492 () = timeout => {
16493 log::warn!("timed out waiting for formatting");
16494 None
16495 }
16496 };
16497
16498 buffer
16499 .update(cx, |buffer, cx| {
16500 if let Some(transaction) = transaction {
16501 if !buffer.is_singleton() {
16502 buffer.push_transaction(&transaction.0, cx);
16503 }
16504 }
16505 cx.notify();
16506 })
16507 .ok();
16508
16509 if let Some(transaction_id_now) =
16510 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
16511 {
16512 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
16513 if has_new_transaction {
16514 _ = editor.update(cx, |editor, _| {
16515 editor
16516 .selection_history
16517 .insert_transaction(transaction_id_now, selections_prev);
16518 });
16519 }
16520 }
16521
16522 Ok(())
16523 })
16524 }
16525
16526 fn organize_imports(
16527 &mut self,
16528 _: &OrganizeImports,
16529 window: &mut Window,
16530 cx: &mut Context<Self>,
16531 ) -> Option<Task<Result<()>>> {
16532 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16533 let project = match &self.project {
16534 Some(project) => project.clone(),
16535 None => return None,
16536 };
16537 Some(self.perform_code_action_kind(
16538 project,
16539 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
16540 window,
16541 cx,
16542 ))
16543 }
16544
16545 fn perform_code_action_kind(
16546 &mut self,
16547 project: Entity<Project>,
16548 kind: CodeActionKind,
16549 window: &mut Window,
16550 cx: &mut Context<Self>,
16551 ) -> Task<Result<()>> {
16552 let buffer = self.buffer.clone();
16553 let buffers = buffer.read(cx).all_buffers();
16554 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
16555 let apply_action = project.update(cx, |project, cx| {
16556 project.apply_code_action_kind(buffers, kind, true, cx)
16557 });
16558 cx.spawn_in(window, async move |_, cx| {
16559 let transaction = futures::select_biased! {
16560 () = timeout => {
16561 log::warn!("timed out waiting for executing code action");
16562 None
16563 }
16564 transaction = apply_action.log_err().fuse() => transaction,
16565 };
16566 buffer
16567 .update(cx, |buffer, cx| {
16568 // check if we need this
16569 if let Some(transaction) = transaction {
16570 if !buffer.is_singleton() {
16571 buffer.push_transaction(&transaction.0, cx);
16572 }
16573 }
16574 cx.notify();
16575 })
16576 .ok();
16577 Ok(())
16578 })
16579 }
16580
16581 pub fn restart_language_server(
16582 &mut self,
16583 _: &RestartLanguageServer,
16584 _: &mut Window,
16585 cx: &mut Context<Self>,
16586 ) {
16587 if let Some(project) = self.project.clone() {
16588 self.buffer.update(cx, |multi_buffer, cx| {
16589 project.update(cx, |project, cx| {
16590 project.restart_language_servers_for_buffers(
16591 multi_buffer.all_buffers().into_iter().collect(),
16592 HashSet::default(),
16593 cx,
16594 );
16595 });
16596 })
16597 }
16598 }
16599
16600 pub fn stop_language_server(
16601 &mut self,
16602 _: &StopLanguageServer,
16603 _: &mut Window,
16604 cx: &mut Context<Self>,
16605 ) {
16606 if let Some(project) = self.project.clone() {
16607 self.buffer.update(cx, |multi_buffer, cx| {
16608 project.update(cx, |project, cx| {
16609 project.stop_language_servers_for_buffers(
16610 multi_buffer.all_buffers().into_iter().collect(),
16611 HashSet::default(),
16612 cx,
16613 );
16614 cx.emit(project::Event::RefreshInlayHints);
16615 });
16616 });
16617 }
16618 }
16619
16620 fn cancel_language_server_work(
16621 workspace: &mut Workspace,
16622 _: &actions::CancelLanguageServerWork,
16623 _: &mut Window,
16624 cx: &mut Context<Workspace>,
16625 ) {
16626 let project = workspace.project();
16627 let buffers = workspace
16628 .active_item(cx)
16629 .and_then(|item| item.act_as::<Editor>(cx))
16630 .map_or(HashSet::default(), |editor| {
16631 editor.read(cx).buffer.read(cx).all_buffers()
16632 });
16633 project.update(cx, |project, cx| {
16634 project.cancel_language_server_work_for_buffers(buffers, cx);
16635 });
16636 }
16637
16638 fn show_character_palette(
16639 &mut self,
16640 _: &ShowCharacterPalette,
16641 window: &mut Window,
16642 _: &mut Context<Self>,
16643 ) {
16644 window.show_character_palette();
16645 }
16646
16647 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
16648 if !self.diagnostics_enabled() {
16649 return;
16650 }
16651
16652 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
16653 let buffer = self.buffer.read(cx).snapshot(cx);
16654 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
16655 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
16656 let is_valid = buffer
16657 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
16658 .any(|entry| {
16659 entry.diagnostic.is_primary
16660 && !entry.range.is_empty()
16661 && entry.range.start == primary_range_start
16662 && entry.diagnostic.message == active_diagnostics.active_message
16663 });
16664
16665 if !is_valid {
16666 self.dismiss_diagnostics(cx);
16667 }
16668 }
16669 }
16670
16671 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
16672 match &self.active_diagnostics {
16673 ActiveDiagnostic::Group(group) => Some(group),
16674 _ => None,
16675 }
16676 }
16677
16678 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
16679 if !self.diagnostics_enabled() {
16680 return;
16681 }
16682 self.dismiss_diagnostics(cx);
16683 self.active_diagnostics = ActiveDiagnostic::All;
16684 }
16685
16686 fn activate_diagnostics(
16687 &mut self,
16688 buffer_id: BufferId,
16689 diagnostic: DiagnosticEntry<usize>,
16690 window: &mut Window,
16691 cx: &mut Context<Self>,
16692 ) {
16693 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16694 return;
16695 }
16696 self.dismiss_diagnostics(cx);
16697 let snapshot = self.snapshot(window, cx);
16698 let buffer = self.buffer.read(cx).snapshot(cx);
16699 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
16700 return;
16701 };
16702
16703 let diagnostic_group = buffer
16704 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
16705 .collect::<Vec<_>>();
16706
16707 let blocks =
16708 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
16709
16710 let blocks = self.display_map.update(cx, |display_map, cx| {
16711 display_map.insert_blocks(blocks, cx).into_iter().collect()
16712 });
16713 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
16714 active_range: buffer.anchor_before(diagnostic.range.start)
16715 ..buffer.anchor_after(diagnostic.range.end),
16716 active_message: diagnostic.diagnostic.message.clone(),
16717 group_id: diagnostic.diagnostic.group_id,
16718 blocks,
16719 });
16720 cx.notify();
16721 }
16722
16723 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
16724 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16725 return;
16726 };
16727
16728 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
16729 if let ActiveDiagnostic::Group(group) = prev {
16730 self.display_map.update(cx, |display_map, cx| {
16731 display_map.remove_blocks(group.blocks, cx);
16732 });
16733 cx.notify();
16734 }
16735 }
16736
16737 /// Disable inline diagnostics rendering for this editor.
16738 pub fn disable_inline_diagnostics(&mut self) {
16739 self.inline_diagnostics_enabled = false;
16740 self.inline_diagnostics_update = Task::ready(());
16741 self.inline_diagnostics.clear();
16742 }
16743
16744 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
16745 self.diagnostics_enabled = false;
16746 self.dismiss_diagnostics(cx);
16747 self.inline_diagnostics_update = Task::ready(());
16748 self.inline_diagnostics.clear();
16749 }
16750
16751 pub fn diagnostics_enabled(&self) -> bool {
16752 self.diagnostics_enabled && self.mode.is_full()
16753 }
16754
16755 pub fn inline_diagnostics_enabled(&self) -> bool {
16756 self.inline_diagnostics_enabled && self.diagnostics_enabled()
16757 }
16758
16759 pub fn show_inline_diagnostics(&self) -> bool {
16760 self.show_inline_diagnostics
16761 }
16762
16763 pub fn toggle_inline_diagnostics(
16764 &mut self,
16765 _: &ToggleInlineDiagnostics,
16766 window: &mut Window,
16767 cx: &mut Context<Editor>,
16768 ) {
16769 self.show_inline_diagnostics = !self.show_inline_diagnostics;
16770 self.refresh_inline_diagnostics(false, window, cx);
16771 }
16772
16773 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
16774 self.diagnostics_max_severity = severity;
16775 self.display_map.update(cx, |display_map, _| {
16776 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
16777 });
16778 }
16779
16780 pub fn toggle_diagnostics(
16781 &mut self,
16782 _: &ToggleDiagnostics,
16783 window: &mut Window,
16784 cx: &mut Context<Editor>,
16785 ) {
16786 if !self.diagnostics_enabled() {
16787 return;
16788 }
16789
16790 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16791 EditorSettings::get_global(cx)
16792 .diagnostics_max_severity
16793 .filter(|severity| severity != &DiagnosticSeverity::Off)
16794 .unwrap_or(DiagnosticSeverity::Hint)
16795 } else {
16796 DiagnosticSeverity::Off
16797 };
16798 self.set_max_diagnostics_severity(new_severity, cx);
16799 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16800 self.active_diagnostics = ActiveDiagnostic::None;
16801 self.inline_diagnostics_update = Task::ready(());
16802 self.inline_diagnostics.clear();
16803 } else {
16804 self.refresh_inline_diagnostics(false, window, cx);
16805 }
16806
16807 cx.notify();
16808 }
16809
16810 pub fn toggle_minimap(
16811 &mut self,
16812 _: &ToggleMinimap,
16813 window: &mut Window,
16814 cx: &mut Context<Editor>,
16815 ) {
16816 if self.supports_minimap(cx) {
16817 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
16818 }
16819 }
16820
16821 fn refresh_inline_diagnostics(
16822 &mut self,
16823 debounce: bool,
16824 window: &mut Window,
16825 cx: &mut Context<Self>,
16826 ) {
16827 let max_severity = ProjectSettings::get_global(cx)
16828 .diagnostics
16829 .inline
16830 .max_severity
16831 .unwrap_or(self.diagnostics_max_severity);
16832
16833 if !self.inline_diagnostics_enabled()
16834 || !self.show_inline_diagnostics
16835 || max_severity == DiagnosticSeverity::Off
16836 {
16837 self.inline_diagnostics_update = Task::ready(());
16838 self.inline_diagnostics.clear();
16839 return;
16840 }
16841
16842 let debounce_ms = ProjectSettings::get_global(cx)
16843 .diagnostics
16844 .inline
16845 .update_debounce_ms;
16846 let debounce = if debounce && debounce_ms > 0 {
16847 Some(Duration::from_millis(debounce_ms))
16848 } else {
16849 None
16850 };
16851 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
16852 if let Some(debounce) = debounce {
16853 cx.background_executor().timer(debounce).await;
16854 }
16855 let Some(snapshot) = editor.upgrade().and_then(|editor| {
16856 editor
16857 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
16858 .ok()
16859 }) else {
16860 return;
16861 };
16862
16863 let new_inline_diagnostics = cx
16864 .background_spawn(async move {
16865 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
16866 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
16867 let message = diagnostic_entry
16868 .diagnostic
16869 .message
16870 .split_once('\n')
16871 .map(|(line, _)| line)
16872 .map(SharedString::new)
16873 .unwrap_or_else(|| {
16874 SharedString::from(diagnostic_entry.diagnostic.message)
16875 });
16876 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
16877 let (Ok(i) | Err(i)) = inline_diagnostics
16878 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
16879 inline_diagnostics.insert(
16880 i,
16881 (
16882 start_anchor,
16883 InlineDiagnostic {
16884 message,
16885 group_id: diagnostic_entry.diagnostic.group_id,
16886 start: diagnostic_entry.range.start.to_point(&snapshot),
16887 is_primary: diagnostic_entry.diagnostic.is_primary,
16888 severity: diagnostic_entry.diagnostic.severity,
16889 },
16890 ),
16891 );
16892 }
16893 inline_diagnostics
16894 })
16895 .await;
16896
16897 editor
16898 .update(cx, |editor, cx| {
16899 editor.inline_diagnostics = new_inline_diagnostics;
16900 cx.notify();
16901 })
16902 .ok();
16903 });
16904 }
16905
16906 fn pull_diagnostics(
16907 &mut self,
16908 buffer_id: Option<BufferId>,
16909 window: &Window,
16910 cx: &mut Context<Self>,
16911 ) -> Option<()> {
16912 if !self.mode().is_full() {
16913 return None;
16914 }
16915 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
16916 .diagnostics
16917 .lsp_pull_diagnostics;
16918 if !pull_diagnostics_settings.enabled {
16919 return None;
16920 }
16921 let project = self.project.as_ref()?.downgrade();
16922 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
16923 let mut buffers = self.buffer.read(cx).all_buffers();
16924 if let Some(buffer_id) = buffer_id {
16925 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
16926 }
16927
16928 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
16929 cx.background_executor().timer(debounce).await;
16930
16931 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
16932 buffers
16933 .into_iter()
16934 .filter_map(|buffer| {
16935 project
16936 .update(cx, |project, cx| {
16937 project.lsp_store().update(cx, |lsp_store, cx| {
16938 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
16939 })
16940 })
16941 .ok()
16942 })
16943 .collect::<FuturesUnordered<_>>()
16944 }) else {
16945 return;
16946 };
16947
16948 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
16949 match pull_task {
16950 Ok(()) => {
16951 if editor
16952 .update_in(cx, |editor, window, cx| {
16953 editor.update_diagnostics_state(window, cx);
16954 })
16955 .is_err()
16956 {
16957 return;
16958 }
16959 }
16960 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
16961 }
16962 }
16963 });
16964
16965 Some(())
16966 }
16967
16968 pub fn set_selections_from_remote(
16969 &mut self,
16970 selections: Vec<Selection<Anchor>>,
16971 pending_selection: Option<Selection<Anchor>>,
16972 window: &mut Window,
16973 cx: &mut Context<Self>,
16974 ) {
16975 let old_cursor_position = self.selections.newest_anchor().head();
16976 self.selections.change_with(cx, |s| {
16977 s.select_anchors(selections);
16978 if let Some(pending_selection) = pending_selection {
16979 s.set_pending(pending_selection, SelectMode::Character);
16980 } else {
16981 s.clear_pending();
16982 }
16983 });
16984 self.selections_did_change(
16985 false,
16986 &old_cursor_position,
16987 SelectionEffects::default(),
16988 window,
16989 cx,
16990 );
16991 }
16992
16993 pub fn transact(
16994 &mut self,
16995 window: &mut Window,
16996 cx: &mut Context<Self>,
16997 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
16998 ) -> Option<TransactionId> {
16999 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17000 this.start_transaction_at(Instant::now(), window, cx);
17001 update(this, window, cx);
17002 this.end_transaction_at(Instant::now(), cx)
17003 })
17004 }
17005
17006 pub fn start_transaction_at(
17007 &mut self,
17008 now: Instant,
17009 window: &mut Window,
17010 cx: &mut Context<Self>,
17011 ) -> Option<TransactionId> {
17012 self.end_selection(window, cx);
17013 if let Some(tx_id) = self
17014 .buffer
17015 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
17016 {
17017 self.selection_history
17018 .insert_transaction(tx_id, self.selections.disjoint_anchors());
17019 cx.emit(EditorEvent::TransactionBegun {
17020 transaction_id: tx_id,
17021 });
17022 Some(tx_id)
17023 } else {
17024 None
17025 }
17026 }
17027
17028 pub fn end_transaction_at(
17029 &mut self,
17030 now: Instant,
17031 cx: &mut Context<Self>,
17032 ) -> Option<TransactionId> {
17033 if let Some(transaction_id) = self
17034 .buffer
17035 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
17036 {
17037 if let Some((_, end_selections)) =
17038 self.selection_history.transaction_mut(transaction_id)
17039 {
17040 *end_selections = Some(self.selections.disjoint_anchors());
17041 } else {
17042 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
17043 }
17044
17045 cx.emit(EditorEvent::Edited { transaction_id });
17046 Some(transaction_id)
17047 } else {
17048 None
17049 }
17050 }
17051
17052 pub fn modify_transaction_selection_history(
17053 &mut self,
17054 transaction_id: TransactionId,
17055 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
17056 ) -> bool {
17057 self.selection_history
17058 .transaction_mut(transaction_id)
17059 .map(modify)
17060 .is_some()
17061 }
17062
17063 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
17064 if self.selection_mark_mode {
17065 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17066 s.move_with(|_, sel| {
17067 sel.collapse_to(sel.head(), SelectionGoal::None);
17068 });
17069 })
17070 }
17071 self.selection_mark_mode = true;
17072 cx.notify();
17073 }
17074
17075 pub fn swap_selection_ends(
17076 &mut self,
17077 _: &actions::SwapSelectionEnds,
17078 window: &mut Window,
17079 cx: &mut Context<Self>,
17080 ) {
17081 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17082 s.move_with(|_, sel| {
17083 if sel.start != sel.end {
17084 sel.reversed = !sel.reversed
17085 }
17086 });
17087 });
17088 self.request_autoscroll(Autoscroll::newest(), cx);
17089 cx.notify();
17090 }
17091
17092 pub fn toggle_focus(
17093 workspace: &mut Workspace,
17094 _: &actions::ToggleFocus,
17095 window: &mut Window,
17096 cx: &mut Context<Workspace>,
17097 ) {
17098 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
17099 return;
17100 };
17101 workspace.activate_item(&item, true, true, window, cx);
17102 }
17103
17104 pub fn toggle_fold(
17105 &mut self,
17106 _: &actions::ToggleFold,
17107 window: &mut Window,
17108 cx: &mut Context<Self>,
17109 ) {
17110 if self.is_singleton(cx) {
17111 let selection = self.selections.newest::<Point>(cx);
17112
17113 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17114 let range = if selection.is_empty() {
17115 let point = selection.head().to_display_point(&display_map);
17116 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17117 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17118 .to_point(&display_map);
17119 start..end
17120 } else {
17121 selection.range()
17122 };
17123 if display_map.folds_in_range(range).next().is_some() {
17124 self.unfold_lines(&Default::default(), window, cx)
17125 } else {
17126 self.fold(&Default::default(), window, cx)
17127 }
17128 } else {
17129 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17130 let buffer_ids: HashSet<_> = self
17131 .selections
17132 .disjoint_anchor_ranges()
17133 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17134 .collect();
17135
17136 let should_unfold = buffer_ids
17137 .iter()
17138 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17139
17140 for buffer_id in buffer_ids {
17141 if should_unfold {
17142 self.unfold_buffer(buffer_id, cx);
17143 } else {
17144 self.fold_buffer(buffer_id, cx);
17145 }
17146 }
17147 }
17148 }
17149
17150 pub fn toggle_fold_recursive(
17151 &mut self,
17152 _: &actions::ToggleFoldRecursive,
17153 window: &mut Window,
17154 cx: &mut Context<Self>,
17155 ) {
17156 let selection = self.selections.newest::<Point>(cx);
17157
17158 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17159 let range = if selection.is_empty() {
17160 let point = selection.head().to_display_point(&display_map);
17161 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17162 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17163 .to_point(&display_map);
17164 start..end
17165 } else {
17166 selection.range()
17167 };
17168 if display_map.folds_in_range(range).next().is_some() {
17169 self.unfold_recursive(&Default::default(), window, cx)
17170 } else {
17171 self.fold_recursive(&Default::default(), window, cx)
17172 }
17173 }
17174
17175 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
17176 if self.is_singleton(cx) {
17177 let mut to_fold = Vec::new();
17178 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17179 let selections = self.selections.all_adjusted(cx);
17180
17181 for selection in selections {
17182 let range = selection.range().sorted();
17183 let buffer_start_row = range.start.row;
17184
17185 if range.start.row != range.end.row {
17186 let mut found = false;
17187 let mut row = range.start.row;
17188 while row <= range.end.row {
17189 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
17190 {
17191 found = true;
17192 row = crease.range().end.row + 1;
17193 to_fold.push(crease);
17194 } else {
17195 row += 1
17196 }
17197 }
17198 if found {
17199 continue;
17200 }
17201 }
17202
17203 for row in (0..=range.start.row).rev() {
17204 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17205 if crease.range().end.row >= buffer_start_row {
17206 to_fold.push(crease);
17207 if row <= range.start.row {
17208 break;
17209 }
17210 }
17211 }
17212 }
17213 }
17214
17215 self.fold_creases(to_fold, true, window, cx);
17216 } else {
17217 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17218 let buffer_ids = self
17219 .selections
17220 .disjoint_anchor_ranges()
17221 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17222 .collect::<HashSet<_>>();
17223 for buffer_id in buffer_ids {
17224 self.fold_buffer(buffer_id, cx);
17225 }
17226 }
17227 }
17228
17229 pub fn toggle_fold_all(
17230 &mut self,
17231 _: &actions::ToggleFoldAll,
17232 window: &mut Window,
17233 cx: &mut Context<Self>,
17234 ) {
17235 if self.buffer.read(cx).is_singleton() {
17236 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17237 let has_folds = display_map
17238 .folds_in_range(0..display_map.buffer_snapshot.len())
17239 .next()
17240 .is_some();
17241
17242 if has_folds {
17243 self.unfold_all(&actions::UnfoldAll, window, cx);
17244 } else {
17245 self.fold_all(&actions::FoldAll, window, cx);
17246 }
17247 } else {
17248 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
17249 let should_unfold = buffer_ids
17250 .iter()
17251 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17252
17253 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17254 editor
17255 .update_in(cx, |editor, _, cx| {
17256 for buffer_id in buffer_ids {
17257 if should_unfold {
17258 editor.unfold_buffer(buffer_id, cx);
17259 } else {
17260 editor.fold_buffer(buffer_id, cx);
17261 }
17262 }
17263 })
17264 .ok();
17265 });
17266 }
17267 }
17268
17269 fn fold_at_level(
17270 &mut self,
17271 fold_at: &FoldAtLevel,
17272 window: &mut Window,
17273 cx: &mut Context<Self>,
17274 ) {
17275 if !self.buffer.read(cx).is_singleton() {
17276 return;
17277 }
17278
17279 let fold_at_level = fold_at.0;
17280 let snapshot = self.buffer.read(cx).snapshot(cx);
17281 let mut to_fold = Vec::new();
17282 let mut stack = vec![(0, snapshot.max_row().0, 1)];
17283
17284 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
17285 while start_row < end_row {
17286 match self
17287 .snapshot(window, cx)
17288 .crease_for_buffer_row(MultiBufferRow(start_row))
17289 {
17290 Some(crease) => {
17291 let nested_start_row = crease.range().start.row + 1;
17292 let nested_end_row = crease.range().end.row;
17293
17294 if current_level < fold_at_level {
17295 stack.push((nested_start_row, nested_end_row, current_level + 1));
17296 } else if current_level == fold_at_level {
17297 to_fold.push(crease);
17298 }
17299
17300 start_row = nested_end_row + 1;
17301 }
17302 None => start_row += 1,
17303 }
17304 }
17305 }
17306
17307 self.fold_creases(to_fold, true, window, cx);
17308 }
17309
17310 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
17311 if self.buffer.read(cx).is_singleton() {
17312 let mut fold_ranges = Vec::new();
17313 let snapshot = self.buffer.read(cx).snapshot(cx);
17314
17315 for row in 0..snapshot.max_row().0 {
17316 if let Some(foldable_range) = self
17317 .snapshot(window, cx)
17318 .crease_for_buffer_row(MultiBufferRow(row))
17319 {
17320 fold_ranges.push(foldable_range);
17321 }
17322 }
17323
17324 self.fold_creases(fold_ranges, true, window, cx);
17325 } else {
17326 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17327 editor
17328 .update_in(cx, |editor, _, cx| {
17329 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17330 editor.fold_buffer(buffer_id, cx);
17331 }
17332 })
17333 .ok();
17334 });
17335 }
17336 }
17337
17338 pub fn fold_function_bodies(
17339 &mut self,
17340 _: &actions::FoldFunctionBodies,
17341 window: &mut Window,
17342 cx: &mut Context<Self>,
17343 ) {
17344 let snapshot = self.buffer.read(cx).snapshot(cx);
17345
17346 let ranges = snapshot
17347 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
17348 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
17349 .collect::<Vec<_>>();
17350
17351 let creases = ranges
17352 .into_iter()
17353 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
17354 .collect();
17355
17356 self.fold_creases(creases, true, window, cx);
17357 }
17358
17359 pub fn fold_recursive(
17360 &mut self,
17361 _: &actions::FoldRecursive,
17362 window: &mut Window,
17363 cx: &mut Context<Self>,
17364 ) {
17365 let mut to_fold = Vec::new();
17366 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17367 let selections = self.selections.all_adjusted(cx);
17368
17369 for selection in selections {
17370 let range = selection.range().sorted();
17371 let buffer_start_row = range.start.row;
17372
17373 if range.start.row != range.end.row {
17374 let mut found = false;
17375 for row in range.start.row..=range.end.row {
17376 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17377 found = true;
17378 to_fold.push(crease);
17379 }
17380 }
17381 if found {
17382 continue;
17383 }
17384 }
17385
17386 for row in (0..=range.start.row).rev() {
17387 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17388 if crease.range().end.row >= buffer_start_row {
17389 to_fold.push(crease);
17390 } else {
17391 break;
17392 }
17393 }
17394 }
17395 }
17396
17397 self.fold_creases(to_fold, true, window, cx);
17398 }
17399
17400 pub fn fold_at(
17401 &mut self,
17402 buffer_row: MultiBufferRow,
17403 window: &mut Window,
17404 cx: &mut Context<Self>,
17405 ) {
17406 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17407
17408 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
17409 let autoscroll = self
17410 .selections
17411 .all::<Point>(cx)
17412 .iter()
17413 .any(|selection| crease.range().overlaps(&selection.range()));
17414
17415 self.fold_creases(vec![crease], autoscroll, window, cx);
17416 }
17417 }
17418
17419 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
17420 if self.is_singleton(cx) {
17421 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17422 let buffer = &display_map.buffer_snapshot;
17423 let selections = self.selections.all::<Point>(cx);
17424 let ranges = selections
17425 .iter()
17426 .map(|s| {
17427 let range = s.display_range(&display_map).sorted();
17428 let mut start = range.start.to_point(&display_map);
17429 let mut end = range.end.to_point(&display_map);
17430 start.column = 0;
17431 end.column = buffer.line_len(MultiBufferRow(end.row));
17432 start..end
17433 })
17434 .collect::<Vec<_>>();
17435
17436 self.unfold_ranges(&ranges, true, true, cx);
17437 } else {
17438 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17439 let buffer_ids = self
17440 .selections
17441 .disjoint_anchor_ranges()
17442 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17443 .collect::<HashSet<_>>();
17444 for buffer_id in buffer_ids {
17445 self.unfold_buffer(buffer_id, cx);
17446 }
17447 }
17448 }
17449
17450 pub fn unfold_recursive(
17451 &mut self,
17452 _: &UnfoldRecursive,
17453 _window: &mut Window,
17454 cx: &mut Context<Self>,
17455 ) {
17456 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17457 let selections = self.selections.all::<Point>(cx);
17458 let ranges = selections
17459 .iter()
17460 .map(|s| {
17461 let mut range = s.display_range(&display_map).sorted();
17462 *range.start.column_mut() = 0;
17463 *range.end.column_mut() = display_map.line_len(range.end.row());
17464 let start = range.start.to_point(&display_map);
17465 let end = range.end.to_point(&display_map);
17466 start..end
17467 })
17468 .collect::<Vec<_>>();
17469
17470 self.unfold_ranges(&ranges, true, true, cx);
17471 }
17472
17473 pub fn unfold_at(
17474 &mut self,
17475 buffer_row: MultiBufferRow,
17476 _window: &mut Window,
17477 cx: &mut Context<Self>,
17478 ) {
17479 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17480
17481 let intersection_range = Point::new(buffer_row.0, 0)
17482 ..Point::new(
17483 buffer_row.0,
17484 display_map.buffer_snapshot.line_len(buffer_row),
17485 );
17486
17487 let autoscroll = self
17488 .selections
17489 .all::<Point>(cx)
17490 .iter()
17491 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
17492
17493 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
17494 }
17495
17496 pub fn unfold_all(
17497 &mut self,
17498 _: &actions::UnfoldAll,
17499 _window: &mut Window,
17500 cx: &mut Context<Self>,
17501 ) {
17502 if self.buffer.read(cx).is_singleton() {
17503 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17504 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
17505 } else {
17506 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
17507 editor
17508 .update(cx, |editor, cx| {
17509 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17510 editor.unfold_buffer(buffer_id, cx);
17511 }
17512 })
17513 .ok();
17514 });
17515 }
17516 }
17517
17518 pub fn fold_selected_ranges(
17519 &mut self,
17520 _: &FoldSelectedRanges,
17521 window: &mut Window,
17522 cx: &mut Context<Self>,
17523 ) {
17524 let selections = self.selections.all_adjusted(cx);
17525 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17526 let ranges = selections
17527 .into_iter()
17528 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
17529 .collect::<Vec<_>>();
17530 self.fold_creases(ranges, true, window, cx);
17531 }
17532
17533 pub fn fold_ranges<T: ToOffset + Clone>(
17534 &mut self,
17535 ranges: Vec<Range<T>>,
17536 auto_scroll: bool,
17537 window: &mut Window,
17538 cx: &mut Context<Self>,
17539 ) {
17540 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17541 let ranges = ranges
17542 .into_iter()
17543 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
17544 .collect::<Vec<_>>();
17545 self.fold_creases(ranges, auto_scroll, window, cx);
17546 }
17547
17548 pub fn fold_creases<T: ToOffset + Clone>(
17549 &mut self,
17550 creases: Vec<Crease<T>>,
17551 auto_scroll: bool,
17552 _window: &mut Window,
17553 cx: &mut Context<Self>,
17554 ) {
17555 if creases.is_empty() {
17556 return;
17557 }
17558
17559 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
17560
17561 if auto_scroll {
17562 self.request_autoscroll(Autoscroll::fit(), cx);
17563 }
17564
17565 cx.notify();
17566
17567 self.scrollbar_marker_state.dirty = true;
17568 self.folds_did_change(cx);
17569 }
17570
17571 /// Removes any folds whose ranges intersect any of the given ranges.
17572 pub fn unfold_ranges<T: ToOffset + Clone>(
17573 &mut self,
17574 ranges: &[Range<T>],
17575 inclusive: bool,
17576 auto_scroll: bool,
17577 cx: &mut Context<Self>,
17578 ) {
17579 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17580 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
17581 });
17582 self.folds_did_change(cx);
17583 }
17584
17585 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17586 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
17587 return;
17588 }
17589 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17590 self.display_map.update(cx, |display_map, cx| {
17591 display_map.fold_buffers([buffer_id], cx)
17592 });
17593 cx.emit(EditorEvent::BufferFoldToggled {
17594 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
17595 folded: true,
17596 });
17597 cx.notify();
17598 }
17599
17600 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17601 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
17602 return;
17603 }
17604 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17605 self.display_map.update(cx, |display_map, cx| {
17606 display_map.unfold_buffers([buffer_id], cx);
17607 });
17608 cx.emit(EditorEvent::BufferFoldToggled {
17609 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
17610 folded: false,
17611 });
17612 cx.notify();
17613 }
17614
17615 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
17616 self.display_map.read(cx).is_buffer_folded(buffer)
17617 }
17618
17619 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
17620 self.display_map.read(cx).folded_buffers()
17621 }
17622
17623 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17624 self.display_map.update(cx, |display_map, cx| {
17625 display_map.disable_header_for_buffer(buffer_id, cx);
17626 });
17627 cx.notify();
17628 }
17629
17630 /// Removes any folds with the given ranges.
17631 pub fn remove_folds_with_type<T: ToOffset + Clone>(
17632 &mut self,
17633 ranges: &[Range<T>],
17634 type_id: TypeId,
17635 auto_scroll: bool,
17636 cx: &mut Context<Self>,
17637 ) {
17638 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17639 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
17640 });
17641 self.folds_did_change(cx);
17642 }
17643
17644 fn remove_folds_with<T: ToOffset + Clone>(
17645 &mut self,
17646 ranges: &[Range<T>],
17647 auto_scroll: bool,
17648 cx: &mut Context<Self>,
17649 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
17650 ) {
17651 if ranges.is_empty() {
17652 return;
17653 }
17654
17655 let mut buffers_affected = HashSet::default();
17656 let multi_buffer = self.buffer().read(cx);
17657 for range in ranges {
17658 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
17659 buffers_affected.insert(buffer.read(cx).remote_id());
17660 };
17661 }
17662
17663 self.display_map.update(cx, update);
17664
17665 if auto_scroll {
17666 self.request_autoscroll(Autoscroll::fit(), cx);
17667 }
17668
17669 cx.notify();
17670 self.scrollbar_marker_state.dirty = true;
17671 self.active_indent_guides_state.dirty = true;
17672 }
17673
17674 pub fn update_renderer_widths(
17675 &mut self,
17676 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
17677 cx: &mut Context<Self>,
17678 ) -> bool {
17679 self.display_map
17680 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
17681 }
17682
17683 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
17684 self.display_map.read(cx).fold_placeholder.clone()
17685 }
17686
17687 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
17688 self.buffer.update(cx, |buffer, cx| {
17689 buffer.set_all_diff_hunks_expanded(cx);
17690 });
17691 }
17692
17693 pub fn expand_all_diff_hunks(
17694 &mut self,
17695 _: &ExpandAllDiffHunks,
17696 _window: &mut Window,
17697 cx: &mut Context<Self>,
17698 ) {
17699 self.buffer.update(cx, |buffer, cx| {
17700 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
17701 });
17702 }
17703
17704 pub fn toggle_selected_diff_hunks(
17705 &mut self,
17706 _: &ToggleSelectedDiffHunks,
17707 _window: &mut Window,
17708 cx: &mut Context<Self>,
17709 ) {
17710 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17711 self.toggle_diff_hunks_in_ranges(ranges, cx);
17712 }
17713
17714 pub fn diff_hunks_in_ranges<'a>(
17715 &'a self,
17716 ranges: &'a [Range<Anchor>],
17717 buffer: &'a MultiBufferSnapshot,
17718 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
17719 ranges.iter().flat_map(move |range| {
17720 let end_excerpt_id = range.end.excerpt_id;
17721 let range = range.to_point(buffer);
17722 let mut peek_end = range.end;
17723 if range.end.row < buffer.max_row().0 {
17724 peek_end = Point::new(range.end.row + 1, 0);
17725 }
17726 buffer
17727 .diff_hunks_in_range(range.start..peek_end)
17728 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
17729 })
17730 }
17731
17732 pub fn has_stageable_diff_hunks_in_ranges(
17733 &self,
17734 ranges: &[Range<Anchor>],
17735 snapshot: &MultiBufferSnapshot,
17736 ) -> bool {
17737 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
17738 hunks.any(|hunk| hunk.status().has_secondary_hunk())
17739 }
17740
17741 pub fn toggle_staged_selected_diff_hunks(
17742 &mut self,
17743 _: &::git::ToggleStaged,
17744 _: &mut Window,
17745 cx: &mut Context<Self>,
17746 ) {
17747 let snapshot = self.buffer.read(cx).snapshot(cx);
17748 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17749 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
17750 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17751 }
17752
17753 pub fn set_render_diff_hunk_controls(
17754 &mut self,
17755 render_diff_hunk_controls: RenderDiffHunkControlsFn,
17756 cx: &mut Context<Self>,
17757 ) {
17758 self.render_diff_hunk_controls = render_diff_hunk_controls;
17759 cx.notify();
17760 }
17761
17762 pub fn stage_and_next(
17763 &mut self,
17764 _: &::git::StageAndNext,
17765 window: &mut Window,
17766 cx: &mut Context<Self>,
17767 ) {
17768 self.do_stage_or_unstage_and_next(true, window, cx);
17769 }
17770
17771 pub fn unstage_and_next(
17772 &mut self,
17773 _: &::git::UnstageAndNext,
17774 window: &mut Window,
17775 cx: &mut Context<Self>,
17776 ) {
17777 self.do_stage_or_unstage_and_next(false, window, cx);
17778 }
17779
17780 pub fn stage_or_unstage_diff_hunks(
17781 &mut self,
17782 stage: bool,
17783 ranges: Vec<Range<Anchor>>,
17784 cx: &mut Context<Self>,
17785 ) {
17786 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
17787 cx.spawn(async move |this, cx| {
17788 task.await?;
17789 this.update(cx, |this, cx| {
17790 let snapshot = this.buffer.read(cx).snapshot(cx);
17791 let chunk_by = this
17792 .diff_hunks_in_ranges(&ranges, &snapshot)
17793 .chunk_by(|hunk| hunk.buffer_id);
17794 for (buffer_id, hunks) in &chunk_by {
17795 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
17796 }
17797 })
17798 })
17799 .detach_and_log_err(cx);
17800 }
17801
17802 fn save_buffers_for_ranges_if_needed(
17803 &mut self,
17804 ranges: &[Range<Anchor>],
17805 cx: &mut Context<Editor>,
17806 ) -> Task<Result<()>> {
17807 let multibuffer = self.buffer.read(cx);
17808 let snapshot = multibuffer.read(cx);
17809 let buffer_ids: HashSet<_> = ranges
17810 .iter()
17811 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
17812 .collect();
17813 drop(snapshot);
17814
17815 let mut buffers = HashSet::default();
17816 for buffer_id in buffer_ids {
17817 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
17818 let buffer = buffer_entity.read(cx);
17819 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
17820 {
17821 buffers.insert(buffer_entity);
17822 }
17823 }
17824 }
17825
17826 if let Some(project) = &self.project {
17827 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
17828 } else {
17829 Task::ready(Ok(()))
17830 }
17831 }
17832
17833 fn do_stage_or_unstage_and_next(
17834 &mut self,
17835 stage: bool,
17836 window: &mut Window,
17837 cx: &mut Context<Self>,
17838 ) {
17839 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
17840
17841 if ranges.iter().any(|range| range.start != range.end) {
17842 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17843 return;
17844 }
17845
17846 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17847 let snapshot = self.snapshot(window, cx);
17848 let position = self.selections.newest::<Point>(cx).head();
17849 let mut row = snapshot
17850 .buffer_snapshot
17851 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
17852 .find(|hunk| hunk.row_range.start.0 > position.row)
17853 .map(|hunk| hunk.row_range.start);
17854
17855 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
17856 // Outside of the project diff editor, wrap around to the beginning.
17857 if !all_diff_hunks_expanded {
17858 row = row.or_else(|| {
17859 snapshot
17860 .buffer_snapshot
17861 .diff_hunks_in_range(Point::zero()..position)
17862 .find(|hunk| hunk.row_range.end.0 < position.row)
17863 .map(|hunk| hunk.row_range.start)
17864 });
17865 }
17866
17867 if let Some(row) = row {
17868 let destination = Point::new(row.0, 0);
17869 let autoscroll = Autoscroll::center();
17870
17871 self.unfold_ranges(&[destination..destination], false, false, cx);
17872 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17873 s.select_ranges([destination..destination]);
17874 });
17875 }
17876 }
17877
17878 fn do_stage_or_unstage(
17879 &self,
17880 stage: bool,
17881 buffer_id: BufferId,
17882 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
17883 cx: &mut App,
17884 ) -> Option<()> {
17885 let project = self.project.as_ref()?;
17886 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
17887 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
17888 let buffer_snapshot = buffer.read(cx).snapshot();
17889 let file_exists = buffer_snapshot
17890 .file()
17891 .is_some_and(|file| file.disk_state().exists());
17892 diff.update(cx, |diff, cx| {
17893 diff.stage_or_unstage_hunks(
17894 stage,
17895 &hunks
17896 .map(|hunk| buffer_diff::DiffHunk {
17897 buffer_range: hunk.buffer_range,
17898 diff_base_byte_range: hunk.diff_base_byte_range,
17899 secondary_status: hunk.secondary_status,
17900 range: Point::zero()..Point::zero(), // unused
17901 })
17902 .collect::<Vec<_>>(),
17903 &buffer_snapshot,
17904 file_exists,
17905 cx,
17906 )
17907 });
17908 None
17909 }
17910
17911 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
17912 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17913 self.buffer
17914 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
17915 }
17916
17917 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
17918 self.buffer.update(cx, |buffer, cx| {
17919 let ranges = vec![Anchor::min()..Anchor::max()];
17920 if !buffer.all_diff_hunks_expanded()
17921 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
17922 {
17923 buffer.collapse_diff_hunks(ranges, cx);
17924 true
17925 } else {
17926 false
17927 }
17928 })
17929 }
17930
17931 fn toggle_diff_hunks_in_ranges(
17932 &mut self,
17933 ranges: Vec<Range<Anchor>>,
17934 cx: &mut Context<Editor>,
17935 ) {
17936 self.buffer.update(cx, |buffer, cx| {
17937 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
17938 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
17939 })
17940 }
17941
17942 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
17943 self.buffer.update(cx, |buffer, cx| {
17944 let snapshot = buffer.snapshot(cx);
17945 let excerpt_id = range.end.excerpt_id;
17946 let point_range = range.to_point(&snapshot);
17947 let expand = !buffer.single_hunk_is_expanded(range, cx);
17948 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
17949 })
17950 }
17951
17952 pub(crate) fn apply_all_diff_hunks(
17953 &mut self,
17954 _: &ApplyAllDiffHunks,
17955 window: &mut Window,
17956 cx: &mut Context<Self>,
17957 ) {
17958 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17959
17960 let buffers = self.buffer.read(cx).all_buffers();
17961 for branch_buffer in buffers {
17962 branch_buffer.update(cx, |branch_buffer, cx| {
17963 branch_buffer.merge_into_base(Vec::new(), cx);
17964 });
17965 }
17966
17967 if let Some(project) = self.project.clone() {
17968 self.save(
17969 SaveOptions {
17970 format: true,
17971 autosave: false,
17972 },
17973 project,
17974 window,
17975 cx,
17976 )
17977 .detach_and_log_err(cx);
17978 }
17979 }
17980
17981 pub(crate) fn apply_selected_diff_hunks(
17982 &mut self,
17983 _: &ApplyDiffHunk,
17984 window: &mut Window,
17985 cx: &mut Context<Self>,
17986 ) {
17987 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17988 let snapshot = self.snapshot(window, cx);
17989 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
17990 let mut ranges_by_buffer = HashMap::default();
17991 self.transact(window, cx, |editor, _window, cx| {
17992 for hunk in hunks {
17993 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
17994 ranges_by_buffer
17995 .entry(buffer.clone())
17996 .or_insert_with(Vec::new)
17997 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
17998 }
17999 }
18000
18001 for (buffer, ranges) in ranges_by_buffer {
18002 buffer.update(cx, |buffer, cx| {
18003 buffer.merge_into_base(ranges, cx);
18004 });
18005 }
18006 });
18007
18008 if let Some(project) = self.project.clone() {
18009 self.save(
18010 SaveOptions {
18011 format: true,
18012 autosave: false,
18013 },
18014 project,
18015 window,
18016 cx,
18017 )
18018 .detach_and_log_err(cx);
18019 }
18020 }
18021
18022 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
18023 if hovered != self.gutter_hovered {
18024 self.gutter_hovered = hovered;
18025 cx.notify();
18026 }
18027 }
18028
18029 pub fn insert_blocks(
18030 &mut self,
18031 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
18032 autoscroll: Option<Autoscroll>,
18033 cx: &mut Context<Self>,
18034 ) -> Vec<CustomBlockId> {
18035 let blocks = self
18036 .display_map
18037 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
18038 if let Some(autoscroll) = autoscroll {
18039 self.request_autoscroll(autoscroll, cx);
18040 }
18041 cx.notify();
18042 blocks
18043 }
18044
18045 pub fn resize_blocks(
18046 &mut self,
18047 heights: HashMap<CustomBlockId, u32>,
18048 autoscroll: Option<Autoscroll>,
18049 cx: &mut Context<Self>,
18050 ) {
18051 self.display_map
18052 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
18053 if let Some(autoscroll) = autoscroll {
18054 self.request_autoscroll(autoscroll, cx);
18055 }
18056 cx.notify();
18057 }
18058
18059 pub fn replace_blocks(
18060 &mut self,
18061 renderers: HashMap<CustomBlockId, RenderBlock>,
18062 autoscroll: Option<Autoscroll>,
18063 cx: &mut Context<Self>,
18064 ) {
18065 self.display_map
18066 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
18067 if let Some(autoscroll) = autoscroll {
18068 self.request_autoscroll(autoscroll, cx);
18069 }
18070 cx.notify();
18071 }
18072
18073 pub fn remove_blocks(
18074 &mut self,
18075 block_ids: HashSet<CustomBlockId>,
18076 autoscroll: Option<Autoscroll>,
18077 cx: &mut Context<Self>,
18078 ) {
18079 self.display_map.update(cx, |display_map, cx| {
18080 display_map.remove_blocks(block_ids, cx)
18081 });
18082 if let Some(autoscroll) = autoscroll {
18083 self.request_autoscroll(autoscroll, cx);
18084 }
18085 cx.notify();
18086 }
18087
18088 pub fn row_for_block(
18089 &self,
18090 block_id: CustomBlockId,
18091 cx: &mut Context<Self>,
18092 ) -> Option<DisplayRow> {
18093 self.display_map
18094 .update(cx, |map, cx| map.row_for_block(block_id, cx))
18095 }
18096
18097 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
18098 self.focused_block = Some(focused_block);
18099 }
18100
18101 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
18102 self.focused_block.take()
18103 }
18104
18105 pub fn insert_creases(
18106 &mut self,
18107 creases: impl IntoIterator<Item = Crease<Anchor>>,
18108 cx: &mut Context<Self>,
18109 ) -> Vec<CreaseId> {
18110 self.display_map
18111 .update(cx, |map, cx| map.insert_creases(creases, cx))
18112 }
18113
18114 pub fn remove_creases(
18115 &mut self,
18116 ids: impl IntoIterator<Item = CreaseId>,
18117 cx: &mut Context<Self>,
18118 ) -> Vec<(CreaseId, Range<Anchor>)> {
18119 self.display_map
18120 .update(cx, |map, cx| map.remove_creases(ids, cx))
18121 }
18122
18123 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
18124 self.display_map
18125 .update(cx, |map, cx| map.snapshot(cx))
18126 .longest_row()
18127 }
18128
18129 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
18130 self.display_map
18131 .update(cx, |map, cx| map.snapshot(cx))
18132 .max_point()
18133 }
18134
18135 pub fn text(&self, cx: &App) -> String {
18136 self.buffer.read(cx).read(cx).text()
18137 }
18138
18139 pub fn is_empty(&self, cx: &App) -> bool {
18140 self.buffer.read(cx).read(cx).is_empty()
18141 }
18142
18143 pub fn text_option(&self, cx: &App) -> Option<String> {
18144 let text = self.text(cx);
18145 let text = text.trim();
18146
18147 if text.is_empty() {
18148 return None;
18149 }
18150
18151 Some(text.to_string())
18152 }
18153
18154 pub fn set_text(
18155 &mut self,
18156 text: impl Into<Arc<str>>,
18157 window: &mut Window,
18158 cx: &mut Context<Self>,
18159 ) {
18160 self.transact(window, cx, |this, _, cx| {
18161 this.buffer
18162 .read(cx)
18163 .as_singleton()
18164 .expect("you can only call set_text on editors for singleton buffers")
18165 .update(cx, |buffer, cx| buffer.set_text(text, cx));
18166 });
18167 }
18168
18169 pub fn display_text(&self, cx: &mut App) -> String {
18170 self.display_map
18171 .update(cx, |map, cx| map.snapshot(cx))
18172 .text()
18173 }
18174
18175 fn create_minimap(
18176 &self,
18177 minimap_settings: MinimapSettings,
18178 window: &mut Window,
18179 cx: &mut Context<Self>,
18180 ) -> Option<Entity<Self>> {
18181 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
18182 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
18183 }
18184
18185 fn initialize_new_minimap(
18186 &self,
18187 minimap_settings: MinimapSettings,
18188 window: &mut Window,
18189 cx: &mut Context<Self>,
18190 ) -> Entity<Self> {
18191 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
18192
18193 let mut minimap = Editor::new_internal(
18194 EditorMode::Minimap {
18195 parent: cx.weak_entity(),
18196 },
18197 self.buffer.clone(),
18198 None,
18199 Some(self.display_map.clone()),
18200 window,
18201 cx,
18202 );
18203 minimap.scroll_manager.clone_state(&self.scroll_manager);
18204 minimap.set_text_style_refinement(TextStyleRefinement {
18205 font_size: Some(MINIMAP_FONT_SIZE),
18206 font_weight: Some(MINIMAP_FONT_WEIGHT),
18207 ..Default::default()
18208 });
18209 minimap.update_minimap_configuration(minimap_settings, cx);
18210 cx.new(|_| minimap)
18211 }
18212
18213 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
18214 let current_line_highlight = minimap_settings
18215 .current_line_highlight
18216 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
18217 self.set_current_line_highlight(Some(current_line_highlight));
18218 }
18219
18220 pub fn minimap(&self) -> Option<&Entity<Self>> {
18221 self.minimap
18222 .as_ref()
18223 .filter(|_| self.minimap_visibility.visible())
18224 }
18225
18226 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
18227 let mut wrap_guides = smallvec![];
18228
18229 if self.show_wrap_guides == Some(false) {
18230 return wrap_guides;
18231 }
18232
18233 let settings = self.buffer.read(cx).language_settings(cx);
18234 if settings.show_wrap_guides {
18235 match self.soft_wrap_mode(cx) {
18236 SoftWrap::Column(soft_wrap) => {
18237 wrap_guides.push((soft_wrap as usize, true));
18238 }
18239 SoftWrap::Bounded(soft_wrap) => {
18240 wrap_guides.push((soft_wrap as usize, true));
18241 }
18242 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
18243 }
18244 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
18245 }
18246
18247 wrap_guides
18248 }
18249
18250 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
18251 let settings = self.buffer.read(cx).language_settings(cx);
18252 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
18253 match mode {
18254 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
18255 SoftWrap::None
18256 }
18257 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
18258 language_settings::SoftWrap::PreferredLineLength => {
18259 SoftWrap::Column(settings.preferred_line_length)
18260 }
18261 language_settings::SoftWrap::Bounded => {
18262 SoftWrap::Bounded(settings.preferred_line_length)
18263 }
18264 }
18265 }
18266
18267 pub fn set_soft_wrap_mode(
18268 &mut self,
18269 mode: language_settings::SoftWrap,
18270
18271 cx: &mut Context<Self>,
18272 ) {
18273 self.soft_wrap_mode_override = Some(mode);
18274 cx.notify();
18275 }
18276
18277 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
18278 self.hard_wrap = hard_wrap;
18279 cx.notify();
18280 }
18281
18282 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
18283 self.text_style_refinement = Some(style);
18284 }
18285
18286 /// called by the Element so we know what style we were most recently rendered with.
18287 pub(crate) fn set_style(
18288 &mut self,
18289 style: EditorStyle,
18290 window: &mut Window,
18291 cx: &mut Context<Self>,
18292 ) {
18293 // We intentionally do not inform the display map about the minimap style
18294 // so that wrapping is not recalculated and stays consistent for the editor
18295 // and its linked minimap.
18296 if !self.mode.is_minimap() {
18297 let rem_size = window.rem_size();
18298 self.display_map.update(cx, |map, cx| {
18299 map.set_font(
18300 style.text.font(),
18301 style.text.font_size.to_pixels(rem_size),
18302 cx,
18303 )
18304 });
18305 }
18306 self.style = Some(style);
18307 }
18308
18309 pub fn style(&self) -> Option<&EditorStyle> {
18310 self.style.as_ref()
18311 }
18312
18313 // Called by the element. This method is not designed to be called outside of the editor
18314 // element's layout code because it does not notify when rewrapping is computed synchronously.
18315 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
18316 self.display_map
18317 .update(cx, |map, cx| map.set_wrap_width(width, cx))
18318 }
18319
18320 pub fn set_soft_wrap(&mut self) {
18321 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
18322 }
18323
18324 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
18325 if self.soft_wrap_mode_override.is_some() {
18326 self.soft_wrap_mode_override.take();
18327 } else {
18328 let soft_wrap = match self.soft_wrap_mode(cx) {
18329 SoftWrap::GitDiff => return,
18330 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
18331 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
18332 language_settings::SoftWrap::None
18333 }
18334 };
18335 self.soft_wrap_mode_override = Some(soft_wrap);
18336 }
18337 cx.notify();
18338 }
18339
18340 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
18341 let Some(workspace) = self.workspace() else {
18342 return;
18343 };
18344 let fs = workspace.read(cx).app_state().fs.clone();
18345 let current_show = TabBarSettings::get_global(cx).show;
18346 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
18347 setting.show = Some(!current_show);
18348 });
18349 }
18350
18351 pub fn toggle_indent_guides(
18352 &mut self,
18353 _: &ToggleIndentGuides,
18354 _: &mut Window,
18355 cx: &mut Context<Self>,
18356 ) {
18357 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
18358 self.buffer
18359 .read(cx)
18360 .language_settings(cx)
18361 .indent_guides
18362 .enabled
18363 });
18364 self.show_indent_guides = Some(!currently_enabled);
18365 cx.notify();
18366 }
18367
18368 fn should_show_indent_guides(&self) -> Option<bool> {
18369 self.show_indent_guides
18370 }
18371
18372 pub fn toggle_line_numbers(
18373 &mut self,
18374 _: &ToggleLineNumbers,
18375 _: &mut Window,
18376 cx: &mut Context<Self>,
18377 ) {
18378 let mut editor_settings = EditorSettings::get_global(cx).clone();
18379 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
18380 EditorSettings::override_global(editor_settings, cx);
18381 }
18382
18383 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
18384 if let Some(show_line_numbers) = self.show_line_numbers {
18385 return show_line_numbers;
18386 }
18387 EditorSettings::get_global(cx).gutter.line_numbers
18388 }
18389
18390 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
18391 self.use_relative_line_numbers
18392 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
18393 }
18394
18395 pub fn toggle_relative_line_numbers(
18396 &mut self,
18397 _: &ToggleRelativeLineNumbers,
18398 _: &mut Window,
18399 cx: &mut Context<Self>,
18400 ) {
18401 let is_relative = self.should_use_relative_line_numbers(cx);
18402 self.set_relative_line_number(Some(!is_relative), cx)
18403 }
18404
18405 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
18406 self.use_relative_line_numbers = is_relative;
18407 cx.notify();
18408 }
18409
18410 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
18411 self.show_gutter = show_gutter;
18412 cx.notify();
18413 }
18414
18415 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
18416 self.show_scrollbars = ScrollbarAxes {
18417 horizontal: show,
18418 vertical: show,
18419 };
18420 cx.notify();
18421 }
18422
18423 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18424 self.show_scrollbars.vertical = show;
18425 cx.notify();
18426 }
18427
18428 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18429 self.show_scrollbars.horizontal = show;
18430 cx.notify();
18431 }
18432
18433 pub fn set_minimap_visibility(
18434 &mut self,
18435 minimap_visibility: MinimapVisibility,
18436 window: &mut Window,
18437 cx: &mut Context<Self>,
18438 ) {
18439 if self.minimap_visibility != minimap_visibility {
18440 if minimap_visibility.visible() && self.minimap.is_none() {
18441 let minimap_settings = EditorSettings::get_global(cx).minimap;
18442 self.minimap =
18443 self.create_minimap(minimap_settings.with_show_override(), window, cx);
18444 }
18445 self.minimap_visibility = minimap_visibility;
18446 cx.notify();
18447 }
18448 }
18449
18450 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18451 self.set_show_scrollbars(false, cx);
18452 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
18453 }
18454
18455 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18456 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
18457 }
18458
18459 /// Normally the text in full mode and auto height editors is padded on the
18460 /// left side by roughly half a character width for improved hit testing.
18461 ///
18462 /// Use this method to disable this for cases where this is not wanted (e.g.
18463 /// if you want to align the editor text with some other text above or below)
18464 /// or if you want to add this padding to single-line editors.
18465 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
18466 self.offset_content = offset_content;
18467 cx.notify();
18468 }
18469
18470 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
18471 self.show_line_numbers = Some(show_line_numbers);
18472 cx.notify();
18473 }
18474
18475 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
18476 self.disable_expand_excerpt_buttons = true;
18477 cx.notify();
18478 }
18479
18480 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
18481 self.show_git_diff_gutter = Some(show_git_diff_gutter);
18482 cx.notify();
18483 }
18484
18485 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
18486 self.show_code_actions = Some(show_code_actions);
18487 cx.notify();
18488 }
18489
18490 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
18491 self.show_runnables = Some(show_runnables);
18492 cx.notify();
18493 }
18494
18495 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
18496 self.show_breakpoints = Some(show_breakpoints);
18497 cx.notify();
18498 }
18499
18500 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
18501 if self.display_map.read(cx).masked != masked {
18502 self.display_map.update(cx, |map, _| map.masked = masked);
18503 }
18504 cx.notify()
18505 }
18506
18507 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
18508 self.show_wrap_guides = Some(show_wrap_guides);
18509 cx.notify();
18510 }
18511
18512 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
18513 self.show_indent_guides = Some(show_indent_guides);
18514 cx.notify();
18515 }
18516
18517 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
18518 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
18519 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
18520 if let Some(dir) = file.abs_path(cx).parent() {
18521 return Some(dir.to_owned());
18522 }
18523 }
18524
18525 if let Some(project_path) = buffer.read(cx).project_path(cx) {
18526 return Some(project_path.path.to_path_buf());
18527 }
18528 }
18529
18530 None
18531 }
18532
18533 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
18534 self.active_excerpt(cx)?
18535 .1
18536 .read(cx)
18537 .file()
18538 .and_then(|f| f.as_local())
18539 }
18540
18541 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18542 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18543 let buffer = buffer.read(cx);
18544 if let Some(project_path) = buffer.project_path(cx) {
18545 let project = self.project.as_ref()?.read(cx);
18546 project.absolute_path(&project_path, cx)
18547 } else {
18548 buffer
18549 .file()
18550 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
18551 }
18552 })
18553 }
18554
18555 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18556 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18557 let project_path = buffer.read(cx).project_path(cx)?;
18558 let project = self.project.as_ref()?.read(cx);
18559 let entry = project.entry_for_path(&project_path, cx)?;
18560 let path = entry.path.to_path_buf();
18561 Some(path)
18562 })
18563 }
18564
18565 pub fn reveal_in_finder(
18566 &mut self,
18567 _: &RevealInFileManager,
18568 _window: &mut Window,
18569 cx: &mut Context<Self>,
18570 ) {
18571 if let Some(target) = self.target_file(cx) {
18572 cx.reveal_path(&target.abs_path(cx));
18573 }
18574 }
18575
18576 pub fn copy_path(
18577 &mut self,
18578 _: &zed_actions::workspace::CopyPath,
18579 _window: &mut Window,
18580 cx: &mut Context<Self>,
18581 ) {
18582 if let Some(path) = self.target_file_abs_path(cx) {
18583 if let Some(path) = path.to_str() {
18584 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18585 }
18586 }
18587 }
18588
18589 pub fn copy_relative_path(
18590 &mut self,
18591 _: &zed_actions::workspace::CopyRelativePath,
18592 _window: &mut Window,
18593 cx: &mut Context<Self>,
18594 ) {
18595 if let Some(path) = self.target_file_path(cx) {
18596 if let Some(path) = path.to_str() {
18597 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18598 }
18599 }
18600 }
18601
18602 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
18603 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
18604 buffer.read(cx).project_path(cx)
18605 } else {
18606 None
18607 }
18608 }
18609
18610 // Returns true if the editor handled a go-to-line request
18611 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
18612 maybe!({
18613 let breakpoint_store = self.breakpoint_store.as_ref()?;
18614
18615 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
18616 else {
18617 self.clear_row_highlights::<ActiveDebugLine>();
18618 return None;
18619 };
18620
18621 let position = active_stack_frame.position;
18622 let buffer_id = position.buffer_id?;
18623 let snapshot = self
18624 .project
18625 .as_ref()?
18626 .read(cx)
18627 .buffer_for_id(buffer_id, cx)?
18628 .read(cx)
18629 .snapshot();
18630
18631 let mut handled = false;
18632 for (id, ExcerptRange { context, .. }) in
18633 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
18634 {
18635 if context.start.cmp(&position, &snapshot).is_ge()
18636 || context.end.cmp(&position, &snapshot).is_lt()
18637 {
18638 continue;
18639 }
18640 let snapshot = self.buffer.read(cx).snapshot(cx);
18641 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
18642
18643 handled = true;
18644 self.clear_row_highlights::<ActiveDebugLine>();
18645
18646 self.go_to_line::<ActiveDebugLine>(
18647 multibuffer_anchor,
18648 Some(cx.theme().colors().editor_debugger_active_line_background),
18649 window,
18650 cx,
18651 );
18652
18653 cx.notify();
18654 }
18655
18656 handled.then_some(())
18657 })
18658 .is_some()
18659 }
18660
18661 pub fn copy_file_name_without_extension(
18662 &mut self,
18663 _: &CopyFileNameWithoutExtension,
18664 _: &mut Window,
18665 cx: &mut Context<Self>,
18666 ) {
18667 if let Some(file) = self.target_file(cx) {
18668 if let Some(file_stem) = file.path().file_stem() {
18669 if let Some(name) = file_stem.to_str() {
18670 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18671 }
18672 }
18673 }
18674 }
18675
18676 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
18677 if let Some(file) = self.target_file(cx) {
18678 if let Some(file_name) = file.path().file_name() {
18679 if let Some(name) = file_name.to_str() {
18680 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18681 }
18682 }
18683 }
18684 }
18685
18686 pub fn toggle_git_blame(
18687 &mut self,
18688 _: &::git::Blame,
18689 window: &mut Window,
18690 cx: &mut Context<Self>,
18691 ) {
18692 self.show_git_blame_gutter = !self.show_git_blame_gutter;
18693
18694 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
18695 self.start_git_blame(true, window, cx);
18696 }
18697
18698 cx.notify();
18699 }
18700
18701 pub fn toggle_git_blame_inline(
18702 &mut self,
18703 _: &ToggleGitBlameInline,
18704 window: &mut Window,
18705 cx: &mut Context<Self>,
18706 ) {
18707 self.toggle_git_blame_inline_internal(true, window, cx);
18708 cx.notify();
18709 }
18710
18711 pub fn open_git_blame_commit(
18712 &mut self,
18713 _: &OpenGitBlameCommit,
18714 window: &mut Window,
18715 cx: &mut Context<Self>,
18716 ) {
18717 self.open_git_blame_commit_internal(window, cx);
18718 }
18719
18720 fn open_git_blame_commit_internal(
18721 &mut self,
18722 window: &mut Window,
18723 cx: &mut Context<Self>,
18724 ) -> Option<()> {
18725 let blame = self.blame.as_ref()?;
18726 let snapshot = self.snapshot(window, cx);
18727 let cursor = self.selections.newest::<Point>(cx).head();
18728 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
18729 let blame_entry = blame
18730 .update(cx, |blame, cx| {
18731 blame
18732 .blame_for_rows(
18733 &[RowInfo {
18734 buffer_id: Some(buffer.remote_id()),
18735 buffer_row: Some(point.row),
18736 ..Default::default()
18737 }],
18738 cx,
18739 )
18740 .next()
18741 })
18742 .flatten()?;
18743 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
18744 let repo = blame.read(cx).repository(cx)?;
18745 let workspace = self.workspace()?.downgrade();
18746 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
18747 None
18748 }
18749
18750 pub fn git_blame_inline_enabled(&self) -> bool {
18751 self.git_blame_inline_enabled
18752 }
18753
18754 pub fn toggle_selection_menu(
18755 &mut self,
18756 _: &ToggleSelectionMenu,
18757 _: &mut Window,
18758 cx: &mut Context<Self>,
18759 ) {
18760 self.show_selection_menu = self
18761 .show_selection_menu
18762 .map(|show_selections_menu| !show_selections_menu)
18763 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
18764
18765 cx.notify();
18766 }
18767
18768 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
18769 self.show_selection_menu
18770 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
18771 }
18772
18773 fn start_git_blame(
18774 &mut self,
18775 user_triggered: bool,
18776 window: &mut Window,
18777 cx: &mut Context<Self>,
18778 ) {
18779 if let Some(project) = self.project.as_ref() {
18780 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
18781 return;
18782 };
18783
18784 if buffer.read(cx).file().is_none() {
18785 return;
18786 }
18787
18788 let focused = self.focus_handle(cx).contains_focused(window, cx);
18789
18790 let project = project.clone();
18791 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
18792 self.blame_subscription =
18793 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
18794 self.blame = Some(blame);
18795 }
18796 }
18797
18798 fn toggle_git_blame_inline_internal(
18799 &mut self,
18800 user_triggered: bool,
18801 window: &mut Window,
18802 cx: &mut Context<Self>,
18803 ) {
18804 if self.git_blame_inline_enabled {
18805 self.git_blame_inline_enabled = false;
18806 self.show_git_blame_inline = false;
18807 self.show_git_blame_inline_delay_task.take();
18808 } else {
18809 self.git_blame_inline_enabled = true;
18810 self.start_git_blame_inline(user_triggered, window, cx);
18811 }
18812
18813 cx.notify();
18814 }
18815
18816 fn start_git_blame_inline(
18817 &mut self,
18818 user_triggered: bool,
18819 window: &mut Window,
18820 cx: &mut Context<Self>,
18821 ) {
18822 self.start_git_blame(user_triggered, window, cx);
18823
18824 if ProjectSettings::get_global(cx)
18825 .git
18826 .inline_blame_delay()
18827 .is_some()
18828 {
18829 self.start_inline_blame_timer(window, cx);
18830 } else {
18831 self.show_git_blame_inline = true
18832 }
18833 }
18834
18835 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
18836 self.blame.as_ref()
18837 }
18838
18839 pub fn show_git_blame_gutter(&self) -> bool {
18840 self.show_git_blame_gutter
18841 }
18842
18843 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
18844 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
18845 }
18846
18847 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
18848 self.show_git_blame_inline
18849 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
18850 && !self.newest_selection_head_on_empty_line(cx)
18851 && self.has_blame_entries(cx)
18852 }
18853
18854 fn has_blame_entries(&self, cx: &App) -> bool {
18855 self.blame()
18856 .map_or(false, |blame| blame.read(cx).has_generated_entries())
18857 }
18858
18859 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
18860 let cursor_anchor = self.selections.newest_anchor().head();
18861
18862 let snapshot = self.buffer.read(cx).snapshot(cx);
18863 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
18864
18865 snapshot.line_len(buffer_row) == 0
18866 }
18867
18868 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
18869 let buffer_and_selection = maybe!({
18870 let selection = self.selections.newest::<Point>(cx);
18871 let selection_range = selection.range();
18872
18873 let multi_buffer = self.buffer().read(cx);
18874 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18875 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
18876
18877 let (buffer, range, _) = if selection.reversed {
18878 buffer_ranges.first()
18879 } else {
18880 buffer_ranges.last()
18881 }?;
18882
18883 let selection = text::ToPoint::to_point(&range.start, &buffer).row
18884 ..text::ToPoint::to_point(&range.end, &buffer).row;
18885 Some((
18886 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
18887 selection,
18888 ))
18889 });
18890
18891 let Some((buffer, selection)) = buffer_and_selection else {
18892 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
18893 };
18894
18895 let Some(project) = self.project.as_ref() else {
18896 return Task::ready(Err(anyhow!("editor does not have project")));
18897 };
18898
18899 project.update(cx, |project, cx| {
18900 project.get_permalink_to_line(&buffer, selection, cx)
18901 })
18902 }
18903
18904 pub fn copy_permalink_to_line(
18905 &mut self,
18906 _: &CopyPermalinkToLine,
18907 window: &mut Window,
18908 cx: &mut Context<Self>,
18909 ) {
18910 let permalink_task = self.get_permalink_to_line(cx);
18911 let workspace = self.workspace();
18912
18913 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18914 Ok(permalink) => {
18915 cx.update(|_, cx| {
18916 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
18917 })
18918 .ok();
18919 }
18920 Err(err) => {
18921 let message = format!("Failed to copy permalink: {err}");
18922
18923 anyhow::Result::<()>::Err(err).log_err();
18924
18925 if let Some(workspace) = workspace {
18926 workspace
18927 .update_in(cx, |workspace, _, cx| {
18928 struct CopyPermalinkToLine;
18929
18930 workspace.show_toast(
18931 Toast::new(
18932 NotificationId::unique::<CopyPermalinkToLine>(),
18933 message,
18934 ),
18935 cx,
18936 )
18937 })
18938 .ok();
18939 }
18940 }
18941 })
18942 .detach();
18943 }
18944
18945 pub fn copy_file_location(
18946 &mut self,
18947 _: &CopyFileLocation,
18948 _: &mut Window,
18949 cx: &mut Context<Self>,
18950 ) {
18951 let selection = self.selections.newest::<Point>(cx).start.row + 1;
18952 if let Some(file) = self.target_file(cx) {
18953 if let Some(path) = file.path().to_str() {
18954 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
18955 }
18956 }
18957 }
18958
18959 pub fn open_permalink_to_line(
18960 &mut self,
18961 _: &OpenPermalinkToLine,
18962 window: &mut Window,
18963 cx: &mut Context<Self>,
18964 ) {
18965 let permalink_task = self.get_permalink_to_line(cx);
18966 let workspace = self.workspace();
18967
18968 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18969 Ok(permalink) => {
18970 cx.update(|_, cx| {
18971 cx.open_url(permalink.as_ref());
18972 })
18973 .ok();
18974 }
18975 Err(err) => {
18976 let message = format!("Failed to open permalink: {err}");
18977
18978 anyhow::Result::<()>::Err(err).log_err();
18979
18980 if let Some(workspace) = workspace {
18981 workspace
18982 .update(cx, |workspace, cx| {
18983 struct OpenPermalinkToLine;
18984
18985 workspace.show_toast(
18986 Toast::new(
18987 NotificationId::unique::<OpenPermalinkToLine>(),
18988 message,
18989 ),
18990 cx,
18991 )
18992 })
18993 .ok();
18994 }
18995 }
18996 })
18997 .detach();
18998 }
18999
19000 pub fn insert_uuid_v4(
19001 &mut self,
19002 _: &InsertUuidV4,
19003 window: &mut Window,
19004 cx: &mut Context<Self>,
19005 ) {
19006 self.insert_uuid(UuidVersion::V4, window, cx);
19007 }
19008
19009 pub fn insert_uuid_v7(
19010 &mut self,
19011 _: &InsertUuidV7,
19012 window: &mut Window,
19013 cx: &mut Context<Self>,
19014 ) {
19015 self.insert_uuid(UuidVersion::V7, window, cx);
19016 }
19017
19018 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
19019 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19020 self.transact(window, cx, |this, window, cx| {
19021 let edits = this
19022 .selections
19023 .all::<Point>(cx)
19024 .into_iter()
19025 .map(|selection| {
19026 let uuid = match version {
19027 UuidVersion::V4 => uuid::Uuid::new_v4(),
19028 UuidVersion::V7 => uuid::Uuid::now_v7(),
19029 };
19030
19031 (selection.range(), uuid.to_string())
19032 });
19033 this.edit(edits, cx);
19034 this.refresh_edit_prediction(true, false, window, cx);
19035 });
19036 }
19037
19038 pub fn open_selections_in_multibuffer(
19039 &mut self,
19040 _: &OpenSelectionsInMultibuffer,
19041 window: &mut Window,
19042 cx: &mut Context<Self>,
19043 ) {
19044 let multibuffer = self.buffer.read(cx);
19045
19046 let Some(buffer) = multibuffer.as_singleton() else {
19047 return;
19048 };
19049
19050 let Some(workspace) = self.workspace() else {
19051 return;
19052 };
19053
19054 let title = multibuffer.title(cx).to_string();
19055
19056 let locations = self
19057 .selections
19058 .all_anchors(cx)
19059 .into_iter()
19060 .map(|selection| Location {
19061 buffer: buffer.clone(),
19062 range: selection.start.text_anchor..selection.end.text_anchor,
19063 })
19064 .collect::<Vec<_>>();
19065
19066 cx.spawn_in(window, async move |_, cx| {
19067 workspace.update_in(cx, |workspace, window, cx| {
19068 Self::open_locations_in_multibuffer(
19069 workspace,
19070 locations,
19071 format!("Selections for '{title}'"),
19072 false,
19073 MultibufferSelectionMode::All,
19074 window,
19075 cx,
19076 );
19077 })
19078 })
19079 .detach();
19080 }
19081
19082 /// Adds a row highlight for the given range. If a row has multiple highlights, the
19083 /// last highlight added will be used.
19084 ///
19085 /// If the range ends at the beginning of a line, then that line will not be highlighted.
19086 pub fn highlight_rows<T: 'static>(
19087 &mut self,
19088 range: Range<Anchor>,
19089 color: Hsla,
19090 options: RowHighlightOptions,
19091 cx: &mut Context<Self>,
19092 ) {
19093 let snapshot = self.buffer().read(cx).snapshot(cx);
19094 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19095 let ix = row_highlights.binary_search_by(|highlight| {
19096 Ordering::Equal
19097 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
19098 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
19099 });
19100
19101 if let Err(mut ix) = ix {
19102 let index = post_inc(&mut self.highlight_order);
19103
19104 // If this range intersects with the preceding highlight, then merge it with
19105 // the preceding highlight. Otherwise insert a new highlight.
19106 let mut merged = false;
19107 if ix > 0 {
19108 let prev_highlight = &mut row_highlights[ix - 1];
19109 if prev_highlight
19110 .range
19111 .end
19112 .cmp(&range.start, &snapshot)
19113 .is_ge()
19114 {
19115 ix -= 1;
19116 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
19117 prev_highlight.range.end = range.end;
19118 }
19119 merged = true;
19120 prev_highlight.index = index;
19121 prev_highlight.color = color;
19122 prev_highlight.options = options;
19123 }
19124 }
19125
19126 if !merged {
19127 row_highlights.insert(
19128 ix,
19129 RowHighlight {
19130 range: range.clone(),
19131 index,
19132 color,
19133 options,
19134 type_id: TypeId::of::<T>(),
19135 },
19136 );
19137 }
19138
19139 // If any of the following highlights intersect with this one, merge them.
19140 while let Some(next_highlight) = row_highlights.get(ix + 1) {
19141 let highlight = &row_highlights[ix];
19142 if next_highlight
19143 .range
19144 .start
19145 .cmp(&highlight.range.end, &snapshot)
19146 .is_le()
19147 {
19148 if next_highlight
19149 .range
19150 .end
19151 .cmp(&highlight.range.end, &snapshot)
19152 .is_gt()
19153 {
19154 row_highlights[ix].range.end = next_highlight.range.end;
19155 }
19156 row_highlights.remove(ix + 1);
19157 } else {
19158 break;
19159 }
19160 }
19161 }
19162 }
19163
19164 /// Remove any highlighted row ranges of the given type that intersect the
19165 /// given ranges.
19166 pub fn remove_highlighted_rows<T: 'static>(
19167 &mut self,
19168 ranges_to_remove: Vec<Range<Anchor>>,
19169 cx: &mut Context<Self>,
19170 ) {
19171 let snapshot = self.buffer().read(cx).snapshot(cx);
19172 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19173 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19174 row_highlights.retain(|highlight| {
19175 while let Some(range_to_remove) = ranges_to_remove.peek() {
19176 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
19177 Ordering::Less | Ordering::Equal => {
19178 ranges_to_remove.next();
19179 }
19180 Ordering::Greater => {
19181 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
19182 Ordering::Less | Ordering::Equal => {
19183 return false;
19184 }
19185 Ordering::Greater => break,
19186 }
19187 }
19188 }
19189 }
19190
19191 true
19192 })
19193 }
19194
19195 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
19196 pub fn clear_row_highlights<T: 'static>(&mut self) {
19197 self.highlighted_rows.remove(&TypeId::of::<T>());
19198 }
19199
19200 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
19201 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
19202 self.highlighted_rows
19203 .get(&TypeId::of::<T>())
19204 .map_or(&[] as &[_], |vec| vec.as_slice())
19205 .iter()
19206 .map(|highlight| (highlight.range.clone(), highlight.color))
19207 }
19208
19209 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
19210 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
19211 /// Allows to ignore certain kinds of highlights.
19212 pub fn highlighted_display_rows(
19213 &self,
19214 window: &mut Window,
19215 cx: &mut App,
19216 ) -> BTreeMap<DisplayRow, LineHighlight> {
19217 let snapshot = self.snapshot(window, cx);
19218 let mut used_highlight_orders = HashMap::default();
19219 self.highlighted_rows
19220 .iter()
19221 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
19222 .fold(
19223 BTreeMap::<DisplayRow, LineHighlight>::new(),
19224 |mut unique_rows, highlight| {
19225 let start = highlight.range.start.to_display_point(&snapshot);
19226 let end = highlight.range.end.to_display_point(&snapshot);
19227 let start_row = start.row().0;
19228 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
19229 && end.column() == 0
19230 {
19231 end.row().0.saturating_sub(1)
19232 } else {
19233 end.row().0
19234 };
19235 for row in start_row..=end_row {
19236 let used_index =
19237 used_highlight_orders.entry(row).or_insert(highlight.index);
19238 if highlight.index >= *used_index {
19239 *used_index = highlight.index;
19240 unique_rows.insert(
19241 DisplayRow(row),
19242 LineHighlight {
19243 include_gutter: highlight.options.include_gutter,
19244 border: None,
19245 background: highlight.color.into(),
19246 type_id: Some(highlight.type_id),
19247 },
19248 );
19249 }
19250 }
19251 unique_rows
19252 },
19253 )
19254 }
19255
19256 pub fn highlighted_display_row_for_autoscroll(
19257 &self,
19258 snapshot: &DisplaySnapshot,
19259 ) -> Option<DisplayRow> {
19260 self.highlighted_rows
19261 .values()
19262 .flat_map(|highlighted_rows| highlighted_rows.iter())
19263 .filter_map(|highlight| {
19264 if highlight.options.autoscroll {
19265 Some(highlight.range.start.to_display_point(snapshot).row())
19266 } else {
19267 None
19268 }
19269 })
19270 .min()
19271 }
19272
19273 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
19274 self.highlight_background::<SearchWithinRange>(
19275 ranges,
19276 |colors| colors.colors().editor_document_highlight_read_background,
19277 cx,
19278 )
19279 }
19280
19281 pub fn set_breadcrumb_header(&mut self, new_header: String) {
19282 self.breadcrumb_header = Some(new_header);
19283 }
19284
19285 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
19286 self.clear_background_highlights::<SearchWithinRange>(cx);
19287 }
19288
19289 pub fn highlight_background<T: 'static>(
19290 &mut self,
19291 ranges: &[Range<Anchor>],
19292 color_fetcher: fn(&Theme) -> Hsla,
19293 cx: &mut Context<Self>,
19294 ) {
19295 self.background_highlights.insert(
19296 HighlightKey::Type(TypeId::of::<T>()),
19297 (color_fetcher, Arc::from(ranges)),
19298 );
19299 self.scrollbar_marker_state.dirty = true;
19300 cx.notify();
19301 }
19302
19303 pub fn highlight_background_key<T: 'static>(
19304 &mut self,
19305 key: usize,
19306 ranges: &[Range<Anchor>],
19307 color_fetcher: fn(&Theme) -> Hsla,
19308 cx: &mut Context<Self>,
19309 ) {
19310 self.background_highlights.insert(
19311 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19312 (color_fetcher, Arc::from(ranges)),
19313 );
19314 self.scrollbar_marker_state.dirty = true;
19315 cx.notify();
19316 }
19317
19318 pub fn clear_background_highlights<T: 'static>(
19319 &mut self,
19320 cx: &mut Context<Self>,
19321 ) -> Option<BackgroundHighlight> {
19322 let text_highlights = self
19323 .background_highlights
19324 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
19325 if !text_highlights.1.is_empty() {
19326 self.scrollbar_marker_state.dirty = true;
19327 cx.notify();
19328 }
19329 Some(text_highlights)
19330 }
19331
19332 pub fn highlight_gutter<T: 'static>(
19333 &mut self,
19334 ranges: impl Into<Vec<Range<Anchor>>>,
19335 color_fetcher: fn(&App) -> Hsla,
19336 cx: &mut Context<Self>,
19337 ) {
19338 self.gutter_highlights
19339 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
19340 cx.notify();
19341 }
19342
19343 pub fn clear_gutter_highlights<T: 'static>(
19344 &mut self,
19345 cx: &mut Context<Self>,
19346 ) -> Option<GutterHighlight> {
19347 cx.notify();
19348 self.gutter_highlights.remove(&TypeId::of::<T>())
19349 }
19350
19351 pub fn insert_gutter_highlight<T: 'static>(
19352 &mut self,
19353 range: Range<Anchor>,
19354 color_fetcher: fn(&App) -> Hsla,
19355 cx: &mut Context<Self>,
19356 ) {
19357 let snapshot = self.buffer().read(cx).snapshot(cx);
19358 let mut highlights = self
19359 .gutter_highlights
19360 .remove(&TypeId::of::<T>())
19361 .map(|(_, highlights)| highlights)
19362 .unwrap_or_default();
19363 let ix = highlights.binary_search_by(|highlight| {
19364 Ordering::Equal
19365 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
19366 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
19367 });
19368 if let Err(ix) = ix {
19369 highlights.insert(ix, range);
19370 }
19371 self.gutter_highlights
19372 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
19373 }
19374
19375 pub fn remove_gutter_highlights<T: 'static>(
19376 &mut self,
19377 ranges_to_remove: Vec<Range<Anchor>>,
19378 cx: &mut Context<Self>,
19379 ) {
19380 let snapshot = self.buffer().read(cx).snapshot(cx);
19381 let Some((color_fetcher, mut gutter_highlights)) =
19382 self.gutter_highlights.remove(&TypeId::of::<T>())
19383 else {
19384 return;
19385 };
19386 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19387 gutter_highlights.retain(|highlight| {
19388 while let Some(range_to_remove) = ranges_to_remove.peek() {
19389 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
19390 Ordering::Less | Ordering::Equal => {
19391 ranges_to_remove.next();
19392 }
19393 Ordering::Greater => {
19394 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
19395 Ordering::Less | Ordering::Equal => {
19396 return false;
19397 }
19398 Ordering::Greater => break,
19399 }
19400 }
19401 }
19402 }
19403
19404 true
19405 });
19406 self.gutter_highlights
19407 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
19408 }
19409
19410 #[cfg(feature = "test-support")]
19411 pub fn all_text_highlights(
19412 &self,
19413 window: &mut Window,
19414 cx: &mut Context<Self>,
19415 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
19416 let snapshot = self.snapshot(window, cx);
19417 self.display_map.update(cx, |display_map, _| {
19418 display_map
19419 .all_text_highlights()
19420 .map(|highlight| {
19421 let (style, ranges) = highlight.as_ref();
19422 (
19423 *style,
19424 ranges
19425 .iter()
19426 .map(|range| range.clone().to_display_points(&snapshot))
19427 .collect(),
19428 )
19429 })
19430 .collect()
19431 })
19432 }
19433
19434 #[cfg(feature = "test-support")]
19435 pub fn all_text_background_highlights(
19436 &self,
19437 window: &mut Window,
19438 cx: &mut Context<Self>,
19439 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19440 let snapshot = self.snapshot(window, cx);
19441 let buffer = &snapshot.buffer_snapshot;
19442 let start = buffer.anchor_before(0);
19443 let end = buffer.anchor_after(buffer.len());
19444 self.background_highlights_in_range(start..end, &snapshot, cx.theme())
19445 }
19446
19447 #[cfg(feature = "test-support")]
19448 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
19449 let snapshot = self.buffer().read(cx).snapshot(cx);
19450
19451 let highlights = self
19452 .background_highlights
19453 .get(&HighlightKey::Type(TypeId::of::<
19454 items::BufferSearchHighlights,
19455 >()));
19456
19457 if let Some((_color, ranges)) = highlights {
19458 ranges
19459 .iter()
19460 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
19461 .collect_vec()
19462 } else {
19463 vec![]
19464 }
19465 }
19466
19467 fn document_highlights_for_position<'a>(
19468 &'a self,
19469 position: Anchor,
19470 buffer: &'a MultiBufferSnapshot,
19471 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
19472 let read_highlights = self
19473 .background_highlights
19474 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
19475 .map(|h| &h.1);
19476 let write_highlights = self
19477 .background_highlights
19478 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
19479 .map(|h| &h.1);
19480 let left_position = position.bias_left(buffer);
19481 let right_position = position.bias_right(buffer);
19482 read_highlights
19483 .into_iter()
19484 .chain(write_highlights)
19485 .flat_map(move |ranges| {
19486 let start_ix = match ranges.binary_search_by(|probe| {
19487 let cmp = probe.end.cmp(&left_position, buffer);
19488 if cmp.is_ge() {
19489 Ordering::Greater
19490 } else {
19491 Ordering::Less
19492 }
19493 }) {
19494 Ok(i) | Err(i) => i,
19495 };
19496
19497 ranges[start_ix..]
19498 .iter()
19499 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
19500 })
19501 }
19502
19503 pub fn has_background_highlights<T: 'static>(&self) -> bool {
19504 self.background_highlights
19505 .get(&HighlightKey::Type(TypeId::of::<T>()))
19506 .map_or(false, |(_, highlights)| !highlights.is_empty())
19507 }
19508
19509 pub fn background_highlights_in_range(
19510 &self,
19511 search_range: Range<Anchor>,
19512 display_snapshot: &DisplaySnapshot,
19513 theme: &Theme,
19514 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19515 let mut results = Vec::new();
19516 for (color_fetcher, ranges) in self.background_highlights.values() {
19517 let color = color_fetcher(theme);
19518 let start_ix = match ranges.binary_search_by(|probe| {
19519 let cmp = probe
19520 .end
19521 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19522 if cmp.is_gt() {
19523 Ordering::Greater
19524 } else {
19525 Ordering::Less
19526 }
19527 }) {
19528 Ok(i) | Err(i) => i,
19529 };
19530 for range in &ranges[start_ix..] {
19531 if range
19532 .start
19533 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19534 .is_ge()
19535 {
19536 break;
19537 }
19538
19539 let start = range.start.to_display_point(display_snapshot);
19540 let end = range.end.to_display_point(display_snapshot);
19541 results.push((start..end, color))
19542 }
19543 }
19544 results
19545 }
19546
19547 pub fn background_highlight_row_ranges<T: 'static>(
19548 &self,
19549 search_range: Range<Anchor>,
19550 display_snapshot: &DisplaySnapshot,
19551 count: usize,
19552 ) -> Vec<RangeInclusive<DisplayPoint>> {
19553 let mut results = Vec::new();
19554 let Some((_, ranges)) = self
19555 .background_highlights
19556 .get(&HighlightKey::Type(TypeId::of::<T>()))
19557 else {
19558 return vec![];
19559 };
19560
19561 let start_ix = match ranges.binary_search_by(|probe| {
19562 let cmp = probe
19563 .end
19564 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19565 if cmp.is_gt() {
19566 Ordering::Greater
19567 } else {
19568 Ordering::Less
19569 }
19570 }) {
19571 Ok(i) | Err(i) => i,
19572 };
19573 let mut push_region = |start: Option<Point>, end: Option<Point>| {
19574 if let (Some(start_display), Some(end_display)) = (start, end) {
19575 results.push(
19576 start_display.to_display_point(display_snapshot)
19577 ..=end_display.to_display_point(display_snapshot),
19578 );
19579 }
19580 };
19581 let mut start_row: Option<Point> = None;
19582 let mut end_row: Option<Point> = None;
19583 if ranges.len() > count {
19584 return Vec::new();
19585 }
19586 for range in &ranges[start_ix..] {
19587 if range
19588 .start
19589 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19590 .is_ge()
19591 {
19592 break;
19593 }
19594 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
19595 if let Some(current_row) = &end_row {
19596 if end.row == current_row.row {
19597 continue;
19598 }
19599 }
19600 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
19601 if start_row.is_none() {
19602 assert_eq!(end_row, None);
19603 start_row = Some(start);
19604 end_row = Some(end);
19605 continue;
19606 }
19607 if let Some(current_end) = end_row.as_mut() {
19608 if start.row > current_end.row + 1 {
19609 push_region(start_row, end_row);
19610 start_row = Some(start);
19611 end_row = Some(end);
19612 } else {
19613 // Merge two hunks.
19614 *current_end = end;
19615 }
19616 } else {
19617 unreachable!();
19618 }
19619 }
19620 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
19621 push_region(start_row, end_row);
19622 results
19623 }
19624
19625 pub fn gutter_highlights_in_range(
19626 &self,
19627 search_range: Range<Anchor>,
19628 display_snapshot: &DisplaySnapshot,
19629 cx: &App,
19630 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19631 let mut results = Vec::new();
19632 for (color_fetcher, ranges) in self.gutter_highlights.values() {
19633 let color = color_fetcher(cx);
19634 let start_ix = match ranges.binary_search_by(|probe| {
19635 let cmp = probe
19636 .end
19637 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19638 if cmp.is_gt() {
19639 Ordering::Greater
19640 } else {
19641 Ordering::Less
19642 }
19643 }) {
19644 Ok(i) | Err(i) => i,
19645 };
19646 for range in &ranges[start_ix..] {
19647 if range
19648 .start
19649 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19650 .is_ge()
19651 {
19652 break;
19653 }
19654
19655 let start = range.start.to_display_point(display_snapshot);
19656 let end = range.end.to_display_point(display_snapshot);
19657 results.push((start..end, color))
19658 }
19659 }
19660 results
19661 }
19662
19663 /// Get the text ranges corresponding to the redaction query
19664 pub fn redacted_ranges(
19665 &self,
19666 search_range: Range<Anchor>,
19667 display_snapshot: &DisplaySnapshot,
19668 cx: &App,
19669 ) -> Vec<Range<DisplayPoint>> {
19670 display_snapshot
19671 .buffer_snapshot
19672 .redacted_ranges(search_range, |file| {
19673 if let Some(file) = file {
19674 file.is_private()
19675 && EditorSettings::get(
19676 Some(SettingsLocation {
19677 worktree_id: file.worktree_id(cx),
19678 path: file.path().as_ref(),
19679 }),
19680 cx,
19681 )
19682 .redact_private_values
19683 } else {
19684 false
19685 }
19686 })
19687 .map(|range| {
19688 range.start.to_display_point(display_snapshot)
19689 ..range.end.to_display_point(display_snapshot)
19690 })
19691 .collect()
19692 }
19693
19694 pub fn highlight_text_key<T: 'static>(
19695 &mut self,
19696 key: usize,
19697 ranges: Vec<Range<Anchor>>,
19698 style: HighlightStyle,
19699 cx: &mut Context<Self>,
19700 ) {
19701 self.display_map.update(cx, |map, _| {
19702 map.highlight_text(
19703 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19704 ranges,
19705 style,
19706 );
19707 });
19708 cx.notify();
19709 }
19710
19711 pub fn highlight_text<T: 'static>(
19712 &mut self,
19713 ranges: Vec<Range<Anchor>>,
19714 style: HighlightStyle,
19715 cx: &mut Context<Self>,
19716 ) {
19717 self.display_map.update(cx, |map, _| {
19718 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
19719 });
19720 cx.notify();
19721 }
19722
19723 pub(crate) fn highlight_inlays<T: 'static>(
19724 &mut self,
19725 highlights: Vec<InlayHighlight>,
19726 style: HighlightStyle,
19727 cx: &mut Context<Self>,
19728 ) {
19729 self.display_map.update(cx, |map, _| {
19730 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
19731 });
19732 cx.notify();
19733 }
19734
19735 pub fn text_highlights<'a, T: 'static>(
19736 &'a self,
19737 cx: &'a App,
19738 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
19739 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
19740 }
19741
19742 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
19743 let cleared = self
19744 .display_map
19745 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
19746 if cleared {
19747 cx.notify();
19748 }
19749 }
19750
19751 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
19752 (self.read_only(cx) || self.blink_manager.read(cx).visible())
19753 && self.focus_handle.is_focused(window)
19754 }
19755
19756 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
19757 self.show_cursor_when_unfocused = is_enabled;
19758 cx.notify();
19759 }
19760
19761 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
19762 cx.notify();
19763 }
19764
19765 fn on_debug_session_event(
19766 &mut self,
19767 _session: Entity<Session>,
19768 event: &SessionEvent,
19769 cx: &mut Context<Self>,
19770 ) {
19771 match event {
19772 SessionEvent::InvalidateInlineValue => {
19773 self.refresh_inline_values(cx);
19774 }
19775 _ => {}
19776 }
19777 }
19778
19779 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
19780 let Some(project) = self.project.clone() else {
19781 return;
19782 };
19783
19784 if !self.inline_value_cache.enabled {
19785 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
19786 self.splice_inlays(&inlays, Vec::new(), cx);
19787 return;
19788 }
19789
19790 let current_execution_position = self
19791 .highlighted_rows
19792 .get(&TypeId::of::<ActiveDebugLine>())
19793 .and_then(|lines| lines.last().map(|line| line.range.end));
19794
19795 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
19796 let inline_values = editor
19797 .update(cx, |editor, cx| {
19798 let Some(current_execution_position) = current_execution_position else {
19799 return Some(Task::ready(Ok(Vec::new())));
19800 };
19801
19802 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
19803 let snapshot = buffer.snapshot(cx);
19804
19805 let excerpt = snapshot.excerpt_containing(
19806 current_execution_position..current_execution_position,
19807 )?;
19808
19809 editor.buffer.read(cx).buffer(excerpt.buffer_id())
19810 })?;
19811
19812 let range =
19813 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
19814
19815 project.inline_values(buffer, range, cx)
19816 })
19817 .ok()
19818 .flatten()?
19819 .await
19820 .context("refreshing debugger inlays")
19821 .log_err()?;
19822
19823 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
19824
19825 for (buffer_id, inline_value) in inline_values
19826 .into_iter()
19827 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
19828 {
19829 buffer_inline_values
19830 .entry(buffer_id)
19831 .or_default()
19832 .push(inline_value);
19833 }
19834
19835 editor
19836 .update(cx, |editor, cx| {
19837 let snapshot = editor.buffer.read(cx).snapshot(cx);
19838 let mut new_inlays = Vec::default();
19839
19840 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
19841 let buffer_id = buffer_snapshot.remote_id();
19842 buffer_inline_values
19843 .get(&buffer_id)
19844 .into_iter()
19845 .flatten()
19846 .for_each(|hint| {
19847 let inlay = Inlay::debugger(
19848 post_inc(&mut editor.next_inlay_id),
19849 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
19850 hint.text(),
19851 );
19852 if !inlay.text.chars().contains(&'\n') {
19853 new_inlays.push(inlay);
19854 }
19855 });
19856 }
19857
19858 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
19859 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
19860
19861 editor.splice_inlays(&inlay_ids, new_inlays, cx);
19862 })
19863 .ok()?;
19864 Some(())
19865 });
19866 }
19867
19868 fn on_buffer_event(
19869 &mut self,
19870 multibuffer: &Entity<MultiBuffer>,
19871 event: &multi_buffer::Event,
19872 window: &mut Window,
19873 cx: &mut Context<Self>,
19874 ) {
19875 match event {
19876 multi_buffer::Event::Edited {
19877 singleton_buffer_edited,
19878 edited_buffer,
19879 } => {
19880 self.scrollbar_marker_state.dirty = true;
19881 self.active_indent_guides_state.dirty = true;
19882 self.refresh_active_diagnostics(cx);
19883 self.refresh_code_actions(window, cx);
19884 self.refresh_selected_text_highlights(true, window, cx);
19885 self.refresh_single_line_folds(window, cx);
19886 refresh_matching_bracket_highlights(self, window, cx);
19887 if self.has_active_edit_prediction() {
19888 self.update_visible_edit_prediction(window, cx);
19889 }
19890 if let Some(project) = self.project.as_ref() {
19891 if let Some(edited_buffer) = edited_buffer {
19892 project.update(cx, |project, cx| {
19893 self.registered_buffers
19894 .entry(edited_buffer.read(cx).remote_id())
19895 .or_insert_with(|| {
19896 project
19897 .register_buffer_with_language_servers(&edited_buffer, cx)
19898 });
19899 });
19900 }
19901 }
19902 cx.emit(EditorEvent::BufferEdited);
19903 cx.emit(SearchEvent::MatchesInvalidated);
19904
19905 if let Some(buffer) = edited_buffer {
19906 self.update_lsp_data(false, Some(buffer.read(cx).remote_id()), window, cx);
19907 }
19908
19909 if *singleton_buffer_edited {
19910 if let Some(buffer) = edited_buffer {
19911 if buffer.read(cx).file().is_none() {
19912 cx.emit(EditorEvent::TitleChanged);
19913 }
19914 }
19915 if let Some(project) = &self.project {
19916 #[allow(clippy::mutable_key_type)]
19917 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
19918 multibuffer
19919 .all_buffers()
19920 .into_iter()
19921 .filter_map(|buffer| {
19922 buffer.update(cx, |buffer, cx| {
19923 let language = buffer.language()?;
19924 let should_discard = project.update(cx, |project, cx| {
19925 project.is_local()
19926 && !project.has_language_servers_for(buffer, cx)
19927 });
19928 should_discard.not().then_some(language.clone())
19929 })
19930 })
19931 .collect::<HashSet<_>>()
19932 });
19933 if !languages_affected.is_empty() {
19934 self.refresh_inlay_hints(
19935 InlayHintRefreshReason::BufferEdited(languages_affected),
19936 cx,
19937 );
19938 }
19939 }
19940 }
19941
19942 let Some(project) = &self.project else { return };
19943 let (telemetry, is_via_ssh) = {
19944 let project = project.read(cx);
19945 let telemetry = project.client().telemetry().clone();
19946 let is_via_ssh = project.is_via_ssh();
19947 (telemetry, is_via_ssh)
19948 };
19949 refresh_linked_ranges(self, window, cx);
19950 telemetry.log_edit_event("editor", is_via_ssh);
19951 }
19952 multi_buffer::Event::ExcerptsAdded {
19953 buffer,
19954 predecessor,
19955 excerpts,
19956 } => {
19957 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19958 let buffer_id = buffer.read(cx).remote_id();
19959 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
19960 if let Some(project) = &self.project {
19961 update_uncommitted_diff_for_buffer(
19962 cx.entity(),
19963 project,
19964 [buffer.clone()],
19965 self.buffer.clone(),
19966 cx,
19967 )
19968 .detach();
19969 }
19970 }
19971 self.update_lsp_data(false, Some(buffer_id), window, cx);
19972 cx.emit(EditorEvent::ExcerptsAdded {
19973 buffer: buffer.clone(),
19974 predecessor: *predecessor,
19975 excerpts: excerpts.clone(),
19976 });
19977 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
19978 }
19979 multi_buffer::Event::ExcerptsRemoved {
19980 ids,
19981 removed_buffer_ids,
19982 } => {
19983 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
19984 let buffer = self.buffer.read(cx);
19985 self.registered_buffers
19986 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
19987 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19988 cx.emit(EditorEvent::ExcerptsRemoved {
19989 ids: ids.clone(),
19990 removed_buffer_ids: removed_buffer_ids.clone(),
19991 });
19992 }
19993 multi_buffer::Event::ExcerptsEdited {
19994 excerpt_ids,
19995 buffer_ids,
19996 } => {
19997 self.display_map.update(cx, |map, cx| {
19998 map.unfold_buffers(buffer_ids.iter().copied(), cx)
19999 });
20000 cx.emit(EditorEvent::ExcerptsEdited {
20001 ids: excerpt_ids.clone(),
20002 });
20003 }
20004 multi_buffer::Event::ExcerptsExpanded { ids } => {
20005 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20006 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
20007 }
20008 multi_buffer::Event::Reparsed(buffer_id) => {
20009 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20010 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20011
20012 cx.emit(EditorEvent::Reparsed(*buffer_id));
20013 }
20014 multi_buffer::Event::DiffHunksToggled => {
20015 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20016 }
20017 multi_buffer::Event::LanguageChanged(buffer_id) => {
20018 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
20019 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20020 cx.emit(EditorEvent::Reparsed(*buffer_id));
20021 cx.notify();
20022 }
20023 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
20024 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
20025 multi_buffer::Event::FileHandleChanged
20026 | multi_buffer::Event::Reloaded
20027 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
20028 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
20029 multi_buffer::Event::DiagnosticsUpdated => {
20030 self.update_diagnostics_state(window, cx);
20031 }
20032 _ => {}
20033 };
20034 }
20035
20036 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
20037 if !self.diagnostics_enabled() {
20038 return;
20039 }
20040 self.refresh_active_diagnostics(cx);
20041 self.refresh_inline_diagnostics(true, window, cx);
20042 self.scrollbar_marker_state.dirty = true;
20043 cx.notify();
20044 }
20045
20046 pub fn start_temporary_diff_override(&mut self) {
20047 self.load_diff_task.take();
20048 self.temporary_diff_override = true;
20049 }
20050
20051 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
20052 self.temporary_diff_override = false;
20053 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
20054 self.buffer.update(cx, |buffer, cx| {
20055 buffer.set_all_diff_hunks_collapsed(cx);
20056 });
20057
20058 if let Some(project) = self.project.clone() {
20059 self.load_diff_task = Some(
20060 update_uncommitted_diff_for_buffer(
20061 cx.entity(),
20062 &project,
20063 self.buffer.read(cx).all_buffers(),
20064 self.buffer.clone(),
20065 cx,
20066 )
20067 .shared(),
20068 );
20069 }
20070 }
20071
20072 fn on_display_map_changed(
20073 &mut self,
20074 _: Entity<DisplayMap>,
20075 _: &mut Window,
20076 cx: &mut Context<Self>,
20077 ) {
20078 cx.notify();
20079 }
20080
20081 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20082 if self.diagnostics_enabled() {
20083 let new_severity = EditorSettings::get_global(cx)
20084 .diagnostics_max_severity
20085 .unwrap_or(DiagnosticSeverity::Hint);
20086 self.set_max_diagnostics_severity(new_severity, cx);
20087 }
20088 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20089 self.update_edit_prediction_settings(cx);
20090 self.refresh_edit_prediction(true, false, window, cx);
20091 self.refresh_inline_values(cx);
20092 self.refresh_inlay_hints(
20093 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
20094 self.selections.newest_anchor().head(),
20095 &self.buffer.read(cx).snapshot(cx),
20096 cx,
20097 )),
20098 cx,
20099 );
20100
20101 let old_cursor_shape = self.cursor_shape;
20102
20103 {
20104 let editor_settings = EditorSettings::get_global(cx);
20105 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
20106 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
20107 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
20108 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
20109 }
20110
20111 if old_cursor_shape != self.cursor_shape {
20112 cx.emit(EditorEvent::CursorShapeChanged);
20113 }
20114
20115 let project_settings = ProjectSettings::get_global(cx);
20116 self.serialize_dirty_buffers =
20117 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
20118
20119 if self.mode.is_full() {
20120 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
20121 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
20122 if self.show_inline_diagnostics != show_inline_diagnostics {
20123 self.show_inline_diagnostics = show_inline_diagnostics;
20124 self.refresh_inline_diagnostics(false, window, cx);
20125 }
20126
20127 if self.git_blame_inline_enabled != inline_blame_enabled {
20128 self.toggle_git_blame_inline_internal(false, window, cx);
20129 }
20130
20131 let minimap_settings = EditorSettings::get_global(cx).minimap;
20132 if self.minimap_visibility != MinimapVisibility::Disabled {
20133 if self.minimap_visibility.settings_visibility()
20134 != minimap_settings.minimap_enabled()
20135 {
20136 self.set_minimap_visibility(
20137 MinimapVisibility::for_mode(self.mode(), cx),
20138 window,
20139 cx,
20140 );
20141 } else if let Some(minimap_entity) = self.minimap.as_ref() {
20142 minimap_entity.update(cx, |minimap_editor, cx| {
20143 minimap_editor.update_minimap_configuration(minimap_settings, cx)
20144 })
20145 }
20146 }
20147 }
20148
20149 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
20150 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
20151 }) {
20152 if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
20153 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
20154 }
20155 self.refresh_colors(false, None, window, cx);
20156 }
20157
20158 cx.notify();
20159 }
20160
20161 pub fn set_searchable(&mut self, searchable: bool) {
20162 self.searchable = searchable;
20163 }
20164
20165 pub fn searchable(&self) -> bool {
20166 self.searchable
20167 }
20168
20169 fn open_proposed_changes_editor(
20170 &mut self,
20171 _: &OpenProposedChangesEditor,
20172 window: &mut Window,
20173 cx: &mut Context<Self>,
20174 ) {
20175 let Some(workspace) = self.workspace() else {
20176 cx.propagate();
20177 return;
20178 };
20179
20180 let selections = self.selections.all::<usize>(cx);
20181 let multi_buffer = self.buffer.read(cx);
20182 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20183 let mut new_selections_by_buffer = HashMap::default();
20184 for selection in selections {
20185 for (buffer, range, _) in
20186 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
20187 {
20188 let mut range = range.to_point(buffer);
20189 range.start.column = 0;
20190 range.end.column = buffer.line_len(range.end.row);
20191 new_selections_by_buffer
20192 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
20193 .or_insert(Vec::new())
20194 .push(range)
20195 }
20196 }
20197
20198 let proposed_changes_buffers = new_selections_by_buffer
20199 .into_iter()
20200 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
20201 .collect::<Vec<_>>();
20202 let proposed_changes_editor = cx.new(|cx| {
20203 ProposedChangesEditor::new(
20204 "Proposed changes",
20205 proposed_changes_buffers,
20206 self.project.clone(),
20207 window,
20208 cx,
20209 )
20210 });
20211
20212 window.defer(cx, move |window, cx| {
20213 workspace.update(cx, |workspace, cx| {
20214 workspace.active_pane().update(cx, |pane, cx| {
20215 pane.add_item(
20216 Box::new(proposed_changes_editor),
20217 true,
20218 true,
20219 None,
20220 window,
20221 cx,
20222 );
20223 });
20224 });
20225 });
20226 }
20227
20228 pub fn open_excerpts_in_split(
20229 &mut self,
20230 _: &OpenExcerptsSplit,
20231 window: &mut Window,
20232 cx: &mut Context<Self>,
20233 ) {
20234 self.open_excerpts_common(None, true, window, cx)
20235 }
20236
20237 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
20238 self.open_excerpts_common(None, false, window, cx)
20239 }
20240
20241 fn open_excerpts_common(
20242 &mut self,
20243 jump_data: Option<JumpData>,
20244 split: bool,
20245 window: &mut Window,
20246 cx: &mut Context<Self>,
20247 ) {
20248 let Some(workspace) = self.workspace() else {
20249 cx.propagate();
20250 return;
20251 };
20252
20253 if self.buffer.read(cx).is_singleton() {
20254 cx.propagate();
20255 return;
20256 }
20257
20258 let mut new_selections_by_buffer = HashMap::default();
20259 match &jump_data {
20260 Some(JumpData::MultiBufferPoint {
20261 excerpt_id,
20262 position,
20263 anchor,
20264 line_offset_from_top,
20265 }) => {
20266 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20267 if let Some(buffer) = multi_buffer_snapshot
20268 .buffer_id_for_excerpt(*excerpt_id)
20269 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
20270 {
20271 let buffer_snapshot = buffer.read(cx).snapshot();
20272 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
20273 language::ToPoint::to_point(anchor, &buffer_snapshot)
20274 } else {
20275 buffer_snapshot.clip_point(*position, Bias::Left)
20276 };
20277 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
20278 new_selections_by_buffer.insert(
20279 buffer,
20280 (
20281 vec![jump_to_offset..jump_to_offset],
20282 Some(*line_offset_from_top),
20283 ),
20284 );
20285 }
20286 }
20287 Some(JumpData::MultiBufferRow {
20288 row,
20289 line_offset_from_top,
20290 }) => {
20291 let point = MultiBufferPoint::new(row.0, 0);
20292 if let Some((buffer, buffer_point, _)) =
20293 self.buffer.read(cx).point_to_buffer_point(point, cx)
20294 {
20295 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
20296 new_selections_by_buffer
20297 .entry(buffer)
20298 .or_insert((Vec::new(), Some(*line_offset_from_top)))
20299 .0
20300 .push(buffer_offset..buffer_offset)
20301 }
20302 }
20303 None => {
20304 let selections = self.selections.all::<usize>(cx);
20305 let multi_buffer = self.buffer.read(cx);
20306 for selection in selections {
20307 for (snapshot, range, _, anchor) in multi_buffer
20308 .snapshot(cx)
20309 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
20310 {
20311 if let Some(anchor) = anchor {
20312 // selection is in a deleted hunk
20313 let Some(buffer_id) = anchor.buffer_id else {
20314 continue;
20315 };
20316 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
20317 continue;
20318 };
20319 let offset = text::ToOffset::to_offset(
20320 &anchor.text_anchor,
20321 &buffer_handle.read(cx).snapshot(),
20322 );
20323 let range = offset..offset;
20324 new_selections_by_buffer
20325 .entry(buffer_handle)
20326 .or_insert((Vec::new(), None))
20327 .0
20328 .push(range)
20329 } else {
20330 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
20331 else {
20332 continue;
20333 };
20334 new_selections_by_buffer
20335 .entry(buffer_handle)
20336 .or_insert((Vec::new(), None))
20337 .0
20338 .push(range)
20339 }
20340 }
20341 }
20342 }
20343 }
20344
20345 new_selections_by_buffer
20346 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
20347
20348 if new_selections_by_buffer.is_empty() {
20349 return;
20350 }
20351
20352 // We defer the pane interaction because we ourselves are a workspace item
20353 // and activating a new item causes the pane to call a method on us reentrantly,
20354 // which panics if we're on the stack.
20355 window.defer(cx, move |window, cx| {
20356 workspace.update(cx, |workspace, cx| {
20357 let pane = if split {
20358 workspace.adjacent_pane(window, cx)
20359 } else {
20360 workspace.active_pane().clone()
20361 };
20362
20363 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
20364 let editor = buffer
20365 .read(cx)
20366 .file()
20367 .is_none()
20368 .then(|| {
20369 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
20370 // so `workspace.open_project_item` will never find them, always opening a new editor.
20371 // Instead, we try to activate the existing editor in the pane first.
20372 let (editor, pane_item_index) =
20373 pane.read(cx).items().enumerate().find_map(|(i, item)| {
20374 let editor = item.downcast::<Editor>()?;
20375 let singleton_buffer =
20376 editor.read(cx).buffer().read(cx).as_singleton()?;
20377 if singleton_buffer == buffer {
20378 Some((editor, i))
20379 } else {
20380 None
20381 }
20382 })?;
20383 pane.update(cx, |pane, cx| {
20384 pane.activate_item(pane_item_index, true, true, window, cx)
20385 });
20386 Some(editor)
20387 })
20388 .flatten()
20389 .unwrap_or_else(|| {
20390 workspace.open_project_item::<Self>(
20391 pane.clone(),
20392 buffer,
20393 true,
20394 true,
20395 window,
20396 cx,
20397 )
20398 });
20399
20400 editor.update(cx, |editor, cx| {
20401 let autoscroll = match scroll_offset {
20402 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
20403 None => Autoscroll::newest(),
20404 };
20405 let nav_history = editor.nav_history.take();
20406 editor.change_selections(
20407 SelectionEffects::scroll(autoscroll),
20408 window,
20409 cx,
20410 |s| {
20411 s.select_ranges(ranges);
20412 },
20413 );
20414 editor.nav_history = nav_history;
20415 });
20416 }
20417 })
20418 });
20419 }
20420
20421 // For now, don't allow opening excerpts in buffers that aren't backed by
20422 // regular project files.
20423 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
20424 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
20425 }
20426
20427 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
20428 let snapshot = self.buffer.read(cx).read(cx);
20429 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
20430 Some(
20431 ranges
20432 .iter()
20433 .map(move |range| {
20434 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
20435 })
20436 .collect(),
20437 )
20438 }
20439
20440 fn selection_replacement_ranges(
20441 &self,
20442 range: Range<OffsetUtf16>,
20443 cx: &mut App,
20444 ) -> Vec<Range<OffsetUtf16>> {
20445 let selections = self.selections.all::<OffsetUtf16>(cx);
20446 let newest_selection = selections
20447 .iter()
20448 .max_by_key(|selection| selection.id)
20449 .unwrap();
20450 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
20451 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
20452 let snapshot = self.buffer.read(cx).read(cx);
20453 selections
20454 .into_iter()
20455 .map(|mut selection| {
20456 selection.start.0 =
20457 (selection.start.0 as isize).saturating_add(start_delta) as usize;
20458 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
20459 snapshot.clip_offset_utf16(selection.start, Bias::Left)
20460 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
20461 })
20462 .collect()
20463 }
20464
20465 fn report_editor_event(
20466 &self,
20467 event_type: &'static str,
20468 file_extension: Option<String>,
20469 cx: &App,
20470 ) {
20471 if cfg!(any(test, feature = "test-support")) {
20472 return;
20473 }
20474
20475 let Some(project) = &self.project else { return };
20476
20477 // If None, we are in a file without an extension
20478 let file = self
20479 .buffer
20480 .read(cx)
20481 .as_singleton()
20482 .and_then(|b| b.read(cx).file());
20483 let file_extension = file_extension.or(file
20484 .as_ref()
20485 .and_then(|file| Path::new(file.file_name(cx)).extension())
20486 .and_then(|e| e.to_str())
20487 .map(|a| a.to_string()));
20488
20489 let vim_mode = vim_enabled(cx);
20490
20491 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
20492 let copilot_enabled = edit_predictions_provider
20493 == language::language_settings::EditPredictionProvider::Copilot;
20494 let copilot_enabled_for_language = self
20495 .buffer
20496 .read(cx)
20497 .language_settings(cx)
20498 .show_edit_predictions;
20499
20500 let project = project.read(cx);
20501 telemetry::event!(
20502 event_type,
20503 file_extension,
20504 vim_mode,
20505 copilot_enabled,
20506 copilot_enabled_for_language,
20507 edit_predictions_provider,
20508 is_via_ssh = project.is_via_ssh(),
20509 );
20510 }
20511
20512 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
20513 /// with each line being an array of {text, highlight} objects.
20514 fn copy_highlight_json(
20515 &mut self,
20516 _: &CopyHighlightJson,
20517 window: &mut Window,
20518 cx: &mut Context<Self>,
20519 ) {
20520 #[derive(Serialize)]
20521 struct Chunk<'a> {
20522 text: String,
20523 highlight: Option<&'a str>,
20524 }
20525
20526 let snapshot = self.buffer.read(cx).snapshot(cx);
20527 let range = self
20528 .selected_text_range(false, window, cx)
20529 .and_then(|selection| {
20530 if selection.range.is_empty() {
20531 None
20532 } else {
20533 Some(selection.range)
20534 }
20535 })
20536 .unwrap_or_else(|| 0..snapshot.len());
20537
20538 let chunks = snapshot.chunks(range, true);
20539 let mut lines = Vec::new();
20540 let mut line: VecDeque<Chunk> = VecDeque::new();
20541
20542 let Some(style) = self.style.as_ref() else {
20543 return;
20544 };
20545
20546 for chunk in chunks {
20547 let highlight = chunk
20548 .syntax_highlight_id
20549 .and_then(|id| id.name(&style.syntax));
20550 let mut chunk_lines = chunk.text.split('\n').peekable();
20551 while let Some(text) = chunk_lines.next() {
20552 let mut merged_with_last_token = false;
20553 if let Some(last_token) = line.back_mut() {
20554 if last_token.highlight == highlight {
20555 last_token.text.push_str(text);
20556 merged_with_last_token = true;
20557 }
20558 }
20559
20560 if !merged_with_last_token {
20561 line.push_back(Chunk {
20562 text: text.into(),
20563 highlight,
20564 });
20565 }
20566
20567 if chunk_lines.peek().is_some() {
20568 if line.len() > 1 && line.front().unwrap().text.is_empty() {
20569 line.pop_front();
20570 }
20571 if line.len() > 1 && line.back().unwrap().text.is_empty() {
20572 line.pop_back();
20573 }
20574
20575 lines.push(mem::take(&mut line));
20576 }
20577 }
20578 }
20579
20580 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
20581 return;
20582 };
20583 cx.write_to_clipboard(ClipboardItem::new_string(lines));
20584 }
20585
20586 pub fn open_context_menu(
20587 &mut self,
20588 _: &OpenContextMenu,
20589 window: &mut Window,
20590 cx: &mut Context<Self>,
20591 ) {
20592 self.request_autoscroll(Autoscroll::newest(), cx);
20593 let position = self.selections.newest_display(cx).start;
20594 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
20595 }
20596
20597 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
20598 &self.inlay_hint_cache
20599 }
20600
20601 pub fn replay_insert_event(
20602 &mut self,
20603 text: &str,
20604 relative_utf16_range: Option<Range<isize>>,
20605 window: &mut Window,
20606 cx: &mut Context<Self>,
20607 ) {
20608 if !self.input_enabled {
20609 cx.emit(EditorEvent::InputIgnored { text: text.into() });
20610 return;
20611 }
20612 if let Some(relative_utf16_range) = relative_utf16_range {
20613 let selections = self.selections.all::<OffsetUtf16>(cx);
20614 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20615 let new_ranges = selections.into_iter().map(|range| {
20616 let start = OffsetUtf16(
20617 range
20618 .head()
20619 .0
20620 .saturating_add_signed(relative_utf16_range.start),
20621 );
20622 let end = OffsetUtf16(
20623 range
20624 .head()
20625 .0
20626 .saturating_add_signed(relative_utf16_range.end),
20627 );
20628 start..end
20629 });
20630 s.select_ranges(new_ranges);
20631 });
20632 }
20633
20634 self.handle_input(text, window, cx);
20635 }
20636
20637 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
20638 let Some(provider) = self.semantics_provider.as_ref() else {
20639 return false;
20640 };
20641
20642 let mut supports = false;
20643 self.buffer().update(cx, |this, cx| {
20644 this.for_each_buffer(|buffer| {
20645 supports |= provider.supports_inlay_hints(buffer, cx);
20646 });
20647 });
20648
20649 supports
20650 }
20651
20652 pub fn is_focused(&self, window: &Window) -> bool {
20653 self.focus_handle.is_focused(window)
20654 }
20655
20656 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20657 cx.emit(EditorEvent::Focused);
20658
20659 if let Some(descendant) = self
20660 .last_focused_descendant
20661 .take()
20662 .and_then(|descendant| descendant.upgrade())
20663 {
20664 window.focus(&descendant);
20665 } else {
20666 if let Some(blame) = self.blame.as_ref() {
20667 blame.update(cx, GitBlame::focus)
20668 }
20669
20670 self.blink_manager.update(cx, BlinkManager::enable);
20671 self.show_cursor_names(window, cx);
20672 self.buffer.update(cx, |buffer, cx| {
20673 buffer.finalize_last_transaction(cx);
20674 if self.leader_id.is_none() {
20675 buffer.set_active_selections(
20676 &self.selections.disjoint_anchors(),
20677 self.selections.line_mode,
20678 self.cursor_shape,
20679 cx,
20680 );
20681 }
20682 });
20683 }
20684 }
20685
20686 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
20687 cx.emit(EditorEvent::FocusedIn)
20688 }
20689
20690 fn handle_focus_out(
20691 &mut self,
20692 event: FocusOutEvent,
20693 _window: &mut Window,
20694 cx: &mut Context<Self>,
20695 ) {
20696 if event.blurred != self.focus_handle {
20697 self.last_focused_descendant = Some(event.blurred);
20698 }
20699 self.selection_drag_state = SelectionDragState::None;
20700 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
20701 }
20702
20703 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20704 self.blink_manager.update(cx, BlinkManager::disable);
20705 self.buffer
20706 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
20707
20708 if let Some(blame) = self.blame.as_ref() {
20709 blame.update(cx, GitBlame::blur)
20710 }
20711 if !self.hover_state.focused(window, cx) {
20712 hide_hover(self, cx);
20713 }
20714 if !self
20715 .context_menu
20716 .borrow()
20717 .as_ref()
20718 .is_some_and(|context_menu| context_menu.focused(window, cx))
20719 {
20720 self.hide_context_menu(window, cx);
20721 }
20722 self.discard_edit_prediction(false, cx);
20723 cx.emit(EditorEvent::Blurred);
20724 cx.notify();
20725 }
20726
20727 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20728 let mut pending: String = window
20729 .pending_input_keystrokes()
20730 .into_iter()
20731 .flatten()
20732 .filter_map(|keystroke| {
20733 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
20734 keystroke.key_char.clone()
20735 } else {
20736 None
20737 }
20738 })
20739 .collect();
20740
20741 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
20742 pending = "".to_string();
20743 }
20744
20745 let existing_pending = self
20746 .text_highlights::<PendingInput>(cx)
20747 .map(|(_, ranges)| ranges.iter().cloned().collect::<Vec<_>>());
20748 if existing_pending.is_none() && pending.is_empty() {
20749 return;
20750 }
20751 let transaction =
20752 self.transact(window, cx, |this, window, cx| {
20753 let selections = this.selections.all::<usize>(cx);
20754 let edits = selections
20755 .iter()
20756 .map(|selection| (selection.end..selection.end, pending.clone()));
20757 this.edit(edits, cx);
20758 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20759 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
20760 sel.start + ix * pending.len()..sel.end + ix * pending.len()
20761 }));
20762 });
20763 if let Some(existing_ranges) = existing_pending {
20764 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
20765 this.edit(edits, cx);
20766 }
20767 });
20768
20769 let snapshot = self.snapshot(window, cx);
20770 let ranges = self
20771 .selections
20772 .all::<usize>(cx)
20773 .into_iter()
20774 .map(|selection| {
20775 snapshot.buffer_snapshot.anchor_after(selection.end)
20776 ..snapshot
20777 .buffer_snapshot
20778 .anchor_before(selection.end + pending.len())
20779 })
20780 .collect();
20781
20782 if pending.is_empty() {
20783 self.clear_highlights::<PendingInput>(cx);
20784 } else {
20785 self.highlight_text::<PendingInput>(
20786 ranges,
20787 HighlightStyle {
20788 underline: Some(UnderlineStyle {
20789 thickness: px(1.),
20790 color: None,
20791 wavy: false,
20792 }),
20793 ..Default::default()
20794 },
20795 cx,
20796 );
20797 }
20798
20799 self.ime_transaction = self.ime_transaction.or(transaction);
20800 if let Some(transaction) = self.ime_transaction {
20801 self.buffer.update(cx, |buffer, cx| {
20802 buffer.group_until_transaction(transaction, cx);
20803 });
20804 }
20805
20806 if self.text_highlights::<PendingInput>(cx).is_none() {
20807 self.ime_transaction.take();
20808 }
20809 }
20810
20811 pub fn register_action_renderer(
20812 &mut self,
20813 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
20814 ) -> Subscription {
20815 let id = self.next_editor_action_id.post_inc();
20816 self.editor_actions
20817 .borrow_mut()
20818 .insert(id, Box::new(listener));
20819
20820 let editor_actions = self.editor_actions.clone();
20821 Subscription::new(move || {
20822 editor_actions.borrow_mut().remove(&id);
20823 })
20824 }
20825
20826 pub fn register_action<A: Action>(
20827 &mut self,
20828 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
20829 ) -> Subscription {
20830 let id = self.next_editor_action_id.post_inc();
20831 let listener = Arc::new(listener);
20832 self.editor_actions.borrow_mut().insert(
20833 id,
20834 Box::new(move |_, window, _| {
20835 let listener = listener.clone();
20836 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
20837 let action = action.downcast_ref().unwrap();
20838 if phase == DispatchPhase::Bubble {
20839 listener(action, window, cx)
20840 }
20841 })
20842 }),
20843 );
20844
20845 let editor_actions = self.editor_actions.clone();
20846 Subscription::new(move || {
20847 editor_actions.borrow_mut().remove(&id);
20848 })
20849 }
20850
20851 pub fn file_header_size(&self) -> u32 {
20852 FILE_HEADER_HEIGHT
20853 }
20854
20855 pub fn restore(
20856 &mut self,
20857 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
20858 window: &mut Window,
20859 cx: &mut Context<Self>,
20860 ) {
20861 let workspace = self.workspace();
20862 let project = self.project.as_ref();
20863 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
20864 let mut tasks = Vec::new();
20865 for (buffer_id, changes) in revert_changes {
20866 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
20867 buffer.update(cx, |buffer, cx| {
20868 buffer.edit(
20869 changes
20870 .into_iter()
20871 .map(|(range, text)| (range, text.to_string())),
20872 None,
20873 cx,
20874 );
20875 });
20876
20877 if let Some(project) =
20878 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
20879 {
20880 project.update(cx, |project, cx| {
20881 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
20882 })
20883 }
20884 }
20885 }
20886 tasks
20887 });
20888 cx.spawn_in(window, async move |_, cx| {
20889 for (buffer, task) in save_tasks {
20890 let result = task.await;
20891 if result.is_err() {
20892 let Some(path) = buffer
20893 .read_with(cx, |buffer, cx| buffer.project_path(cx))
20894 .ok()
20895 else {
20896 continue;
20897 };
20898 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
20899 let Some(task) = cx
20900 .update_window_entity(&workspace, |workspace, window, cx| {
20901 workspace
20902 .open_path_preview(path, None, false, false, false, window, cx)
20903 })
20904 .ok()
20905 else {
20906 continue;
20907 };
20908 task.await.log_err();
20909 }
20910 }
20911 }
20912 })
20913 .detach();
20914 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
20915 selections.refresh()
20916 });
20917 }
20918
20919 pub fn to_pixel_point(
20920 &self,
20921 source: multi_buffer::Anchor,
20922 editor_snapshot: &EditorSnapshot,
20923 window: &mut Window,
20924 ) -> Option<gpui::Point<Pixels>> {
20925 let source_point = source.to_display_point(editor_snapshot);
20926 self.display_to_pixel_point(source_point, editor_snapshot, window)
20927 }
20928
20929 pub fn display_to_pixel_point(
20930 &self,
20931 source: DisplayPoint,
20932 editor_snapshot: &EditorSnapshot,
20933 window: &mut Window,
20934 ) -> Option<gpui::Point<Pixels>> {
20935 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
20936 let text_layout_details = self.text_layout_details(window);
20937 let scroll_top = text_layout_details
20938 .scroll_anchor
20939 .scroll_position(editor_snapshot)
20940 .y;
20941
20942 if source.row().as_f32() < scroll_top.floor() {
20943 return None;
20944 }
20945 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
20946 let source_y = line_height * (source.row().as_f32() - scroll_top);
20947 Some(gpui::Point::new(source_x, source_y))
20948 }
20949
20950 pub fn has_visible_completions_menu(&self) -> bool {
20951 !self.edit_prediction_preview_is_active()
20952 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
20953 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
20954 })
20955 }
20956
20957 pub fn register_addon<T: Addon>(&mut self, instance: T) {
20958 if self.mode.is_minimap() {
20959 return;
20960 }
20961 self.addons
20962 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
20963 }
20964
20965 pub fn unregister_addon<T: Addon>(&mut self) {
20966 self.addons.remove(&std::any::TypeId::of::<T>());
20967 }
20968
20969 pub fn addon<T: Addon>(&self) -> Option<&T> {
20970 let type_id = std::any::TypeId::of::<T>();
20971 self.addons
20972 .get(&type_id)
20973 .and_then(|item| item.to_any().downcast_ref::<T>())
20974 }
20975
20976 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
20977 let type_id = std::any::TypeId::of::<T>();
20978 self.addons
20979 .get_mut(&type_id)
20980 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
20981 }
20982
20983 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
20984 let text_layout_details = self.text_layout_details(window);
20985 let style = &text_layout_details.editor_style;
20986 let font_id = window.text_system().resolve_font(&style.text.font());
20987 let font_size = style.text.font_size.to_pixels(window.rem_size());
20988 let line_height = style.text.line_height_in_pixels(window.rem_size());
20989 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
20990 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
20991
20992 CharacterDimensions {
20993 em_width,
20994 em_advance,
20995 line_height,
20996 }
20997 }
20998
20999 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
21000 self.load_diff_task.clone()
21001 }
21002
21003 fn read_metadata_from_db(
21004 &mut self,
21005 item_id: u64,
21006 workspace_id: WorkspaceId,
21007 window: &mut Window,
21008 cx: &mut Context<Editor>,
21009 ) {
21010 if self.is_singleton(cx)
21011 && !self.mode.is_minimap()
21012 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
21013 {
21014 let buffer_snapshot = OnceCell::new();
21015
21016 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
21017 if !folds.is_empty() {
21018 let snapshot =
21019 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21020 self.fold_ranges(
21021 folds
21022 .into_iter()
21023 .map(|(start, end)| {
21024 snapshot.clip_offset(start, Bias::Left)
21025 ..snapshot.clip_offset(end, Bias::Right)
21026 })
21027 .collect(),
21028 false,
21029 window,
21030 cx,
21031 );
21032 }
21033 }
21034
21035 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
21036 if !selections.is_empty() {
21037 let snapshot =
21038 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21039 // skip adding the initial selection to selection history
21040 self.selection_history.mode = SelectionHistoryMode::Skipping;
21041 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21042 s.select_ranges(selections.into_iter().map(|(start, end)| {
21043 snapshot.clip_offset(start, Bias::Left)
21044 ..snapshot.clip_offset(end, Bias::Right)
21045 }));
21046 });
21047 self.selection_history.mode = SelectionHistoryMode::Normal;
21048 }
21049 };
21050 }
21051
21052 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
21053 }
21054
21055 fn update_lsp_data(
21056 &mut self,
21057 ignore_cache: bool,
21058 for_buffer: Option<BufferId>,
21059 window: &mut Window,
21060 cx: &mut Context<'_, Self>,
21061 ) {
21062 self.pull_diagnostics(for_buffer, window, cx);
21063 self.refresh_colors(ignore_cache, for_buffer, window, cx);
21064 }
21065}
21066
21067fn vim_enabled(cx: &App) -> bool {
21068 cx.global::<SettingsStore>()
21069 .raw_user_settings()
21070 .get("vim_mode")
21071 == Some(&serde_json::Value::Bool(true))
21072}
21073
21074fn process_completion_for_edit(
21075 completion: &Completion,
21076 intent: CompletionIntent,
21077 buffer: &Entity<Buffer>,
21078 cursor_position: &text::Anchor,
21079 cx: &mut Context<Editor>,
21080) -> CompletionEdit {
21081 let buffer = buffer.read(cx);
21082 let buffer_snapshot = buffer.snapshot();
21083 let (snippet, new_text) = if completion.is_snippet() {
21084 // Workaround for typescript language server issues so that methods don't expand within
21085 // strings and functions with type expressions. The previous point is used because the query
21086 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
21087 let mut snippet_source = completion.new_text.clone();
21088 let mut previous_point = text::ToPoint::to_point(cursor_position, buffer);
21089 previous_point.column = previous_point.column.saturating_sub(1);
21090 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point) {
21091 if scope.prefers_label_for_snippet_in_completion() {
21092 if let Some(label) = completion.label() {
21093 if matches!(
21094 completion.kind(),
21095 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
21096 ) {
21097 snippet_source = label;
21098 }
21099 }
21100 }
21101 }
21102 match Snippet::parse(&snippet_source).log_err() {
21103 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
21104 None => (None, completion.new_text.clone()),
21105 }
21106 } else {
21107 (None, completion.new_text.clone())
21108 };
21109
21110 let mut range_to_replace = {
21111 let replace_range = &completion.replace_range;
21112 if let CompletionSource::Lsp {
21113 insert_range: Some(insert_range),
21114 ..
21115 } = &completion.source
21116 {
21117 debug_assert_eq!(
21118 insert_range.start, replace_range.start,
21119 "insert_range and replace_range should start at the same position"
21120 );
21121 debug_assert!(
21122 insert_range
21123 .start
21124 .cmp(&cursor_position, &buffer_snapshot)
21125 .is_le(),
21126 "insert_range should start before or at cursor position"
21127 );
21128 debug_assert!(
21129 replace_range
21130 .start
21131 .cmp(&cursor_position, &buffer_snapshot)
21132 .is_le(),
21133 "replace_range should start before or at cursor position"
21134 );
21135
21136 let should_replace = match intent {
21137 CompletionIntent::CompleteWithInsert => false,
21138 CompletionIntent::CompleteWithReplace => true,
21139 CompletionIntent::Complete | CompletionIntent::Compose => {
21140 let insert_mode =
21141 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
21142 .completions
21143 .lsp_insert_mode;
21144 match insert_mode {
21145 LspInsertMode::Insert => false,
21146 LspInsertMode::Replace => true,
21147 LspInsertMode::ReplaceSubsequence => {
21148 let mut text_to_replace = buffer.chars_for_range(
21149 buffer.anchor_before(replace_range.start)
21150 ..buffer.anchor_after(replace_range.end),
21151 );
21152 let mut current_needle = text_to_replace.next();
21153 for haystack_ch in completion.label.text.chars() {
21154 if let Some(needle_ch) = current_needle {
21155 if haystack_ch.eq_ignore_ascii_case(&needle_ch) {
21156 current_needle = text_to_replace.next();
21157 }
21158 }
21159 }
21160 current_needle.is_none()
21161 }
21162 LspInsertMode::ReplaceSuffix => {
21163 if replace_range
21164 .end
21165 .cmp(&cursor_position, &buffer_snapshot)
21166 .is_gt()
21167 {
21168 let range_after_cursor = *cursor_position..replace_range.end;
21169 let text_after_cursor = buffer
21170 .text_for_range(
21171 buffer.anchor_before(range_after_cursor.start)
21172 ..buffer.anchor_after(range_after_cursor.end),
21173 )
21174 .collect::<String>()
21175 .to_ascii_lowercase();
21176 completion
21177 .label
21178 .text
21179 .to_ascii_lowercase()
21180 .ends_with(&text_after_cursor)
21181 } else {
21182 true
21183 }
21184 }
21185 }
21186 }
21187 };
21188
21189 if should_replace {
21190 replace_range.clone()
21191 } else {
21192 insert_range.clone()
21193 }
21194 } else {
21195 replace_range.clone()
21196 }
21197 };
21198
21199 if range_to_replace
21200 .end
21201 .cmp(&cursor_position, &buffer_snapshot)
21202 .is_lt()
21203 {
21204 range_to_replace.end = *cursor_position;
21205 }
21206
21207 CompletionEdit {
21208 new_text,
21209 replace_range: range_to_replace.to_offset(&buffer),
21210 snippet,
21211 }
21212}
21213
21214struct CompletionEdit {
21215 new_text: String,
21216 replace_range: Range<usize>,
21217 snippet: Option<Snippet>,
21218}
21219
21220fn insert_extra_newline_brackets(
21221 buffer: &MultiBufferSnapshot,
21222 range: Range<usize>,
21223 language: &language::LanguageScope,
21224) -> bool {
21225 let leading_whitespace_len = buffer
21226 .reversed_chars_at(range.start)
21227 .take_while(|c| c.is_whitespace() && *c != '\n')
21228 .map(|c| c.len_utf8())
21229 .sum::<usize>();
21230 let trailing_whitespace_len = buffer
21231 .chars_at(range.end)
21232 .take_while(|c| c.is_whitespace() && *c != '\n')
21233 .map(|c| c.len_utf8())
21234 .sum::<usize>();
21235 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
21236
21237 language.brackets().any(|(pair, enabled)| {
21238 let pair_start = pair.start.trim_end();
21239 let pair_end = pair.end.trim_start();
21240
21241 enabled
21242 && pair.newline
21243 && buffer.contains_str_at(range.end, pair_end)
21244 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
21245 })
21246}
21247
21248fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
21249 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
21250 [(buffer, range, _)] => (*buffer, range.clone()),
21251 _ => return false,
21252 };
21253 let pair = {
21254 let mut result: Option<BracketMatch> = None;
21255
21256 for pair in buffer
21257 .all_bracket_ranges(range.clone())
21258 .filter(move |pair| {
21259 pair.open_range.start <= range.start && pair.close_range.end >= range.end
21260 })
21261 {
21262 let len = pair.close_range.end - pair.open_range.start;
21263
21264 if let Some(existing) = &result {
21265 let existing_len = existing.close_range.end - existing.open_range.start;
21266 if len > existing_len {
21267 continue;
21268 }
21269 }
21270
21271 result = Some(pair);
21272 }
21273
21274 result
21275 };
21276 let Some(pair) = pair else {
21277 return false;
21278 };
21279 pair.newline_only
21280 && buffer
21281 .chars_for_range(pair.open_range.end..range.start)
21282 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
21283 .all(|c| c.is_whitespace() && c != '\n')
21284}
21285
21286fn update_uncommitted_diff_for_buffer(
21287 editor: Entity<Editor>,
21288 project: &Entity<Project>,
21289 buffers: impl IntoIterator<Item = Entity<Buffer>>,
21290 buffer: Entity<MultiBuffer>,
21291 cx: &mut App,
21292) -> Task<()> {
21293 let mut tasks = Vec::new();
21294 project.update(cx, |project, cx| {
21295 for buffer in buffers {
21296 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
21297 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
21298 }
21299 }
21300 });
21301 cx.spawn(async move |cx| {
21302 let diffs = future::join_all(tasks).await;
21303 if editor
21304 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
21305 .unwrap_or(false)
21306 {
21307 return;
21308 }
21309
21310 buffer
21311 .update(cx, |buffer, cx| {
21312 for diff in diffs.into_iter().flatten() {
21313 buffer.add_diff(diff, cx);
21314 }
21315 })
21316 .ok();
21317 })
21318}
21319
21320fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
21321 let tab_size = tab_size.get() as usize;
21322 let mut width = offset;
21323
21324 for ch in text.chars() {
21325 width += if ch == '\t' {
21326 tab_size - (width % tab_size)
21327 } else {
21328 1
21329 };
21330 }
21331
21332 width - offset
21333}
21334
21335#[cfg(test)]
21336mod tests {
21337 use super::*;
21338
21339 #[test]
21340 fn test_string_size_with_expanded_tabs() {
21341 let nz = |val| NonZeroU32::new(val).unwrap();
21342 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
21343 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
21344 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
21345 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
21346 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
21347 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
21348 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
21349 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
21350 }
21351}
21352
21353/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
21354struct WordBreakingTokenizer<'a> {
21355 input: &'a str,
21356}
21357
21358impl<'a> WordBreakingTokenizer<'a> {
21359 fn new(input: &'a str) -> Self {
21360 Self { input }
21361 }
21362}
21363
21364fn is_char_ideographic(ch: char) -> bool {
21365 use unicode_script::Script::*;
21366 use unicode_script::UnicodeScript;
21367 matches!(ch.script(), Han | Tangut | Yi)
21368}
21369
21370fn is_grapheme_ideographic(text: &str) -> bool {
21371 text.chars().any(is_char_ideographic)
21372}
21373
21374fn is_grapheme_whitespace(text: &str) -> bool {
21375 text.chars().any(|x| x.is_whitespace())
21376}
21377
21378fn should_stay_with_preceding_ideograph(text: &str) -> bool {
21379 text.chars().next().map_or(false, |ch| {
21380 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
21381 })
21382}
21383
21384#[derive(PartialEq, Eq, Debug, Clone, Copy)]
21385enum WordBreakToken<'a> {
21386 Word { token: &'a str, grapheme_len: usize },
21387 InlineWhitespace { token: &'a str, grapheme_len: usize },
21388 Newline,
21389}
21390
21391impl<'a> Iterator for WordBreakingTokenizer<'a> {
21392 /// Yields a span, the count of graphemes in the token, and whether it was
21393 /// whitespace. Note that it also breaks at word boundaries.
21394 type Item = WordBreakToken<'a>;
21395
21396 fn next(&mut self) -> Option<Self::Item> {
21397 use unicode_segmentation::UnicodeSegmentation;
21398 if self.input.is_empty() {
21399 return None;
21400 }
21401
21402 let mut iter = self.input.graphemes(true).peekable();
21403 let mut offset = 0;
21404 let mut grapheme_len = 0;
21405 if let Some(first_grapheme) = iter.next() {
21406 let is_newline = first_grapheme == "\n";
21407 let is_whitespace = is_grapheme_whitespace(first_grapheme);
21408 offset += first_grapheme.len();
21409 grapheme_len += 1;
21410 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
21411 if let Some(grapheme) = iter.peek().copied() {
21412 if should_stay_with_preceding_ideograph(grapheme) {
21413 offset += grapheme.len();
21414 grapheme_len += 1;
21415 }
21416 }
21417 } else {
21418 let mut words = self.input[offset..].split_word_bound_indices().peekable();
21419 let mut next_word_bound = words.peek().copied();
21420 if next_word_bound.map_or(false, |(i, _)| i == 0) {
21421 next_word_bound = words.next();
21422 }
21423 while let Some(grapheme) = iter.peek().copied() {
21424 if next_word_bound.map_or(false, |(i, _)| i == offset) {
21425 break;
21426 };
21427 if is_grapheme_whitespace(grapheme) != is_whitespace
21428 || (grapheme == "\n") != is_newline
21429 {
21430 break;
21431 };
21432 offset += grapheme.len();
21433 grapheme_len += 1;
21434 iter.next();
21435 }
21436 }
21437 let token = &self.input[..offset];
21438 self.input = &self.input[offset..];
21439 if token == "\n" {
21440 Some(WordBreakToken::Newline)
21441 } else if is_whitespace {
21442 Some(WordBreakToken::InlineWhitespace {
21443 token,
21444 grapheme_len,
21445 })
21446 } else {
21447 Some(WordBreakToken::Word {
21448 token,
21449 grapheme_len,
21450 })
21451 }
21452 } else {
21453 None
21454 }
21455 }
21456}
21457
21458#[test]
21459fn test_word_breaking_tokenizer() {
21460 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
21461 ("", &[]),
21462 (" ", &[whitespace(" ", 2)]),
21463 ("Ʒ", &[word("Ʒ", 1)]),
21464 ("Ǽ", &[word("Ǽ", 1)]),
21465 ("⋑", &[word("⋑", 1)]),
21466 ("⋑⋑", &[word("⋑⋑", 2)]),
21467 (
21468 "原理,进而",
21469 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
21470 ),
21471 (
21472 "hello world",
21473 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
21474 ),
21475 (
21476 "hello, world",
21477 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
21478 ),
21479 (
21480 " hello world",
21481 &[
21482 whitespace(" ", 2),
21483 word("hello", 5),
21484 whitespace(" ", 1),
21485 word("world", 5),
21486 ],
21487 ),
21488 (
21489 "这是什么 \n 钢笔",
21490 &[
21491 word("这", 1),
21492 word("是", 1),
21493 word("什", 1),
21494 word("么", 1),
21495 whitespace(" ", 1),
21496 newline(),
21497 whitespace(" ", 1),
21498 word("钢", 1),
21499 word("笔", 1),
21500 ],
21501 ),
21502 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
21503 ];
21504
21505 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21506 WordBreakToken::Word {
21507 token,
21508 grapheme_len,
21509 }
21510 }
21511
21512 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21513 WordBreakToken::InlineWhitespace {
21514 token,
21515 grapheme_len,
21516 }
21517 }
21518
21519 fn newline() -> WordBreakToken<'static> {
21520 WordBreakToken::Newline
21521 }
21522
21523 for (input, result) in tests {
21524 assert_eq!(
21525 WordBreakingTokenizer::new(input)
21526 .collect::<Vec<_>>()
21527 .as_slice(),
21528 *result,
21529 );
21530 }
21531}
21532
21533fn wrap_with_prefix(
21534 first_line_prefix: String,
21535 subsequent_lines_prefix: String,
21536 unwrapped_text: String,
21537 wrap_column: usize,
21538 tab_size: NonZeroU32,
21539 preserve_existing_whitespace: bool,
21540) -> String {
21541 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
21542 let subsequent_lines_prefix_len =
21543 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
21544 let mut wrapped_text = String::new();
21545 let mut current_line = first_line_prefix.clone();
21546 let mut is_first_line = true;
21547
21548 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
21549 let mut current_line_len = first_line_prefix_len;
21550 let mut in_whitespace = false;
21551 for token in tokenizer {
21552 let have_preceding_whitespace = in_whitespace;
21553 match token {
21554 WordBreakToken::Word {
21555 token,
21556 grapheme_len,
21557 } => {
21558 in_whitespace = false;
21559 let current_prefix_len = if is_first_line {
21560 first_line_prefix_len
21561 } else {
21562 subsequent_lines_prefix_len
21563 };
21564 if current_line_len + grapheme_len > wrap_column
21565 && current_line_len != current_prefix_len
21566 {
21567 wrapped_text.push_str(current_line.trim_end());
21568 wrapped_text.push('\n');
21569 is_first_line = false;
21570 current_line = subsequent_lines_prefix.clone();
21571 current_line_len = subsequent_lines_prefix_len;
21572 }
21573 current_line.push_str(token);
21574 current_line_len += grapheme_len;
21575 }
21576 WordBreakToken::InlineWhitespace {
21577 mut token,
21578 mut grapheme_len,
21579 } => {
21580 in_whitespace = true;
21581 if have_preceding_whitespace && !preserve_existing_whitespace {
21582 continue;
21583 }
21584 if !preserve_existing_whitespace {
21585 token = " ";
21586 grapheme_len = 1;
21587 }
21588 let current_prefix_len = if is_first_line {
21589 first_line_prefix_len
21590 } else {
21591 subsequent_lines_prefix_len
21592 };
21593 if current_line_len + grapheme_len > wrap_column {
21594 wrapped_text.push_str(current_line.trim_end());
21595 wrapped_text.push('\n');
21596 is_first_line = false;
21597 current_line = subsequent_lines_prefix.clone();
21598 current_line_len = subsequent_lines_prefix_len;
21599 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
21600 current_line.push_str(token);
21601 current_line_len += grapheme_len;
21602 }
21603 }
21604 WordBreakToken::Newline => {
21605 in_whitespace = true;
21606 let current_prefix_len = if is_first_line {
21607 first_line_prefix_len
21608 } else {
21609 subsequent_lines_prefix_len
21610 };
21611 if preserve_existing_whitespace {
21612 wrapped_text.push_str(current_line.trim_end());
21613 wrapped_text.push('\n');
21614 is_first_line = false;
21615 current_line = subsequent_lines_prefix.clone();
21616 current_line_len = subsequent_lines_prefix_len;
21617 } else if have_preceding_whitespace {
21618 continue;
21619 } else if current_line_len + 1 > wrap_column
21620 && current_line_len != current_prefix_len
21621 {
21622 wrapped_text.push_str(current_line.trim_end());
21623 wrapped_text.push('\n');
21624 is_first_line = false;
21625 current_line = subsequent_lines_prefix.clone();
21626 current_line_len = subsequent_lines_prefix_len;
21627 } else if current_line_len != current_prefix_len {
21628 current_line.push(' ');
21629 current_line_len += 1;
21630 }
21631 }
21632 }
21633 }
21634
21635 if !current_line.is_empty() {
21636 wrapped_text.push_str(¤t_line);
21637 }
21638 wrapped_text
21639}
21640
21641#[test]
21642fn test_wrap_with_prefix() {
21643 assert_eq!(
21644 wrap_with_prefix(
21645 "# ".to_string(),
21646 "# ".to_string(),
21647 "abcdefg".to_string(),
21648 4,
21649 NonZeroU32::new(4).unwrap(),
21650 false,
21651 ),
21652 "# abcdefg"
21653 );
21654 assert_eq!(
21655 wrap_with_prefix(
21656 "".to_string(),
21657 "".to_string(),
21658 "\thello world".to_string(),
21659 8,
21660 NonZeroU32::new(4).unwrap(),
21661 false,
21662 ),
21663 "hello\nworld"
21664 );
21665 assert_eq!(
21666 wrap_with_prefix(
21667 "// ".to_string(),
21668 "// ".to_string(),
21669 "xx \nyy zz aa bb cc".to_string(),
21670 12,
21671 NonZeroU32::new(4).unwrap(),
21672 false,
21673 ),
21674 "// xx yy zz\n// aa bb cc"
21675 );
21676 assert_eq!(
21677 wrap_with_prefix(
21678 String::new(),
21679 String::new(),
21680 "这是什么 \n 钢笔".to_string(),
21681 3,
21682 NonZeroU32::new(4).unwrap(),
21683 false,
21684 ),
21685 "这是什\n么 钢\n笔"
21686 );
21687}
21688
21689pub trait CollaborationHub {
21690 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
21691 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
21692 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
21693}
21694
21695impl CollaborationHub for Entity<Project> {
21696 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
21697 self.read(cx).collaborators()
21698 }
21699
21700 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
21701 self.read(cx).user_store().read(cx).participant_indices()
21702 }
21703
21704 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
21705 let this = self.read(cx);
21706 let user_ids = this.collaborators().values().map(|c| c.user_id);
21707 this.user_store().read(cx).participant_names(user_ids, cx)
21708 }
21709}
21710
21711pub trait SemanticsProvider {
21712 fn hover(
21713 &self,
21714 buffer: &Entity<Buffer>,
21715 position: text::Anchor,
21716 cx: &mut App,
21717 ) -> Option<Task<Vec<project::Hover>>>;
21718
21719 fn inline_values(
21720 &self,
21721 buffer_handle: Entity<Buffer>,
21722 range: Range<text::Anchor>,
21723 cx: &mut App,
21724 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21725
21726 fn inlay_hints(
21727 &self,
21728 buffer_handle: Entity<Buffer>,
21729 range: Range<text::Anchor>,
21730 cx: &mut App,
21731 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21732
21733 fn resolve_inlay_hint(
21734 &self,
21735 hint: InlayHint,
21736 buffer_handle: Entity<Buffer>,
21737 server_id: LanguageServerId,
21738 cx: &mut App,
21739 ) -> Option<Task<anyhow::Result<InlayHint>>>;
21740
21741 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
21742
21743 fn document_highlights(
21744 &self,
21745 buffer: &Entity<Buffer>,
21746 position: text::Anchor,
21747 cx: &mut App,
21748 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
21749
21750 fn definitions(
21751 &self,
21752 buffer: &Entity<Buffer>,
21753 position: text::Anchor,
21754 kind: GotoDefinitionKind,
21755 cx: &mut App,
21756 ) -> Option<Task<Result<Vec<LocationLink>>>>;
21757
21758 fn range_for_rename(
21759 &self,
21760 buffer: &Entity<Buffer>,
21761 position: text::Anchor,
21762 cx: &mut App,
21763 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
21764
21765 fn perform_rename(
21766 &self,
21767 buffer: &Entity<Buffer>,
21768 position: text::Anchor,
21769 new_name: String,
21770 cx: &mut App,
21771 ) -> Option<Task<Result<ProjectTransaction>>>;
21772}
21773
21774pub trait CompletionProvider {
21775 fn completions(
21776 &self,
21777 excerpt_id: ExcerptId,
21778 buffer: &Entity<Buffer>,
21779 buffer_position: text::Anchor,
21780 trigger: CompletionContext,
21781 window: &mut Window,
21782 cx: &mut Context<Editor>,
21783 ) -> Task<Result<Vec<CompletionResponse>>>;
21784
21785 fn resolve_completions(
21786 &self,
21787 _buffer: Entity<Buffer>,
21788 _completion_indices: Vec<usize>,
21789 _completions: Rc<RefCell<Box<[Completion]>>>,
21790 _cx: &mut Context<Editor>,
21791 ) -> Task<Result<bool>> {
21792 Task::ready(Ok(false))
21793 }
21794
21795 fn apply_additional_edits_for_completion(
21796 &self,
21797 _buffer: Entity<Buffer>,
21798 _completions: Rc<RefCell<Box<[Completion]>>>,
21799 _completion_index: usize,
21800 _push_to_history: bool,
21801 _cx: &mut Context<Editor>,
21802 ) -> Task<Result<Option<language::Transaction>>> {
21803 Task::ready(Ok(None))
21804 }
21805
21806 fn is_completion_trigger(
21807 &self,
21808 buffer: &Entity<Buffer>,
21809 position: language::Anchor,
21810 text: &str,
21811 trigger_in_words: bool,
21812 menu_is_open: bool,
21813 cx: &mut Context<Editor>,
21814 ) -> bool;
21815
21816 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
21817
21818 fn sort_completions(&self) -> bool {
21819 true
21820 }
21821
21822 fn filter_completions(&self) -> bool {
21823 true
21824 }
21825}
21826
21827pub trait CodeActionProvider {
21828 fn id(&self) -> Arc<str>;
21829
21830 fn code_actions(
21831 &self,
21832 buffer: &Entity<Buffer>,
21833 range: Range<text::Anchor>,
21834 window: &mut Window,
21835 cx: &mut App,
21836 ) -> Task<Result<Vec<CodeAction>>>;
21837
21838 fn apply_code_action(
21839 &self,
21840 buffer_handle: Entity<Buffer>,
21841 action: CodeAction,
21842 excerpt_id: ExcerptId,
21843 push_to_history: bool,
21844 window: &mut Window,
21845 cx: &mut App,
21846 ) -> Task<Result<ProjectTransaction>>;
21847}
21848
21849impl CodeActionProvider for Entity<Project> {
21850 fn id(&self) -> Arc<str> {
21851 "project".into()
21852 }
21853
21854 fn code_actions(
21855 &self,
21856 buffer: &Entity<Buffer>,
21857 range: Range<text::Anchor>,
21858 _window: &mut Window,
21859 cx: &mut App,
21860 ) -> Task<Result<Vec<CodeAction>>> {
21861 self.update(cx, |project, cx| {
21862 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
21863 let code_actions = project.code_actions(buffer, range, None, cx);
21864 cx.background_spawn(async move {
21865 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
21866 Ok(code_lens_actions
21867 .context("code lens fetch")?
21868 .into_iter()
21869 .chain(code_actions.context("code action fetch")?)
21870 .collect())
21871 })
21872 })
21873 }
21874
21875 fn apply_code_action(
21876 &self,
21877 buffer_handle: Entity<Buffer>,
21878 action: CodeAction,
21879 _excerpt_id: ExcerptId,
21880 push_to_history: bool,
21881 _window: &mut Window,
21882 cx: &mut App,
21883 ) -> Task<Result<ProjectTransaction>> {
21884 self.update(cx, |project, cx| {
21885 project.apply_code_action(buffer_handle, action, push_to_history, cx)
21886 })
21887 }
21888}
21889
21890fn snippet_completions(
21891 project: &Project,
21892 buffer: &Entity<Buffer>,
21893 buffer_position: text::Anchor,
21894 cx: &mut App,
21895) -> Task<Result<CompletionResponse>> {
21896 let languages = buffer.read(cx).languages_at(buffer_position);
21897 let snippet_store = project.snippets().read(cx);
21898
21899 let scopes: Vec<_> = languages
21900 .iter()
21901 .filter_map(|language| {
21902 let language_name = language.lsp_id();
21903 let snippets = snippet_store.snippets_for(Some(language_name), cx);
21904
21905 if snippets.is_empty() {
21906 None
21907 } else {
21908 Some((language.default_scope(), snippets))
21909 }
21910 })
21911 .collect();
21912
21913 if scopes.is_empty() {
21914 return Task::ready(Ok(CompletionResponse {
21915 completions: vec![],
21916 is_incomplete: false,
21917 }));
21918 }
21919
21920 let snapshot = buffer.read(cx).text_snapshot();
21921 let chars: String = snapshot
21922 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
21923 .collect();
21924 let executor = cx.background_executor().clone();
21925
21926 cx.background_spawn(async move {
21927 let mut is_incomplete = false;
21928 let mut completions: Vec<Completion> = Vec::new();
21929 for (scope, snippets) in scopes.into_iter() {
21930 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
21931 let mut last_word = chars
21932 .chars()
21933 .take_while(|c| classifier.is_word(*c))
21934 .collect::<String>();
21935 last_word = last_word.chars().rev().collect();
21936
21937 if last_word.is_empty() {
21938 return Ok(CompletionResponse {
21939 completions: vec![],
21940 is_incomplete: true,
21941 });
21942 }
21943
21944 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
21945 let to_lsp = |point: &text::Anchor| {
21946 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
21947 point_to_lsp(end)
21948 };
21949 let lsp_end = to_lsp(&buffer_position);
21950
21951 let candidates = snippets
21952 .iter()
21953 .enumerate()
21954 .flat_map(|(ix, snippet)| {
21955 snippet
21956 .prefix
21957 .iter()
21958 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
21959 })
21960 .collect::<Vec<StringMatchCandidate>>();
21961
21962 const MAX_RESULTS: usize = 100;
21963 let mut matches = fuzzy::match_strings(
21964 &candidates,
21965 &last_word,
21966 last_word.chars().any(|c| c.is_uppercase()),
21967 true,
21968 MAX_RESULTS,
21969 &Default::default(),
21970 executor.clone(),
21971 )
21972 .await;
21973
21974 if matches.len() >= MAX_RESULTS {
21975 is_incomplete = true;
21976 }
21977
21978 // Remove all candidates where the query's start does not match the start of any word in the candidate
21979 if let Some(query_start) = last_word.chars().next() {
21980 matches.retain(|string_match| {
21981 split_words(&string_match.string).any(|word| {
21982 // Check that the first codepoint of the word as lowercase matches the first
21983 // codepoint of the query as lowercase
21984 word.chars()
21985 .flat_map(|codepoint| codepoint.to_lowercase())
21986 .zip(query_start.to_lowercase())
21987 .all(|(word_cp, query_cp)| word_cp == query_cp)
21988 })
21989 });
21990 }
21991
21992 let matched_strings = matches
21993 .into_iter()
21994 .map(|m| m.string)
21995 .collect::<HashSet<_>>();
21996
21997 completions.extend(snippets.iter().filter_map(|snippet| {
21998 let matching_prefix = snippet
21999 .prefix
22000 .iter()
22001 .find(|prefix| matched_strings.contains(*prefix))?;
22002 let start = as_offset - last_word.len();
22003 let start = snapshot.anchor_before(start);
22004 let range = start..buffer_position;
22005 let lsp_start = to_lsp(&start);
22006 let lsp_range = lsp::Range {
22007 start: lsp_start,
22008 end: lsp_end,
22009 };
22010 Some(Completion {
22011 replace_range: range,
22012 new_text: snippet.body.clone(),
22013 source: CompletionSource::Lsp {
22014 insert_range: None,
22015 server_id: LanguageServerId(usize::MAX),
22016 resolved: true,
22017 lsp_completion: Box::new(lsp::CompletionItem {
22018 label: snippet.prefix.first().unwrap().clone(),
22019 kind: Some(CompletionItemKind::SNIPPET),
22020 label_details: snippet.description.as_ref().map(|description| {
22021 lsp::CompletionItemLabelDetails {
22022 detail: Some(description.clone()),
22023 description: None,
22024 }
22025 }),
22026 insert_text_format: Some(InsertTextFormat::SNIPPET),
22027 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22028 lsp::InsertReplaceEdit {
22029 new_text: snippet.body.clone(),
22030 insert: lsp_range,
22031 replace: lsp_range,
22032 },
22033 )),
22034 filter_text: Some(snippet.body.clone()),
22035 sort_text: Some(char::MAX.to_string()),
22036 ..lsp::CompletionItem::default()
22037 }),
22038 lsp_defaults: None,
22039 },
22040 label: CodeLabel {
22041 text: matching_prefix.clone(),
22042 runs: Vec::new(),
22043 filter_range: 0..matching_prefix.len(),
22044 },
22045 icon_path: None,
22046 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
22047 single_line: snippet.name.clone().into(),
22048 plain_text: snippet
22049 .description
22050 .clone()
22051 .map(|description| description.into()),
22052 }),
22053 insert_text_mode: None,
22054 confirm: None,
22055 })
22056 }))
22057 }
22058
22059 Ok(CompletionResponse {
22060 completions,
22061 is_incomplete,
22062 })
22063 })
22064}
22065
22066impl CompletionProvider for Entity<Project> {
22067 fn completions(
22068 &self,
22069 _excerpt_id: ExcerptId,
22070 buffer: &Entity<Buffer>,
22071 buffer_position: text::Anchor,
22072 options: CompletionContext,
22073 _window: &mut Window,
22074 cx: &mut Context<Editor>,
22075 ) -> Task<Result<Vec<CompletionResponse>>> {
22076 self.update(cx, |project, cx| {
22077 let snippets = snippet_completions(project, buffer, buffer_position, cx);
22078 let project_completions = project.completions(buffer, buffer_position, options, cx);
22079 cx.background_spawn(async move {
22080 let mut responses = project_completions.await?;
22081 let snippets = snippets.await?;
22082 if !snippets.completions.is_empty() {
22083 responses.push(snippets);
22084 }
22085 Ok(responses)
22086 })
22087 })
22088 }
22089
22090 fn resolve_completions(
22091 &self,
22092 buffer: Entity<Buffer>,
22093 completion_indices: Vec<usize>,
22094 completions: Rc<RefCell<Box<[Completion]>>>,
22095 cx: &mut Context<Editor>,
22096 ) -> Task<Result<bool>> {
22097 self.update(cx, |project, cx| {
22098 project.lsp_store().update(cx, |lsp_store, cx| {
22099 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
22100 })
22101 })
22102 }
22103
22104 fn apply_additional_edits_for_completion(
22105 &self,
22106 buffer: Entity<Buffer>,
22107 completions: Rc<RefCell<Box<[Completion]>>>,
22108 completion_index: usize,
22109 push_to_history: bool,
22110 cx: &mut Context<Editor>,
22111 ) -> Task<Result<Option<language::Transaction>>> {
22112 self.update(cx, |project, cx| {
22113 project.lsp_store().update(cx, |lsp_store, cx| {
22114 lsp_store.apply_additional_edits_for_completion(
22115 buffer,
22116 completions,
22117 completion_index,
22118 push_to_history,
22119 cx,
22120 )
22121 })
22122 })
22123 }
22124
22125 fn is_completion_trigger(
22126 &self,
22127 buffer: &Entity<Buffer>,
22128 position: language::Anchor,
22129 text: &str,
22130 trigger_in_words: bool,
22131 menu_is_open: bool,
22132 cx: &mut Context<Editor>,
22133 ) -> bool {
22134 let mut chars = text.chars();
22135 let char = if let Some(char) = chars.next() {
22136 char
22137 } else {
22138 return false;
22139 };
22140 if chars.next().is_some() {
22141 return false;
22142 }
22143
22144 let buffer = buffer.read(cx);
22145 let snapshot = buffer.snapshot();
22146 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
22147 return false;
22148 }
22149 let classifier = snapshot.char_classifier_at(position).for_completion(true);
22150 if trigger_in_words && classifier.is_word(char) {
22151 return true;
22152 }
22153
22154 buffer.completion_triggers().contains(text)
22155 }
22156}
22157
22158impl SemanticsProvider for Entity<Project> {
22159 fn hover(
22160 &self,
22161 buffer: &Entity<Buffer>,
22162 position: text::Anchor,
22163 cx: &mut App,
22164 ) -> Option<Task<Vec<project::Hover>>> {
22165 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
22166 }
22167
22168 fn document_highlights(
22169 &self,
22170 buffer: &Entity<Buffer>,
22171 position: text::Anchor,
22172 cx: &mut App,
22173 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
22174 Some(self.update(cx, |project, cx| {
22175 project.document_highlights(buffer, position, cx)
22176 }))
22177 }
22178
22179 fn definitions(
22180 &self,
22181 buffer: &Entity<Buffer>,
22182 position: text::Anchor,
22183 kind: GotoDefinitionKind,
22184 cx: &mut App,
22185 ) -> Option<Task<Result<Vec<LocationLink>>>> {
22186 Some(self.update(cx, |project, cx| match kind {
22187 GotoDefinitionKind::Symbol => project.definitions(&buffer, position, cx),
22188 GotoDefinitionKind::Declaration => project.declarations(&buffer, position, cx),
22189 GotoDefinitionKind::Type => project.type_definitions(&buffer, position, cx),
22190 GotoDefinitionKind::Implementation => project.implementations(&buffer, position, cx),
22191 }))
22192 }
22193
22194 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
22195 self.update(cx, |project, cx| {
22196 if project
22197 .active_debug_session(cx)
22198 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
22199 {
22200 return true;
22201 }
22202
22203 buffer.update(cx, |buffer, cx| {
22204 project.any_language_server_supports_inlay_hints(buffer, cx)
22205 })
22206 })
22207 }
22208
22209 fn inline_values(
22210 &self,
22211 buffer_handle: Entity<Buffer>,
22212 range: Range<text::Anchor>,
22213 cx: &mut App,
22214 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22215 self.update(cx, |project, cx| {
22216 let (session, active_stack_frame) = project.active_debug_session(cx)?;
22217
22218 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
22219 })
22220 }
22221
22222 fn inlay_hints(
22223 &self,
22224 buffer_handle: Entity<Buffer>,
22225 range: Range<text::Anchor>,
22226 cx: &mut App,
22227 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22228 Some(self.update(cx, |project, cx| {
22229 project.inlay_hints(buffer_handle, range, cx)
22230 }))
22231 }
22232
22233 fn resolve_inlay_hint(
22234 &self,
22235 hint: InlayHint,
22236 buffer_handle: Entity<Buffer>,
22237 server_id: LanguageServerId,
22238 cx: &mut App,
22239 ) -> Option<Task<anyhow::Result<InlayHint>>> {
22240 Some(self.update(cx, |project, cx| {
22241 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
22242 }))
22243 }
22244
22245 fn range_for_rename(
22246 &self,
22247 buffer: &Entity<Buffer>,
22248 position: text::Anchor,
22249 cx: &mut App,
22250 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
22251 Some(self.update(cx, |project, cx| {
22252 let buffer = buffer.clone();
22253 let task = project.prepare_rename(buffer.clone(), position, cx);
22254 cx.spawn(async move |_, cx| {
22255 Ok(match task.await? {
22256 PrepareRenameResponse::Success(range) => Some(range),
22257 PrepareRenameResponse::InvalidPosition => None,
22258 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
22259 // Fallback on using TreeSitter info to determine identifier range
22260 buffer.read_with(cx, |buffer, _| {
22261 let snapshot = buffer.snapshot();
22262 let (range, kind) = snapshot.surrounding_word(position, false);
22263 if kind != Some(CharKind::Word) {
22264 return None;
22265 }
22266 Some(
22267 snapshot.anchor_before(range.start)
22268 ..snapshot.anchor_after(range.end),
22269 )
22270 })?
22271 }
22272 })
22273 })
22274 }))
22275 }
22276
22277 fn perform_rename(
22278 &self,
22279 buffer: &Entity<Buffer>,
22280 position: text::Anchor,
22281 new_name: String,
22282 cx: &mut App,
22283 ) -> Option<Task<Result<ProjectTransaction>>> {
22284 Some(self.update(cx, |project, cx| {
22285 project.perform_rename(buffer.clone(), position, new_name, cx)
22286 }))
22287 }
22288}
22289
22290fn inlay_hint_settings(
22291 location: Anchor,
22292 snapshot: &MultiBufferSnapshot,
22293 cx: &mut Context<Editor>,
22294) -> InlayHintSettings {
22295 let file = snapshot.file_at(location);
22296 let language = snapshot.language_at(location).map(|l| l.name());
22297 language_settings(language, file, cx).inlay_hints
22298}
22299
22300fn consume_contiguous_rows(
22301 contiguous_row_selections: &mut Vec<Selection<Point>>,
22302 selection: &Selection<Point>,
22303 display_map: &DisplaySnapshot,
22304 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
22305) -> (MultiBufferRow, MultiBufferRow) {
22306 contiguous_row_selections.push(selection.clone());
22307 let start_row = starting_row(selection, display_map);
22308 let mut end_row = ending_row(selection, display_map);
22309
22310 while let Some(next_selection) = selections.peek() {
22311 if next_selection.start.row <= end_row.0 {
22312 end_row = ending_row(next_selection, display_map);
22313 contiguous_row_selections.push(selections.next().unwrap().clone());
22314 } else {
22315 break;
22316 }
22317 }
22318 (start_row, end_row)
22319}
22320
22321fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22322 if selection.start.column > 0 {
22323 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
22324 } else {
22325 MultiBufferRow(selection.start.row)
22326 }
22327}
22328
22329fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22330 if next_selection.end.column > 0 || next_selection.is_empty() {
22331 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
22332 } else {
22333 MultiBufferRow(next_selection.end.row)
22334 }
22335}
22336
22337impl EditorSnapshot {
22338 pub fn remote_selections_in_range<'a>(
22339 &'a self,
22340 range: &'a Range<Anchor>,
22341 collaboration_hub: &dyn CollaborationHub,
22342 cx: &'a App,
22343 ) -> impl 'a + Iterator<Item = RemoteSelection> {
22344 let participant_names = collaboration_hub.user_names(cx);
22345 let participant_indices = collaboration_hub.user_participant_indices(cx);
22346 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
22347 let collaborators_by_replica_id = collaborators_by_peer_id
22348 .values()
22349 .map(|collaborator| (collaborator.replica_id, collaborator))
22350 .collect::<HashMap<_, _>>();
22351 self.buffer_snapshot
22352 .selections_in_range(range, false)
22353 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
22354 if replica_id == AGENT_REPLICA_ID {
22355 Some(RemoteSelection {
22356 replica_id,
22357 selection,
22358 cursor_shape,
22359 line_mode,
22360 collaborator_id: CollaboratorId::Agent,
22361 user_name: Some("Agent".into()),
22362 color: cx.theme().players().agent(),
22363 })
22364 } else {
22365 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
22366 let participant_index = participant_indices.get(&collaborator.user_id).copied();
22367 let user_name = participant_names.get(&collaborator.user_id).cloned();
22368 Some(RemoteSelection {
22369 replica_id,
22370 selection,
22371 cursor_shape,
22372 line_mode,
22373 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
22374 user_name,
22375 color: if let Some(index) = participant_index {
22376 cx.theme().players().color_for_participant(index.0)
22377 } else {
22378 cx.theme().players().absent()
22379 },
22380 })
22381 }
22382 })
22383 }
22384
22385 pub fn hunks_for_ranges(
22386 &self,
22387 ranges: impl IntoIterator<Item = Range<Point>>,
22388 ) -> Vec<MultiBufferDiffHunk> {
22389 let mut hunks = Vec::new();
22390 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
22391 HashMap::default();
22392 for query_range in ranges {
22393 let query_rows =
22394 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
22395 for hunk in self.buffer_snapshot.diff_hunks_in_range(
22396 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
22397 ) {
22398 // Include deleted hunks that are adjacent to the query range, because
22399 // otherwise they would be missed.
22400 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
22401 if hunk.status().is_deleted() {
22402 intersects_range |= hunk.row_range.start == query_rows.end;
22403 intersects_range |= hunk.row_range.end == query_rows.start;
22404 }
22405 if intersects_range {
22406 if !processed_buffer_rows
22407 .entry(hunk.buffer_id)
22408 .or_default()
22409 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
22410 {
22411 continue;
22412 }
22413 hunks.push(hunk);
22414 }
22415 }
22416 }
22417
22418 hunks
22419 }
22420
22421 fn display_diff_hunks_for_rows<'a>(
22422 &'a self,
22423 display_rows: Range<DisplayRow>,
22424 folded_buffers: &'a HashSet<BufferId>,
22425 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
22426 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
22427 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
22428
22429 self.buffer_snapshot
22430 .diff_hunks_in_range(buffer_start..buffer_end)
22431 .filter_map(|hunk| {
22432 if folded_buffers.contains(&hunk.buffer_id) {
22433 return None;
22434 }
22435
22436 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
22437 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
22438
22439 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
22440 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
22441
22442 let display_hunk = if hunk_display_start.column() != 0 {
22443 DisplayDiffHunk::Folded {
22444 display_row: hunk_display_start.row(),
22445 }
22446 } else {
22447 let mut end_row = hunk_display_end.row();
22448 if hunk_display_end.column() > 0 {
22449 end_row.0 += 1;
22450 }
22451 let is_created_file = hunk.is_created_file();
22452 DisplayDiffHunk::Unfolded {
22453 status: hunk.status(),
22454 diff_base_byte_range: hunk.diff_base_byte_range,
22455 display_row_range: hunk_display_start.row()..end_row,
22456 multi_buffer_range: Anchor::range_in_buffer(
22457 hunk.excerpt_id,
22458 hunk.buffer_id,
22459 hunk.buffer_range,
22460 ),
22461 is_created_file,
22462 }
22463 };
22464
22465 Some(display_hunk)
22466 })
22467 }
22468
22469 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
22470 self.display_snapshot.buffer_snapshot.language_at(position)
22471 }
22472
22473 pub fn is_focused(&self) -> bool {
22474 self.is_focused
22475 }
22476
22477 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
22478 self.placeholder_text.as_ref()
22479 }
22480
22481 pub fn scroll_position(&self) -> gpui::Point<f32> {
22482 self.scroll_anchor.scroll_position(&self.display_snapshot)
22483 }
22484
22485 fn gutter_dimensions(
22486 &self,
22487 font_id: FontId,
22488 font_size: Pixels,
22489 max_line_number_width: Pixels,
22490 cx: &App,
22491 ) -> Option<GutterDimensions> {
22492 if !self.show_gutter {
22493 return None;
22494 }
22495
22496 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
22497 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
22498
22499 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
22500 matches!(
22501 ProjectSettings::get_global(cx).git.git_gutter,
22502 Some(GitGutterSetting::TrackedFiles)
22503 )
22504 });
22505 let gutter_settings = EditorSettings::get_global(cx).gutter;
22506 let show_line_numbers = self
22507 .show_line_numbers
22508 .unwrap_or(gutter_settings.line_numbers);
22509 let line_gutter_width = if show_line_numbers {
22510 // Avoid flicker-like gutter resizes when the line number gains another digit by
22511 // only resizing the gutter on files with > 10**min_line_number_digits lines.
22512 let min_width_for_number_on_gutter =
22513 ch_advance * gutter_settings.min_line_number_digits as f32;
22514 max_line_number_width.max(min_width_for_number_on_gutter)
22515 } else {
22516 0.0.into()
22517 };
22518
22519 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
22520 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
22521
22522 let git_blame_entries_width =
22523 self.git_blame_gutter_max_author_length
22524 .map(|max_author_length| {
22525 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
22526 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
22527
22528 /// The number of characters to dedicate to gaps and margins.
22529 const SPACING_WIDTH: usize = 4;
22530
22531 let max_char_count = max_author_length.min(renderer.max_author_length())
22532 + ::git::SHORT_SHA_LENGTH
22533 + MAX_RELATIVE_TIMESTAMP.len()
22534 + SPACING_WIDTH;
22535
22536 ch_advance * max_char_count
22537 });
22538
22539 let is_singleton = self.buffer_snapshot.is_singleton();
22540
22541 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
22542 left_padding += if !is_singleton {
22543 ch_width * 4.0
22544 } else if show_runnables || show_breakpoints {
22545 ch_width * 3.0
22546 } else if show_git_gutter && show_line_numbers {
22547 ch_width * 2.0
22548 } else if show_git_gutter || show_line_numbers {
22549 ch_width
22550 } else {
22551 px(0.)
22552 };
22553
22554 let shows_folds = is_singleton && gutter_settings.folds;
22555
22556 let right_padding = if shows_folds && show_line_numbers {
22557 ch_width * 4.0
22558 } else if shows_folds || (!is_singleton && show_line_numbers) {
22559 ch_width * 3.0
22560 } else if show_line_numbers {
22561 ch_width
22562 } else {
22563 px(0.)
22564 };
22565
22566 Some(GutterDimensions {
22567 left_padding,
22568 right_padding,
22569 width: line_gutter_width + left_padding + right_padding,
22570 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
22571 git_blame_entries_width,
22572 })
22573 }
22574
22575 pub fn render_crease_toggle(
22576 &self,
22577 buffer_row: MultiBufferRow,
22578 row_contains_cursor: bool,
22579 editor: Entity<Editor>,
22580 window: &mut Window,
22581 cx: &mut App,
22582 ) -> Option<AnyElement> {
22583 let folded = self.is_line_folded(buffer_row);
22584 let mut is_foldable = false;
22585
22586 if let Some(crease) = self
22587 .crease_snapshot
22588 .query_row(buffer_row, &self.buffer_snapshot)
22589 {
22590 is_foldable = true;
22591 match crease {
22592 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
22593 if let Some(render_toggle) = render_toggle {
22594 let toggle_callback =
22595 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
22596 if folded {
22597 editor.update(cx, |editor, cx| {
22598 editor.fold_at(buffer_row, window, cx)
22599 });
22600 } else {
22601 editor.update(cx, |editor, cx| {
22602 editor.unfold_at(buffer_row, window, cx)
22603 });
22604 }
22605 });
22606 return Some((render_toggle)(
22607 buffer_row,
22608 folded,
22609 toggle_callback,
22610 window,
22611 cx,
22612 ));
22613 }
22614 }
22615 }
22616 }
22617
22618 is_foldable |= self.starts_indent(buffer_row);
22619
22620 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
22621 Some(
22622 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
22623 .toggle_state(folded)
22624 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
22625 if folded {
22626 this.unfold_at(buffer_row, window, cx);
22627 } else {
22628 this.fold_at(buffer_row, window, cx);
22629 }
22630 }))
22631 .into_any_element(),
22632 )
22633 } else {
22634 None
22635 }
22636 }
22637
22638 pub fn render_crease_trailer(
22639 &self,
22640 buffer_row: MultiBufferRow,
22641 window: &mut Window,
22642 cx: &mut App,
22643 ) -> Option<AnyElement> {
22644 let folded = self.is_line_folded(buffer_row);
22645 if let Crease::Inline { render_trailer, .. } = self
22646 .crease_snapshot
22647 .query_row(buffer_row, &self.buffer_snapshot)?
22648 {
22649 let render_trailer = render_trailer.as_ref()?;
22650 Some(render_trailer(buffer_row, folded, window, cx))
22651 } else {
22652 None
22653 }
22654 }
22655}
22656
22657impl Deref for EditorSnapshot {
22658 type Target = DisplaySnapshot;
22659
22660 fn deref(&self) -> &Self::Target {
22661 &self.display_snapshot
22662 }
22663}
22664
22665#[derive(Clone, Debug, PartialEq, Eq)]
22666pub enum EditorEvent {
22667 InputIgnored {
22668 text: Arc<str>,
22669 },
22670 InputHandled {
22671 utf16_range_to_replace: Option<Range<isize>>,
22672 text: Arc<str>,
22673 },
22674 ExcerptsAdded {
22675 buffer: Entity<Buffer>,
22676 predecessor: ExcerptId,
22677 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
22678 },
22679 ExcerptsRemoved {
22680 ids: Vec<ExcerptId>,
22681 removed_buffer_ids: Vec<BufferId>,
22682 },
22683 BufferFoldToggled {
22684 ids: Vec<ExcerptId>,
22685 folded: bool,
22686 },
22687 ExcerptsEdited {
22688 ids: Vec<ExcerptId>,
22689 },
22690 ExcerptsExpanded {
22691 ids: Vec<ExcerptId>,
22692 },
22693 BufferEdited,
22694 Edited {
22695 transaction_id: clock::Lamport,
22696 },
22697 Reparsed(BufferId),
22698 Focused,
22699 FocusedIn,
22700 Blurred,
22701 DirtyChanged,
22702 Saved,
22703 TitleChanged,
22704 DiffBaseChanged,
22705 SelectionsChanged {
22706 local: bool,
22707 },
22708 ScrollPositionChanged {
22709 local: bool,
22710 autoscroll: bool,
22711 },
22712 Closed,
22713 TransactionUndone {
22714 transaction_id: clock::Lamport,
22715 },
22716 TransactionBegun {
22717 transaction_id: clock::Lamport,
22718 },
22719 Reloaded,
22720 CursorShapeChanged,
22721 PushedToNavHistory {
22722 anchor: Anchor,
22723 is_deactivate: bool,
22724 },
22725}
22726
22727impl EventEmitter<EditorEvent> for Editor {}
22728
22729impl Focusable for Editor {
22730 fn focus_handle(&self, _cx: &App) -> FocusHandle {
22731 self.focus_handle.clone()
22732 }
22733}
22734
22735impl Render for Editor {
22736 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22737 let settings = ThemeSettings::get_global(cx);
22738
22739 let mut text_style = match self.mode {
22740 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
22741 color: cx.theme().colors().editor_foreground,
22742 font_family: settings.ui_font.family.clone(),
22743 font_features: settings.ui_font.features.clone(),
22744 font_fallbacks: settings.ui_font.fallbacks.clone(),
22745 font_size: rems(0.875).into(),
22746 font_weight: settings.ui_font.weight,
22747 line_height: relative(settings.buffer_line_height.value()),
22748 ..Default::default()
22749 },
22750 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
22751 color: cx.theme().colors().editor_foreground,
22752 font_family: settings.buffer_font.family.clone(),
22753 font_features: settings.buffer_font.features.clone(),
22754 font_fallbacks: settings.buffer_font.fallbacks.clone(),
22755 font_size: settings.buffer_font_size(cx).into(),
22756 font_weight: settings.buffer_font.weight,
22757 line_height: relative(settings.buffer_line_height.value()),
22758 ..Default::default()
22759 },
22760 };
22761 if let Some(text_style_refinement) = &self.text_style_refinement {
22762 text_style.refine(text_style_refinement)
22763 }
22764
22765 let background = match self.mode {
22766 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
22767 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
22768 EditorMode::Full { .. } => cx.theme().colors().editor_background,
22769 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
22770 };
22771
22772 EditorElement::new(
22773 &cx.entity(),
22774 EditorStyle {
22775 background,
22776 border: cx.theme().colors().border,
22777 local_player: cx.theme().players().local(),
22778 text: text_style,
22779 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
22780 syntax: cx.theme().syntax().clone(),
22781 status: cx.theme().status().clone(),
22782 inlay_hints_style: make_inlay_hints_style(cx),
22783 edit_prediction_styles: make_suggestion_styles(cx),
22784 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
22785 show_underlines: self.diagnostics_enabled(),
22786 },
22787 )
22788 }
22789}
22790
22791impl EntityInputHandler for Editor {
22792 fn text_for_range(
22793 &mut self,
22794 range_utf16: Range<usize>,
22795 adjusted_range: &mut Option<Range<usize>>,
22796 _: &mut Window,
22797 cx: &mut Context<Self>,
22798 ) -> Option<String> {
22799 let snapshot = self.buffer.read(cx).read(cx);
22800 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
22801 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
22802 if (start.0..end.0) != range_utf16 {
22803 adjusted_range.replace(start.0..end.0);
22804 }
22805 Some(snapshot.text_for_range(start..end).collect())
22806 }
22807
22808 fn selected_text_range(
22809 &mut self,
22810 ignore_disabled_input: bool,
22811 _: &mut Window,
22812 cx: &mut Context<Self>,
22813 ) -> Option<UTF16Selection> {
22814 // Prevent the IME menu from appearing when holding down an alphabetic key
22815 // while input is disabled.
22816 if !ignore_disabled_input && !self.input_enabled {
22817 return None;
22818 }
22819
22820 let selection = self.selections.newest::<OffsetUtf16>(cx);
22821 let range = selection.range();
22822
22823 Some(UTF16Selection {
22824 range: range.start.0..range.end.0,
22825 reversed: selection.reversed,
22826 })
22827 }
22828
22829 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
22830 let snapshot = self.buffer.read(cx).read(cx);
22831 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
22832 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
22833 }
22834
22835 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
22836 self.clear_highlights::<InputComposition>(cx);
22837 self.ime_transaction.take();
22838 }
22839
22840 fn replace_text_in_range(
22841 &mut self,
22842 range_utf16: Option<Range<usize>>,
22843 text: &str,
22844 window: &mut Window,
22845 cx: &mut Context<Self>,
22846 ) {
22847 if !self.input_enabled {
22848 cx.emit(EditorEvent::InputIgnored { text: text.into() });
22849 return;
22850 }
22851
22852 self.transact(window, cx, |this, window, cx| {
22853 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
22854 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
22855 Some(this.selection_replacement_ranges(range_utf16, cx))
22856 } else {
22857 this.marked_text_ranges(cx)
22858 };
22859
22860 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
22861 let newest_selection_id = this.selections.newest_anchor().id;
22862 this.selections
22863 .all::<OffsetUtf16>(cx)
22864 .iter()
22865 .zip(ranges_to_replace.iter())
22866 .find_map(|(selection, range)| {
22867 if selection.id == newest_selection_id {
22868 Some(
22869 (range.start.0 as isize - selection.head().0 as isize)
22870 ..(range.end.0 as isize - selection.head().0 as isize),
22871 )
22872 } else {
22873 None
22874 }
22875 })
22876 });
22877
22878 cx.emit(EditorEvent::InputHandled {
22879 utf16_range_to_replace: range_to_replace,
22880 text: text.into(),
22881 });
22882
22883 if let Some(new_selected_ranges) = new_selected_ranges {
22884 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22885 selections.select_ranges(new_selected_ranges)
22886 });
22887 this.backspace(&Default::default(), window, cx);
22888 }
22889
22890 this.handle_input(text, window, cx);
22891 });
22892
22893 if let Some(transaction) = self.ime_transaction {
22894 self.buffer.update(cx, |buffer, cx| {
22895 buffer.group_until_transaction(transaction, cx);
22896 });
22897 }
22898
22899 self.unmark_text(window, cx);
22900 }
22901
22902 fn replace_and_mark_text_in_range(
22903 &mut self,
22904 range_utf16: Option<Range<usize>>,
22905 text: &str,
22906 new_selected_range_utf16: Option<Range<usize>>,
22907 window: &mut Window,
22908 cx: &mut Context<Self>,
22909 ) {
22910 if !self.input_enabled {
22911 return;
22912 }
22913
22914 let transaction = self.transact(window, cx, |this, window, cx| {
22915 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
22916 let snapshot = this.buffer.read(cx).read(cx);
22917 if let Some(relative_range_utf16) = range_utf16.as_ref() {
22918 for marked_range in &mut marked_ranges {
22919 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
22920 marked_range.start.0 += relative_range_utf16.start;
22921 marked_range.start =
22922 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
22923 marked_range.end =
22924 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
22925 }
22926 }
22927 Some(marked_ranges)
22928 } else if let Some(range_utf16) = range_utf16 {
22929 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
22930 Some(this.selection_replacement_ranges(range_utf16, cx))
22931 } else {
22932 None
22933 };
22934
22935 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
22936 let newest_selection_id = this.selections.newest_anchor().id;
22937 this.selections
22938 .all::<OffsetUtf16>(cx)
22939 .iter()
22940 .zip(ranges_to_replace.iter())
22941 .find_map(|(selection, range)| {
22942 if selection.id == newest_selection_id {
22943 Some(
22944 (range.start.0 as isize - selection.head().0 as isize)
22945 ..(range.end.0 as isize - selection.head().0 as isize),
22946 )
22947 } else {
22948 None
22949 }
22950 })
22951 });
22952
22953 cx.emit(EditorEvent::InputHandled {
22954 utf16_range_to_replace: range_to_replace,
22955 text: text.into(),
22956 });
22957
22958 if let Some(ranges) = ranges_to_replace {
22959 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22960 s.select_ranges(ranges)
22961 });
22962 }
22963
22964 let marked_ranges = {
22965 let snapshot = this.buffer.read(cx).read(cx);
22966 this.selections
22967 .disjoint_anchors()
22968 .iter()
22969 .map(|selection| {
22970 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
22971 })
22972 .collect::<Vec<_>>()
22973 };
22974
22975 if text.is_empty() {
22976 this.unmark_text(window, cx);
22977 } else {
22978 this.highlight_text::<InputComposition>(
22979 marked_ranges.clone(),
22980 HighlightStyle {
22981 underline: Some(UnderlineStyle {
22982 thickness: px(1.),
22983 color: None,
22984 wavy: false,
22985 }),
22986 ..Default::default()
22987 },
22988 cx,
22989 );
22990 }
22991
22992 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
22993 let use_autoclose = this.use_autoclose;
22994 let use_auto_surround = this.use_auto_surround;
22995 this.set_use_autoclose(false);
22996 this.set_use_auto_surround(false);
22997 this.handle_input(text, window, cx);
22998 this.set_use_autoclose(use_autoclose);
22999 this.set_use_auto_surround(use_auto_surround);
23000
23001 if let Some(new_selected_range) = new_selected_range_utf16 {
23002 let snapshot = this.buffer.read(cx).read(cx);
23003 let new_selected_ranges = marked_ranges
23004 .into_iter()
23005 .map(|marked_range| {
23006 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
23007 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
23008 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
23009 snapshot.clip_offset_utf16(new_start, Bias::Left)
23010 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
23011 })
23012 .collect::<Vec<_>>();
23013
23014 drop(snapshot);
23015 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23016 selections.select_ranges(new_selected_ranges)
23017 });
23018 }
23019 });
23020
23021 self.ime_transaction = self.ime_transaction.or(transaction);
23022 if let Some(transaction) = self.ime_transaction {
23023 self.buffer.update(cx, |buffer, cx| {
23024 buffer.group_until_transaction(transaction, cx);
23025 });
23026 }
23027
23028 if self.text_highlights::<InputComposition>(cx).is_none() {
23029 self.ime_transaction.take();
23030 }
23031 }
23032
23033 fn bounds_for_range(
23034 &mut self,
23035 range_utf16: Range<usize>,
23036 element_bounds: gpui::Bounds<Pixels>,
23037 window: &mut Window,
23038 cx: &mut Context<Self>,
23039 ) -> Option<gpui::Bounds<Pixels>> {
23040 let text_layout_details = self.text_layout_details(window);
23041 let CharacterDimensions {
23042 em_width,
23043 em_advance,
23044 line_height,
23045 } = self.character_dimensions(window);
23046
23047 let snapshot = self.snapshot(window, cx);
23048 let scroll_position = snapshot.scroll_position();
23049 let scroll_left = scroll_position.x * em_advance;
23050
23051 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
23052 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
23053 + self.gutter_dimensions.full_width();
23054 let y = line_height * (start.row().as_f32() - scroll_position.y);
23055
23056 Some(Bounds {
23057 origin: element_bounds.origin + point(x, y),
23058 size: size(em_width, line_height),
23059 })
23060 }
23061
23062 fn character_index_for_point(
23063 &mut self,
23064 point: gpui::Point<Pixels>,
23065 _window: &mut Window,
23066 _cx: &mut Context<Self>,
23067 ) -> Option<usize> {
23068 let position_map = self.last_position_map.as_ref()?;
23069 if !position_map.text_hitbox.contains(&point) {
23070 return None;
23071 }
23072 let display_point = position_map.point_for_position(point).previous_valid;
23073 let anchor = position_map
23074 .snapshot
23075 .display_point_to_anchor(display_point, Bias::Left);
23076 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
23077 Some(utf16_offset.0)
23078 }
23079}
23080
23081trait SelectionExt {
23082 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
23083 fn spanned_rows(
23084 &self,
23085 include_end_if_at_line_start: bool,
23086 map: &DisplaySnapshot,
23087 ) -> Range<MultiBufferRow>;
23088}
23089
23090impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
23091 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
23092 let start = self
23093 .start
23094 .to_point(&map.buffer_snapshot)
23095 .to_display_point(map);
23096 let end = self
23097 .end
23098 .to_point(&map.buffer_snapshot)
23099 .to_display_point(map);
23100 if self.reversed {
23101 end..start
23102 } else {
23103 start..end
23104 }
23105 }
23106
23107 fn spanned_rows(
23108 &self,
23109 include_end_if_at_line_start: bool,
23110 map: &DisplaySnapshot,
23111 ) -> Range<MultiBufferRow> {
23112 let start = self.start.to_point(&map.buffer_snapshot);
23113 let mut end = self.end.to_point(&map.buffer_snapshot);
23114 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
23115 end.row -= 1;
23116 }
23117
23118 let buffer_start = map.prev_line_boundary(start).0;
23119 let buffer_end = map.next_line_boundary(end).0;
23120 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
23121 }
23122}
23123
23124impl<T: InvalidationRegion> InvalidationStack<T> {
23125 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
23126 where
23127 S: Clone + ToOffset,
23128 {
23129 while let Some(region) = self.last() {
23130 let all_selections_inside_invalidation_ranges =
23131 if selections.len() == region.ranges().len() {
23132 selections
23133 .iter()
23134 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
23135 .all(|(selection, invalidation_range)| {
23136 let head = selection.head().to_offset(buffer);
23137 invalidation_range.start <= head && invalidation_range.end >= head
23138 })
23139 } else {
23140 false
23141 };
23142
23143 if all_selections_inside_invalidation_ranges {
23144 break;
23145 } else {
23146 self.pop();
23147 }
23148 }
23149 }
23150}
23151
23152impl<T> Default for InvalidationStack<T> {
23153 fn default() -> Self {
23154 Self(Default::default())
23155 }
23156}
23157
23158impl<T> Deref for InvalidationStack<T> {
23159 type Target = Vec<T>;
23160
23161 fn deref(&self) -> &Self::Target {
23162 &self.0
23163 }
23164}
23165
23166impl<T> DerefMut for InvalidationStack<T> {
23167 fn deref_mut(&mut self) -> &mut Self::Target {
23168 &mut self.0
23169 }
23170}
23171
23172impl InvalidationRegion for SnippetState {
23173 fn ranges(&self) -> &[Range<Anchor>] {
23174 &self.ranges[self.active_index]
23175 }
23176}
23177
23178fn edit_prediction_edit_text(
23179 current_snapshot: &BufferSnapshot,
23180 edits: &[(Range<Anchor>, String)],
23181 edit_preview: &EditPreview,
23182 include_deletions: bool,
23183 cx: &App,
23184) -> HighlightedText {
23185 let edits = edits
23186 .iter()
23187 .map(|(anchor, text)| {
23188 (
23189 anchor.start.text_anchor..anchor.end.text_anchor,
23190 text.clone(),
23191 )
23192 })
23193 .collect::<Vec<_>>();
23194
23195 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
23196}
23197
23198pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
23199 match severity {
23200 lsp::DiagnosticSeverity::ERROR => colors.error,
23201 lsp::DiagnosticSeverity::WARNING => colors.warning,
23202 lsp::DiagnosticSeverity::INFORMATION => colors.info,
23203 lsp::DiagnosticSeverity::HINT => colors.info,
23204 _ => colors.ignored,
23205 }
23206}
23207
23208pub fn styled_runs_for_code_label<'a>(
23209 label: &'a CodeLabel,
23210 syntax_theme: &'a theme::SyntaxTheme,
23211) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
23212 let fade_out = HighlightStyle {
23213 fade_out: Some(0.35),
23214 ..Default::default()
23215 };
23216
23217 let mut prev_end = label.filter_range.end;
23218 label
23219 .runs
23220 .iter()
23221 .enumerate()
23222 .flat_map(move |(ix, (range, highlight_id))| {
23223 let style = if let Some(style) = highlight_id.style(syntax_theme) {
23224 style
23225 } else {
23226 return Default::default();
23227 };
23228 let mut muted_style = style;
23229 muted_style.highlight(fade_out);
23230
23231 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
23232 if range.start >= label.filter_range.end {
23233 if range.start > prev_end {
23234 runs.push((prev_end..range.start, fade_out));
23235 }
23236 runs.push((range.clone(), muted_style));
23237 } else if range.end <= label.filter_range.end {
23238 runs.push((range.clone(), style));
23239 } else {
23240 runs.push((range.start..label.filter_range.end, style));
23241 runs.push((label.filter_range.end..range.end, muted_style));
23242 }
23243 prev_end = cmp::max(prev_end, range.end);
23244
23245 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
23246 runs.push((prev_end..label.text.len(), fade_out));
23247 }
23248
23249 runs
23250 })
23251}
23252
23253pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
23254 let mut prev_index = 0;
23255 let mut prev_codepoint: Option<char> = None;
23256 text.char_indices()
23257 .chain([(text.len(), '\0')])
23258 .filter_map(move |(index, codepoint)| {
23259 let prev_codepoint = prev_codepoint.replace(codepoint)?;
23260 let is_boundary = index == text.len()
23261 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
23262 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
23263 if is_boundary {
23264 let chunk = &text[prev_index..index];
23265 prev_index = index;
23266 Some(chunk)
23267 } else {
23268 None
23269 }
23270 })
23271}
23272
23273pub trait RangeToAnchorExt: Sized {
23274 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
23275
23276 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
23277 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
23278 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
23279 }
23280}
23281
23282impl<T: ToOffset> RangeToAnchorExt for Range<T> {
23283 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
23284 let start_offset = self.start.to_offset(snapshot);
23285 let end_offset = self.end.to_offset(snapshot);
23286 if start_offset == end_offset {
23287 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
23288 } else {
23289 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
23290 }
23291 }
23292}
23293
23294pub trait RowExt {
23295 fn as_f32(&self) -> f32;
23296
23297 fn next_row(&self) -> Self;
23298
23299 fn previous_row(&self) -> Self;
23300
23301 fn minus(&self, other: Self) -> u32;
23302}
23303
23304impl RowExt for DisplayRow {
23305 fn as_f32(&self) -> f32 {
23306 self.0 as f32
23307 }
23308
23309 fn next_row(&self) -> Self {
23310 Self(self.0 + 1)
23311 }
23312
23313 fn previous_row(&self) -> Self {
23314 Self(self.0.saturating_sub(1))
23315 }
23316
23317 fn minus(&self, other: Self) -> u32 {
23318 self.0 - other.0
23319 }
23320}
23321
23322impl RowExt for MultiBufferRow {
23323 fn as_f32(&self) -> f32 {
23324 self.0 as f32
23325 }
23326
23327 fn next_row(&self) -> Self {
23328 Self(self.0 + 1)
23329 }
23330
23331 fn previous_row(&self) -> Self {
23332 Self(self.0.saturating_sub(1))
23333 }
23334
23335 fn minus(&self, other: Self) -> u32 {
23336 self.0 - other.0
23337 }
23338}
23339
23340trait RowRangeExt {
23341 type Row;
23342
23343 fn len(&self) -> usize;
23344
23345 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
23346}
23347
23348impl RowRangeExt for Range<MultiBufferRow> {
23349 type Row = MultiBufferRow;
23350
23351 fn len(&self) -> usize {
23352 (self.end.0 - self.start.0) as usize
23353 }
23354
23355 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
23356 (self.start.0..self.end.0).map(MultiBufferRow)
23357 }
23358}
23359
23360impl RowRangeExt for Range<DisplayRow> {
23361 type Row = DisplayRow;
23362
23363 fn len(&self) -> usize {
23364 (self.end.0 - self.start.0) as usize
23365 }
23366
23367 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
23368 (self.start.0..self.end.0).map(DisplayRow)
23369 }
23370}
23371
23372/// If select range has more than one line, we
23373/// just point the cursor to range.start.
23374fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
23375 if range.start.row == range.end.row {
23376 range
23377 } else {
23378 range.start..range.start
23379 }
23380}
23381pub struct KillRing(ClipboardItem);
23382impl Global for KillRing {}
23383
23384const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
23385
23386enum BreakpointPromptEditAction {
23387 Log,
23388 Condition,
23389 HitCondition,
23390}
23391
23392struct BreakpointPromptEditor {
23393 pub(crate) prompt: Entity<Editor>,
23394 editor: WeakEntity<Editor>,
23395 breakpoint_anchor: Anchor,
23396 breakpoint: Breakpoint,
23397 edit_action: BreakpointPromptEditAction,
23398 block_ids: HashSet<CustomBlockId>,
23399 editor_margins: Arc<Mutex<EditorMargins>>,
23400 _subscriptions: Vec<Subscription>,
23401}
23402
23403impl BreakpointPromptEditor {
23404 const MAX_LINES: u8 = 4;
23405
23406 fn new(
23407 editor: WeakEntity<Editor>,
23408 breakpoint_anchor: Anchor,
23409 breakpoint: Breakpoint,
23410 edit_action: BreakpointPromptEditAction,
23411 window: &mut Window,
23412 cx: &mut Context<Self>,
23413 ) -> Self {
23414 let base_text = match edit_action {
23415 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
23416 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
23417 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
23418 }
23419 .map(|msg| msg.to_string())
23420 .unwrap_or_default();
23421
23422 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
23423 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
23424
23425 let prompt = cx.new(|cx| {
23426 let mut prompt = Editor::new(
23427 EditorMode::AutoHeight {
23428 min_lines: 1,
23429 max_lines: Some(Self::MAX_LINES as usize),
23430 },
23431 buffer,
23432 None,
23433 window,
23434 cx,
23435 );
23436 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
23437 prompt.set_show_cursor_when_unfocused(false, cx);
23438 prompt.set_placeholder_text(
23439 match edit_action {
23440 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
23441 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
23442 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
23443 },
23444 cx,
23445 );
23446
23447 prompt
23448 });
23449
23450 Self {
23451 prompt,
23452 editor,
23453 breakpoint_anchor,
23454 breakpoint,
23455 edit_action,
23456 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
23457 block_ids: Default::default(),
23458 _subscriptions: vec![],
23459 }
23460 }
23461
23462 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
23463 self.block_ids.extend(block_ids)
23464 }
23465
23466 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
23467 if let Some(editor) = self.editor.upgrade() {
23468 let message = self
23469 .prompt
23470 .read(cx)
23471 .buffer
23472 .read(cx)
23473 .as_singleton()
23474 .expect("A multi buffer in breakpoint prompt isn't possible")
23475 .read(cx)
23476 .as_rope()
23477 .to_string();
23478
23479 editor.update(cx, |editor, cx| {
23480 editor.edit_breakpoint_at_anchor(
23481 self.breakpoint_anchor,
23482 self.breakpoint.clone(),
23483 match self.edit_action {
23484 BreakpointPromptEditAction::Log => {
23485 BreakpointEditAction::EditLogMessage(message.into())
23486 }
23487 BreakpointPromptEditAction::Condition => {
23488 BreakpointEditAction::EditCondition(message.into())
23489 }
23490 BreakpointPromptEditAction::HitCondition => {
23491 BreakpointEditAction::EditHitCondition(message.into())
23492 }
23493 },
23494 cx,
23495 );
23496
23497 editor.remove_blocks(self.block_ids.clone(), None, cx);
23498 cx.focus_self(window);
23499 });
23500 }
23501 }
23502
23503 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
23504 self.editor
23505 .update(cx, |editor, cx| {
23506 editor.remove_blocks(self.block_ids.clone(), None, cx);
23507 window.focus(&editor.focus_handle);
23508 })
23509 .log_err();
23510 }
23511
23512 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
23513 let settings = ThemeSettings::get_global(cx);
23514 let text_style = TextStyle {
23515 color: if self.prompt.read(cx).read_only(cx) {
23516 cx.theme().colors().text_disabled
23517 } else {
23518 cx.theme().colors().text
23519 },
23520 font_family: settings.buffer_font.family.clone(),
23521 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23522 font_size: settings.buffer_font_size(cx).into(),
23523 font_weight: settings.buffer_font.weight,
23524 line_height: relative(settings.buffer_line_height.value()),
23525 ..Default::default()
23526 };
23527 EditorElement::new(
23528 &self.prompt,
23529 EditorStyle {
23530 background: cx.theme().colors().editor_background,
23531 local_player: cx.theme().players().local(),
23532 text: text_style,
23533 ..Default::default()
23534 },
23535 )
23536 }
23537}
23538
23539impl Render for BreakpointPromptEditor {
23540 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23541 let editor_margins = *self.editor_margins.lock();
23542 let gutter_dimensions = editor_margins.gutter;
23543 h_flex()
23544 .key_context("Editor")
23545 .bg(cx.theme().colors().editor_background)
23546 .border_y_1()
23547 .border_color(cx.theme().status().info_border)
23548 .size_full()
23549 .py(window.line_height() / 2.5)
23550 .on_action(cx.listener(Self::confirm))
23551 .on_action(cx.listener(Self::cancel))
23552 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
23553 .child(div().flex_1().child(self.render_prompt_editor(cx)))
23554 }
23555}
23556
23557impl Focusable for BreakpointPromptEditor {
23558 fn focus_handle(&self, cx: &App) -> FocusHandle {
23559 self.prompt.focus_handle(cx)
23560 }
23561}
23562
23563fn all_edits_insertions_or_deletions(
23564 edits: &Vec<(Range<Anchor>, String)>,
23565 snapshot: &MultiBufferSnapshot,
23566) -> bool {
23567 let mut all_insertions = true;
23568 let mut all_deletions = true;
23569
23570 for (range, new_text) in edits.iter() {
23571 let range_is_empty = range.to_offset(&snapshot).is_empty();
23572 let text_is_empty = new_text.is_empty();
23573
23574 if range_is_empty != text_is_empty {
23575 if range_is_empty {
23576 all_deletions = false;
23577 } else {
23578 all_insertions = false;
23579 }
23580 } else {
23581 return false;
23582 }
23583
23584 if !all_insertions && !all_deletions {
23585 return false;
23586 }
23587 }
23588 all_insertions || all_deletions
23589}
23590
23591struct MissingEditPredictionKeybindingTooltip;
23592
23593impl Render for MissingEditPredictionKeybindingTooltip {
23594 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23595 ui::tooltip_container(window, cx, |container, _, cx| {
23596 container
23597 .flex_shrink_0()
23598 .max_w_80()
23599 .min_h(rems_from_px(124.))
23600 .justify_between()
23601 .child(
23602 v_flex()
23603 .flex_1()
23604 .text_ui_sm(cx)
23605 .child(Label::new("Conflict with Accept Keybinding"))
23606 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
23607 )
23608 .child(
23609 h_flex()
23610 .pb_1()
23611 .gap_1()
23612 .items_end()
23613 .w_full()
23614 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
23615 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
23616 }))
23617 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
23618 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
23619 })),
23620 )
23621 })
23622 }
23623}
23624
23625#[derive(Debug, Clone, Copy, PartialEq)]
23626pub struct LineHighlight {
23627 pub background: Background,
23628 pub border: Option<gpui::Hsla>,
23629 pub include_gutter: bool,
23630 pub type_id: Option<TypeId>,
23631}
23632
23633struct LineManipulationResult {
23634 pub new_text: String,
23635 pub line_count_before: usize,
23636 pub line_count_after: usize,
23637}
23638
23639fn render_diff_hunk_controls(
23640 row: u32,
23641 status: &DiffHunkStatus,
23642 hunk_range: Range<Anchor>,
23643 is_created_file: bool,
23644 line_height: Pixels,
23645 editor: &Entity<Editor>,
23646 _window: &mut Window,
23647 cx: &mut App,
23648) -> AnyElement {
23649 h_flex()
23650 .h(line_height)
23651 .mr_1()
23652 .gap_1()
23653 .px_0p5()
23654 .pb_1()
23655 .border_x_1()
23656 .border_b_1()
23657 .border_color(cx.theme().colors().border_variant)
23658 .rounded_b_lg()
23659 .bg(cx.theme().colors().editor_background)
23660 .gap_1()
23661 .block_mouse_except_scroll()
23662 .shadow_md()
23663 .child(if status.has_secondary_hunk() {
23664 Button::new(("stage", row as u64), "Stage")
23665 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
23666 .tooltip({
23667 let focus_handle = editor.focus_handle(cx);
23668 move |window, cx| {
23669 Tooltip::for_action_in(
23670 "Stage Hunk",
23671 &::git::ToggleStaged,
23672 &focus_handle,
23673 window,
23674 cx,
23675 )
23676 }
23677 })
23678 .on_click({
23679 let editor = editor.clone();
23680 move |_event, _window, cx| {
23681 editor.update(cx, |editor, cx| {
23682 editor.stage_or_unstage_diff_hunks(
23683 true,
23684 vec![hunk_range.start..hunk_range.start],
23685 cx,
23686 );
23687 });
23688 }
23689 })
23690 } else {
23691 Button::new(("unstage", row as u64), "Unstage")
23692 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
23693 .tooltip({
23694 let focus_handle = editor.focus_handle(cx);
23695 move |window, cx| {
23696 Tooltip::for_action_in(
23697 "Unstage Hunk",
23698 &::git::ToggleStaged,
23699 &focus_handle,
23700 window,
23701 cx,
23702 )
23703 }
23704 })
23705 .on_click({
23706 let editor = editor.clone();
23707 move |_event, _window, cx| {
23708 editor.update(cx, |editor, cx| {
23709 editor.stage_or_unstage_diff_hunks(
23710 false,
23711 vec![hunk_range.start..hunk_range.start],
23712 cx,
23713 );
23714 });
23715 }
23716 })
23717 })
23718 .child(
23719 Button::new(("restore", row as u64), "Restore")
23720 .tooltip({
23721 let focus_handle = editor.focus_handle(cx);
23722 move |window, cx| {
23723 Tooltip::for_action_in(
23724 "Restore Hunk",
23725 &::git::Restore,
23726 &focus_handle,
23727 window,
23728 cx,
23729 )
23730 }
23731 })
23732 .on_click({
23733 let editor = editor.clone();
23734 move |_event, window, cx| {
23735 editor.update(cx, |editor, cx| {
23736 let snapshot = editor.snapshot(window, cx);
23737 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
23738 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
23739 });
23740 }
23741 })
23742 .disabled(is_created_file),
23743 )
23744 .when(
23745 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
23746 |el| {
23747 el.child(
23748 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
23749 .shape(IconButtonShape::Square)
23750 .icon_size(IconSize::Small)
23751 // .disabled(!has_multiple_hunks)
23752 .tooltip({
23753 let focus_handle = editor.focus_handle(cx);
23754 move |window, cx| {
23755 Tooltip::for_action_in(
23756 "Next Hunk",
23757 &GoToHunk,
23758 &focus_handle,
23759 window,
23760 cx,
23761 )
23762 }
23763 })
23764 .on_click({
23765 let editor = editor.clone();
23766 move |_event, window, cx| {
23767 editor.update(cx, |editor, cx| {
23768 let snapshot = editor.snapshot(window, cx);
23769 let position =
23770 hunk_range.end.to_point(&snapshot.buffer_snapshot);
23771 editor.go_to_hunk_before_or_after_position(
23772 &snapshot,
23773 position,
23774 Direction::Next,
23775 window,
23776 cx,
23777 );
23778 editor.expand_selected_diff_hunks(cx);
23779 });
23780 }
23781 }),
23782 )
23783 .child(
23784 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
23785 .shape(IconButtonShape::Square)
23786 .icon_size(IconSize::Small)
23787 // .disabled(!has_multiple_hunks)
23788 .tooltip({
23789 let focus_handle = editor.focus_handle(cx);
23790 move |window, cx| {
23791 Tooltip::for_action_in(
23792 "Previous Hunk",
23793 &GoToPreviousHunk,
23794 &focus_handle,
23795 window,
23796 cx,
23797 )
23798 }
23799 })
23800 .on_click({
23801 let editor = editor.clone();
23802 move |_event, window, cx| {
23803 editor.update(cx, |editor, cx| {
23804 let snapshot = editor.snapshot(window, cx);
23805 let point =
23806 hunk_range.start.to_point(&snapshot.buffer_snapshot);
23807 editor.go_to_hunk_before_or_after_position(
23808 &snapshot,
23809 point,
23810 Direction::Prev,
23811 window,
23812 cx,
23813 );
23814 editor.expand_selected_diff_hunks(cx);
23815 });
23816 }
23817 }),
23818 )
23819 },
23820 )
23821 .into_any_element()
23822}