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 #[cfg(any(test, feature = "test-support"))]
2709 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
2710 self.completion_provider.clone()
2711 }
2712
2713 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2714 self.semantics_provider.clone()
2715 }
2716
2717 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2718 self.semantics_provider = provider;
2719 }
2720
2721 pub fn set_edit_prediction_provider<T>(
2722 &mut self,
2723 provider: Option<Entity<T>>,
2724 window: &mut Window,
2725 cx: &mut Context<Self>,
2726 ) where
2727 T: EditPredictionProvider,
2728 {
2729 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionProvider {
2730 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2731 if this.focus_handle.is_focused(window) {
2732 this.update_visible_edit_prediction(window, cx);
2733 }
2734 }),
2735 provider: Arc::new(provider),
2736 });
2737 self.update_edit_prediction_settings(cx);
2738 self.refresh_edit_prediction(false, false, window, cx);
2739 }
2740
2741 pub fn placeholder_text(&self) -> Option<&str> {
2742 self.placeholder_text.as_deref()
2743 }
2744
2745 pub fn set_placeholder_text(
2746 &mut self,
2747 placeholder_text: impl Into<Arc<str>>,
2748 cx: &mut Context<Self>,
2749 ) {
2750 let placeholder_text = Some(placeholder_text.into());
2751 if self.placeholder_text != placeholder_text {
2752 self.placeholder_text = placeholder_text;
2753 cx.notify();
2754 }
2755 }
2756
2757 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2758 self.cursor_shape = cursor_shape;
2759
2760 // Disrupt blink for immediate user feedback that the cursor shape has changed
2761 self.blink_manager.update(cx, BlinkManager::show_cursor);
2762
2763 cx.notify();
2764 }
2765
2766 pub fn set_current_line_highlight(
2767 &mut self,
2768 current_line_highlight: Option<CurrentLineHighlight>,
2769 ) {
2770 self.current_line_highlight = current_line_highlight;
2771 }
2772
2773 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2774 self.collapse_matches = collapse_matches;
2775 }
2776
2777 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2778 let buffers = self.buffer.read(cx).all_buffers();
2779 let Some(project) = self.project.as_ref() else {
2780 return;
2781 };
2782 project.update(cx, |project, cx| {
2783 for buffer in buffers {
2784 self.registered_buffers
2785 .entry(buffer.read(cx).remote_id())
2786 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2787 }
2788 })
2789 }
2790
2791 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2792 if self.collapse_matches {
2793 return range.start..range.start;
2794 }
2795 range.clone()
2796 }
2797
2798 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2799 if self.display_map.read(cx).clip_at_line_ends != clip {
2800 self.display_map
2801 .update(cx, |map, _| map.clip_at_line_ends = clip);
2802 }
2803 }
2804
2805 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2806 self.input_enabled = input_enabled;
2807 }
2808
2809 pub fn set_edit_predictions_hidden_for_vim_mode(
2810 &mut self,
2811 hidden: bool,
2812 window: &mut Window,
2813 cx: &mut Context<Self>,
2814 ) {
2815 if hidden != self.edit_predictions_hidden_for_vim_mode {
2816 self.edit_predictions_hidden_for_vim_mode = hidden;
2817 if hidden {
2818 self.update_visible_edit_prediction(window, cx);
2819 } else {
2820 self.refresh_edit_prediction(true, false, window, cx);
2821 }
2822 }
2823 }
2824
2825 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
2826 self.menu_edit_predictions_policy = value;
2827 }
2828
2829 pub fn set_autoindent(&mut self, autoindent: bool) {
2830 if autoindent {
2831 self.autoindent_mode = Some(AutoindentMode::EachLine);
2832 } else {
2833 self.autoindent_mode = None;
2834 }
2835 }
2836
2837 pub fn read_only(&self, cx: &App) -> bool {
2838 self.read_only || self.buffer.read(cx).read_only()
2839 }
2840
2841 pub fn set_read_only(&mut self, read_only: bool) {
2842 self.read_only = read_only;
2843 }
2844
2845 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2846 self.use_autoclose = autoclose;
2847 }
2848
2849 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2850 self.use_auto_surround = auto_surround;
2851 }
2852
2853 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2854 self.auto_replace_emoji_shortcode = auto_replace;
2855 }
2856
2857 pub fn toggle_edit_predictions(
2858 &mut self,
2859 _: &ToggleEditPrediction,
2860 window: &mut Window,
2861 cx: &mut Context<Self>,
2862 ) {
2863 if self.show_edit_predictions_override.is_some() {
2864 self.set_show_edit_predictions(None, window, cx);
2865 } else {
2866 let show_edit_predictions = !self.edit_predictions_enabled();
2867 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2868 }
2869 }
2870
2871 pub fn set_show_edit_predictions(
2872 &mut self,
2873 show_edit_predictions: Option<bool>,
2874 window: &mut Window,
2875 cx: &mut Context<Self>,
2876 ) {
2877 self.show_edit_predictions_override = show_edit_predictions;
2878 self.update_edit_prediction_settings(cx);
2879
2880 if let Some(false) = show_edit_predictions {
2881 self.discard_edit_prediction(false, cx);
2882 } else {
2883 self.refresh_edit_prediction(false, true, window, cx);
2884 }
2885 }
2886
2887 fn edit_predictions_disabled_in_scope(
2888 &self,
2889 buffer: &Entity<Buffer>,
2890 buffer_position: language::Anchor,
2891 cx: &App,
2892 ) -> bool {
2893 let snapshot = buffer.read(cx).snapshot();
2894 let settings = snapshot.settings_at(buffer_position, cx);
2895
2896 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2897 return false;
2898 };
2899
2900 scope.override_name().map_or(false, |scope_name| {
2901 settings
2902 .edit_predictions_disabled_in
2903 .iter()
2904 .any(|s| s == scope_name)
2905 })
2906 }
2907
2908 pub fn set_use_modal_editing(&mut self, to: bool) {
2909 self.use_modal_editing = to;
2910 }
2911
2912 pub fn use_modal_editing(&self) -> bool {
2913 self.use_modal_editing
2914 }
2915
2916 fn selections_did_change(
2917 &mut self,
2918 local: bool,
2919 old_cursor_position: &Anchor,
2920 effects: SelectionEffects,
2921 window: &mut Window,
2922 cx: &mut Context<Self>,
2923 ) {
2924 window.invalidate_character_coordinates();
2925
2926 // Copy selections to primary selection buffer
2927 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2928 if local {
2929 let selections = self.selections.all::<usize>(cx);
2930 let buffer_handle = self.buffer.read(cx).read(cx);
2931
2932 let mut text = String::new();
2933 for (index, selection) in selections.iter().enumerate() {
2934 let text_for_selection = buffer_handle
2935 .text_for_range(selection.start..selection.end)
2936 .collect::<String>();
2937
2938 text.push_str(&text_for_selection);
2939 if index != selections.len() - 1 {
2940 text.push('\n');
2941 }
2942 }
2943
2944 if !text.is_empty() {
2945 cx.write_to_primary(ClipboardItem::new_string(text));
2946 }
2947 }
2948
2949 let selection_anchors = self.selections.disjoint_anchors();
2950
2951 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
2952 self.buffer.update(cx, |buffer, cx| {
2953 buffer.set_active_selections(
2954 &selection_anchors,
2955 self.selections.line_mode,
2956 self.cursor_shape,
2957 cx,
2958 )
2959 });
2960 }
2961 let display_map = self
2962 .display_map
2963 .update(cx, |display_map, cx| display_map.snapshot(cx));
2964 let buffer = &display_map.buffer_snapshot;
2965 if self.selections.count() == 1 {
2966 self.add_selections_state = None;
2967 }
2968 self.select_next_state = None;
2969 self.select_prev_state = None;
2970 self.select_syntax_node_history.try_clear();
2971 self.invalidate_autoclose_regions(&selection_anchors, buffer);
2972 self.snippet_stack.invalidate(&selection_anchors, buffer);
2973 self.take_rename(false, window, cx);
2974
2975 let newest_selection = self.selections.newest_anchor();
2976 let new_cursor_position = newest_selection.head();
2977 let selection_start = newest_selection.start;
2978
2979 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
2980 self.push_to_nav_history(
2981 *old_cursor_position,
2982 Some(new_cursor_position.to_point(buffer)),
2983 false,
2984 effects.nav_history == Some(true),
2985 cx,
2986 );
2987 }
2988
2989 if local {
2990 if let Some(buffer_id) = new_cursor_position.buffer_id {
2991 if !self.registered_buffers.contains_key(&buffer_id) {
2992 if let Some(project) = self.project.as_ref() {
2993 project.update(cx, |project, cx| {
2994 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
2995 return;
2996 };
2997 self.registered_buffers.insert(
2998 buffer_id,
2999 project.register_buffer_with_language_servers(&buffer, cx),
3000 );
3001 })
3002 }
3003 }
3004 }
3005
3006 let mut context_menu = self.context_menu.borrow_mut();
3007 let completion_menu = match context_menu.as_ref() {
3008 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3009 Some(CodeContextMenu::CodeActions(_)) => {
3010 *context_menu = None;
3011 None
3012 }
3013 None => None,
3014 };
3015 let completion_position = completion_menu.map(|menu| menu.initial_position);
3016 drop(context_menu);
3017
3018 if effects.completions {
3019 if let Some(completion_position) = completion_position {
3020 let start_offset = selection_start.to_offset(buffer);
3021 let position_matches = start_offset == completion_position.to_offset(buffer);
3022 let continue_showing = if position_matches {
3023 if self.snippet_stack.is_empty() {
3024 buffer.char_kind_before(start_offset, true) == Some(CharKind::Word)
3025 } else {
3026 // Snippet choices can be shown even when the cursor is in whitespace.
3027 // Dismissing the menu with actions like backspace is handled by
3028 // invalidation regions.
3029 true
3030 }
3031 } else {
3032 false
3033 };
3034
3035 if continue_showing {
3036 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
3037 } else {
3038 self.hide_context_menu(window, cx);
3039 }
3040 }
3041 }
3042
3043 hide_hover(self, cx);
3044
3045 if old_cursor_position.to_display_point(&display_map).row()
3046 != new_cursor_position.to_display_point(&display_map).row()
3047 {
3048 self.available_code_actions.take();
3049 }
3050 self.refresh_code_actions(window, cx);
3051 self.refresh_document_highlights(cx);
3052 self.refresh_selected_text_highlights(false, window, cx);
3053 refresh_matching_bracket_highlights(self, window, cx);
3054 self.update_visible_edit_prediction(window, cx);
3055 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3056 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
3057 self.inline_blame_popover.take();
3058 if self.git_blame_inline_enabled {
3059 self.start_inline_blame_timer(window, cx);
3060 }
3061 }
3062
3063 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3064 cx.emit(EditorEvent::SelectionsChanged { local });
3065
3066 let selections = &self.selections.disjoint;
3067 if selections.len() == 1 {
3068 cx.emit(SearchEvent::ActiveMatchChanged)
3069 }
3070 if local {
3071 if let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3072 let inmemory_selections = selections
3073 .iter()
3074 .map(|s| {
3075 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3076 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3077 })
3078 .collect();
3079 self.update_restoration_data(cx, |data| {
3080 data.selections = inmemory_selections;
3081 });
3082
3083 if WorkspaceSettings::get(None, cx).restore_on_startup
3084 != RestoreOnStartupBehavior::None
3085 {
3086 if let Some(workspace_id) =
3087 self.workspace.as_ref().and_then(|workspace| workspace.1)
3088 {
3089 let snapshot = self.buffer().read(cx).snapshot(cx);
3090 let selections = selections.clone();
3091 let background_executor = cx.background_executor().clone();
3092 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3093 self.serialize_selections = cx.background_spawn(async move {
3094 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3095 let db_selections = selections
3096 .iter()
3097 .map(|selection| {
3098 (
3099 selection.start.to_offset(&snapshot),
3100 selection.end.to_offset(&snapshot),
3101 )
3102 })
3103 .collect();
3104
3105 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3106 .await
3107 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
3108 .log_err();
3109 });
3110 }
3111 }
3112 }
3113 }
3114
3115 cx.notify();
3116 }
3117
3118 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3119 use text::ToOffset as _;
3120 use text::ToPoint as _;
3121
3122 if self.mode.is_minimap()
3123 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3124 {
3125 return;
3126 }
3127
3128 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
3129 return;
3130 };
3131
3132 let snapshot = singleton.read(cx).snapshot();
3133 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
3134 let display_snapshot = display_map.snapshot(cx);
3135
3136 display_snapshot
3137 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
3138 .map(|fold| {
3139 fold.range.start.text_anchor.to_point(&snapshot)
3140 ..fold.range.end.text_anchor.to_point(&snapshot)
3141 })
3142 .collect()
3143 });
3144 self.update_restoration_data(cx, |data| {
3145 data.folds = inmemory_folds;
3146 });
3147
3148 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
3149 return;
3150 };
3151 let background_executor = cx.background_executor().clone();
3152 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3153 let db_folds = self.display_map.update(cx, |display_map, cx| {
3154 display_map
3155 .snapshot(cx)
3156 .folds_in_range(0..snapshot.len())
3157 .map(|fold| {
3158 (
3159 fold.range.start.text_anchor.to_offset(&snapshot),
3160 fold.range.end.text_anchor.to_offset(&snapshot),
3161 )
3162 })
3163 .collect()
3164 });
3165 self.serialize_folds = cx.background_spawn(async move {
3166 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3167 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3168 .await
3169 .with_context(|| {
3170 format!(
3171 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3172 )
3173 })
3174 .log_err();
3175 });
3176 }
3177
3178 pub fn sync_selections(
3179 &mut self,
3180 other: Entity<Editor>,
3181 cx: &mut Context<Self>,
3182 ) -> gpui::Subscription {
3183 let other_selections = other.read(cx).selections.disjoint.to_vec();
3184 self.selections.change_with(cx, |selections| {
3185 selections.select_anchors(other_selections);
3186 });
3187
3188 let other_subscription =
3189 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
3190 EditorEvent::SelectionsChanged { local: true } => {
3191 let other_selections = other.read(cx).selections.disjoint.to_vec();
3192 if other_selections.is_empty() {
3193 return;
3194 }
3195 this.selections.change_with(cx, |selections| {
3196 selections.select_anchors(other_selections);
3197 });
3198 }
3199 _ => {}
3200 });
3201
3202 let this_subscription =
3203 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
3204 EditorEvent::SelectionsChanged { local: true } => {
3205 let these_selections = this.selections.disjoint.to_vec();
3206 if these_selections.is_empty() {
3207 return;
3208 }
3209 other.update(cx, |other_editor, cx| {
3210 other_editor.selections.change_with(cx, |selections| {
3211 selections.select_anchors(these_selections);
3212 })
3213 });
3214 }
3215 _ => {}
3216 });
3217
3218 Subscription::join(other_subscription, this_subscription)
3219 }
3220
3221 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3222 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3223 /// effects of selection change occur at the end of the transaction.
3224 pub fn change_selections<R>(
3225 &mut self,
3226 effects: SelectionEffects,
3227 window: &mut Window,
3228 cx: &mut Context<Self>,
3229 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3230 ) -> R {
3231 if let Some(state) = &mut self.deferred_selection_effects_state {
3232 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3233 state.effects.completions = effects.completions;
3234 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3235 let (changed, result) = self.selections.change_with(cx, change);
3236 state.changed |= changed;
3237 return result;
3238 }
3239 let mut state = DeferredSelectionEffectsState {
3240 changed: false,
3241 effects,
3242 old_cursor_position: self.selections.newest_anchor().head(),
3243 history_entry: SelectionHistoryEntry {
3244 selections: self.selections.disjoint_anchors(),
3245 select_next_state: self.select_next_state.clone(),
3246 select_prev_state: self.select_prev_state.clone(),
3247 add_selections_state: self.add_selections_state.clone(),
3248 },
3249 };
3250 let (changed, result) = self.selections.change_with(cx, change);
3251 state.changed = state.changed || changed;
3252 if self.defer_selection_effects {
3253 self.deferred_selection_effects_state = Some(state);
3254 } else {
3255 self.apply_selection_effects(state, window, cx);
3256 }
3257 result
3258 }
3259
3260 /// Defers the effects of selection change, so that the effects of multiple calls to
3261 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3262 /// to selection history and the state of popovers based on selection position aren't
3263 /// erroneously updated.
3264 pub fn with_selection_effects_deferred<R>(
3265 &mut self,
3266 window: &mut Window,
3267 cx: &mut Context<Self>,
3268 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3269 ) -> R {
3270 let already_deferred = self.defer_selection_effects;
3271 self.defer_selection_effects = true;
3272 let result = update(self, window, cx);
3273 if !already_deferred {
3274 self.defer_selection_effects = false;
3275 if let Some(state) = self.deferred_selection_effects_state.take() {
3276 self.apply_selection_effects(state, window, cx);
3277 }
3278 }
3279 result
3280 }
3281
3282 fn apply_selection_effects(
3283 &mut self,
3284 state: DeferredSelectionEffectsState,
3285 window: &mut Window,
3286 cx: &mut Context<Self>,
3287 ) {
3288 if state.changed {
3289 self.selection_history.push(state.history_entry);
3290
3291 if let Some(autoscroll) = state.effects.scroll {
3292 self.request_autoscroll(autoscroll, cx);
3293 }
3294
3295 let old_cursor_position = &state.old_cursor_position;
3296
3297 self.selections_did_change(true, &old_cursor_position, state.effects, window, cx);
3298
3299 if self.should_open_signature_help_automatically(&old_cursor_position, cx) {
3300 self.show_signature_help(&ShowSignatureHelp, window, cx);
3301 }
3302 }
3303 }
3304
3305 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3306 where
3307 I: IntoIterator<Item = (Range<S>, T)>,
3308 S: ToOffset,
3309 T: Into<Arc<str>>,
3310 {
3311 if self.read_only(cx) {
3312 return;
3313 }
3314
3315 self.buffer
3316 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3317 }
3318
3319 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3320 where
3321 I: IntoIterator<Item = (Range<S>, T)>,
3322 S: ToOffset,
3323 T: Into<Arc<str>>,
3324 {
3325 if self.read_only(cx) {
3326 return;
3327 }
3328
3329 self.buffer.update(cx, |buffer, cx| {
3330 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3331 });
3332 }
3333
3334 pub fn edit_with_block_indent<I, S, T>(
3335 &mut self,
3336 edits: I,
3337 original_indent_columns: Vec<Option<u32>>,
3338 cx: &mut Context<Self>,
3339 ) where
3340 I: IntoIterator<Item = (Range<S>, T)>,
3341 S: ToOffset,
3342 T: Into<Arc<str>>,
3343 {
3344 if self.read_only(cx) {
3345 return;
3346 }
3347
3348 self.buffer.update(cx, |buffer, cx| {
3349 buffer.edit(
3350 edits,
3351 Some(AutoindentMode::Block {
3352 original_indent_columns,
3353 }),
3354 cx,
3355 )
3356 });
3357 }
3358
3359 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3360 self.hide_context_menu(window, cx);
3361
3362 match phase {
3363 SelectPhase::Begin {
3364 position,
3365 add,
3366 click_count,
3367 } => self.begin_selection(position, add, click_count, window, cx),
3368 SelectPhase::BeginColumnar {
3369 position,
3370 goal_column,
3371 reset,
3372 mode,
3373 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3374 SelectPhase::Extend {
3375 position,
3376 click_count,
3377 } => self.extend_selection(position, click_count, window, cx),
3378 SelectPhase::Update {
3379 position,
3380 goal_column,
3381 scroll_delta,
3382 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3383 SelectPhase::End => self.end_selection(window, cx),
3384 }
3385 }
3386
3387 fn extend_selection(
3388 &mut self,
3389 position: DisplayPoint,
3390 click_count: usize,
3391 window: &mut Window,
3392 cx: &mut Context<Self>,
3393 ) {
3394 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3395 let tail = self.selections.newest::<usize>(cx).tail();
3396 self.begin_selection(position, false, click_count, window, cx);
3397
3398 let position = position.to_offset(&display_map, Bias::Left);
3399 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3400
3401 let mut pending_selection = self
3402 .selections
3403 .pending_anchor()
3404 .expect("extend_selection not called with pending selection");
3405 if position >= tail {
3406 pending_selection.start = tail_anchor;
3407 } else {
3408 pending_selection.end = tail_anchor;
3409 pending_selection.reversed = true;
3410 }
3411
3412 let mut pending_mode = self.selections.pending_mode().unwrap();
3413 match &mut pending_mode {
3414 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3415 _ => {}
3416 }
3417
3418 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3419 SelectionEffects::scroll(Autoscroll::fit())
3420 } else {
3421 SelectionEffects::no_scroll()
3422 };
3423
3424 self.change_selections(effects, window, cx, |s| {
3425 s.set_pending(pending_selection, pending_mode)
3426 });
3427 }
3428
3429 fn begin_selection(
3430 &mut self,
3431 position: DisplayPoint,
3432 add: bool,
3433 click_count: usize,
3434 window: &mut Window,
3435 cx: &mut Context<Self>,
3436 ) {
3437 if !self.focus_handle.is_focused(window) {
3438 self.last_focused_descendant = None;
3439 window.focus(&self.focus_handle);
3440 }
3441
3442 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3443 let buffer = &display_map.buffer_snapshot;
3444 let position = display_map.clip_point(position, Bias::Left);
3445
3446 let start;
3447 let end;
3448 let mode;
3449 let mut auto_scroll;
3450 match click_count {
3451 1 => {
3452 start = buffer.anchor_before(position.to_point(&display_map));
3453 end = start;
3454 mode = SelectMode::Character;
3455 auto_scroll = true;
3456 }
3457 2 => {
3458 let position = display_map
3459 .clip_point(position, Bias::Left)
3460 .to_offset(&display_map, Bias::Left);
3461 let (range, _) = buffer.surrounding_word(position, false);
3462 start = buffer.anchor_before(range.start);
3463 end = buffer.anchor_before(range.end);
3464 mode = SelectMode::Word(start..end);
3465 auto_scroll = true;
3466 }
3467 3 => {
3468 let position = display_map
3469 .clip_point(position, Bias::Left)
3470 .to_point(&display_map);
3471 let line_start = display_map.prev_line_boundary(position).0;
3472 let next_line_start = buffer.clip_point(
3473 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3474 Bias::Left,
3475 );
3476 start = buffer.anchor_before(line_start);
3477 end = buffer.anchor_before(next_line_start);
3478 mode = SelectMode::Line(start..end);
3479 auto_scroll = true;
3480 }
3481 _ => {
3482 start = buffer.anchor_before(0);
3483 end = buffer.anchor_before(buffer.len());
3484 mode = SelectMode::All;
3485 auto_scroll = false;
3486 }
3487 }
3488 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3489
3490 let point_to_delete: Option<usize> = {
3491 let selected_points: Vec<Selection<Point>> =
3492 self.selections.disjoint_in_range(start..end, cx);
3493
3494 if !add || click_count > 1 {
3495 None
3496 } else if !selected_points.is_empty() {
3497 Some(selected_points[0].id)
3498 } else {
3499 let clicked_point_already_selected =
3500 self.selections.disjoint.iter().find(|selection| {
3501 selection.start.to_point(buffer) == start.to_point(buffer)
3502 || selection.end.to_point(buffer) == end.to_point(buffer)
3503 });
3504
3505 clicked_point_already_selected.map(|selection| selection.id)
3506 }
3507 };
3508
3509 let selections_count = self.selections.count();
3510 let effects = if auto_scroll {
3511 SelectionEffects::default()
3512 } else {
3513 SelectionEffects::no_scroll()
3514 };
3515
3516 self.change_selections(effects, window, cx, |s| {
3517 if let Some(point_to_delete) = point_to_delete {
3518 s.delete(point_to_delete);
3519
3520 if selections_count == 1 {
3521 s.set_pending_anchor_range(start..end, mode);
3522 }
3523 } else {
3524 if !add {
3525 s.clear_disjoint();
3526 }
3527
3528 s.set_pending_anchor_range(start..end, mode);
3529 }
3530 });
3531 }
3532
3533 fn begin_columnar_selection(
3534 &mut self,
3535 position: DisplayPoint,
3536 goal_column: u32,
3537 reset: bool,
3538 mode: ColumnarMode,
3539 window: &mut Window,
3540 cx: &mut Context<Self>,
3541 ) {
3542 if !self.focus_handle.is_focused(window) {
3543 self.last_focused_descendant = None;
3544 window.focus(&self.focus_handle);
3545 }
3546
3547 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3548
3549 if reset {
3550 let pointer_position = display_map
3551 .buffer_snapshot
3552 .anchor_before(position.to_point(&display_map));
3553
3554 self.change_selections(
3555 SelectionEffects::scroll(Autoscroll::newest()),
3556 window,
3557 cx,
3558 |s| {
3559 s.clear_disjoint();
3560 s.set_pending_anchor_range(
3561 pointer_position..pointer_position,
3562 SelectMode::Character,
3563 );
3564 },
3565 );
3566 };
3567
3568 let tail = self.selections.newest::<Point>(cx).tail();
3569 let selection_anchor = display_map.buffer_snapshot.anchor_before(tail);
3570 self.columnar_selection_state = match mode {
3571 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3572 selection_tail: selection_anchor,
3573 display_point: if reset {
3574 if position.column() != goal_column {
3575 Some(DisplayPoint::new(position.row(), goal_column))
3576 } else {
3577 None
3578 }
3579 } else {
3580 None
3581 },
3582 }),
3583 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3584 selection_tail: selection_anchor,
3585 }),
3586 };
3587
3588 if !reset {
3589 self.select_columns(position, goal_column, &display_map, window, cx);
3590 }
3591 }
3592
3593 fn update_selection(
3594 &mut self,
3595 position: DisplayPoint,
3596 goal_column: u32,
3597 scroll_delta: gpui::Point<f32>,
3598 window: &mut Window,
3599 cx: &mut Context<Self>,
3600 ) {
3601 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3602
3603 if self.columnar_selection_state.is_some() {
3604 self.select_columns(position, goal_column, &display_map, window, cx);
3605 } else if let Some(mut pending) = self.selections.pending_anchor() {
3606 let buffer = &display_map.buffer_snapshot;
3607 let head;
3608 let tail;
3609 let mode = self.selections.pending_mode().unwrap();
3610 match &mode {
3611 SelectMode::Character => {
3612 head = position.to_point(&display_map);
3613 tail = pending.tail().to_point(buffer);
3614 }
3615 SelectMode::Word(original_range) => {
3616 let offset = display_map
3617 .clip_point(position, Bias::Left)
3618 .to_offset(&display_map, Bias::Left);
3619 let original_range = original_range.to_offset(buffer);
3620
3621 let head_offset = if buffer.is_inside_word(offset, false)
3622 || original_range.contains(&offset)
3623 {
3624 let (word_range, _) = buffer.surrounding_word(offset, false);
3625 if word_range.start < original_range.start {
3626 word_range.start
3627 } else {
3628 word_range.end
3629 }
3630 } else {
3631 offset
3632 };
3633
3634 head = head_offset.to_point(buffer);
3635 if head_offset <= original_range.start {
3636 tail = original_range.end.to_point(buffer);
3637 } else {
3638 tail = original_range.start.to_point(buffer);
3639 }
3640 }
3641 SelectMode::Line(original_range) => {
3642 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3643
3644 let position = display_map
3645 .clip_point(position, Bias::Left)
3646 .to_point(&display_map);
3647 let line_start = display_map.prev_line_boundary(position).0;
3648 let next_line_start = buffer.clip_point(
3649 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3650 Bias::Left,
3651 );
3652
3653 if line_start < original_range.start {
3654 head = line_start
3655 } else {
3656 head = next_line_start
3657 }
3658
3659 if head <= original_range.start {
3660 tail = original_range.end;
3661 } else {
3662 tail = original_range.start;
3663 }
3664 }
3665 SelectMode::All => {
3666 return;
3667 }
3668 };
3669
3670 if head < tail {
3671 pending.start = buffer.anchor_before(head);
3672 pending.end = buffer.anchor_before(tail);
3673 pending.reversed = true;
3674 } else {
3675 pending.start = buffer.anchor_before(tail);
3676 pending.end = buffer.anchor_before(head);
3677 pending.reversed = false;
3678 }
3679
3680 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3681 s.set_pending(pending, mode);
3682 });
3683 } else {
3684 log::error!("update_selection dispatched with no pending selection");
3685 return;
3686 }
3687
3688 self.apply_scroll_delta(scroll_delta, window, cx);
3689 cx.notify();
3690 }
3691
3692 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3693 self.columnar_selection_state.take();
3694 if self.selections.pending_anchor().is_some() {
3695 let selections = self.selections.all::<usize>(cx);
3696 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3697 s.select(selections);
3698 s.clear_pending();
3699 });
3700 }
3701 }
3702
3703 fn select_columns(
3704 &mut self,
3705 head: DisplayPoint,
3706 goal_column: u32,
3707 display_map: &DisplaySnapshot,
3708 window: &mut Window,
3709 cx: &mut Context<Self>,
3710 ) {
3711 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3712 return;
3713 };
3714
3715 let tail = match columnar_state {
3716 ColumnarSelectionState::FromMouse {
3717 selection_tail,
3718 display_point,
3719 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(&display_map)),
3720 ColumnarSelectionState::FromSelection { selection_tail } => {
3721 selection_tail.to_display_point(&display_map)
3722 }
3723 };
3724
3725 let start_row = cmp::min(tail.row(), head.row());
3726 let end_row = cmp::max(tail.row(), head.row());
3727 let start_column = cmp::min(tail.column(), goal_column);
3728 let end_column = cmp::max(tail.column(), goal_column);
3729 let reversed = start_column < tail.column();
3730
3731 let selection_ranges = (start_row.0..=end_row.0)
3732 .map(DisplayRow)
3733 .filter_map(|row| {
3734 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3735 || start_column <= display_map.line_len(row))
3736 && !display_map.is_block_line(row)
3737 {
3738 let start = display_map
3739 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3740 .to_point(display_map);
3741 let end = display_map
3742 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3743 .to_point(display_map);
3744 if reversed {
3745 Some(end..start)
3746 } else {
3747 Some(start..end)
3748 }
3749 } else {
3750 None
3751 }
3752 })
3753 .collect::<Vec<_>>();
3754
3755 let ranges = match columnar_state {
3756 ColumnarSelectionState::FromMouse { .. } => {
3757 let mut non_empty_ranges = selection_ranges
3758 .iter()
3759 .filter(|selection_range| selection_range.start != selection_range.end)
3760 .peekable();
3761 if non_empty_ranges.peek().is_some() {
3762 non_empty_ranges.cloned().collect()
3763 } else {
3764 selection_ranges
3765 }
3766 }
3767 _ => selection_ranges,
3768 };
3769
3770 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3771 s.select_ranges(ranges);
3772 });
3773 cx.notify();
3774 }
3775
3776 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3777 self.selections
3778 .all_adjusted(cx)
3779 .iter()
3780 .any(|selection| !selection.is_empty())
3781 }
3782
3783 pub fn has_pending_nonempty_selection(&self) -> bool {
3784 let pending_nonempty_selection = match self.selections.pending_anchor() {
3785 Some(Selection { start, end, .. }) => start != end,
3786 None => false,
3787 };
3788
3789 pending_nonempty_selection
3790 || (self.columnar_selection_state.is_some() && self.selections.disjoint.len() > 1)
3791 }
3792
3793 pub fn has_pending_selection(&self) -> bool {
3794 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
3795 }
3796
3797 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3798 self.selection_mark_mode = false;
3799 self.selection_drag_state = SelectionDragState::None;
3800
3801 if self.clear_expanded_diff_hunks(cx) {
3802 cx.notify();
3803 return;
3804 }
3805 if self.dismiss_menus_and_popups(true, window, cx) {
3806 return;
3807 }
3808
3809 if self.mode.is_full()
3810 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
3811 {
3812 return;
3813 }
3814
3815 cx.propagate();
3816 }
3817
3818 pub fn dismiss_menus_and_popups(
3819 &mut self,
3820 is_user_requested: bool,
3821 window: &mut Window,
3822 cx: &mut Context<Self>,
3823 ) -> bool {
3824 if self.take_rename(false, window, cx).is_some() {
3825 return true;
3826 }
3827
3828 if hide_hover(self, cx) {
3829 return true;
3830 }
3831
3832 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3833 return true;
3834 }
3835
3836 if self.hide_context_menu(window, cx).is_some() {
3837 return true;
3838 }
3839
3840 if self.mouse_context_menu.take().is_some() {
3841 return true;
3842 }
3843
3844 if is_user_requested && self.discard_edit_prediction(true, cx) {
3845 return true;
3846 }
3847
3848 if self.snippet_stack.pop().is_some() {
3849 return true;
3850 }
3851
3852 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3853 self.dismiss_diagnostics(cx);
3854 return true;
3855 }
3856
3857 false
3858 }
3859
3860 fn linked_editing_ranges_for(
3861 &self,
3862 selection: Range<text::Anchor>,
3863 cx: &App,
3864 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3865 if self.linked_edit_ranges.is_empty() {
3866 return None;
3867 }
3868 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3869 selection.end.buffer_id.and_then(|end_buffer_id| {
3870 if selection.start.buffer_id != Some(end_buffer_id) {
3871 return None;
3872 }
3873 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3874 let snapshot = buffer.read(cx).snapshot();
3875 self.linked_edit_ranges
3876 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3877 .map(|ranges| (ranges, snapshot, buffer))
3878 })?;
3879 use text::ToOffset as TO;
3880 // find offset from the start of current range to current cursor position
3881 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3882
3883 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3884 let start_difference = start_offset - start_byte_offset;
3885 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3886 let end_difference = end_offset - start_byte_offset;
3887 // Current range has associated linked ranges.
3888 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3889 for range in linked_ranges.iter() {
3890 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3891 let end_offset = start_offset + end_difference;
3892 let start_offset = start_offset + start_difference;
3893 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3894 continue;
3895 }
3896 if self.selections.disjoint_anchor_ranges().any(|s| {
3897 if s.start.buffer_id != selection.start.buffer_id
3898 || s.end.buffer_id != selection.end.buffer_id
3899 {
3900 return false;
3901 }
3902 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3903 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3904 }) {
3905 continue;
3906 }
3907 let start = buffer_snapshot.anchor_after(start_offset);
3908 let end = buffer_snapshot.anchor_after(end_offset);
3909 linked_edits
3910 .entry(buffer.clone())
3911 .or_default()
3912 .push(start..end);
3913 }
3914 Some(linked_edits)
3915 }
3916
3917 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3918 let text: Arc<str> = text.into();
3919
3920 if self.read_only(cx) {
3921 return;
3922 }
3923
3924 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
3925
3926 let selections = self.selections.all_adjusted(cx);
3927 let mut bracket_inserted = false;
3928 let mut edits = Vec::new();
3929 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3930 let mut new_selections = Vec::with_capacity(selections.len());
3931 let mut new_autoclose_regions = Vec::new();
3932 let snapshot = self.buffer.read(cx).read(cx);
3933 let mut clear_linked_edit_ranges = false;
3934
3935 for (selection, autoclose_region) in
3936 self.selections_with_autoclose_regions(selections, &snapshot)
3937 {
3938 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3939 // Determine if the inserted text matches the opening or closing
3940 // bracket of any of this language's bracket pairs.
3941 let mut bracket_pair = None;
3942 let mut is_bracket_pair_start = false;
3943 let mut is_bracket_pair_end = false;
3944 if !text.is_empty() {
3945 let mut bracket_pair_matching_end = None;
3946 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3947 // and they are removing the character that triggered IME popup.
3948 for (pair, enabled) in scope.brackets() {
3949 if !pair.close && !pair.surround {
3950 continue;
3951 }
3952
3953 if enabled && pair.start.ends_with(text.as_ref()) {
3954 let prefix_len = pair.start.len() - text.len();
3955 let preceding_text_matches_prefix = prefix_len == 0
3956 || (selection.start.column >= (prefix_len as u32)
3957 && snapshot.contains_str_at(
3958 Point::new(
3959 selection.start.row,
3960 selection.start.column - (prefix_len as u32),
3961 ),
3962 &pair.start[..prefix_len],
3963 ));
3964 if preceding_text_matches_prefix {
3965 bracket_pair = Some(pair.clone());
3966 is_bracket_pair_start = true;
3967 break;
3968 }
3969 }
3970 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
3971 {
3972 // take first bracket pair matching end, but don't break in case a later bracket
3973 // pair matches start
3974 bracket_pair_matching_end = Some(pair.clone());
3975 }
3976 }
3977 if let Some(end) = bracket_pair_matching_end
3978 && bracket_pair.is_none()
3979 {
3980 bracket_pair = Some(end);
3981 is_bracket_pair_end = true;
3982 }
3983 }
3984
3985 if let Some(bracket_pair) = bracket_pair {
3986 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
3987 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
3988 let auto_surround =
3989 self.use_auto_surround && snapshot_settings.use_auto_surround;
3990 if selection.is_empty() {
3991 if is_bracket_pair_start {
3992 // If the inserted text is a suffix of an opening bracket and the
3993 // selection is preceded by the rest of the opening bracket, then
3994 // insert the closing bracket.
3995 let following_text_allows_autoclose = snapshot
3996 .chars_at(selection.start)
3997 .next()
3998 .map_or(true, |c| scope.should_autoclose_before(c));
3999
4000 let preceding_text_allows_autoclose = selection.start.column == 0
4001 || snapshot.reversed_chars_at(selection.start).next().map_or(
4002 true,
4003 |c| {
4004 bracket_pair.start != bracket_pair.end
4005 || !snapshot
4006 .char_classifier_at(selection.start)
4007 .is_word(c)
4008 },
4009 );
4010
4011 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4012 && bracket_pair.start.len() == 1
4013 {
4014 let target = bracket_pair.start.chars().next().unwrap();
4015 let current_line_count = snapshot
4016 .reversed_chars_at(selection.start)
4017 .take_while(|&c| c != '\n')
4018 .filter(|&c| c == target)
4019 .count();
4020 current_line_count % 2 == 1
4021 } else {
4022 false
4023 };
4024
4025 if autoclose
4026 && bracket_pair.close
4027 && following_text_allows_autoclose
4028 && preceding_text_allows_autoclose
4029 && !is_closing_quote
4030 {
4031 let anchor = snapshot.anchor_before(selection.end);
4032 new_selections.push((selection.map(|_| anchor), text.len()));
4033 new_autoclose_regions.push((
4034 anchor,
4035 text.len(),
4036 selection.id,
4037 bracket_pair.clone(),
4038 ));
4039 edits.push((
4040 selection.range(),
4041 format!("{}{}", text, bracket_pair.end).into(),
4042 ));
4043 bracket_inserted = true;
4044 continue;
4045 }
4046 }
4047
4048 if let Some(region) = autoclose_region {
4049 // If the selection is followed by an auto-inserted closing bracket,
4050 // then don't insert that closing bracket again; just move the selection
4051 // past the closing bracket.
4052 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4053 && text.as_ref() == region.pair.end.as_str()
4054 && snapshot.contains_str_at(region.range.end, text.as_ref());
4055 if should_skip {
4056 let anchor = snapshot.anchor_after(selection.end);
4057 new_selections
4058 .push((selection.map(|_| anchor), region.pair.end.len()));
4059 continue;
4060 }
4061 }
4062
4063 let always_treat_brackets_as_autoclosed = snapshot
4064 .language_settings_at(selection.start, cx)
4065 .always_treat_brackets_as_autoclosed;
4066 if always_treat_brackets_as_autoclosed
4067 && is_bracket_pair_end
4068 && snapshot.contains_str_at(selection.end, text.as_ref())
4069 {
4070 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4071 // and the inserted text is a closing bracket and the selection is followed
4072 // by the closing bracket then move the selection past the closing bracket.
4073 let anchor = snapshot.anchor_after(selection.end);
4074 new_selections.push((selection.map(|_| anchor), text.len()));
4075 continue;
4076 }
4077 }
4078 // If an opening bracket is 1 character long and is typed while
4079 // text is selected, then surround that text with the bracket pair.
4080 else if auto_surround
4081 && bracket_pair.surround
4082 && is_bracket_pair_start
4083 && bracket_pair.start.chars().count() == 1
4084 {
4085 edits.push((selection.start..selection.start, text.clone()));
4086 edits.push((
4087 selection.end..selection.end,
4088 bracket_pair.end.as_str().into(),
4089 ));
4090 bracket_inserted = true;
4091 new_selections.push((
4092 Selection {
4093 id: selection.id,
4094 start: snapshot.anchor_after(selection.start),
4095 end: snapshot.anchor_before(selection.end),
4096 reversed: selection.reversed,
4097 goal: selection.goal,
4098 },
4099 0,
4100 ));
4101 continue;
4102 }
4103 }
4104 }
4105
4106 if self.auto_replace_emoji_shortcode
4107 && selection.is_empty()
4108 && text.as_ref().ends_with(':')
4109 {
4110 if let Some(possible_emoji_short_code) =
4111 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4112 {
4113 if !possible_emoji_short_code.is_empty() {
4114 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
4115 let emoji_shortcode_start = Point::new(
4116 selection.start.row,
4117 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4118 );
4119
4120 // Remove shortcode from buffer
4121 edits.push((
4122 emoji_shortcode_start..selection.start,
4123 "".to_string().into(),
4124 ));
4125 new_selections.push((
4126 Selection {
4127 id: selection.id,
4128 start: snapshot.anchor_after(emoji_shortcode_start),
4129 end: snapshot.anchor_before(selection.start),
4130 reversed: selection.reversed,
4131 goal: selection.goal,
4132 },
4133 0,
4134 ));
4135
4136 // Insert emoji
4137 let selection_start_anchor = snapshot.anchor_after(selection.start);
4138 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4139 edits.push((selection.start..selection.end, emoji.to_string().into()));
4140
4141 continue;
4142 }
4143 }
4144 }
4145 }
4146
4147 // If not handling any auto-close operation, then just replace the selected
4148 // text with the given input and move the selection to the end of the
4149 // newly inserted text.
4150 let anchor = snapshot.anchor_after(selection.end);
4151 if !self.linked_edit_ranges.is_empty() {
4152 let start_anchor = snapshot.anchor_before(selection.start);
4153
4154 let is_word_char = text.chars().next().map_or(true, |char| {
4155 let classifier = snapshot
4156 .char_classifier_at(start_anchor.to_offset(&snapshot))
4157 .ignore_punctuation(true);
4158 classifier.is_word(char)
4159 });
4160
4161 if is_word_char {
4162 if let Some(ranges) = self
4163 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4164 {
4165 for (buffer, edits) in ranges {
4166 linked_edits
4167 .entry(buffer.clone())
4168 .or_default()
4169 .extend(edits.into_iter().map(|range| (range, text.clone())));
4170 }
4171 }
4172 } else {
4173 clear_linked_edit_ranges = true;
4174 }
4175 }
4176
4177 new_selections.push((selection.map(|_| anchor), 0));
4178 edits.push((selection.start..selection.end, text.clone()));
4179 }
4180
4181 drop(snapshot);
4182
4183 self.transact(window, cx, |this, window, cx| {
4184 if clear_linked_edit_ranges {
4185 this.linked_edit_ranges.clear();
4186 }
4187 let initial_buffer_versions =
4188 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4189
4190 this.buffer.update(cx, |buffer, cx| {
4191 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4192 });
4193 for (buffer, edits) in linked_edits {
4194 buffer.update(cx, |buffer, cx| {
4195 let snapshot = buffer.snapshot();
4196 let edits = edits
4197 .into_iter()
4198 .map(|(range, text)| {
4199 use text::ToPoint as TP;
4200 let end_point = TP::to_point(&range.end, &snapshot);
4201 let start_point = TP::to_point(&range.start, &snapshot);
4202 (start_point..end_point, text)
4203 })
4204 .sorted_by_key(|(range, _)| range.start);
4205 buffer.edit(edits, None, cx);
4206 })
4207 }
4208 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4209 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4210 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4211 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
4212 .zip(new_selection_deltas)
4213 .map(|(selection, delta)| Selection {
4214 id: selection.id,
4215 start: selection.start + delta,
4216 end: selection.end + delta,
4217 reversed: selection.reversed,
4218 goal: SelectionGoal::None,
4219 })
4220 .collect::<Vec<_>>();
4221
4222 let mut i = 0;
4223 for (position, delta, selection_id, pair) in new_autoclose_regions {
4224 let position = position.to_offset(&map.buffer_snapshot) + delta;
4225 let start = map.buffer_snapshot.anchor_before(position);
4226 let end = map.buffer_snapshot.anchor_after(position);
4227 while let Some(existing_state) = this.autoclose_regions.get(i) {
4228 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
4229 Ordering::Less => i += 1,
4230 Ordering::Greater => break,
4231 Ordering::Equal => {
4232 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
4233 Ordering::Less => i += 1,
4234 Ordering::Equal => break,
4235 Ordering::Greater => break,
4236 }
4237 }
4238 }
4239 }
4240 this.autoclose_regions.insert(
4241 i,
4242 AutocloseRegion {
4243 selection_id,
4244 range: start..end,
4245 pair,
4246 },
4247 );
4248 }
4249
4250 let had_active_edit_prediction = this.has_active_edit_prediction();
4251 this.change_selections(
4252 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4253 window,
4254 cx,
4255 |s| s.select(new_selections),
4256 );
4257
4258 if !bracket_inserted {
4259 if let Some(on_type_format_task) =
4260 this.trigger_on_type_formatting(text.to_string(), window, cx)
4261 {
4262 on_type_format_task.detach_and_log_err(cx);
4263 }
4264 }
4265
4266 let editor_settings = EditorSettings::get_global(cx);
4267 if bracket_inserted
4268 && (editor_settings.auto_signature_help
4269 || editor_settings.show_signature_help_after_edits)
4270 {
4271 this.show_signature_help(&ShowSignatureHelp, window, cx);
4272 }
4273
4274 let trigger_in_words =
4275 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4276 if this.hard_wrap.is_some() {
4277 let latest: Range<Point> = this.selections.newest(cx).range();
4278 if latest.is_empty()
4279 && this
4280 .buffer()
4281 .read(cx)
4282 .snapshot(cx)
4283 .line_len(MultiBufferRow(latest.start.row))
4284 == latest.start.column
4285 {
4286 this.rewrap_impl(
4287 RewrapOptions {
4288 override_language_settings: true,
4289 preserve_existing_whitespace: true,
4290 },
4291 cx,
4292 )
4293 }
4294 }
4295 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4296 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
4297 this.refresh_edit_prediction(true, false, window, cx);
4298 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4299 });
4300 }
4301
4302 fn find_possible_emoji_shortcode_at_position(
4303 snapshot: &MultiBufferSnapshot,
4304 position: Point,
4305 ) -> Option<String> {
4306 let mut chars = Vec::new();
4307 let mut found_colon = false;
4308 for char in snapshot.reversed_chars_at(position).take(100) {
4309 // Found a possible emoji shortcode in the middle of the buffer
4310 if found_colon {
4311 if char.is_whitespace() {
4312 chars.reverse();
4313 return Some(chars.iter().collect());
4314 }
4315 // If the previous character is not a whitespace, we are in the middle of a word
4316 // and we only want to complete the shortcode if the word is made up of other emojis
4317 let mut containing_word = String::new();
4318 for ch in snapshot
4319 .reversed_chars_at(position)
4320 .skip(chars.len() + 1)
4321 .take(100)
4322 {
4323 if ch.is_whitespace() {
4324 break;
4325 }
4326 containing_word.push(ch);
4327 }
4328 let containing_word = containing_word.chars().rev().collect::<String>();
4329 if util::word_consists_of_emojis(containing_word.as_str()) {
4330 chars.reverse();
4331 return Some(chars.iter().collect());
4332 }
4333 }
4334
4335 if char.is_whitespace() || !char.is_ascii() {
4336 return None;
4337 }
4338 if char == ':' {
4339 found_colon = true;
4340 } else {
4341 chars.push(char);
4342 }
4343 }
4344 // Found a possible emoji shortcode at the beginning of the buffer
4345 chars.reverse();
4346 Some(chars.iter().collect())
4347 }
4348
4349 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4350 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4351 self.transact(window, cx, |this, window, cx| {
4352 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4353 let selections = this.selections.all::<usize>(cx);
4354 let multi_buffer = this.buffer.read(cx);
4355 let buffer = multi_buffer.snapshot(cx);
4356 selections
4357 .iter()
4358 .map(|selection| {
4359 let start_point = selection.start.to_point(&buffer);
4360 let mut existing_indent =
4361 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4362 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4363 let start = selection.start;
4364 let end = selection.end;
4365 let selection_is_empty = start == end;
4366 let language_scope = buffer.language_scope_at(start);
4367 let (
4368 comment_delimiter,
4369 doc_delimiter,
4370 insert_extra_newline,
4371 indent_on_newline,
4372 indent_on_extra_newline,
4373 ) = if let Some(language) = &language_scope {
4374 let mut insert_extra_newline =
4375 insert_extra_newline_brackets(&buffer, start..end, language)
4376 || insert_extra_newline_tree_sitter(&buffer, start..end);
4377
4378 // Comment extension on newline is allowed only for cursor selections
4379 let comment_delimiter = maybe!({
4380 if !selection_is_empty {
4381 return None;
4382 }
4383
4384 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4385 return None;
4386 }
4387
4388 let delimiters = language.line_comment_prefixes();
4389 let max_len_of_delimiter =
4390 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4391 let (snapshot, range) =
4392 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4393
4394 let num_of_whitespaces = snapshot
4395 .chars_for_range(range.clone())
4396 .take_while(|c| c.is_whitespace())
4397 .count();
4398 let comment_candidate = snapshot
4399 .chars_for_range(range.clone())
4400 .skip(num_of_whitespaces)
4401 .take(max_len_of_delimiter)
4402 .collect::<String>();
4403 let (delimiter, trimmed_len) = delimiters
4404 .iter()
4405 .filter_map(|delimiter| {
4406 let prefix = delimiter.trim_end();
4407 if comment_candidate.starts_with(prefix) {
4408 Some((delimiter, prefix.len()))
4409 } else {
4410 None
4411 }
4412 })
4413 .max_by_key(|(_, len)| *len)?;
4414
4415 if let Some(BlockCommentConfig {
4416 start: block_start, ..
4417 }) = language.block_comment()
4418 {
4419 let block_start_trimmed = block_start.trim_end();
4420 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4421 let line_content = snapshot
4422 .chars_for_range(range)
4423 .skip(num_of_whitespaces)
4424 .take(block_start_trimmed.len())
4425 .collect::<String>();
4426
4427 if line_content.starts_with(block_start_trimmed) {
4428 return None;
4429 }
4430 }
4431 }
4432
4433 let cursor_is_placed_after_comment_marker =
4434 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4435 if cursor_is_placed_after_comment_marker {
4436 Some(delimiter.clone())
4437 } else {
4438 None
4439 }
4440 });
4441
4442 let mut indent_on_newline = IndentSize::spaces(0);
4443 let mut indent_on_extra_newline = IndentSize::spaces(0);
4444
4445 let doc_delimiter = maybe!({
4446 if !selection_is_empty {
4447 return None;
4448 }
4449
4450 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4451 return None;
4452 }
4453
4454 let BlockCommentConfig {
4455 start: start_tag,
4456 end: end_tag,
4457 prefix: delimiter,
4458 tab_size: len,
4459 } = language.documentation_comment()?;
4460 let is_within_block_comment = buffer
4461 .language_scope_at(start_point)
4462 .is_some_and(|scope| scope.override_name() == Some("comment"));
4463 if !is_within_block_comment {
4464 return None;
4465 }
4466
4467 let (snapshot, range) =
4468 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4469
4470 let num_of_whitespaces = snapshot
4471 .chars_for_range(range.clone())
4472 .take_while(|c| c.is_whitespace())
4473 .count();
4474
4475 // 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.
4476 let column = start_point.column;
4477 let cursor_is_after_start_tag = {
4478 let start_tag_len = start_tag.len();
4479 let start_tag_line = snapshot
4480 .chars_for_range(range.clone())
4481 .skip(num_of_whitespaces)
4482 .take(start_tag_len)
4483 .collect::<String>();
4484 if start_tag_line.starts_with(start_tag.as_ref()) {
4485 num_of_whitespaces + start_tag_len <= column as usize
4486 } else {
4487 false
4488 }
4489 };
4490
4491 let cursor_is_after_delimiter = {
4492 let delimiter_trim = delimiter.trim_end();
4493 let delimiter_line = snapshot
4494 .chars_for_range(range.clone())
4495 .skip(num_of_whitespaces)
4496 .take(delimiter_trim.len())
4497 .collect::<String>();
4498 if delimiter_line.starts_with(delimiter_trim) {
4499 num_of_whitespaces + delimiter_trim.len() <= column as usize
4500 } else {
4501 false
4502 }
4503 };
4504
4505 let cursor_is_before_end_tag_if_exists = {
4506 let mut char_position = 0u32;
4507 let mut end_tag_offset = None;
4508
4509 'outer: for chunk in snapshot.text_for_range(range.clone()) {
4510 if let Some(byte_pos) = chunk.find(&**end_tag) {
4511 let chars_before_match =
4512 chunk[..byte_pos].chars().count() as u32;
4513 end_tag_offset =
4514 Some(char_position + chars_before_match);
4515 break 'outer;
4516 }
4517 char_position += chunk.chars().count() as u32;
4518 }
4519
4520 if let Some(end_tag_offset) = end_tag_offset {
4521 let cursor_is_before_end_tag = column <= end_tag_offset;
4522 if cursor_is_after_start_tag {
4523 if cursor_is_before_end_tag {
4524 insert_extra_newline = true;
4525 }
4526 let cursor_is_at_start_of_end_tag =
4527 column == end_tag_offset;
4528 if cursor_is_at_start_of_end_tag {
4529 indent_on_extra_newline.len = *len;
4530 }
4531 }
4532 cursor_is_before_end_tag
4533 } else {
4534 true
4535 }
4536 };
4537
4538 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4539 && cursor_is_before_end_tag_if_exists
4540 {
4541 if cursor_is_after_start_tag {
4542 indent_on_newline.len = *len;
4543 }
4544 Some(delimiter.clone())
4545 } else {
4546 None
4547 }
4548 });
4549
4550 (
4551 comment_delimiter,
4552 doc_delimiter,
4553 insert_extra_newline,
4554 indent_on_newline,
4555 indent_on_extra_newline,
4556 )
4557 } else {
4558 (
4559 None,
4560 None,
4561 false,
4562 IndentSize::default(),
4563 IndentSize::default(),
4564 )
4565 };
4566
4567 let prevent_auto_indent = doc_delimiter.is_some();
4568 let delimiter = comment_delimiter.or(doc_delimiter);
4569
4570 let capacity_for_delimiter =
4571 delimiter.as_deref().map(str::len).unwrap_or_default();
4572 let mut new_text = String::with_capacity(
4573 1 + capacity_for_delimiter
4574 + existing_indent.len as usize
4575 + indent_on_newline.len as usize
4576 + indent_on_extra_newline.len as usize,
4577 );
4578 new_text.push('\n');
4579 new_text.extend(existing_indent.chars());
4580 new_text.extend(indent_on_newline.chars());
4581
4582 if let Some(delimiter) = &delimiter {
4583 new_text.push_str(delimiter);
4584 }
4585
4586 if insert_extra_newline {
4587 new_text.push('\n');
4588 new_text.extend(existing_indent.chars());
4589 new_text.extend(indent_on_extra_newline.chars());
4590 }
4591
4592 let anchor = buffer.anchor_after(end);
4593 let new_selection = selection.map(|_| anchor);
4594 (
4595 ((start..end, new_text), prevent_auto_indent),
4596 (insert_extra_newline, new_selection),
4597 )
4598 })
4599 .unzip()
4600 };
4601
4602 let mut auto_indent_edits = Vec::new();
4603 let mut edits = Vec::new();
4604 for (edit, prevent_auto_indent) in edits_with_flags {
4605 if prevent_auto_indent {
4606 edits.push(edit);
4607 } else {
4608 auto_indent_edits.push(edit);
4609 }
4610 }
4611 if !edits.is_empty() {
4612 this.edit(edits, cx);
4613 }
4614 if !auto_indent_edits.is_empty() {
4615 this.edit_with_autoindent(auto_indent_edits, cx);
4616 }
4617
4618 let buffer = this.buffer.read(cx).snapshot(cx);
4619 let new_selections = selection_info
4620 .into_iter()
4621 .map(|(extra_newline_inserted, new_selection)| {
4622 let mut cursor = new_selection.end.to_point(&buffer);
4623 if extra_newline_inserted {
4624 cursor.row -= 1;
4625 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4626 }
4627 new_selection.map(|_| cursor)
4628 })
4629 .collect();
4630
4631 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4632 this.refresh_edit_prediction(true, false, window, cx);
4633 });
4634 }
4635
4636 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4637 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4638
4639 let buffer = self.buffer.read(cx);
4640 let snapshot = buffer.snapshot(cx);
4641
4642 let mut edits = Vec::new();
4643 let mut rows = Vec::new();
4644
4645 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4646 let cursor = selection.head();
4647 let row = cursor.row;
4648
4649 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4650
4651 let newline = "\n".to_string();
4652 edits.push((start_of_line..start_of_line, newline));
4653
4654 rows.push(row + rows_inserted as u32);
4655 }
4656
4657 self.transact(window, cx, |editor, window, cx| {
4658 editor.edit(edits, cx);
4659
4660 editor.change_selections(Default::default(), window, cx, |s| {
4661 let mut index = 0;
4662 s.move_cursors_with(|map, _, _| {
4663 let row = rows[index];
4664 index += 1;
4665
4666 let point = Point::new(row, 0);
4667 let boundary = map.next_line_boundary(point).1;
4668 let clipped = map.clip_point(boundary, Bias::Left);
4669
4670 (clipped, SelectionGoal::None)
4671 });
4672 });
4673
4674 let mut indent_edits = Vec::new();
4675 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4676 for row in rows {
4677 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4678 for (row, indent) in indents {
4679 if indent.len == 0 {
4680 continue;
4681 }
4682
4683 let text = match indent.kind {
4684 IndentKind::Space => " ".repeat(indent.len as usize),
4685 IndentKind::Tab => "\t".repeat(indent.len as usize),
4686 };
4687 let point = Point::new(row.0, 0);
4688 indent_edits.push((point..point, text));
4689 }
4690 }
4691 editor.edit(indent_edits, cx);
4692 });
4693 }
4694
4695 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4696 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4697
4698 let buffer = self.buffer.read(cx);
4699 let snapshot = buffer.snapshot(cx);
4700
4701 let mut edits = Vec::new();
4702 let mut rows = Vec::new();
4703 let mut rows_inserted = 0;
4704
4705 for selection in self.selections.all_adjusted(cx) {
4706 let cursor = selection.head();
4707 let row = cursor.row;
4708
4709 let point = Point::new(row + 1, 0);
4710 let start_of_line = snapshot.clip_point(point, Bias::Left);
4711
4712 let newline = "\n".to_string();
4713 edits.push((start_of_line..start_of_line, newline));
4714
4715 rows_inserted += 1;
4716 rows.push(row + rows_inserted);
4717 }
4718
4719 self.transact(window, cx, |editor, window, cx| {
4720 editor.edit(edits, cx);
4721
4722 editor.change_selections(Default::default(), window, cx, |s| {
4723 let mut index = 0;
4724 s.move_cursors_with(|map, _, _| {
4725 let row = rows[index];
4726 index += 1;
4727
4728 let point = Point::new(row, 0);
4729 let boundary = map.next_line_boundary(point).1;
4730 let clipped = map.clip_point(boundary, Bias::Left);
4731
4732 (clipped, SelectionGoal::None)
4733 });
4734 });
4735
4736 let mut indent_edits = Vec::new();
4737 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4738 for row in rows {
4739 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4740 for (row, indent) in indents {
4741 if indent.len == 0 {
4742 continue;
4743 }
4744
4745 let text = match indent.kind {
4746 IndentKind::Space => " ".repeat(indent.len as usize),
4747 IndentKind::Tab => "\t".repeat(indent.len as usize),
4748 };
4749 let point = Point::new(row.0, 0);
4750 indent_edits.push((point..point, text));
4751 }
4752 }
4753 editor.edit(indent_edits, cx);
4754 });
4755 }
4756
4757 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4758 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4759 original_indent_columns: Vec::new(),
4760 });
4761 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4762 }
4763
4764 fn insert_with_autoindent_mode(
4765 &mut self,
4766 text: &str,
4767 autoindent_mode: Option<AutoindentMode>,
4768 window: &mut Window,
4769 cx: &mut Context<Self>,
4770 ) {
4771 if self.read_only(cx) {
4772 return;
4773 }
4774
4775 let text: Arc<str> = text.into();
4776 self.transact(window, cx, |this, window, cx| {
4777 let old_selections = this.selections.all_adjusted(cx);
4778 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4779 let anchors = {
4780 let snapshot = buffer.read(cx);
4781 old_selections
4782 .iter()
4783 .map(|s| {
4784 let anchor = snapshot.anchor_after(s.head());
4785 s.map(|_| anchor)
4786 })
4787 .collect::<Vec<_>>()
4788 };
4789 buffer.edit(
4790 old_selections
4791 .iter()
4792 .map(|s| (s.start..s.end, text.clone())),
4793 autoindent_mode,
4794 cx,
4795 );
4796 anchors
4797 });
4798
4799 this.change_selections(Default::default(), window, cx, |s| {
4800 s.select_anchors(selection_anchors);
4801 });
4802
4803 cx.notify();
4804 });
4805 }
4806
4807 fn trigger_completion_on_input(
4808 &mut self,
4809 text: &str,
4810 trigger_in_words: bool,
4811 window: &mut Window,
4812 cx: &mut Context<Self>,
4813 ) {
4814 let completions_source = self
4815 .context_menu
4816 .borrow()
4817 .as_ref()
4818 .and_then(|menu| match menu {
4819 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4820 CodeContextMenu::CodeActions(_) => None,
4821 });
4822
4823 match completions_source {
4824 Some(CompletionsMenuSource::Words) => {
4825 self.show_word_completions(&ShowWordCompletions, window, cx)
4826 }
4827 Some(CompletionsMenuSource::Normal)
4828 | Some(CompletionsMenuSource::SnippetChoices)
4829 | None
4830 if self.is_completion_trigger(
4831 text,
4832 trigger_in_words,
4833 completions_source.is_some(),
4834 cx,
4835 ) =>
4836 {
4837 self.show_completions(
4838 &ShowCompletions {
4839 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4840 },
4841 window,
4842 cx,
4843 )
4844 }
4845 _ => {
4846 self.hide_context_menu(window, cx);
4847 }
4848 }
4849 }
4850
4851 fn is_completion_trigger(
4852 &self,
4853 text: &str,
4854 trigger_in_words: bool,
4855 menu_is_open: bool,
4856 cx: &mut Context<Self>,
4857 ) -> bool {
4858 let position = self.selections.newest_anchor().head();
4859 let multibuffer = self.buffer.read(cx);
4860 let Some(buffer) = position
4861 .buffer_id
4862 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
4863 else {
4864 return false;
4865 };
4866
4867 if let Some(completion_provider) = &self.completion_provider {
4868 completion_provider.is_completion_trigger(
4869 &buffer,
4870 position.text_anchor,
4871 text,
4872 trigger_in_words,
4873 menu_is_open,
4874 cx,
4875 )
4876 } else {
4877 false
4878 }
4879 }
4880
4881 /// If any empty selections is touching the start of its innermost containing autoclose
4882 /// region, expand it to select the brackets.
4883 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4884 let selections = self.selections.all::<usize>(cx);
4885 let buffer = self.buffer.read(cx).read(cx);
4886 let new_selections = self
4887 .selections_with_autoclose_regions(selections, &buffer)
4888 .map(|(mut selection, region)| {
4889 if !selection.is_empty() {
4890 return selection;
4891 }
4892
4893 if let Some(region) = region {
4894 let mut range = region.range.to_offset(&buffer);
4895 if selection.start == range.start && range.start >= region.pair.start.len() {
4896 range.start -= region.pair.start.len();
4897 if buffer.contains_str_at(range.start, ®ion.pair.start)
4898 && buffer.contains_str_at(range.end, ®ion.pair.end)
4899 {
4900 range.end += region.pair.end.len();
4901 selection.start = range.start;
4902 selection.end = range.end;
4903
4904 return selection;
4905 }
4906 }
4907 }
4908
4909 let always_treat_brackets_as_autoclosed = buffer
4910 .language_settings_at(selection.start, cx)
4911 .always_treat_brackets_as_autoclosed;
4912
4913 if !always_treat_brackets_as_autoclosed {
4914 return selection;
4915 }
4916
4917 if let Some(scope) = buffer.language_scope_at(selection.start) {
4918 for (pair, enabled) in scope.brackets() {
4919 if !enabled || !pair.close {
4920 continue;
4921 }
4922
4923 if buffer.contains_str_at(selection.start, &pair.end) {
4924 let pair_start_len = pair.start.len();
4925 if buffer.contains_str_at(
4926 selection.start.saturating_sub(pair_start_len),
4927 &pair.start,
4928 ) {
4929 selection.start -= pair_start_len;
4930 selection.end += pair.end.len();
4931
4932 return selection;
4933 }
4934 }
4935 }
4936 }
4937
4938 selection
4939 })
4940 .collect();
4941
4942 drop(buffer);
4943 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
4944 selections.select(new_selections)
4945 });
4946 }
4947
4948 /// Iterate the given selections, and for each one, find the smallest surrounding
4949 /// autoclose region. This uses the ordering of the selections and the autoclose
4950 /// regions to avoid repeated comparisons.
4951 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
4952 &'a self,
4953 selections: impl IntoIterator<Item = Selection<D>>,
4954 buffer: &'a MultiBufferSnapshot,
4955 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
4956 let mut i = 0;
4957 let mut regions = self.autoclose_regions.as_slice();
4958 selections.into_iter().map(move |selection| {
4959 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
4960
4961 let mut enclosing = None;
4962 while let Some(pair_state) = regions.get(i) {
4963 if pair_state.range.end.to_offset(buffer) < range.start {
4964 regions = ®ions[i + 1..];
4965 i = 0;
4966 } else if pair_state.range.start.to_offset(buffer) > range.end {
4967 break;
4968 } else {
4969 if pair_state.selection_id == selection.id {
4970 enclosing = Some(pair_state);
4971 }
4972 i += 1;
4973 }
4974 }
4975
4976 (selection, enclosing)
4977 })
4978 }
4979
4980 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
4981 fn invalidate_autoclose_regions(
4982 &mut self,
4983 mut selections: &[Selection<Anchor>],
4984 buffer: &MultiBufferSnapshot,
4985 ) {
4986 self.autoclose_regions.retain(|state| {
4987 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
4988 return false;
4989 }
4990
4991 let mut i = 0;
4992 while let Some(selection) = selections.get(i) {
4993 if selection.end.cmp(&state.range.start, buffer).is_lt() {
4994 selections = &selections[1..];
4995 continue;
4996 }
4997 if selection.start.cmp(&state.range.end, buffer).is_gt() {
4998 break;
4999 }
5000 if selection.id == state.selection_id {
5001 return true;
5002 } else {
5003 i += 1;
5004 }
5005 }
5006 false
5007 });
5008 }
5009
5010 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5011 let offset = position.to_offset(buffer);
5012 let (word_range, kind) = buffer.surrounding_word(offset, true);
5013 if offset > word_range.start && kind == Some(CharKind::Word) {
5014 Some(
5015 buffer
5016 .text_for_range(word_range.start..offset)
5017 .collect::<String>(),
5018 )
5019 } else {
5020 None
5021 }
5022 }
5023
5024 pub fn toggle_inline_values(
5025 &mut self,
5026 _: &ToggleInlineValues,
5027 _: &mut Window,
5028 cx: &mut Context<Self>,
5029 ) {
5030 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
5031
5032 self.refresh_inline_values(cx);
5033 }
5034
5035 pub fn toggle_inlay_hints(
5036 &mut self,
5037 _: &ToggleInlayHints,
5038 _: &mut Window,
5039 cx: &mut Context<Self>,
5040 ) {
5041 self.refresh_inlay_hints(
5042 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
5043 cx,
5044 );
5045 }
5046
5047 pub fn inlay_hints_enabled(&self) -> bool {
5048 self.inlay_hint_cache.enabled
5049 }
5050
5051 pub fn inline_values_enabled(&self) -> bool {
5052 self.inline_value_cache.enabled
5053 }
5054
5055 #[cfg(any(test, feature = "test-support"))]
5056 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
5057 self.display_map
5058 .read(cx)
5059 .current_inlays()
5060 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
5061 .cloned()
5062 .collect()
5063 }
5064
5065 #[cfg(any(test, feature = "test-support"))]
5066 pub fn all_inlays(&self, cx: &App) -> Vec<Inlay> {
5067 self.display_map
5068 .read(cx)
5069 .current_inlays()
5070 .cloned()
5071 .collect()
5072 }
5073
5074 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
5075 if self.semantics_provider.is_none() || !self.mode.is_full() {
5076 return;
5077 }
5078
5079 let reason_description = reason.description();
5080 let ignore_debounce = matches!(
5081 reason,
5082 InlayHintRefreshReason::SettingsChange(_)
5083 | InlayHintRefreshReason::Toggle(_)
5084 | InlayHintRefreshReason::ExcerptsRemoved(_)
5085 | InlayHintRefreshReason::ModifiersChanged(_)
5086 );
5087 let (invalidate_cache, required_languages) = match reason {
5088 InlayHintRefreshReason::ModifiersChanged(enabled) => {
5089 match self.inlay_hint_cache.modifiers_override(enabled) {
5090 Some(enabled) => {
5091 if enabled {
5092 (InvalidationStrategy::RefreshRequested, None)
5093 } else {
5094 self.splice_inlays(
5095 &self
5096 .visible_inlay_hints(cx)
5097 .iter()
5098 .map(|inlay| inlay.id)
5099 .collect::<Vec<InlayId>>(),
5100 Vec::new(),
5101 cx,
5102 );
5103 return;
5104 }
5105 }
5106 None => return,
5107 }
5108 }
5109 InlayHintRefreshReason::Toggle(enabled) => {
5110 if self.inlay_hint_cache.toggle(enabled) {
5111 if enabled {
5112 (InvalidationStrategy::RefreshRequested, None)
5113 } else {
5114 self.splice_inlays(
5115 &self
5116 .visible_inlay_hints(cx)
5117 .iter()
5118 .map(|inlay| inlay.id)
5119 .collect::<Vec<InlayId>>(),
5120 Vec::new(),
5121 cx,
5122 );
5123 return;
5124 }
5125 } else {
5126 return;
5127 }
5128 }
5129 InlayHintRefreshReason::SettingsChange(new_settings) => {
5130 match self.inlay_hint_cache.update_settings(
5131 &self.buffer,
5132 new_settings,
5133 self.visible_inlay_hints(cx),
5134 cx,
5135 ) {
5136 ControlFlow::Break(Some(InlaySplice {
5137 to_remove,
5138 to_insert,
5139 })) => {
5140 self.splice_inlays(&to_remove, to_insert, cx);
5141 return;
5142 }
5143 ControlFlow::Break(None) => return,
5144 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
5145 }
5146 }
5147 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
5148 if let Some(InlaySplice {
5149 to_remove,
5150 to_insert,
5151 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
5152 {
5153 self.splice_inlays(&to_remove, to_insert, cx);
5154 }
5155 self.display_map.update(cx, |display_map, _| {
5156 display_map.remove_inlays_for_excerpts(&excerpts_removed)
5157 });
5158 return;
5159 }
5160 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
5161 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
5162 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
5163 }
5164 InlayHintRefreshReason::RefreshRequested => {
5165 (InvalidationStrategy::RefreshRequested, None)
5166 }
5167 };
5168
5169 if let Some(InlaySplice {
5170 to_remove,
5171 to_insert,
5172 }) = self.inlay_hint_cache.spawn_hint_refresh(
5173 reason_description,
5174 self.visible_excerpts(required_languages.as_ref(), cx),
5175 invalidate_cache,
5176 ignore_debounce,
5177 cx,
5178 ) {
5179 self.splice_inlays(&to_remove, to_insert, cx);
5180 }
5181 }
5182
5183 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
5184 self.display_map
5185 .read(cx)
5186 .current_inlays()
5187 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
5188 .cloned()
5189 .collect()
5190 }
5191
5192 pub fn visible_excerpts(
5193 &self,
5194 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
5195 cx: &mut Context<Editor>,
5196 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5197 let Some(project) = self.project.as_ref() else {
5198 return HashMap::default();
5199 };
5200 let project = project.read(cx);
5201 let multi_buffer = self.buffer().read(cx);
5202 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5203 let multi_buffer_visible_start = self
5204 .scroll_manager
5205 .anchor()
5206 .anchor
5207 .to_point(&multi_buffer_snapshot);
5208 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5209 multi_buffer_visible_start
5210 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5211 Bias::Left,
5212 );
5213 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
5214 multi_buffer_snapshot
5215 .range_to_buffer_ranges(multi_buffer_visible_range)
5216 .into_iter()
5217 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5218 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5219 let buffer_file = project::File::from_dyn(buffer.file())?;
5220 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5221 let worktree_entry = buffer_worktree
5222 .read(cx)
5223 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
5224 if worktree_entry.is_ignored {
5225 return None;
5226 }
5227
5228 let language = buffer.language()?;
5229 if let Some(restrict_to_languages) = restrict_to_languages {
5230 if !restrict_to_languages.contains(language) {
5231 return None;
5232 }
5233 }
5234 Some((
5235 excerpt_id,
5236 (
5237 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5238 buffer.version().clone(),
5239 excerpt_visible_range,
5240 ),
5241 ))
5242 })
5243 .collect()
5244 }
5245
5246 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5247 TextLayoutDetails {
5248 text_system: window.text_system().clone(),
5249 editor_style: self.style.clone().unwrap(),
5250 rem_size: window.rem_size(),
5251 scroll_anchor: self.scroll_manager.anchor(),
5252 visible_rows: self.visible_line_count(),
5253 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5254 }
5255 }
5256
5257 pub fn splice_inlays(
5258 &self,
5259 to_remove: &[InlayId],
5260 to_insert: Vec<Inlay>,
5261 cx: &mut Context<Self>,
5262 ) {
5263 self.display_map.update(cx, |display_map, cx| {
5264 display_map.splice_inlays(to_remove, to_insert, cx)
5265 });
5266 cx.notify();
5267 }
5268
5269 fn trigger_on_type_formatting(
5270 &self,
5271 input: String,
5272 window: &mut Window,
5273 cx: &mut Context<Self>,
5274 ) -> Option<Task<Result<()>>> {
5275 if input.len() != 1 {
5276 return None;
5277 }
5278
5279 let project = self.project.as_ref()?;
5280 let position = self.selections.newest_anchor().head();
5281 let (buffer, buffer_position) = self
5282 .buffer
5283 .read(cx)
5284 .text_anchor_for_position(position, cx)?;
5285
5286 let settings = language_settings::language_settings(
5287 buffer
5288 .read(cx)
5289 .language_at(buffer_position)
5290 .map(|l| l.name()),
5291 buffer.read(cx).file(),
5292 cx,
5293 );
5294 if !settings.use_on_type_format {
5295 return None;
5296 }
5297
5298 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5299 // hence we do LSP request & edit on host side only — add formats to host's history.
5300 let push_to_lsp_host_history = true;
5301 // If this is not the host, append its history with new edits.
5302 let push_to_client_history = project.read(cx).is_via_collab();
5303
5304 let on_type_formatting = project.update(cx, |project, cx| {
5305 project.on_type_format(
5306 buffer.clone(),
5307 buffer_position,
5308 input,
5309 push_to_lsp_host_history,
5310 cx,
5311 )
5312 });
5313 Some(cx.spawn_in(window, async move |editor, cx| {
5314 if let Some(transaction) = on_type_formatting.await? {
5315 if push_to_client_history {
5316 buffer
5317 .update(cx, |buffer, _| {
5318 buffer.push_transaction(transaction, Instant::now());
5319 buffer.finalize_last_transaction();
5320 })
5321 .ok();
5322 }
5323 editor.update(cx, |editor, cx| {
5324 editor.refresh_document_highlights(cx);
5325 })?;
5326 }
5327 Ok(())
5328 }))
5329 }
5330
5331 pub fn show_word_completions(
5332 &mut self,
5333 _: &ShowWordCompletions,
5334 window: &mut Window,
5335 cx: &mut Context<Self>,
5336 ) {
5337 self.open_or_update_completions_menu(Some(CompletionsMenuSource::Words), None, window, cx);
5338 }
5339
5340 pub fn show_completions(
5341 &mut self,
5342 options: &ShowCompletions,
5343 window: &mut Window,
5344 cx: &mut Context<Self>,
5345 ) {
5346 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5347 }
5348
5349 fn open_or_update_completions_menu(
5350 &mut self,
5351 requested_source: Option<CompletionsMenuSource>,
5352 trigger: Option<&str>,
5353 window: &mut Window,
5354 cx: &mut Context<Self>,
5355 ) {
5356 if self.pending_rename.is_some() {
5357 return;
5358 }
5359
5360 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5361
5362 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5363 // inserted and selected. To handle that case, the start of the selection is used so that
5364 // the menu starts with all choices.
5365 let position = self
5366 .selections
5367 .newest_anchor()
5368 .start
5369 .bias_right(&multibuffer_snapshot);
5370 if position.diff_base_anchor.is_some() {
5371 return;
5372 }
5373 let (buffer, buffer_position) =
5374 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
5375 output
5376 } else {
5377 return;
5378 };
5379 let buffer_snapshot = buffer.read(cx).snapshot();
5380
5381 let query: Option<Arc<String>> =
5382 Self::completion_query(&multibuffer_snapshot, position).map(|query| query.into());
5383
5384 drop(multibuffer_snapshot);
5385
5386 let provider = match requested_source {
5387 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5388 Some(CompletionsMenuSource::Words) => None,
5389 Some(CompletionsMenuSource::SnippetChoices) => {
5390 log::error!("bug: SnippetChoices requested_source is not handled");
5391 None
5392 }
5393 };
5394
5395 let sort_completions = provider
5396 .as_ref()
5397 .map_or(false, |provider| provider.sort_completions());
5398
5399 let filter_completions = provider
5400 .as_ref()
5401 .map_or(true, |provider| provider.filter_completions());
5402
5403 let trigger_kind = match trigger {
5404 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5405 CompletionTriggerKind::TRIGGER_CHARACTER
5406 }
5407 _ => CompletionTriggerKind::INVOKED,
5408 };
5409 let completion_context = CompletionContext {
5410 trigger_character: trigger.and_then(|trigger| {
5411 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5412 Some(String::from(trigger))
5413 } else {
5414 None
5415 }
5416 }),
5417 trigger_kind,
5418 };
5419
5420 // Hide the current completions menu when a trigger char is typed. Without this, cached
5421 // completions from before the trigger char may be reused (#32774). Snippet choices could
5422 // involve trigger chars, so this is skipped in that case.
5423 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER && self.snippet_stack.is_empty()
5424 {
5425 let menu_is_open = matches!(
5426 self.context_menu.borrow().as_ref(),
5427 Some(CodeContextMenu::Completions(_))
5428 );
5429 if menu_is_open {
5430 self.hide_context_menu(window, cx);
5431 }
5432 }
5433
5434 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5435 if filter_completions {
5436 menu.filter(query.clone(), provider.clone(), window, cx);
5437 }
5438 // When `is_incomplete` is false, no need to re-query completions when the current query
5439 // is a suffix of the initial query.
5440 if !menu.is_incomplete {
5441 // If the new query is a suffix of the old query (typing more characters) and
5442 // the previous result was complete, the existing completions can be filtered.
5443 //
5444 // Note that this is always true for snippet completions.
5445 let query_matches = match (&menu.initial_query, &query) {
5446 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5447 (None, _) => true,
5448 _ => false,
5449 };
5450 if query_matches {
5451 let position_matches = if menu.initial_position == position {
5452 true
5453 } else {
5454 let snapshot = self.buffer.read(cx).read(cx);
5455 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5456 };
5457 if position_matches {
5458 return;
5459 }
5460 }
5461 }
5462 };
5463
5464 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5465 buffer_snapshot.surrounding_word(buffer_position, false)
5466 {
5467 let word_to_exclude = buffer_snapshot
5468 .text_for_range(word_range.clone())
5469 .collect::<String>();
5470 (
5471 buffer_snapshot.anchor_before(word_range.start)
5472 ..buffer_snapshot.anchor_after(buffer_position),
5473 Some(word_to_exclude),
5474 )
5475 } else {
5476 (buffer_position..buffer_position, None)
5477 };
5478
5479 let language = buffer_snapshot
5480 .language_at(buffer_position)
5481 .map(|language| language.name());
5482
5483 let completion_settings =
5484 language_settings(language.clone(), buffer_snapshot.file(), cx).completions;
5485
5486 let show_completion_documentation = buffer_snapshot
5487 .settings_at(buffer_position, cx)
5488 .show_completion_documentation;
5489
5490 // The document can be large, so stay in reasonable bounds when searching for words,
5491 // otherwise completion pop-up might be slow to appear.
5492 const WORD_LOOKUP_ROWS: u32 = 5_000;
5493 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5494 let min_word_search = buffer_snapshot.clip_point(
5495 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5496 Bias::Left,
5497 );
5498 let max_word_search = buffer_snapshot.clip_point(
5499 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5500 Bias::Right,
5501 );
5502 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5503 ..buffer_snapshot.point_to_offset(max_word_search);
5504
5505 let skip_digits = query
5506 .as_ref()
5507 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
5508
5509 let (mut words, provider_responses) = match &provider {
5510 Some(provider) => {
5511 let provider_responses = provider.completions(
5512 position.excerpt_id,
5513 &buffer,
5514 buffer_position,
5515 completion_context,
5516 window,
5517 cx,
5518 );
5519
5520 let words = match completion_settings.words {
5521 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
5522 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
5523 .background_spawn(async move {
5524 buffer_snapshot.words_in_range(WordsQuery {
5525 fuzzy_contents: None,
5526 range: word_search_range,
5527 skip_digits,
5528 })
5529 }),
5530 };
5531
5532 (words, provider_responses)
5533 }
5534 None => (
5535 cx.background_spawn(async move {
5536 buffer_snapshot.words_in_range(WordsQuery {
5537 fuzzy_contents: None,
5538 range: word_search_range,
5539 skip_digits,
5540 })
5541 }),
5542 Task::ready(Ok(Vec::new())),
5543 ),
5544 };
5545
5546 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5547
5548 let id = post_inc(&mut self.next_completion_id);
5549 let task = cx.spawn_in(window, async move |editor, cx| {
5550 let Ok(()) = editor.update(cx, |this, _| {
5551 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5552 }) else {
5553 return;
5554 };
5555
5556 // TODO: Ideally completions from different sources would be selectively re-queried, so
5557 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5558 let mut completions = Vec::new();
5559 let mut is_incomplete = false;
5560 if let Some(provider_responses) = provider_responses.await.log_err() {
5561 if !provider_responses.is_empty() {
5562 for response in provider_responses {
5563 completions.extend(response.completions);
5564 is_incomplete = is_incomplete || response.is_incomplete;
5565 }
5566 if completion_settings.words == WordsCompletionMode::Fallback {
5567 words = Task::ready(BTreeMap::default());
5568 }
5569 }
5570 }
5571
5572 let mut words = words.await;
5573 if let Some(word_to_exclude) = &word_to_exclude {
5574 words.remove(word_to_exclude);
5575 }
5576 for lsp_completion in &completions {
5577 words.remove(&lsp_completion.new_text);
5578 }
5579 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5580 replace_range: word_replace_range.clone(),
5581 new_text: word.clone(),
5582 label: CodeLabel::plain(word, None),
5583 icon_path: None,
5584 documentation: None,
5585 source: CompletionSource::BufferWord {
5586 word_range,
5587 resolved: false,
5588 },
5589 insert_text_mode: Some(InsertTextMode::AS_IS),
5590 confirm: None,
5591 }));
5592
5593 let menu = if completions.is_empty() {
5594 None
5595 } else {
5596 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5597 let languages = editor
5598 .workspace
5599 .as_ref()
5600 .and_then(|(workspace, _)| workspace.upgrade())
5601 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5602 let menu = CompletionsMenu::new(
5603 id,
5604 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5605 sort_completions,
5606 show_completion_documentation,
5607 position,
5608 query.clone(),
5609 is_incomplete,
5610 buffer.clone(),
5611 completions.into(),
5612 snippet_sort_order,
5613 languages,
5614 language,
5615 cx,
5616 );
5617
5618 let query = if filter_completions { query } else { None };
5619 let matches_task = if let Some(query) = query {
5620 menu.do_async_filtering(query, cx)
5621 } else {
5622 Task::ready(menu.unfiltered_matches())
5623 };
5624 (menu, matches_task)
5625 }) else {
5626 return;
5627 };
5628
5629 let matches = matches_task.await;
5630
5631 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5632 // Newer menu already set, so exit.
5633 match editor.context_menu.borrow().as_ref() {
5634 Some(CodeContextMenu::Completions(prev_menu)) => {
5635 if prev_menu.id > id {
5636 return;
5637 }
5638 }
5639 _ => {}
5640 };
5641
5642 // Only valid to take prev_menu because it the new menu is immediately set
5643 // below, or the menu is hidden.
5644 match editor.context_menu.borrow_mut().take() {
5645 Some(CodeContextMenu::Completions(prev_menu)) => {
5646 let position_matches =
5647 if prev_menu.initial_position == menu.initial_position {
5648 true
5649 } else {
5650 let snapshot = editor.buffer.read(cx).read(cx);
5651 prev_menu.initial_position.to_offset(&snapshot)
5652 == menu.initial_position.to_offset(&snapshot)
5653 };
5654 if position_matches {
5655 // Preserve markdown cache before `set_filter_results` because it will
5656 // try to populate the documentation cache.
5657 menu.preserve_markdown_cache(prev_menu);
5658 }
5659 }
5660 _ => {}
5661 };
5662
5663 menu.set_filter_results(matches, provider, window, cx);
5664 }) else {
5665 return;
5666 };
5667
5668 menu.visible().then_some(menu)
5669 };
5670
5671 editor
5672 .update_in(cx, |editor, window, cx| {
5673 if editor.focus_handle.is_focused(window) {
5674 if let Some(menu) = menu {
5675 *editor.context_menu.borrow_mut() =
5676 Some(CodeContextMenu::Completions(menu));
5677
5678 crate::hover_popover::hide_hover(editor, cx);
5679 if editor.show_edit_predictions_in_menu() {
5680 editor.update_visible_edit_prediction(window, cx);
5681 } else {
5682 editor.discard_edit_prediction(false, cx);
5683 }
5684
5685 cx.notify();
5686 return;
5687 }
5688 }
5689
5690 if editor.completion_tasks.len() <= 1 {
5691 // If there are no more completion tasks and the last menu was empty, we should hide it.
5692 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5693 // If it was already hidden and we don't show edit predictions in the menu,
5694 // we should also show the edit prediction when available.
5695 if was_hidden && editor.show_edit_predictions_in_menu() {
5696 editor.update_visible_edit_prediction(window, cx);
5697 }
5698 }
5699 })
5700 .ok();
5701 });
5702
5703 self.completion_tasks.push((id, task));
5704 }
5705
5706 #[cfg(feature = "test-support")]
5707 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5708 let menu = self.context_menu.borrow();
5709 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5710 let completions = menu.completions.borrow();
5711 Some(completions.to_vec())
5712 } else {
5713 None
5714 }
5715 }
5716
5717 pub fn with_completions_menu_matching_id<R>(
5718 &self,
5719 id: CompletionId,
5720 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5721 ) -> R {
5722 let mut context_menu = self.context_menu.borrow_mut();
5723 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5724 return f(None);
5725 };
5726 if completions_menu.id != id {
5727 return f(None);
5728 }
5729 f(Some(completions_menu))
5730 }
5731
5732 pub fn confirm_completion(
5733 &mut self,
5734 action: &ConfirmCompletion,
5735 window: &mut Window,
5736 cx: &mut Context<Self>,
5737 ) -> Option<Task<Result<()>>> {
5738 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5739 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5740 }
5741
5742 pub fn confirm_completion_insert(
5743 &mut self,
5744 _: &ConfirmCompletionInsert,
5745 window: &mut Window,
5746 cx: &mut Context<Self>,
5747 ) -> Option<Task<Result<()>>> {
5748 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5749 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5750 }
5751
5752 pub fn confirm_completion_replace(
5753 &mut self,
5754 _: &ConfirmCompletionReplace,
5755 window: &mut Window,
5756 cx: &mut Context<Self>,
5757 ) -> Option<Task<Result<()>>> {
5758 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5759 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5760 }
5761
5762 pub fn compose_completion(
5763 &mut self,
5764 action: &ComposeCompletion,
5765 window: &mut Window,
5766 cx: &mut Context<Self>,
5767 ) -> Option<Task<Result<()>>> {
5768 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5769 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5770 }
5771
5772 fn do_completion(
5773 &mut self,
5774 item_ix: Option<usize>,
5775 intent: CompletionIntent,
5776 window: &mut Window,
5777 cx: &mut Context<Editor>,
5778 ) -> Option<Task<Result<()>>> {
5779 use language::ToOffset as _;
5780
5781 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5782 else {
5783 return None;
5784 };
5785
5786 let candidate_id = {
5787 let entries = completions_menu.entries.borrow();
5788 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5789 if self.show_edit_predictions_in_menu() {
5790 self.discard_edit_prediction(true, cx);
5791 }
5792 mat.candidate_id
5793 };
5794
5795 let completion = completions_menu
5796 .completions
5797 .borrow()
5798 .get(candidate_id)?
5799 .clone();
5800 cx.stop_propagation();
5801
5802 let buffer_handle = completions_menu.buffer.clone();
5803
5804 let CompletionEdit {
5805 new_text,
5806 snippet,
5807 replace_range,
5808 } = process_completion_for_edit(
5809 &completion,
5810 intent,
5811 &buffer_handle,
5812 &completions_menu.initial_position.text_anchor,
5813 cx,
5814 );
5815
5816 let buffer = buffer_handle.read(cx);
5817 let snapshot = self.buffer.read(cx).snapshot(cx);
5818 let newest_anchor = self.selections.newest_anchor();
5819 let replace_range_multibuffer = {
5820 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5821 let multibuffer_anchor = snapshot
5822 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5823 .unwrap()
5824 ..snapshot
5825 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5826 .unwrap();
5827 multibuffer_anchor.start.to_offset(&snapshot)
5828 ..multibuffer_anchor.end.to_offset(&snapshot)
5829 };
5830 if newest_anchor.head().buffer_id != Some(buffer.remote_id()) {
5831 return None;
5832 }
5833
5834 let old_text = buffer
5835 .text_for_range(replace_range.clone())
5836 .collect::<String>();
5837 let lookbehind = newest_anchor
5838 .start
5839 .text_anchor
5840 .to_offset(buffer)
5841 .saturating_sub(replace_range.start);
5842 let lookahead = replace_range
5843 .end
5844 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5845 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5846 let suffix = &old_text[lookbehind.min(old_text.len())..];
5847
5848 let selections = self.selections.all::<usize>(cx);
5849 let mut ranges = Vec::new();
5850 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5851
5852 for selection in &selections {
5853 let range = if selection.id == newest_anchor.id {
5854 replace_range_multibuffer.clone()
5855 } else {
5856 let mut range = selection.range();
5857
5858 // if prefix is present, don't duplicate it
5859 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5860 range.start = range.start.saturating_sub(lookbehind);
5861
5862 // if suffix is also present, mimic the newest cursor and replace it
5863 if selection.id != newest_anchor.id
5864 && snapshot.contains_str_at(range.end, suffix)
5865 {
5866 range.end += lookahead;
5867 }
5868 }
5869 range
5870 };
5871
5872 ranges.push(range.clone());
5873
5874 if !self.linked_edit_ranges.is_empty() {
5875 let start_anchor = snapshot.anchor_before(range.start);
5876 let end_anchor = snapshot.anchor_after(range.end);
5877 if let Some(ranges) = self
5878 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5879 {
5880 for (buffer, edits) in ranges {
5881 linked_edits
5882 .entry(buffer.clone())
5883 .or_default()
5884 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5885 }
5886 }
5887 }
5888 }
5889
5890 let common_prefix_len = old_text
5891 .chars()
5892 .zip(new_text.chars())
5893 .take_while(|(a, b)| a == b)
5894 .map(|(a, _)| a.len_utf8())
5895 .sum::<usize>();
5896
5897 cx.emit(EditorEvent::InputHandled {
5898 utf16_range_to_replace: None,
5899 text: new_text[common_prefix_len..].into(),
5900 });
5901
5902 self.transact(window, cx, |editor, window, cx| {
5903 if let Some(mut snippet) = snippet {
5904 snippet.text = new_text.to_string();
5905 editor
5906 .insert_snippet(&ranges, snippet, window, cx)
5907 .log_err();
5908 } else {
5909 editor.buffer.update(cx, |multi_buffer, cx| {
5910 let auto_indent = match completion.insert_text_mode {
5911 Some(InsertTextMode::AS_IS) => None,
5912 _ => editor.autoindent_mode.clone(),
5913 };
5914 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5915 multi_buffer.edit(edits, auto_indent, cx);
5916 });
5917 }
5918 for (buffer, edits) in linked_edits {
5919 buffer.update(cx, |buffer, cx| {
5920 let snapshot = buffer.snapshot();
5921 let edits = edits
5922 .into_iter()
5923 .map(|(range, text)| {
5924 use text::ToPoint as TP;
5925 let end_point = TP::to_point(&range.end, &snapshot);
5926 let start_point = TP::to_point(&range.start, &snapshot);
5927 (start_point..end_point, text)
5928 })
5929 .sorted_by_key(|(range, _)| range.start);
5930 buffer.edit(edits, None, cx);
5931 })
5932 }
5933
5934 editor.refresh_edit_prediction(true, false, window, cx);
5935 });
5936 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), &snapshot);
5937
5938 let show_new_completions_on_confirm = completion
5939 .confirm
5940 .as_ref()
5941 .map_or(false, |confirm| confirm(intent, window, cx));
5942 if show_new_completions_on_confirm {
5943 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5944 }
5945
5946 let provider = self.completion_provider.as_ref()?;
5947 drop(completion);
5948 let apply_edits = provider.apply_additional_edits_for_completion(
5949 buffer_handle,
5950 completions_menu.completions.clone(),
5951 candidate_id,
5952 true,
5953 cx,
5954 );
5955
5956 let editor_settings = EditorSettings::get_global(cx);
5957 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5958 // After the code completion is finished, users often want to know what signatures are needed.
5959 // so we should automatically call signature_help
5960 self.show_signature_help(&ShowSignatureHelp, window, cx);
5961 }
5962
5963 Some(cx.foreground_executor().spawn(async move {
5964 apply_edits.await?;
5965 Ok(())
5966 }))
5967 }
5968
5969 pub fn toggle_code_actions(
5970 &mut self,
5971 action: &ToggleCodeActions,
5972 window: &mut Window,
5973 cx: &mut Context<Self>,
5974 ) {
5975 let quick_launch = action.quick_launch;
5976 let mut context_menu = self.context_menu.borrow_mut();
5977 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5978 if code_actions.deployed_from == action.deployed_from {
5979 // Toggle if we're selecting the same one
5980 *context_menu = None;
5981 cx.notify();
5982 return;
5983 } else {
5984 // Otherwise, clear it and start a new one
5985 *context_menu = None;
5986 cx.notify();
5987 }
5988 }
5989 drop(context_menu);
5990 let snapshot = self.snapshot(window, cx);
5991 let deployed_from = action.deployed_from.clone();
5992 let action = action.clone();
5993 self.completion_tasks.clear();
5994 self.discard_edit_prediction(false, cx);
5995
5996 let multibuffer_point = match &action.deployed_from {
5997 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
5998 DisplayPoint::new(*row, 0).to_point(&snapshot)
5999 }
6000 _ => self.selections.newest::<Point>(cx).head(),
6001 };
6002 let Some((buffer, buffer_row)) = snapshot
6003 .buffer_snapshot
6004 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6005 .and_then(|(buffer_snapshot, range)| {
6006 self.buffer()
6007 .read(cx)
6008 .buffer(buffer_snapshot.remote_id())
6009 .map(|buffer| (buffer, range.start.row))
6010 })
6011 else {
6012 return;
6013 };
6014 let buffer_id = buffer.read(cx).remote_id();
6015 let tasks = self
6016 .tasks
6017 .get(&(buffer_id, buffer_row))
6018 .map(|t| Arc::new(t.to_owned()));
6019
6020 if !self.focus_handle.is_focused(window) {
6021 return;
6022 }
6023 let project = self.project.clone();
6024
6025 let code_actions_task = match deployed_from {
6026 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6027 _ => self.code_actions(buffer_row, window, cx),
6028 };
6029
6030 let runnable_task = match deployed_from {
6031 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6032 _ => {
6033 let mut task_context_task = Task::ready(None);
6034 if let Some(tasks) = &tasks {
6035 if let Some(project) = project {
6036 task_context_task =
6037 Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
6038 }
6039 }
6040
6041 cx.spawn_in(window, {
6042 let buffer = buffer.clone();
6043 async move |editor, cx| {
6044 let task_context = task_context_task.await;
6045
6046 let resolved_tasks =
6047 tasks
6048 .zip(task_context.clone())
6049 .map(|(tasks, task_context)| ResolvedTasks {
6050 templates: tasks.resolve(&task_context).collect(),
6051 position: snapshot.buffer_snapshot.anchor_before(Point::new(
6052 multibuffer_point.row,
6053 tasks.column,
6054 )),
6055 });
6056 let debug_scenarios = editor
6057 .update(cx, |editor, cx| {
6058 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6059 })?
6060 .await;
6061 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6062 }
6063 })
6064 }
6065 };
6066
6067 cx.spawn_in(window, async move |editor, cx| {
6068 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6069 let code_actions = code_actions_task.await;
6070 let spawn_straight_away = quick_launch
6071 && resolved_tasks
6072 .as_ref()
6073 .map_or(false, |tasks| tasks.templates.len() == 1)
6074 && code_actions
6075 .as_ref()
6076 .map_or(true, |actions| actions.is_empty())
6077 && debug_scenarios.is_empty();
6078
6079 editor.update_in(cx, |editor, window, cx| {
6080 crate::hover_popover::hide_hover(editor, cx);
6081 let actions = CodeActionContents::new(
6082 resolved_tasks,
6083 code_actions,
6084 debug_scenarios,
6085 task_context.unwrap_or_default(),
6086 );
6087
6088 // Don't show the menu if there are no actions available
6089 if actions.is_empty() {
6090 cx.notify();
6091 return Task::ready(Ok(()));
6092 }
6093
6094 *editor.context_menu.borrow_mut() =
6095 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6096 buffer,
6097 actions,
6098 selected_item: Default::default(),
6099 scroll_handle: UniformListScrollHandle::default(),
6100 deployed_from,
6101 }));
6102 cx.notify();
6103 if spawn_straight_away {
6104 if let Some(task) = editor.confirm_code_action(
6105 &ConfirmCodeAction { item_ix: Some(0) },
6106 window,
6107 cx,
6108 ) {
6109 return task;
6110 }
6111 }
6112
6113 Task::ready(Ok(()))
6114 })
6115 })
6116 .detach_and_log_err(cx);
6117 }
6118
6119 fn debug_scenarios(
6120 &mut self,
6121 resolved_tasks: &Option<ResolvedTasks>,
6122 buffer: &Entity<Buffer>,
6123 cx: &mut App,
6124 ) -> Task<Vec<task::DebugScenario>> {
6125 maybe!({
6126 let project = self.project.as_ref()?;
6127 let dap_store = project.read(cx).dap_store();
6128 let mut scenarios = vec![];
6129 let resolved_tasks = resolved_tasks.as_ref()?;
6130 let buffer = buffer.read(cx);
6131 let language = buffer.language()?;
6132 let file = buffer.file();
6133 let debug_adapter = language_settings(language.name().into(), file, cx)
6134 .debuggers
6135 .first()
6136 .map(SharedString::from)
6137 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6138
6139 dap_store.update(cx, |dap_store, cx| {
6140 for (_, task) in &resolved_tasks.templates {
6141 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6142 task.original_task().clone(),
6143 debug_adapter.clone().into(),
6144 task.display_label().to_owned().into(),
6145 cx,
6146 );
6147 scenarios.push(maybe_scenario);
6148 }
6149 });
6150 Some(cx.background_spawn(async move {
6151 let scenarios = futures::future::join_all(scenarios)
6152 .await
6153 .into_iter()
6154 .flatten()
6155 .collect::<Vec<_>>();
6156 scenarios
6157 }))
6158 })
6159 .unwrap_or_else(|| Task::ready(vec![]))
6160 }
6161
6162 fn code_actions(
6163 &mut self,
6164 buffer_row: u32,
6165 window: &mut Window,
6166 cx: &mut Context<Self>,
6167 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6168 let mut task = self.code_actions_task.take();
6169 cx.spawn_in(window, async move |editor, cx| {
6170 while let Some(prev_task) = task {
6171 prev_task.await.log_err();
6172 task = editor
6173 .update(cx, |this, _| this.code_actions_task.take())
6174 .ok()?;
6175 }
6176
6177 editor
6178 .update(cx, |editor, cx| {
6179 editor
6180 .available_code_actions
6181 .clone()
6182 .and_then(|(location, code_actions)| {
6183 let snapshot = location.buffer.read(cx).snapshot();
6184 let point_range = location.range.to_point(&snapshot);
6185 let point_range = point_range.start.row..=point_range.end.row;
6186 if point_range.contains(&buffer_row) {
6187 Some(code_actions)
6188 } else {
6189 None
6190 }
6191 })
6192 })
6193 .ok()
6194 .flatten()
6195 })
6196 }
6197
6198 pub fn confirm_code_action(
6199 &mut self,
6200 action: &ConfirmCodeAction,
6201 window: &mut Window,
6202 cx: &mut Context<Self>,
6203 ) -> Option<Task<Result<()>>> {
6204 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6205
6206 let actions_menu =
6207 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6208 menu
6209 } else {
6210 return None;
6211 };
6212
6213 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6214 let action = actions_menu.actions.get(action_ix)?;
6215 let title = action.label();
6216 let buffer = actions_menu.buffer;
6217 let workspace = self.workspace()?;
6218
6219 match action {
6220 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6221 workspace.update(cx, |workspace, cx| {
6222 workspace.schedule_resolved_task(
6223 task_source_kind,
6224 resolved_task,
6225 false,
6226 window,
6227 cx,
6228 );
6229
6230 Some(Task::ready(Ok(())))
6231 })
6232 }
6233 CodeActionsItem::CodeAction {
6234 excerpt_id,
6235 action,
6236 provider,
6237 } => {
6238 let apply_code_action =
6239 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6240 let workspace = workspace.downgrade();
6241 Some(cx.spawn_in(window, async move |editor, cx| {
6242 let project_transaction = apply_code_action.await?;
6243 Self::open_project_transaction(
6244 &editor,
6245 workspace,
6246 project_transaction,
6247 title,
6248 cx,
6249 )
6250 .await
6251 }))
6252 }
6253 CodeActionsItem::DebugScenario(scenario) => {
6254 let context = actions_menu.actions.context.clone();
6255
6256 workspace.update(cx, |workspace, cx| {
6257 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6258 workspace.start_debug_session(
6259 scenario,
6260 context,
6261 Some(buffer),
6262 None,
6263 window,
6264 cx,
6265 );
6266 });
6267 Some(Task::ready(Ok(())))
6268 }
6269 }
6270 }
6271
6272 pub async fn open_project_transaction(
6273 this: &WeakEntity<Editor>,
6274 workspace: WeakEntity<Workspace>,
6275 transaction: ProjectTransaction,
6276 title: String,
6277 cx: &mut AsyncWindowContext,
6278 ) -> Result<()> {
6279 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6280 cx.update(|_, cx| {
6281 entries.sort_unstable_by_key(|(buffer, _)| {
6282 buffer.read(cx).file().map(|f| f.path().clone())
6283 });
6284 })?;
6285
6286 // If the project transaction's edits are all contained within this editor, then
6287 // avoid opening a new editor to display them.
6288
6289 if let Some((buffer, transaction)) = entries.first() {
6290 if entries.len() == 1 {
6291 let excerpt = this.update(cx, |editor, cx| {
6292 editor
6293 .buffer()
6294 .read(cx)
6295 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6296 })?;
6297 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
6298 if excerpted_buffer == *buffer {
6299 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6300 let excerpt_range = excerpt_range.to_offset(buffer);
6301 buffer
6302 .edited_ranges_for_transaction::<usize>(transaction)
6303 .all(|range| {
6304 excerpt_range.start <= range.start
6305 && excerpt_range.end >= range.end
6306 })
6307 })?;
6308
6309 if all_edits_within_excerpt {
6310 return Ok(());
6311 }
6312 }
6313 }
6314 }
6315 } else {
6316 return Ok(());
6317 }
6318
6319 let mut ranges_to_highlight = Vec::new();
6320 let excerpt_buffer = cx.new(|cx| {
6321 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6322 for (buffer_handle, transaction) in &entries {
6323 let edited_ranges = buffer_handle
6324 .read(cx)
6325 .edited_ranges_for_transaction::<Point>(transaction)
6326 .collect::<Vec<_>>();
6327 let (ranges, _) = multibuffer.set_excerpts_for_path(
6328 PathKey::for_buffer(buffer_handle, cx),
6329 buffer_handle.clone(),
6330 edited_ranges,
6331 DEFAULT_MULTIBUFFER_CONTEXT,
6332 cx,
6333 );
6334
6335 ranges_to_highlight.extend(ranges);
6336 }
6337 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6338 multibuffer
6339 })?;
6340
6341 workspace.update_in(cx, |workspace, window, cx| {
6342 let project = workspace.project().clone();
6343 let editor =
6344 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6345 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6346 editor.update(cx, |editor, cx| {
6347 editor.highlight_background::<Self>(
6348 &ranges_to_highlight,
6349 |theme| theme.colors().editor_highlighted_line_background,
6350 cx,
6351 );
6352 });
6353 })?;
6354
6355 Ok(())
6356 }
6357
6358 pub fn clear_code_action_providers(&mut self) {
6359 self.code_action_providers.clear();
6360 self.available_code_actions.take();
6361 }
6362
6363 pub fn add_code_action_provider(
6364 &mut self,
6365 provider: Rc<dyn CodeActionProvider>,
6366 window: &mut Window,
6367 cx: &mut Context<Self>,
6368 ) {
6369 if self
6370 .code_action_providers
6371 .iter()
6372 .any(|existing_provider| existing_provider.id() == provider.id())
6373 {
6374 return;
6375 }
6376
6377 self.code_action_providers.push(provider);
6378 self.refresh_code_actions(window, cx);
6379 }
6380
6381 pub fn remove_code_action_provider(
6382 &mut self,
6383 id: Arc<str>,
6384 window: &mut Window,
6385 cx: &mut Context<Self>,
6386 ) {
6387 self.code_action_providers
6388 .retain(|provider| provider.id() != id);
6389 self.refresh_code_actions(window, cx);
6390 }
6391
6392 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6393 !self.code_action_providers.is_empty()
6394 && EditorSettings::get_global(cx).toolbar.code_actions
6395 }
6396
6397 pub fn has_available_code_actions(&self) -> bool {
6398 self.available_code_actions
6399 .as_ref()
6400 .is_some_and(|(_, actions)| !actions.is_empty())
6401 }
6402
6403 fn render_inline_code_actions(
6404 &self,
6405 icon_size: ui::IconSize,
6406 display_row: DisplayRow,
6407 is_active: bool,
6408 cx: &mut Context<Self>,
6409 ) -> AnyElement {
6410 let show_tooltip = !self.context_menu_visible();
6411 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6412 .icon_size(icon_size)
6413 .shape(ui::IconButtonShape::Square)
6414 .icon_color(ui::Color::Hidden)
6415 .toggle_state(is_active)
6416 .when(show_tooltip, |this| {
6417 this.tooltip({
6418 let focus_handle = self.focus_handle.clone();
6419 move |window, cx| {
6420 Tooltip::for_action_in(
6421 "Toggle Code Actions",
6422 &ToggleCodeActions {
6423 deployed_from: None,
6424 quick_launch: false,
6425 },
6426 &focus_handle,
6427 window,
6428 cx,
6429 )
6430 }
6431 })
6432 })
6433 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6434 window.focus(&editor.focus_handle(cx));
6435 editor.toggle_code_actions(
6436 &crate::actions::ToggleCodeActions {
6437 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6438 display_row,
6439 )),
6440 quick_launch: false,
6441 },
6442 window,
6443 cx,
6444 );
6445 }))
6446 .into_any_element()
6447 }
6448
6449 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6450 &self.context_menu
6451 }
6452
6453 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
6454 let newest_selection = self.selections.newest_anchor().clone();
6455 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
6456 let buffer = self.buffer.read(cx);
6457 if newest_selection.head().diff_base_anchor.is_some() {
6458 return None;
6459 }
6460 let (start_buffer, start) =
6461 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6462 let (end_buffer, end) =
6463 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6464 if start_buffer != end_buffer {
6465 return None;
6466 }
6467
6468 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6469 cx.background_executor()
6470 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6471 .await;
6472
6473 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6474 let providers = this.code_action_providers.clone();
6475 let tasks = this
6476 .code_action_providers
6477 .iter()
6478 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6479 .collect::<Vec<_>>();
6480 (providers, tasks)
6481 })?;
6482
6483 let mut actions = Vec::new();
6484 for (provider, provider_actions) in
6485 providers.into_iter().zip(future::join_all(tasks).await)
6486 {
6487 if let Some(provider_actions) = provider_actions.log_err() {
6488 actions.extend(provider_actions.into_iter().map(|action| {
6489 AvailableCodeAction {
6490 excerpt_id: newest_selection.start.excerpt_id,
6491 action,
6492 provider: provider.clone(),
6493 }
6494 }));
6495 }
6496 }
6497
6498 this.update(cx, |this, cx| {
6499 this.available_code_actions = if actions.is_empty() {
6500 None
6501 } else {
6502 Some((
6503 Location {
6504 buffer: start_buffer,
6505 range: start..end,
6506 },
6507 actions.into(),
6508 ))
6509 };
6510 cx.notify();
6511 })
6512 }));
6513 None
6514 }
6515
6516 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6517 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6518 self.show_git_blame_inline = false;
6519
6520 self.show_git_blame_inline_delay_task =
6521 Some(cx.spawn_in(window, async move |this, cx| {
6522 cx.background_executor().timer(delay).await;
6523
6524 this.update(cx, |this, cx| {
6525 this.show_git_blame_inline = true;
6526 cx.notify();
6527 })
6528 .log_err();
6529 }));
6530 }
6531 }
6532
6533 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6534 let snapshot = self.snapshot(window, cx);
6535 let cursor = self.selections.newest::<Point>(cx).head();
6536 let Some((buffer, point, _)) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)
6537 else {
6538 return;
6539 };
6540
6541 let Some(blame) = self.blame.as_ref() else {
6542 return;
6543 };
6544
6545 let row_info = RowInfo {
6546 buffer_id: Some(buffer.remote_id()),
6547 buffer_row: Some(point.row),
6548 ..Default::default()
6549 };
6550 let Some(blame_entry) = blame
6551 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6552 .flatten()
6553 else {
6554 return;
6555 };
6556
6557 let anchor = self.selections.newest_anchor().head();
6558 let position = self.to_pixel_point(anchor, &snapshot, window);
6559 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6560 self.show_blame_popover(&blame_entry, position + last_bounds.origin, true, cx);
6561 };
6562 }
6563
6564 fn show_blame_popover(
6565 &mut self,
6566 blame_entry: &BlameEntry,
6567 position: gpui::Point<Pixels>,
6568 ignore_timeout: bool,
6569 cx: &mut Context<Self>,
6570 ) {
6571 if let Some(state) = &mut self.inline_blame_popover {
6572 state.hide_task.take();
6573 } else {
6574 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay;
6575 let blame_entry = blame_entry.clone();
6576 let show_task = cx.spawn(async move |editor, cx| {
6577 if !ignore_timeout {
6578 cx.background_executor()
6579 .timer(std::time::Duration::from_millis(blame_popover_delay))
6580 .await;
6581 }
6582 editor
6583 .update(cx, |editor, cx| {
6584 editor.inline_blame_popover_show_task.take();
6585 let Some(blame) = editor.blame.as_ref() else {
6586 return;
6587 };
6588 let blame = blame.read(cx);
6589 let details = blame.details_for_entry(&blame_entry);
6590 let markdown = cx.new(|cx| {
6591 Markdown::new(
6592 details
6593 .as_ref()
6594 .map(|message| message.message.clone())
6595 .unwrap_or_default(),
6596 None,
6597 None,
6598 cx,
6599 )
6600 });
6601 editor.inline_blame_popover = Some(InlineBlamePopover {
6602 position,
6603 hide_task: None,
6604 popover_bounds: None,
6605 popover_state: InlineBlamePopoverState {
6606 scroll_handle: ScrollHandle::new(),
6607 commit_message: details,
6608 markdown,
6609 },
6610 keyboard_grace: ignore_timeout,
6611 });
6612 cx.notify();
6613 })
6614 .ok();
6615 });
6616 self.inline_blame_popover_show_task = Some(show_task);
6617 }
6618 }
6619
6620 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6621 self.inline_blame_popover_show_task.take();
6622 if let Some(state) = &mut self.inline_blame_popover {
6623 let hide_task = cx.spawn(async move |editor, cx| {
6624 cx.background_executor()
6625 .timer(std::time::Duration::from_millis(100))
6626 .await;
6627 editor
6628 .update(cx, |editor, cx| {
6629 editor.inline_blame_popover.take();
6630 cx.notify();
6631 })
6632 .ok();
6633 });
6634 state.hide_task = Some(hide_task);
6635 }
6636 }
6637
6638 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6639 if self.pending_rename.is_some() {
6640 return None;
6641 }
6642
6643 let provider = self.semantics_provider.clone()?;
6644 let buffer = self.buffer.read(cx);
6645 let newest_selection = self.selections.newest_anchor().clone();
6646 let cursor_position = newest_selection.head();
6647 let (cursor_buffer, cursor_buffer_position) =
6648 buffer.text_anchor_for_position(cursor_position, cx)?;
6649 let (tail_buffer, tail_buffer_position) =
6650 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6651 if cursor_buffer != tail_buffer {
6652 return None;
6653 }
6654
6655 let snapshot = cursor_buffer.read(cx).snapshot();
6656 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, false);
6657 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, false);
6658 if start_word_range != end_word_range {
6659 self.document_highlights_task.take();
6660 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6661 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6662 return None;
6663 }
6664
6665 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6666 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6667 cx.background_executor()
6668 .timer(Duration::from_millis(debounce))
6669 .await;
6670
6671 let highlights = if let Some(highlights) = cx
6672 .update(|cx| {
6673 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6674 })
6675 .ok()
6676 .flatten()
6677 {
6678 highlights.await.log_err()
6679 } else {
6680 None
6681 };
6682
6683 if let Some(highlights) = highlights {
6684 this.update(cx, |this, cx| {
6685 if this.pending_rename.is_some() {
6686 return;
6687 }
6688
6689 let buffer_id = cursor_position.buffer_id;
6690 let buffer = this.buffer.read(cx);
6691 if !buffer
6692 .text_anchor_for_position(cursor_position, cx)
6693 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
6694 {
6695 return;
6696 }
6697
6698 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6699 let mut write_ranges = Vec::new();
6700 let mut read_ranges = Vec::new();
6701 for highlight in highlights {
6702 for (excerpt_id, excerpt_range) in
6703 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
6704 {
6705 let start = highlight
6706 .range
6707 .start
6708 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6709 let end = highlight
6710 .range
6711 .end
6712 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6713 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6714 continue;
6715 }
6716
6717 let range = Anchor {
6718 buffer_id,
6719 excerpt_id,
6720 text_anchor: start,
6721 diff_base_anchor: None,
6722 }..Anchor {
6723 buffer_id,
6724 excerpt_id,
6725 text_anchor: end,
6726 diff_base_anchor: None,
6727 };
6728 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6729 write_ranges.push(range);
6730 } else {
6731 read_ranges.push(range);
6732 }
6733 }
6734 }
6735
6736 this.highlight_background::<DocumentHighlightRead>(
6737 &read_ranges,
6738 |theme| theme.colors().editor_document_highlight_read_background,
6739 cx,
6740 );
6741 this.highlight_background::<DocumentHighlightWrite>(
6742 &write_ranges,
6743 |theme| theme.colors().editor_document_highlight_write_background,
6744 cx,
6745 );
6746 cx.notify();
6747 })
6748 .log_err();
6749 }
6750 }));
6751 None
6752 }
6753
6754 fn prepare_highlight_query_from_selection(
6755 &mut self,
6756 cx: &mut Context<Editor>,
6757 ) -> Option<(String, Range<Anchor>)> {
6758 if matches!(self.mode, EditorMode::SingleLine { .. }) {
6759 return None;
6760 }
6761 if !EditorSettings::get_global(cx).selection_highlight {
6762 return None;
6763 }
6764 if self.selections.count() != 1 || self.selections.line_mode {
6765 return None;
6766 }
6767 let selection = self.selections.newest::<Point>(cx);
6768 if selection.is_empty() || selection.start.row != selection.end.row {
6769 return None;
6770 }
6771 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6772 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6773 let query = multi_buffer_snapshot
6774 .text_for_range(selection_anchor_range.clone())
6775 .collect::<String>();
6776 if query.trim().is_empty() {
6777 return None;
6778 }
6779 Some((query, selection_anchor_range))
6780 }
6781
6782 fn update_selection_occurrence_highlights(
6783 &mut self,
6784 query_text: String,
6785 query_range: Range<Anchor>,
6786 multi_buffer_range_to_query: Range<Point>,
6787 use_debounce: bool,
6788 window: &mut Window,
6789 cx: &mut Context<Editor>,
6790 ) -> Task<()> {
6791 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6792 cx.spawn_in(window, async move |editor, cx| {
6793 if use_debounce {
6794 cx.background_executor()
6795 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6796 .await;
6797 }
6798 let match_task = cx.background_spawn(async move {
6799 let buffer_ranges = multi_buffer_snapshot
6800 .range_to_buffer_ranges(multi_buffer_range_to_query)
6801 .into_iter()
6802 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6803 let mut match_ranges = Vec::new();
6804 let Ok(regex) = project::search::SearchQuery::text(
6805 query_text.clone(),
6806 false,
6807 false,
6808 false,
6809 Default::default(),
6810 Default::default(),
6811 false,
6812 None,
6813 ) else {
6814 return Vec::default();
6815 };
6816 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6817 match_ranges.extend(
6818 regex
6819 .search(&buffer_snapshot, Some(search_range.clone()))
6820 .await
6821 .into_iter()
6822 .filter_map(|match_range| {
6823 let match_start = buffer_snapshot
6824 .anchor_after(search_range.start + match_range.start);
6825 let match_end = buffer_snapshot
6826 .anchor_before(search_range.start + match_range.end);
6827 let match_anchor_range = Anchor::range_in_buffer(
6828 excerpt_id,
6829 buffer_snapshot.remote_id(),
6830 match_start..match_end,
6831 );
6832 (match_anchor_range != query_range).then_some(match_anchor_range)
6833 }),
6834 );
6835 }
6836 match_ranges
6837 });
6838 let match_ranges = match_task.await;
6839 editor
6840 .update_in(cx, |editor, _, cx| {
6841 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6842 if !match_ranges.is_empty() {
6843 editor.highlight_background::<SelectedTextHighlight>(
6844 &match_ranges,
6845 |theme| theme.colors().editor_document_highlight_bracket_background,
6846 cx,
6847 )
6848 }
6849 })
6850 .log_err();
6851 })
6852 }
6853
6854 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
6855 struct NewlineFold;
6856 let type_id = std::any::TypeId::of::<NewlineFold>();
6857 if !self.mode.is_single_line() {
6858 return;
6859 }
6860 let snapshot = self.snapshot(window, cx);
6861 if snapshot.buffer_snapshot.max_point().row == 0 {
6862 return;
6863 }
6864 let task = cx.background_spawn(async move {
6865 let new_newlines = snapshot
6866 .buffer_chars_at(0)
6867 .filter_map(|(c, i)| {
6868 if c == '\n' {
6869 Some(
6870 snapshot.buffer_snapshot.anchor_after(i)
6871 ..snapshot.buffer_snapshot.anchor_before(i + 1),
6872 )
6873 } else {
6874 None
6875 }
6876 })
6877 .collect::<Vec<_>>();
6878 let existing_newlines = snapshot
6879 .folds_in_range(0..snapshot.buffer_snapshot.len())
6880 .filter_map(|fold| {
6881 if fold.placeholder.type_tag == Some(type_id) {
6882 Some(fold.range.start..fold.range.end)
6883 } else {
6884 None
6885 }
6886 })
6887 .collect::<Vec<_>>();
6888
6889 (new_newlines, existing_newlines)
6890 });
6891 self.folding_newlines = cx.spawn(async move |this, cx| {
6892 let (new_newlines, existing_newlines) = task.await;
6893 if new_newlines == existing_newlines {
6894 return;
6895 }
6896 let placeholder = FoldPlaceholder {
6897 render: Arc::new(move |_, _, cx| {
6898 div()
6899 .bg(cx.theme().status().hint_background)
6900 .border_b_1()
6901 .size_full()
6902 .font(ThemeSettings::get_global(cx).buffer_font.clone())
6903 .border_color(cx.theme().status().hint)
6904 .child("\\n")
6905 .into_any()
6906 }),
6907 constrain_width: false,
6908 merge_adjacent: false,
6909 type_tag: Some(type_id),
6910 };
6911 let creases = new_newlines
6912 .into_iter()
6913 .map(|range| Crease::simple(range, placeholder.clone()))
6914 .collect();
6915 this.update(cx, |this, cx| {
6916 this.display_map.update(cx, |display_map, cx| {
6917 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
6918 display_map.fold(creases, cx);
6919 });
6920 })
6921 .ok();
6922 });
6923 }
6924
6925 fn refresh_selected_text_highlights(
6926 &mut self,
6927 on_buffer_edit: bool,
6928 window: &mut Window,
6929 cx: &mut Context<Editor>,
6930 ) {
6931 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
6932 else {
6933 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6934 self.quick_selection_highlight_task.take();
6935 self.debounced_selection_highlight_task.take();
6936 return;
6937 };
6938 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6939 if on_buffer_edit
6940 || self
6941 .quick_selection_highlight_task
6942 .as_ref()
6943 .map_or(true, |(prev_anchor_range, _)| {
6944 prev_anchor_range != &query_range
6945 })
6946 {
6947 let multi_buffer_visible_start = self
6948 .scroll_manager
6949 .anchor()
6950 .anchor
6951 .to_point(&multi_buffer_snapshot);
6952 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
6953 multi_buffer_visible_start
6954 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
6955 Bias::Left,
6956 );
6957 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
6958 self.quick_selection_highlight_task = Some((
6959 query_range.clone(),
6960 self.update_selection_occurrence_highlights(
6961 query_text.clone(),
6962 query_range.clone(),
6963 multi_buffer_visible_range,
6964 false,
6965 window,
6966 cx,
6967 ),
6968 ));
6969 }
6970 if on_buffer_edit
6971 || self
6972 .debounced_selection_highlight_task
6973 .as_ref()
6974 .map_or(true, |(prev_anchor_range, _)| {
6975 prev_anchor_range != &query_range
6976 })
6977 {
6978 let multi_buffer_start = multi_buffer_snapshot
6979 .anchor_before(0)
6980 .to_point(&multi_buffer_snapshot);
6981 let multi_buffer_end = multi_buffer_snapshot
6982 .anchor_after(multi_buffer_snapshot.len())
6983 .to_point(&multi_buffer_snapshot);
6984 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
6985 self.debounced_selection_highlight_task = Some((
6986 query_range.clone(),
6987 self.update_selection_occurrence_highlights(
6988 query_text,
6989 query_range,
6990 multi_buffer_full_range,
6991 true,
6992 window,
6993 cx,
6994 ),
6995 ));
6996 }
6997 }
6998
6999 pub fn refresh_edit_prediction(
7000 &mut self,
7001 debounce: bool,
7002 user_requested: bool,
7003 window: &mut Window,
7004 cx: &mut Context<Self>,
7005 ) -> Option<()> {
7006 if DisableAiSettings::get_global(cx).disable_ai {
7007 return None;
7008 }
7009
7010 let provider = self.edit_prediction_provider()?;
7011 let cursor = self.selections.newest_anchor().head();
7012 let (buffer, cursor_buffer_position) =
7013 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7014
7015 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7016 self.discard_edit_prediction(false, cx);
7017 return None;
7018 }
7019
7020 if !user_requested
7021 && (!self.should_show_edit_predictions()
7022 || !self.is_focused(window)
7023 || buffer.read(cx).is_empty())
7024 {
7025 self.discard_edit_prediction(false, cx);
7026 return None;
7027 }
7028
7029 self.update_visible_edit_prediction(window, cx);
7030 provider.refresh(
7031 self.project.clone(),
7032 buffer,
7033 cursor_buffer_position,
7034 debounce,
7035 cx,
7036 );
7037 Some(())
7038 }
7039
7040 fn show_edit_predictions_in_menu(&self) -> bool {
7041 match self.edit_prediction_settings {
7042 EditPredictionSettings::Disabled => false,
7043 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7044 }
7045 }
7046
7047 pub fn edit_predictions_enabled(&self) -> bool {
7048 match self.edit_prediction_settings {
7049 EditPredictionSettings::Disabled => false,
7050 EditPredictionSettings::Enabled { .. } => true,
7051 }
7052 }
7053
7054 fn edit_prediction_requires_modifier(&self) -> bool {
7055 match self.edit_prediction_settings {
7056 EditPredictionSettings::Disabled => false,
7057 EditPredictionSettings::Enabled {
7058 preview_requires_modifier,
7059 ..
7060 } => preview_requires_modifier,
7061 }
7062 }
7063
7064 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7065 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7066 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7067 self.discard_edit_prediction(false, cx);
7068 } else {
7069 let selection = self.selections.newest_anchor();
7070 let cursor = selection.head();
7071
7072 if let Some((buffer, cursor_buffer_position)) =
7073 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7074 {
7075 self.edit_prediction_settings =
7076 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7077 }
7078 }
7079 }
7080
7081 fn edit_prediction_settings_at_position(
7082 &self,
7083 buffer: &Entity<Buffer>,
7084 buffer_position: language::Anchor,
7085 cx: &App,
7086 ) -> EditPredictionSettings {
7087 if !self.mode.is_full()
7088 || !self.show_edit_predictions_override.unwrap_or(true)
7089 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7090 {
7091 return EditPredictionSettings::Disabled;
7092 }
7093
7094 let buffer = buffer.read(cx);
7095
7096 let file = buffer.file();
7097
7098 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7099 return EditPredictionSettings::Disabled;
7100 };
7101
7102 let by_provider = matches!(
7103 self.menu_edit_predictions_policy,
7104 MenuEditPredictionsPolicy::ByProvider
7105 );
7106
7107 let show_in_menu = by_provider
7108 && self
7109 .edit_prediction_provider
7110 .as_ref()
7111 .map_or(false, |provider| {
7112 provider.provider.show_completions_in_menu()
7113 });
7114
7115 let preview_requires_modifier =
7116 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7117
7118 EditPredictionSettings::Enabled {
7119 show_in_menu,
7120 preview_requires_modifier,
7121 }
7122 }
7123
7124 fn should_show_edit_predictions(&self) -> bool {
7125 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7126 }
7127
7128 pub fn edit_prediction_preview_is_active(&self) -> bool {
7129 matches!(
7130 self.edit_prediction_preview,
7131 EditPredictionPreview::Active { .. }
7132 )
7133 }
7134
7135 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7136 let cursor = self.selections.newest_anchor().head();
7137 if let Some((buffer, cursor_position)) =
7138 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7139 {
7140 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7141 } else {
7142 false
7143 }
7144 }
7145
7146 pub fn supports_minimap(&self, cx: &App) -> bool {
7147 !self.minimap_visibility.disabled() && self.is_singleton(cx)
7148 }
7149
7150 fn edit_predictions_enabled_in_buffer(
7151 &self,
7152 buffer: &Entity<Buffer>,
7153 buffer_position: language::Anchor,
7154 cx: &App,
7155 ) -> bool {
7156 maybe!({
7157 if self.read_only(cx) {
7158 return Some(false);
7159 }
7160 let provider = self.edit_prediction_provider()?;
7161 if !provider.is_enabled(&buffer, buffer_position, cx) {
7162 return Some(false);
7163 }
7164 let buffer = buffer.read(cx);
7165 let Some(file) = buffer.file() else {
7166 return Some(true);
7167 };
7168 let settings = all_language_settings(Some(file), cx);
7169 Some(settings.edit_predictions_enabled_for_file(file, cx))
7170 })
7171 .unwrap_or(false)
7172 }
7173
7174 fn cycle_edit_prediction(
7175 &mut self,
7176 direction: Direction,
7177 window: &mut Window,
7178 cx: &mut Context<Self>,
7179 ) -> Option<()> {
7180 let provider = self.edit_prediction_provider()?;
7181 let cursor = self.selections.newest_anchor().head();
7182 let (buffer, cursor_buffer_position) =
7183 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7184 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7185 return None;
7186 }
7187
7188 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7189 self.update_visible_edit_prediction(window, cx);
7190
7191 Some(())
7192 }
7193
7194 pub fn show_edit_prediction(
7195 &mut self,
7196 _: &ShowEditPrediction,
7197 window: &mut Window,
7198 cx: &mut Context<Self>,
7199 ) {
7200 if !self.has_active_edit_prediction() {
7201 self.refresh_edit_prediction(false, true, window, cx);
7202 return;
7203 }
7204
7205 self.update_visible_edit_prediction(window, cx);
7206 }
7207
7208 pub fn display_cursor_names(
7209 &mut self,
7210 _: &DisplayCursorNames,
7211 window: &mut Window,
7212 cx: &mut Context<Self>,
7213 ) {
7214 self.show_cursor_names(window, cx);
7215 }
7216
7217 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7218 self.show_cursor_names = true;
7219 cx.notify();
7220 cx.spawn_in(window, async move |this, cx| {
7221 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7222 this.update(cx, |this, cx| {
7223 this.show_cursor_names = false;
7224 cx.notify()
7225 })
7226 .ok()
7227 })
7228 .detach();
7229 }
7230
7231 pub fn next_edit_prediction(
7232 &mut self,
7233 _: &NextEditPrediction,
7234 window: &mut Window,
7235 cx: &mut Context<Self>,
7236 ) {
7237 if self.has_active_edit_prediction() {
7238 self.cycle_edit_prediction(Direction::Next, window, cx);
7239 } else {
7240 let is_copilot_disabled = self
7241 .refresh_edit_prediction(false, true, window, cx)
7242 .is_none();
7243 if is_copilot_disabled {
7244 cx.propagate();
7245 }
7246 }
7247 }
7248
7249 pub fn previous_edit_prediction(
7250 &mut self,
7251 _: &PreviousEditPrediction,
7252 window: &mut Window,
7253 cx: &mut Context<Self>,
7254 ) {
7255 if self.has_active_edit_prediction() {
7256 self.cycle_edit_prediction(Direction::Prev, window, cx);
7257 } else {
7258 let is_copilot_disabled = self
7259 .refresh_edit_prediction(false, true, window, cx)
7260 .is_none();
7261 if is_copilot_disabled {
7262 cx.propagate();
7263 }
7264 }
7265 }
7266
7267 pub fn accept_edit_prediction(
7268 &mut self,
7269 _: &AcceptEditPrediction,
7270 window: &mut Window,
7271 cx: &mut Context<Self>,
7272 ) {
7273 if self.show_edit_predictions_in_menu() {
7274 self.hide_context_menu(window, cx);
7275 }
7276
7277 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7278 return;
7279 };
7280
7281 self.report_edit_prediction_event(active_edit_prediction.completion_id.clone(), true, cx);
7282
7283 match &active_edit_prediction.completion {
7284 EditPrediction::Move { target, .. } => {
7285 let target = *target;
7286
7287 if let Some(position_map) = &self.last_position_map {
7288 if position_map
7289 .visible_row_range
7290 .contains(&target.to_display_point(&position_map.snapshot).row())
7291 || !self.edit_prediction_requires_modifier()
7292 {
7293 self.unfold_ranges(&[target..target], true, false, cx);
7294 // Note that this is also done in vim's handler of the Tab action.
7295 self.change_selections(
7296 SelectionEffects::scroll(Autoscroll::newest()),
7297 window,
7298 cx,
7299 |selections| {
7300 selections.select_anchor_ranges([target..target]);
7301 },
7302 );
7303 self.clear_row_highlights::<EditPredictionPreview>();
7304
7305 self.edit_prediction_preview
7306 .set_previous_scroll_position(None);
7307 } else {
7308 self.edit_prediction_preview
7309 .set_previous_scroll_position(Some(
7310 position_map.snapshot.scroll_anchor,
7311 ));
7312
7313 self.highlight_rows::<EditPredictionPreview>(
7314 target..target,
7315 cx.theme().colors().editor_highlighted_line_background,
7316 RowHighlightOptions {
7317 autoscroll: true,
7318 ..Default::default()
7319 },
7320 cx,
7321 );
7322 self.request_autoscroll(Autoscroll::fit(), cx);
7323 }
7324 }
7325 }
7326 EditPrediction::Edit { edits, .. } => {
7327 if let Some(provider) = self.edit_prediction_provider() {
7328 provider.accept(cx);
7329 }
7330
7331 // Store the transaction ID and selections before applying the edit
7332 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7333
7334 let snapshot = self.buffer.read(cx).snapshot(cx);
7335 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7336
7337 self.buffer.update(cx, |buffer, cx| {
7338 buffer.edit(edits.iter().cloned(), None, cx)
7339 });
7340
7341 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7342 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7343 });
7344
7345 let selections = self.selections.disjoint_anchors();
7346 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7347 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7348 if has_new_transaction {
7349 self.selection_history
7350 .insert_transaction(transaction_id_now, selections);
7351 }
7352 }
7353
7354 self.update_visible_edit_prediction(window, cx);
7355 if self.active_edit_prediction.is_none() {
7356 self.refresh_edit_prediction(true, true, window, cx);
7357 }
7358
7359 cx.notify();
7360 }
7361 }
7362
7363 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7364 }
7365
7366 pub fn accept_partial_edit_prediction(
7367 &mut self,
7368 _: &AcceptPartialEditPrediction,
7369 window: &mut Window,
7370 cx: &mut Context<Self>,
7371 ) {
7372 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7373 return;
7374 };
7375 if self.selections.count() != 1 {
7376 return;
7377 }
7378
7379 self.report_edit_prediction_event(active_edit_prediction.completion_id.clone(), true, cx);
7380
7381 match &active_edit_prediction.completion {
7382 EditPrediction::Move { target, .. } => {
7383 let target = *target;
7384 self.change_selections(
7385 SelectionEffects::scroll(Autoscroll::newest()),
7386 window,
7387 cx,
7388 |selections| {
7389 selections.select_anchor_ranges([target..target]);
7390 },
7391 );
7392 }
7393 EditPrediction::Edit { edits, .. } => {
7394 // Find an insertion that starts at the cursor position.
7395 let snapshot = self.buffer.read(cx).snapshot(cx);
7396 let cursor_offset = self.selections.newest::<usize>(cx).head();
7397 let insertion = edits.iter().find_map(|(range, text)| {
7398 let range = range.to_offset(&snapshot);
7399 if range.is_empty() && range.start == cursor_offset {
7400 Some(text)
7401 } else {
7402 None
7403 }
7404 });
7405
7406 if let Some(text) = insertion {
7407 let mut partial_completion = text
7408 .chars()
7409 .by_ref()
7410 .take_while(|c| c.is_alphabetic())
7411 .collect::<String>();
7412 if partial_completion.is_empty() {
7413 partial_completion = text
7414 .chars()
7415 .by_ref()
7416 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7417 .collect::<String>();
7418 }
7419
7420 cx.emit(EditorEvent::InputHandled {
7421 utf16_range_to_replace: None,
7422 text: partial_completion.clone().into(),
7423 });
7424
7425 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7426
7427 self.refresh_edit_prediction(true, true, window, cx);
7428 cx.notify();
7429 } else {
7430 self.accept_edit_prediction(&Default::default(), window, cx);
7431 }
7432 }
7433 }
7434 }
7435
7436 fn discard_edit_prediction(
7437 &mut self,
7438 should_report_edit_prediction_event: bool,
7439 cx: &mut Context<Self>,
7440 ) -> bool {
7441 if should_report_edit_prediction_event {
7442 let completion_id = self
7443 .active_edit_prediction
7444 .as_ref()
7445 .and_then(|active_completion| active_completion.completion_id.clone());
7446
7447 self.report_edit_prediction_event(completion_id, false, cx);
7448 }
7449
7450 if let Some(provider) = self.edit_prediction_provider() {
7451 provider.discard(cx);
7452 }
7453
7454 self.take_active_edit_prediction(cx)
7455 }
7456
7457 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7458 let Some(provider) = self.edit_prediction_provider() else {
7459 return;
7460 };
7461
7462 let Some((_, buffer, _)) = self
7463 .buffer
7464 .read(cx)
7465 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7466 else {
7467 return;
7468 };
7469
7470 let extension = buffer
7471 .read(cx)
7472 .file()
7473 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
7474
7475 let event_type = match accepted {
7476 true => "Edit Prediction Accepted",
7477 false => "Edit Prediction Discarded",
7478 };
7479 telemetry::event!(
7480 event_type,
7481 provider = provider.name(),
7482 prediction_id = id,
7483 suggestion_accepted = accepted,
7484 file_extension = extension,
7485 );
7486 }
7487
7488 pub fn has_active_edit_prediction(&self) -> bool {
7489 self.active_edit_prediction.is_some()
7490 }
7491
7492 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7493 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7494 return false;
7495 };
7496
7497 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7498 self.clear_highlights::<EditPredictionHighlight>(cx);
7499 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7500 true
7501 }
7502
7503 /// Returns true when we're displaying the edit prediction popover below the cursor
7504 /// like we are not previewing and the LSP autocomplete menu is visible
7505 /// or we are in `when_holding_modifier` mode.
7506 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7507 if self.edit_prediction_preview_is_active()
7508 || !self.show_edit_predictions_in_menu()
7509 || !self.edit_predictions_enabled()
7510 {
7511 return false;
7512 }
7513
7514 if self.has_visible_completions_menu() {
7515 return true;
7516 }
7517
7518 has_completion && self.edit_prediction_requires_modifier()
7519 }
7520
7521 fn handle_modifiers_changed(
7522 &mut self,
7523 modifiers: Modifiers,
7524 position_map: &PositionMap,
7525 window: &mut Window,
7526 cx: &mut Context<Self>,
7527 ) {
7528 if self.show_edit_predictions_in_menu() {
7529 self.update_edit_prediction_preview(&modifiers, window, cx);
7530 }
7531
7532 self.update_selection_mode(&modifiers, position_map, window, cx);
7533
7534 let mouse_position = window.mouse_position();
7535 if !position_map.text_hitbox.is_hovered(window) {
7536 return;
7537 }
7538
7539 self.update_hovered_link(
7540 position_map.point_for_position(mouse_position),
7541 &position_map.snapshot,
7542 modifiers,
7543 window,
7544 cx,
7545 )
7546 }
7547
7548 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7549 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7550 if invert {
7551 match multi_cursor_setting {
7552 MultiCursorModifier::Alt => modifiers.alt,
7553 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7554 }
7555 } else {
7556 match multi_cursor_setting {
7557 MultiCursorModifier::Alt => modifiers.secondary(),
7558 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7559 }
7560 }
7561 }
7562
7563 fn columnar_selection_mode(
7564 modifiers: &Modifiers,
7565 cx: &mut Context<Self>,
7566 ) -> Option<ColumnarMode> {
7567 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7568 if Self::multi_cursor_modifier(false, modifiers, cx) {
7569 Some(ColumnarMode::FromMouse)
7570 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7571 Some(ColumnarMode::FromSelection)
7572 } else {
7573 None
7574 }
7575 } else {
7576 None
7577 }
7578 }
7579
7580 fn update_selection_mode(
7581 &mut self,
7582 modifiers: &Modifiers,
7583 position_map: &PositionMap,
7584 window: &mut Window,
7585 cx: &mut Context<Self>,
7586 ) {
7587 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7588 return;
7589 };
7590 if self.selections.pending.is_none() {
7591 return;
7592 }
7593
7594 let mouse_position = window.mouse_position();
7595 let point_for_position = position_map.point_for_position(mouse_position);
7596 let position = point_for_position.previous_valid;
7597
7598 self.select(
7599 SelectPhase::BeginColumnar {
7600 position,
7601 reset: false,
7602 mode,
7603 goal_column: point_for_position.exact_unclipped.column(),
7604 },
7605 window,
7606 cx,
7607 );
7608 }
7609
7610 fn update_edit_prediction_preview(
7611 &mut self,
7612 modifiers: &Modifiers,
7613 window: &mut Window,
7614 cx: &mut Context<Self>,
7615 ) {
7616 let mut modifiers_held = false;
7617 if let Some(accept_keystroke) = self
7618 .accept_edit_prediction_keybind(false, window, cx)
7619 .keystroke()
7620 {
7621 modifiers_held = modifiers_held
7622 || (&accept_keystroke.modifiers == modifiers
7623 && accept_keystroke.modifiers.modified());
7624 };
7625 if let Some(accept_partial_keystroke) = self
7626 .accept_edit_prediction_keybind(true, window, cx)
7627 .keystroke()
7628 {
7629 modifiers_held = modifiers_held
7630 || (&accept_partial_keystroke.modifiers == modifiers
7631 && accept_partial_keystroke.modifiers.modified());
7632 }
7633
7634 if modifiers_held {
7635 if matches!(
7636 self.edit_prediction_preview,
7637 EditPredictionPreview::Inactive { .. }
7638 ) {
7639 self.edit_prediction_preview = EditPredictionPreview::Active {
7640 previous_scroll_position: None,
7641 since: Instant::now(),
7642 };
7643
7644 self.update_visible_edit_prediction(window, cx);
7645 cx.notify();
7646 }
7647 } else if let EditPredictionPreview::Active {
7648 previous_scroll_position,
7649 since,
7650 } = self.edit_prediction_preview
7651 {
7652 if let (Some(previous_scroll_position), Some(position_map)) =
7653 (previous_scroll_position, self.last_position_map.as_ref())
7654 {
7655 self.set_scroll_position(
7656 previous_scroll_position
7657 .scroll_position(&position_map.snapshot.display_snapshot),
7658 window,
7659 cx,
7660 );
7661 }
7662
7663 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7664 released_too_fast: since.elapsed() < Duration::from_millis(200),
7665 };
7666 self.clear_row_highlights::<EditPredictionPreview>();
7667 self.update_visible_edit_prediction(window, cx);
7668 cx.notify();
7669 }
7670 }
7671
7672 fn update_visible_edit_prediction(
7673 &mut self,
7674 _window: &mut Window,
7675 cx: &mut Context<Self>,
7676 ) -> Option<()> {
7677 if DisableAiSettings::get_global(cx).disable_ai {
7678 return None;
7679 }
7680
7681 let selection = self.selections.newest_anchor();
7682 let cursor = selection.head();
7683 let multibuffer = self.buffer.read(cx).snapshot(cx);
7684 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7685 let excerpt_id = cursor.excerpt_id;
7686
7687 let show_in_menu = self.show_edit_predictions_in_menu();
7688 let completions_menu_has_precedence = !show_in_menu
7689 && (self.context_menu.borrow().is_some()
7690 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
7691
7692 if completions_menu_has_precedence
7693 || !offset_selection.is_empty()
7694 || self
7695 .active_edit_prediction
7696 .as_ref()
7697 .map_or(false, |completion| {
7698 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
7699 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7700 !invalidation_range.contains(&offset_selection.head())
7701 })
7702 {
7703 self.discard_edit_prediction(false, cx);
7704 return None;
7705 }
7706
7707 self.take_active_edit_prediction(cx);
7708 let Some(provider) = self.edit_prediction_provider() else {
7709 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7710 return None;
7711 };
7712
7713 let (buffer, cursor_buffer_position) =
7714 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7715
7716 self.edit_prediction_settings =
7717 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7718
7719 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7720
7721 if self.edit_prediction_indent_conflict {
7722 let cursor_point = cursor.to_point(&multibuffer);
7723
7724 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7725
7726 if let Some((_, indent)) = indents.iter().next() {
7727 if indent.len == cursor_point.column {
7728 self.edit_prediction_indent_conflict = false;
7729 }
7730 }
7731 }
7732
7733 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7734 let edits = edit_prediction
7735 .edits
7736 .into_iter()
7737 .flat_map(|(range, new_text)| {
7738 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7739 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7740 Some((start..end, new_text))
7741 })
7742 .collect::<Vec<_>>();
7743 if edits.is_empty() {
7744 return None;
7745 }
7746
7747 let first_edit_start = edits.first().unwrap().0.start;
7748 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7749 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7750
7751 let last_edit_end = edits.last().unwrap().0.end;
7752 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7753 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7754
7755 let cursor_row = cursor.to_point(&multibuffer).row;
7756
7757 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7758
7759 let mut inlay_ids = Vec::new();
7760 let invalidation_row_range;
7761 let move_invalidation_row_range = if cursor_row < edit_start_row {
7762 Some(cursor_row..edit_end_row)
7763 } else if cursor_row > edit_end_row {
7764 Some(edit_start_row..cursor_row)
7765 } else {
7766 None
7767 };
7768 let supports_jump = self
7769 .edit_prediction_provider
7770 .as_ref()
7771 .map(|provider| provider.provider.supports_jump_to_edit())
7772 .unwrap_or(true);
7773
7774 let is_move = supports_jump
7775 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
7776 let completion = if is_move {
7777 invalidation_row_range =
7778 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7779 let target = first_edit_start;
7780 EditPrediction::Move { target, snapshot }
7781 } else {
7782 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7783 && !self.edit_predictions_hidden_for_vim_mode;
7784
7785 if show_completions_in_buffer {
7786 if edits
7787 .iter()
7788 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7789 {
7790 let mut inlays = Vec::new();
7791 for (range, new_text) in &edits {
7792 let inlay = Inlay::edit_prediction(
7793 post_inc(&mut self.next_inlay_id),
7794 range.start,
7795 new_text.as_str(),
7796 );
7797 inlay_ids.push(inlay.id);
7798 inlays.push(inlay);
7799 }
7800
7801 self.splice_inlays(&[], inlays, cx);
7802 } else {
7803 let background_color = cx.theme().status().deleted_background;
7804 self.highlight_text::<EditPredictionHighlight>(
7805 edits.iter().map(|(range, _)| range.clone()).collect(),
7806 HighlightStyle {
7807 background_color: Some(background_color),
7808 ..Default::default()
7809 },
7810 cx,
7811 );
7812 }
7813 }
7814
7815 invalidation_row_range = edit_start_row..edit_end_row;
7816
7817 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7818 if provider.show_tab_accept_marker() {
7819 EditDisplayMode::TabAccept
7820 } else {
7821 EditDisplayMode::Inline
7822 }
7823 } else {
7824 EditDisplayMode::DiffPopover
7825 };
7826
7827 EditPrediction::Edit {
7828 edits,
7829 edit_preview: edit_prediction.edit_preview,
7830 display_mode,
7831 snapshot,
7832 }
7833 };
7834
7835 let invalidation_range = multibuffer
7836 .anchor_before(Point::new(invalidation_row_range.start, 0))
7837 ..multibuffer.anchor_after(Point::new(
7838 invalidation_row_range.end,
7839 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7840 ));
7841
7842 self.stale_edit_prediction_in_menu = None;
7843 self.active_edit_prediction = Some(EditPredictionState {
7844 inlay_ids,
7845 completion,
7846 completion_id: edit_prediction.id,
7847 invalidation_range,
7848 });
7849
7850 cx.notify();
7851
7852 Some(())
7853 }
7854
7855 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionProviderHandle>> {
7856 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7857 }
7858
7859 fn clear_tasks(&mut self) {
7860 self.tasks.clear()
7861 }
7862
7863 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7864 if self.tasks.insert(key, value).is_some() {
7865 // This case should hopefully be rare, but just in case...
7866 log::error!(
7867 "multiple different run targets found on a single line, only the last target will be rendered"
7868 )
7869 }
7870 }
7871
7872 /// Get all display points of breakpoints that will be rendered within editor
7873 ///
7874 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7875 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7876 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7877 fn active_breakpoints(
7878 &self,
7879 range: Range<DisplayRow>,
7880 window: &mut Window,
7881 cx: &mut Context<Self>,
7882 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7883 let mut breakpoint_display_points = HashMap::default();
7884
7885 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7886 return breakpoint_display_points;
7887 };
7888
7889 let snapshot = self.snapshot(window, cx);
7890
7891 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
7892 let Some(project) = self.project.as_ref() else {
7893 return breakpoint_display_points;
7894 };
7895
7896 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7897 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7898
7899 for (buffer_snapshot, range, excerpt_id) in
7900 multi_buffer_snapshot.range_to_buffer_ranges(range)
7901 {
7902 let Some(buffer) = project
7903 .read(cx)
7904 .buffer_for_id(buffer_snapshot.remote_id(), cx)
7905 else {
7906 continue;
7907 };
7908 let breakpoints = breakpoint_store.read(cx).breakpoints(
7909 &buffer,
7910 Some(
7911 buffer_snapshot.anchor_before(range.start)
7912 ..buffer_snapshot.anchor_after(range.end),
7913 ),
7914 buffer_snapshot,
7915 cx,
7916 );
7917 for (breakpoint, state) in breakpoints {
7918 let multi_buffer_anchor =
7919 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
7920 let position = multi_buffer_anchor
7921 .to_point(&multi_buffer_snapshot)
7922 .to_display_point(&snapshot);
7923
7924 breakpoint_display_points.insert(
7925 position.row(),
7926 (multi_buffer_anchor, breakpoint.bp.clone(), state),
7927 );
7928 }
7929 }
7930
7931 breakpoint_display_points
7932 }
7933
7934 fn breakpoint_context_menu(
7935 &self,
7936 anchor: Anchor,
7937 window: &mut Window,
7938 cx: &mut Context<Self>,
7939 ) -> Entity<ui::ContextMenu> {
7940 let weak_editor = cx.weak_entity();
7941 let focus_handle = self.focus_handle(cx);
7942
7943 let row = self
7944 .buffer
7945 .read(cx)
7946 .snapshot(cx)
7947 .summary_for_anchor::<Point>(&anchor)
7948 .row;
7949
7950 let breakpoint = self
7951 .breakpoint_at_row(row, window, cx)
7952 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
7953
7954 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
7955 "Edit Log Breakpoint"
7956 } else {
7957 "Set Log Breakpoint"
7958 };
7959
7960 let condition_breakpoint_msg = if breakpoint
7961 .as_ref()
7962 .is_some_and(|bp| bp.1.condition.is_some())
7963 {
7964 "Edit Condition Breakpoint"
7965 } else {
7966 "Set Condition Breakpoint"
7967 };
7968
7969 let hit_condition_breakpoint_msg = if breakpoint
7970 .as_ref()
7971 .is_some_and(|bp| bp.1.hit_condition.is_some())
7972 {
7973 "Edit Hit Condition Breakpoint"
7974 } else {
7975 "Set Hit Condition Breakpoint"
7976 };
7977
7978 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
7979 "Unset Breakpoint"
7980 } else {
7981 "Set Breakpoint"
7982 };
7983
7984 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
7985
7986 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
7987 BreakpointState::Enabled => Some("Disable"),
7988 BreakpointState::Disabled => Some("Enable"),
7989 });
7990
7991 let (anchor, breakpoint) =
7992 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
7993
7994 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
7995 menu.on_blur_subscription(Subscription::new(|| {}))
7996 .context(focus_handle)
7997 .when(run_to_cursor, |this| {
7998 let weak_editor = weak_editor.clone();
7999 this.entry("Run to cursor", None, move |window, cx| {
8000 weak_editor
8001 .update(cx, |editor, cx| {
8002 editor.change_selections(
8003 SelectionEffects::no_scroll(),
8004 window,
8005 cx,
8006 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8007 );
8008 })
8009 .ok();
8010
8011 window.dispatch_action(Box::new(RunToCursor), cx);
8012 })
8013 .separator()
8014 })
8015 .when_some(toggle_state_msg, |this, msg| {
8016 this.entry(msg, None, {
8017 let weak_editor = weak_editor.clone();
8018 let breakpoint = breakpoint.clone();
8019 move |_window, cx| {
8020 weak_editor
8021 .update(cx, |this, cx| {
8022 this.edit_breakpoint_at_anchor(
8023 anchor,
8024 breakpoint.as_ref().clone(),
8025 BreakpointEditAction::InvertState,
8026 cx,
8027 );
8028 })
8029 .log_err();
8030 }
8031 })
8032 })
8033 .entry(set_breakpoint_msg, None, {
8034 let weak_editor = weak_editor.clone();
8035 let breakpoint = breakpoint.clone();
8036 move |_window, cx| {
8037 weak_editor
8038 .update(cx, |this, cx| {
8039 this.edit_breakpoint_at_anchor(
8040 anchor,
8041 breakpoint.as_ref().clone(),
8042 BreakpointEditAction::Toggle,
8043 cx,
8044 );
8045 })
8046 .log_err();
8047 }
8048 })
8049 .entry(log_breakpoint_msg, None, {
8050 let breakpoint = breakpoint.clone();
8051 let weak_editor = weak_editor.clone();
8052 move |window, cx| {
8053 weak_editor
8054 .update(cx, |this, cx| {
8055 this.add_edit_breakpoint_block(
8056 anchor,
8057 breakpoint.as_ref(),
8058 BreakpointPromptEditAction::Log,
8059 window,
8060 cx,
8061 );
8062 })
8063 .log_err();
8064 }
8065 })
8066 .entry(condition_breakpoint_msg, None, {
8067 let breakpoint = breakpoint.clone();
8068 let weak_editor = weak_editor.clone();
8069 move |window, cx| {
8070 weak_editor
8071 .update(cx, |this, cx| {
8072 this.add_edit_breakpoint_block(
8073 anchor,
8074 breakpoint.as_ref(),
8075 BreakpointPromptEditAction::Condition,
8076 window,
8077 cx,
8078 );
8079 })
8080 .log_err();
8081 }
8082 })
8083 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8084 weak_editor
8085 .update(cx, |this, cx| {
8086 this.add_edit_breakpoint_block(
8087 anchor,
8088 breakpoint.as_ref(),
8089 BreakpointPromptEditAction::HitCondition,
8090 window,
8091 cx,
8092 );
8093 })
8094 .log_err();
8095 })
8096 })
8097 }
8098
8099 fn render_breakpoint(
8100 &self,
8101 position: Anchor,
8102 row: DisplayRow,
8103 breakpoint: &Breakpoint,
8104 state: Option<BreakpointSessionState>,
8105 cx: &mut Context<Self>,
8106 ) -> IconButton {
8107 let is_rejected = state.is_some_and(|s| !s.verified);
8108 // Is it a breakpoint that shows up when hovering over gutter?
8109 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8110 (false, false),
8111 |PhantomBreakpointIndicator {
8112 is_active,
8113 display_row,
8114 collides_with_existing_breakpoint,
8115 }| {
8116 (
8117 is_active && display_row == row,
8118 collides_with_existing_breakpoint,
8119 )
8120 },
8121 );
8122
8123 let (color, icon) = {
8124 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8125 (false, false) => ui::IconName::DebugBreakpoint,
8126 (true, false) => ui::IconName::DebugLogBreakpoint,
8127 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8128 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8129 };
8130
8131 let color = if is_phantom {
8132 Color::Hint
8133 } else if is_rejected {
8134 Color::Disabled
8135 } else {
8136 Color::Debugger
8137 };
8138
8139 (color, icon)
8140 };
8141
8142 let breakpoint = Arc::from(breakpoint.clone());
8143
8144 let alt_as_text = gpui::Keystroke {
8145 modifiers: Modifiers::secondary_key(),
8146 ..Default::default()
8147 };
8148 let primary_action_text = if breakpoint.is_disabled() {
8149 "Enable breakpoint"
8150 } else if is_phantom && !collides_with_existing {
8151 "Set breakpoint"
8152 } else {
8153 "Unset breakpoint"
8154 };
8155 let focus_handle = self.focus_handle.clone();
8156
8157 let meta = if is_rejected {
8158 SharedString::from("No executable code is associated with this line.")
8159 } else if collides_with_existing && !breakpoint.is_disabled() {
8160 SharedString::from(format!(
8161 "{alt_as_text}-click to disable,\nright-click for more options."
8162 ))
8163 } else {
8164 SharedString::from("Right-click for more options.")
8165 };
8166 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8167 .icon_size(IconSize::XSmall)
8168 .size(ui::ButtonSize::None)
8169 .when(is_rejected, |this| {
8170 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8171 })
8172 .icon_color(color)
8173 .style(ButtonStyle::Transparent)
8174 .on_click(cx.listener({
8175 let breakpoint = breakpoint.clone();
8176
8177 move |editor, event: &ClickEvent, window, cx| {
8178 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8179 BreakpointEditAction::InvertState
8180 } else {
8181 BreakpointEditAction::Toggle
8182 };
8183
8184 window.focus(&editor.focus_handle(cx));
8185 editor.edit_breakpoint_at_anchor(
8186 position,
8187 breakpoint.as_ref().clone(),
8188 edit_action,
8189 cx,
8190 );
8191 }
8192 }))
8193 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8194 editor.set_breakpoint_context_menu(
8195 row,
8196 Some(position),
8197 event.position(),
8198 window,
8199 cx,
8200 );
8201 }))
8202 .tooltip(move |window, cx| {
8203 Tooltip::with_meta_in(
8204 primary_action_text,
8205 Some(&ToggleBreakpoint),
8206 meta.clone(),
8207 &focus_handle,
8208 window,
8209 cx,
8210 )
8211 })
8212 }
8213
8214 fn build_tasks_context(
8215 project: &Entity<Project>,
8216 buffer: &Entity<Buffer>,
8217 buffer_row: u32,
8218 tasks: &Arc<RunnableTasks>,
8219 cx: &mut Context<Self>,
8220 ) -> Task<Option<task::TaskContext>> {
8221 let position = Point::new(buffer_row, tasks.column);
8222 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8223 let location = Location {
8224 buffer: buffer.clone(),
8225 range: range_start..range_start,
8226 };
8227 // Fill in the environmental variables from the tree-sitter captures
8228 let mut captured_task_variables = TaskVariables::default();
8229 for (capture_name, value) in tasks.extra_variables.clone() {
8230 captured_task_variables.insert(
8231 task::VariableName::Custom(capture_name.into()),
8232 value.clone(),
8233 );
8234 }
8235 project.update(cx, |project, cx| {
8236 project.task_store().update(cx, |task_store, cx| {
8237 task_store.task_context_for_location(captured_task_variables, location, cx)
8238 })
8239 })
8240 }
8241
8242 pub fn spawn_nearest_task(
8243 &mut self,
8244 action: &SpawnNearestTask,
8245 window: &mut Window,
8246 cx: &mut Context<Self>,
8247 ) {
8248 let Some((workspace, _)) = self.workspace.clone() else {
8249 return;
8250 };
8251 let Some(project) = self.project.clone() else {
8252 return;
8253 };
8254
8255 // Try to find a closest, enclosing node using tree-sitter that has a task
8256 let Some((buffer, buffer_row, tasks)) = self
8257 .find_enclosing_node_task(cx)
8258 // Or find the task that's closest in row-distance.
8259 .or_else(|| self.find_closest_task(cx))
8260 else {
8261 return;
8262 };
8263
8264 let reveal_strategy = action.reveal;
8265 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8266 cx.spawn_in(window, async move |_, cx| {
8267 let context = task_context.await?;
8268 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8269
8270 let resolved = &mut resolved_task.resolved;
8271 resolved.reveal = reveal_strategy;
8272
8273 workspace
8274 .update_in(cx, |workspace, window, cx| {
8275 workspace.schedule_resolved_task(
8276 task_source_kind,
8277 resolved_task,
8278 false,
8279 window,
8280 cx,
8281 );
8282 })
8283 .ok()
8284 })
8285 .detach();
8286 }
8287
8288 fn find_closest_task(
8289 &mut self,
8290 cx: &mut Context<Self>,
8291 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8292 let cursor_row = self.selections.newest_adjusted(cx).head().row;
8293
8294 let ((buffer_id, row), tasks) = self
8295 .tasks
8296 .iter()
8297 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8298
8299 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8300 let tasks = Arc::new(tasks.to_owned());
8301 Some((buffer, *row, tasks))
8302 }
8303
8304 fn find_enclosing_node_task(
8305 &mut self,
8306 cx: &mut Context<Self>,
8307 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8308 let snapshot = self.buffer.read(cx).snapshot(cx);
8309 let offset = self.selections.newest::<usize>(cx).head();
8310 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8311 let buffer_id = excerpt.buffer().remote_id();
8312
8313 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8314 let mut cursor = layer.node().walk();
8315
8316 while cursor.goto_first_child_for_byte(offset).is_some() {
8317 if cursor.node().end_byte() == offset {
8318 cursor.goto_next_sibling();
8319 }
8320 }
8321
8322 // Ascend to the smallest ancestor that contains the range and has a task.
8323 loop {
8324 let node = cursor.node();
8325 let node_range = node.byte_range();
8326 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8327
8328 // Check if this node contains our offset
8329 if node_range.start <= offset && node_range.end >= offset {
8330 // If it contains offset, check for task
8331 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8332 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8333 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8334 }
8335 }
8336
8337 if !cursor.goto_parent() {
8338 break;
8339 }
8340 }
8341 None
8342 }
8343
8344 fn render_run_indicator(
8345 &self,
8346 _style: &EditorStyle,
8347 is_active: bool,
8348 row: DisplayRow,
8349 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8350 cx: &mut Context<Self>,
8351 ) -> IconButton {
8352 let color = Color::Muted;
8353 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8354
8355 IconButton::new(
8356 ("run_indicator", row.0 as usize),
8357 ui::IconName::PlayOutlined,
8358 )
8359 .shape(ui::IconButtonShape::Square)
8360 .icon_size(IconSize::XSmall)
8361 .icon_color(color)
8362 .toggle_state(is_active)
8363 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8364 let quick_launch = match e {
8365 ClickEvent::Keyboard(_) => true,
8366 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8367 };
8368
8369 window.focus(&editor.focus_handle(cx));
8370 editor.toggle_code_actions(
8371 &ToggleCodeActions {
8372 deployed_from: Some(CodeActionSource::RunMenu(row)),
8373 quick_launch,
8374 },
8375 window,
8376 cx,
8377 );
8378 }))
8379 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8380 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8381 }))
8382 }
8383
8384 pub fn context_menu_visible(&self) -> bool {
8385 !self.edit_prediction_preview_is_active()
8386 && self
8387 .context_menu
8388 .borrow()
8389 .as_ref()
8390 .map_or(false, |menu| menu.visible())
8391 }
8392
8393 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8394 self.context_menu
8395 .borrow()
8396 .as_ref()
8397 .map(|menu| menu.origin())
8398 }
8399
8400 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8401 self.context_menu_options = Some(options);
8402 }
8403
8404 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
8405 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
8406
8407 fn render_edit_prediction_popover(
8408 &mut self,
8409 text_bounds: &Bounds<Pixels>,
8410 content_origin: gpui::Point<Pixels>,
8411 right_margin: Pixels,
8412 editor_snapshot: &EditorSnapshot,
8413 visible_row_range: Range<DisplayRow>,
8414 scroll_top: f32,
8415 scroll_bottom: f32,
8416 line_layouts: &[LineWithInvisibles],
8417 line_height: Pixels,
8418 scroll_pixel_position: gpui::Point<Pixels>,
8419 newest_selection_head: Option<DisplayPoint>,
8420 editor_width: Pixels,
8421 style: &EditorStyle,
8422 window: &mut Window,
8423 cx: &mut App,
8424 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8425 if self.mode().is_minimap() {
8426 return None;
8427 }
8428 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8429
8430 if self.edit_prediction_visible_in_cursor_popover(true) {
8431 return None;
8432 }
8433
8434 match &active_edit_prediction.completion {
8435 EditPrediction::Move { target, .. } => {
8436 let target_display_point = target.to_display_point(editor_snapshot);
8437
8438 if self.edit_prediction_requires_modifier() {
8439 if !self.edit_prediction_preview_is_active() {
8440 return None;
8441 }
8442
8443 self.render_edit_prediction_modifier_jump_popover(
8444 text_bounds,
8445 content_origin,
8446 visible_row_range,
8447 line_layouts,
8448 line_height,
8449 scroll_pixel_position,
8450 newest_selection_head,
8451 target_display_point,
8452 window,
8453 cx,
8454 )
8455 } else {
8456 self.render_edit_prediction_eager_jump_popover(
8457 text_bounds,
8458 content_origin,
8459 editor_snapshot,
8460 visible_row_range,
8461 scroll_top,
8462 scroll_bottom,
8463 line_height,
8464 scroll_pixel_position,
8465 target_display_point,
8466 editor_width,
8467 window,
8468 cx,
8469 )
8470 }
8471 }
8472 EditPrediction::Edit {
8473 display_mode: EditDisplayMode::Inline,
8474 ..
8475 } => None,
8476 EditPrediction::Edit {
8477 display_mode: EditDisplayMode::TabAccept,
8478 edits,
8479 ..
8480 } => {
8481 let range = &edits.first()?.0;
8482 let target_display_point = range.end.to_display_point(editor_snapshot);
8483
8484 self.render_edit_prediction_end_of_line_popover(
8485 "Accept",
8486 editor_snapshot,
8487 visible_row_range,
8488 target_display_point,
8489 line_height,
8490 scroll_pixel_position,
8491 content_origin,
8492 editor_width,
8493 window,
8494 cx,
8495 )
8496 }
8497 EditPrediction::Edit {
8498 edits,
8499 edit_preview,
8500 display_mode: EditDisplayMode::DiffPopover,
8501 snapshot,
8502 } => self.render_edit_prediction_diff_popover(
8503 text_bounds,
8504 content_origin,
8505 right_margin,
8506 editor_snapshot,
8507 visible_row_range,
8508 line_layouts,
8509 line_height,
8510 scroll_pixel_position,
8511 newest_selection_head,
8512 editor_width,
8513 style,
8514 edits,
8515 edit_preview,
8516 snapshot,
8517 window,
8518 cx,
8519 ),
8520 }
8521 }
8522
8523 fn render_edit_prediction_modifier_jump_popover(
8524 &mut self,
8525 text_bounds: &Bounds<Pixels>,
8526 content_origin: gpui::Point<Pixels>,
8527 visible_row_range: Range<DisplayRow>,
8528 line_layouts: &[LineWithInvisibles],
8529 line_height: Pixels,
8530 scroll_pixel_position: gpui::Point<Pixels>,
8531 newest_selection_head: Option<DisplayPoint>,
8532 target_display_point: DisplayPoint,
8533 window: &mut Window,
8534 cx: &mut App,
8535 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8536 let scrolled_content_origin =
8537 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
8538
8539 const SCROLL_PADDING_Y: Pixels = px(12.);
8540
8541 if target_display_point.row() < visible_row_range.start {
8542 return self.render_edit_prediction_scroll_popover(
8543 |_| SCROLL_PADDING_Y,
8544 IconName::ArrowUp,
8545 visible_row_range,
8546 line_layouts,
8547 newest_selection_head,
8548 scrolled_content_origin,
8549 window,
8550 cx,
8551 );
8552 } else if target_display_point.row() >= visible_row_range.end {
8553 return self.render_edit_prediction_scroll_popover(
8554 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8555 IconName::ArrowDown,
8556 visible_row_range,
8557 line_layouts,
8558 newest_selection_head,
8559 scrolled_content_origin,
8560 window,
8561 cx,
8562 );
8563 }
8564
8565 const POLE_WIDTH: Pixels = px(2.);
8566
8567 let line_layout =
8568 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8569 let target_column = target_display_point.column() as usize;
8570
8571 let target_x = line_layout.x_for_index(target_column);
8572 let target_y =
8573 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
8574
8575 let flag_on_right = target_x < text_bounds.size.width / 2.;
8576
8577 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8578 border_color.l += 0.001;
8579
8580 let mut element = v_flex()
8581 .items_end()
8582 .when(flag_on_right, |el| el.items_start())
8583 .child(if flag_on_right {
8584 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8585 .rounded_bl(px(0.))
8586 .rounded_tl(px(0.))
8587 .border_l_2()
8588 .border_color(border_color)
8589 } else {
8590 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8591 .rounded_br(px(0.))
8592 .rounded_tr(px(0.))
8593 .border_r_2()
8594 .border_color(border_color)
8595 })
8596 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8597 .into_any();
8598
8599 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8600
8601 let mut origin = scrolled_content_origin + point(target_x, target_y)
8602 - point(
8603 if flag_on_right {
8604 POLE_WIDTH
8605 } else {
8606 size.width - POLE_WIDTH
8607 },
8608 size.height - line_height,
8609 );
8610
8611 origin.x = origin.x.max(content_origin.x);
8612
8613 element.prepaint_at(origin, window, cx);
8614
8615 Some((element, origin))
8616 }
8617
8618 fn render_edit_prediction_scroll_popover(
8619 &mut self,
8620 to_y: impl Fn(Size<Pixels>) -> Pixels,
8621 scroll_icon: IconName,
8622 visible_row_range: Range<DisplayRow>,
8623 line_layouts: &[LineWithInvisibles],
8624 newest_selection_head: Option<DisplayPoint>,
8625 scrolled_content_origin: gpui::Point<Pixels>,
8626 window: &mut Window,
8627 cx: &mut App,
8628 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8629 let mut element = self
8630 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
8631 .into_any();
8632
8633 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8634
8635 let cursor = newest_selection_head?;
8636 let cursor_row_layout =
8637 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8638 let cursor_column = cursor.column() as usize;
8639
8640 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8641
8642 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8643
8644 element.prepaint_at(origin, window, cx);
8645 Some((element, origin))
8646 }
8647
8648 fn render_edit_prediction_eager_jump_popover(
8649 &mut self,
8650 text_bounds: &Bounds<Pixels>,
8651 content_origin: gpui::Point<Pixels>,
8652 editor_snapshot: &EditorSnapshot,
8653 visible_row_range: Range<DisplayRow>,
8654 scroll_top: f32,
8655 scroll_bottom: f32,
8656 line_height: Pixels,
8657 scroll_pixel_position: gpui::Point<Pixels>,
8658 target_display_point: DisplayPoint,
8659 editor_width: Pixels,
8660 window: &mut Window,
8661 cx: &mut App,
8662 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8663 if target_display_point.row().as_f32() < scroll_top {
8664 let mut element = self
8665 .render_edit_prediction_line_popover(
8666 "Jump to Edit",
8667 Some(IconName::ArrowUp),
8668 window,
8669 cx,
8670 )?
8671 .into_any();
8672
8673 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8674 let offset = point(
8675 (text_bounds.size.width - size.width) / 2.,
8676 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8677 );
8678
8679 let origin = text_bounds.origin + offset;
8680 element.prepaint_at(origin, window, cx);
8681 Some((element, origin))
8682 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
8683 let mut element = self
8684 .render_edit_prediction_line_popover(
8685 "Jump to Edit",
8686 Some(IconName::ArrowDown),
8687 window,
8688 cx,
8689 )?
8690 .into_any();
8691
8692 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8693 let offset = point(
8694 (text_bounds.size.width - size.width) / 2.,
8695 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8696 );
8697
8698 let origin = text_bounds.origin + offset;
8699 element.prepaint_at(origin, window, cx);
8700 Some((element, origin))
8701 } else {
8702 self.render_edit_prediction_end_of_line_popover(
8703 "Jump to Edit",
8704 editor_snapshot,
8705 visible_row_range,
8706 target_display_point,
8707 line_height,
8708 scroll_pixel_position,
8709 content_origin,
8710 editor_width,
8711 window,
8712 cx,
8713 )
8714 }
8715 }
8716
8717 fn render_edit_prediction_end_of_line_popover(
8718 self: &mut Editor,
8719 label: &'static str,
8720 editor_snapshot: &EditorSnapshot,
8721 visible_row_range: Range<DisplayRow>,
8722 target_display_point: DisplayPoint,
8723 line_height: Pixels,
8724 scroll_pixel_position: gpui::Point<Pixels>,
8725 content_origin: gpui::Point<Pixels>,
8726 editor_width: Pixels,
8727 window: &mut Window,
8728 cx: &mut App,
8729 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8730 let target_line_end = DisplayPoint::new(
8731 target_display_point.row(),
8732 editor_snapshot.line_len(target_display_point.row()),
8733 );
8734
8735 let mut element = self
8736 .render_edit_prediction_line_popover(label, None, window, cx)?
8737 .into_any();
8738
8739 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8740
8741 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8742
8743 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
8744 let mut origin = start_point
8745 + line_origin
8746 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8747 origin.x = origin.x.max(content_origin.x);
8748
8749 let max_x = content_origin.x + editor_width - size.width;
8750
8751 if origin.x > max_x {
8752 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8753
8754 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8755 origin.y += offset;
8756 IconName::ArrowUp
8757 } else {
8758 origin.y -= offset;
8759 IconName::ArrowDown
8760 };
8761
8762 element = self
8763 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
8764 .into_any();
8765
8766 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8767
8768 origin.x = content_origin.x + editor_width - size.width - px(2.);
8769 }
8770
8771 element.prepaint_at(origin, window, cx);
8772 Some((element, origin))
8773 }
8774
8775 fn render_edit_prediction_diff_popover(
8776 self: &Editor,
8777 text_bounds: &Bounds<Pixels>,
8778 content_origin: gpui::Point<Pixels>,
8779 right_margin: Pixels,
8780 editor_snapshot: &EditorSnapshot,
8781 visible_row_range: Range<DisplayRow>,
8782 line_layouts: &[LineWithInvisibles],
8783 line_height: Pixels,
8784 scroll_pixel_position: gpui::Point<Pixels>,
8785 newest_selection_head: Option<DisplayPoint>,
8786 editor_width: Pixels,
8787 style: &EditorStyle,
8788 edits: &Vec<(Range<Anchor>, String)>,
8789 edit_preview: &Option<language::EditPreview>,
8790 snapshot: &language::BufferSnapshot,
8791 window: &mut Window,
8792 cx: &mut App,
8793 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8794 let edit_start = edits
8795 .first()
8796 .unwrap()
8797 .0
8798 .start
8799 .to_display_point(editor_snapshot);
8800 let edit_end = edits
8801 .last()
8802 .unwrap()
8803 .0
8804 .end
8805 .to_display_point(editor_snapshot);
8806
8807 let is_visible = visible_row_range.contains(&edit_start.row())
8808 || visible_row_range.contains(&edit_end.row());
8809 if !is_visible {
8810 return None;
8811 }
8812
8813 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
8814 crate::edit_prediction_edit_text(&snapshot, edits, edit_preview, false, cx)
8815 } else {
8816 // Fallback for providers without edit_preview
8817 crate::edit_prediction_fallback_text(edits, cx)
8818 };
8819
8820 let styled_text = highlighted_edits.to_styled_text(&style.text);
8821 let line_count = highlighted_edits.text.lines().count();
8822
8823 const BORDER_WIDTH: Pixels = px(1.);
8824
8825 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8826 let has_keybind = keybind.is_some();
8827
8828 let mut element = h_flex()
8829 .items_start()
8830 .child(
8831 h_flex()
8832 .bg(cx.theme().colors().editor_background)
8833 .border(BORDER_WIDTH)
8834 .shadow_xs()
8835 .border_color(cx.theme().colors().border)
8836 .rounded_l_lg()
8837 .when(line_count > 1, |el| el.rounded_br_lg())
8838 .pr_1()
8839 .child(styled_text),
8840 )
8841 .child(
8842 h_flex()
8843 .h(line_height + BORDER_WIDTH * 2.)
8844 .px_1p5()
8845 .gap_1()
8846 // Workaround: For some reason, there's a gap if we don't do this
8847 .ml(-BORDER_WIDTH)
8848 .shadow(vec![gpui::BoxShadow {
8849 color: gpui::black().opacity(0.05),
8850 offset: point(px(1.), px(1.)),
8851 blur_radius: px(2.),
8852 spread_radius: px(0.),
8853 }])
8854 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8855 .border(BORDER_WIDTH)
8856 .border_color(cx.theme().colors().border)
8857 .rounded_r_lg()
8858 .id("edit_prediction_diff_popover_keybind")
8859 .when(!has_keybind, |el| {
8860 let status_colors = cx.theme().status();
8861
8862 el.bg(status_colors.error_background)
8863 .border_color(status_colors.error.opacity(0.6))
8864 .child(Icon::new(IconName::Info).color(Color::Error))
8865 .cursor_default()
8866 .hoverable_tooltip(move |_window, cx| {
8867 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8868 })
8869 })
8870 .children(keybind),
8871 )
8872 .into_any();
8873
8874 let longest_row =
8875 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8876 let longest_line_width = if visible_row_range.contains(&longest_row) {
8877 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8878 } else {
8879 layout_line(
8880 longest_row,
8881 editor_snapshot,
8882 style,
8883 editor_width,
8884 |_| false,
8885 window,
8886 cx,
8887 )
8888 .width
8889 };
8890
8891 let viewport_bounds =
8892 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8893 right: -right_margin,
8894 ..Default::default()
8895 });
8896
8897 let x_after_longest =
8898 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
8899 - scroll_pixel_position.x;
8900
8901 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8902
8903 // Fully visible if it can be displayed within the window (allow overlapping other
8904 // panes). However, this is only allowed if the popover starts within text_bounds.
8905 let can_position_to_the_right = x_after_longest < text_bounds.right()
8906 && x_after_longest + element_bounds.width < viewport_bounds.right();
8907
8908 let mut origin = if can_position_to_the_right {
8909 point(
8910 x_after_longest,
8911 text_bounds.origin.y + edit_start.row().as_f32() * line_height
8912 - scroll_pixel_position.y,
8913 )
8914 } else {
8915 let cursor_row = newest_selection_head.map(|head| head.row());
8916 let above_edit = edit_start
8917 .row()
8918 .0
8919 .checked_sub(line_count as u32)
8920 .map(DisplayRow);
8921 let below_edit = Some(edit_end.row() + 1);
8922 let above_cursor =
8923 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
8924 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
8925
8926 // Place the edit popover adjacent to the edit if there is a location
8927 // available that is onscreen and does not obscure the cursor. Otherwise,
8928 // place it adjacent to the cursor.
8929 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
8930 .into_iter()
8931 .flatten()
8932 .find(|&start_row| {
8933 let end_row = start_row + line_count as u32;
8934 visible_row_range.contains(&start_row)
8935 && visible_row_range.contains(&end_row)
8936 && cursor_row.map_or(true, |cursor_row| {
8937 !((start_row..end_row).contains(&cursor_row))
8938 })
8939 })?;
8940
8941 content_origin
8942 + point(
8943 -scroll_pixel_position.x,
8944 row_target.as_f32() * line_height - scroll_pixel_position.y,
8945 )
8946 };
8947
8948 origin.x -= BORDER_WIDTH;
8949
8950 window.defer_draw(element, origin, 1);
8951
8952 // Do not return an element, since it will already be drawn due to defer_draw.
8953 None
8954 }
8955
8956 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
8957 px(30.)
8958 }
8959
8960 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
8961 if self.read_only(cx) {
8962 cx.theme().players().read_only()
8963 } else {
8964 self.style.as_ref().unwrap().local_player
8965 }
8966 }
8967
8968 fn render_edit_prediction_accept_keybind(
8969 &self,
8970 window: &mut Window,
8971 cx: &App,
8972 ) -> Option<AnyElement> {
8973 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
8974 let accept_keystroke = accept_binding.keystroke()?;
8975
8976 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8977
8978 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
8979 Color::Accent
8980 } else {
8981 Color::Muted
8982 };
8983
8984 h_flex()
8985 .px_0p5()
8986 .when(is_platform_style_mac, |parent| parent.gap_0p5())
8987 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8988 .text_size(TextSize::XSmall.rems(cx))
8989 .child(h_flex().children(ui::render_modifiers(
8990 &accept_keystroke.modifiers,
8991 PlatformStyle::platform(),
8992 Some(modifiers_color),
8993 Some(IconSize::XSmall.rems().into()),
8994 true,
8995 )))
8996 .when(is_platform_style_mac, |parent| {
8997 parent.child(accept_keystroke.key.clone())
8998 })
8999 .when(!is_platform_style_mac, |parent| {
9000 parent.child(
9001 Key::new(
9002 util::capitalize(&accept_keystroke.key),
9003 Some(Color::Default),
9004 )
9005 .size(Some(IconSize::XSmall.rems().into())),
9006 )
9007 })
9008 .into_any()
9009 .into()
9010 }
9011
9012 fn render_edit_prediction_line_popover(
9013 &self,
9014 label: impl Into<SharedString>,
9015 icon: Option<IconName>,
9016 window: &mut Window,
9017 cx: &App,
9018 ) -> Option<Stateful<Div>> {
9019 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9020
9021 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9022 let has_keybind = keybind.is_some();
9023
9024 let result = h_flex()
9025 .id("ep-line-popover")
9026 .py_0p5()
9027 .pl_1()
9028 .pr(padding_right)
9029 .gap_1()
9030 .rounded_md()
9031 .border_1()
9032 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9033 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9034 .shadow_xs()
9035 .when(!has_keybind, |el| {
9036 let status_colors = cx.theme().status();
9037
9038 el.bg(status_colors.error_background)
9039 .border_color(status_colors.error.opacity(0.6))
9040 .pl_2()
9041 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9042 .cursor_default()
9043 .hoverable_tooltip(move |_window, cx| {
9044 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9045 })
9046 })
9047 .children(keybind)
9048 .child(
9049 Label::new(label)
9050 .size(LabelSize::Small)
9051 .when(!has_keybind, |el| {
9052 el.color(cx.theme().status().error.into()).strikethrough()
9053 }),
9054 )
9055 .when(!has_keybind, |el| {
9056 el.child(
9057 h_flex().ml_1().child(
9058 Icon::new(IconName::Info)
9059 .size(IconSize::Small)
9060 .color(cx.theme().status().error.into()),
9061 ),
9062 )
9063 })
9064 .when_some(icon, |element, icon| {
9065 element.child(
9066 div()
9067 .mt(px(1.5))
9068 .child(Icon::new(icon).size(IconSize::Small)),
9069 )
9070 });
9071
9072 Some(result)
9073 }
9074
9075 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9076 let accent_color = cx.theme().colors().text_accent;
9077 let editor_bg_color = cx.theme().colors().editor_background;
9078 editor_bg_color.blend(accent_color.opacity(0.1))
9079 }
9080
9081 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9082 let accent_color = cx.theme().colors().text_accent;
9083 let editor_bg_color = cx.theme().colors().editor_background;
9084 editor_bg_color.blend(accent_color.opacity(0.6))
9085 }
9086 fn get_prediction_provider_icon_name(
9087 provider: &Option<RegisteredEditPredictionProvider>,
9088 ) -> IconName {
9089 match provider {
9090 Some(provider) => match provider.provider.name() {
9091 "copilot" => IconName::Copilot,
9092 "supermaven" => IconName::Supermaven,
9093 _ => IconName::ZedPredict,
9094 },
9095 None => IconName::ZedPredict,
9096 }
9097 }
9098
9099 fn render_edit_prediction_cursor_popover(
9100 &self,
9101 min_width: Pixels,
9102 max_width: Pixels,
9103 cursor_point: Point,
9104 style: &EditorStyle,
9105 accept_keystroke: Option<&gpui::Keystroke>,
9106 _window: &Window,
9107 cx: &mut Context<Editor>,
9108 ) -> Option<AnyElement> {
9109 let provider = self.edit_prediction_provider.as_ref()?;
9110 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9111
9112 if provider.provider.needs_terms_acceptance(cx) {
9113 return Some(
9114 h_flex()
9115 .min_w(min_width)
9116 .flex_1()
9117 .px_2()
9118 .py_1()
9119 .gap_3()
9120 .elevation_2(cx)
9121 .hover(|style| style.bg(cx.theme().colors().element_hover))
9122 .id("accept-terms")
9123 .cursor_pointer()
9124 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
9125 .on_click(cx.listener(|this, _event, window, cx| {
9126 cx.stop_propagation();
9127 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
9128 window.dispatch_action(
9129 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
9130 cx,
9131 );
9132 }))
9133 .child(
9134 h_flex()
9135 .flex_1()
9136 .gap_2()
9137 .child(Icon::new(provider_icon))
9138 .child(Label::new("Accept Terms of Service"))
9139 .child(div().w_full())
9140 .child(
9141 Icon::new(IconName::ArrowUpRight)
9142 .color(Color::Muted)
9143 .size(IconSize::Small),
9144 )
9145 .into_any_element(),
9146 )
9147 .into_any(),
9148 );
9149 }
9150
9151 let is_refreshing = provider.provider.is_refreshing(cx);
9152
9153 fn pending_completion_container(icon: IconName) -> Div {
9154 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9155 }
9156
9157 let completion = match &self.active_edit_prediction {
9158 Some(prediction) => {
9159 if !self.has_visible_completions_menu() {
9160 const RADIUS: Pixels = px(6.);
9161 const BORDER_WIDTH: Pixels = px(1.);
9162
9163 return Some(
9164 h_flex()
9165 .elevation_2(cx)
9166 .border(BORDER_WIDTH)
9167 .border_color(cx.theme().colors().border)
9168 .when(accept_keystroke.is_none(), |el| {
9169 el.border_color(cx.theme().status().error)
9170 })
9171 .rounded(RADIUS)
9172 .rounded_tl(px(0.))
9173 .overflow_hidden()
9174 .child(div().px_1p5().child(match &prediction.completion {
9175 EditPrediction::Move { target, snapshot } => {
9176 use text::ToPoint as _;
9177 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
9178 {
9179 Icon::new(IconName::ZedPredictDown)
9180 } else {
9181 Icon::new(IconName::ZedPredictUp)
9182 }
9183 }
9184 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9185 }))
9186 .child(
9187 h_flex()
9188 .gap_1()
9189 .py_1()
9190 .px_2()
9191 .rounded_r(RADIUS - BORDER_WIDTH)
9192 .border_l_1()
9193 .border_color(cx.theme().colors().border)
9194 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9195 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9196 el.child(
9197 Label::new("Hold")
9198 .size(LabelSize::Small)
9199 .when(accept_keystroke.is_none(), |el| {
9200 el.strikethrough()
9201 })
9202 .line_height_style(LineHeightStyle::UiLabel),
9203 )
9204 })
9205 .id("edit_prediction_cursor_popover_keybind")
9206 .when(accept_keystroke.is_none(), |el| {
9207 let status_colors = cx.theme().status();
9208
9209 el.bg(status_colors.error_background)
9210 .border_color(status_colors.error.opacity(0.6))
9211 .child(Icon::new(IconName::Info).color(Color::Error))
9212 .cursor_default()
9213 .hoverable_tooltip(move |_window, cx| {
9214 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9215 .into()
9216 })
9217 })
9218 .when_some(
9219 accept_keystroke.as_ref(),
9220 |el, accept_keystroke| {
9221 el.child(h_flex().children(ui::render_modifiers(
9222 &accept_keystroke.modifiers,
9223 PlatformStyle::platform(),
9224 Some(Color::Default),
9225 Some(IconSize::XSmall.rems().into()),
9226 false,
9227 )))
9228 },
9229 ),
9230 )
9231 .into_any(),
9232 );
9233 }
9234
9235 self.render_edit_prediction_cursor_popover_preview(
9236 prediction,
9237 cursor_point,
9238 style,
9239 cx,
9240 )?
9241 }
9242
9243 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9244 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9245 stale_completion,
9246 cursor_point,
9247 style,
9248 cx,
9249 )?,
9250
9251 None => pending_completion_container(provider_icon)
9252 .child(Label::new("...").size(LabelSize::Small)),
9253 },
9254
9255 None => pending_completion_container(provider_icon)
9256 .child(Label::new("...").size(LabelSize::Small)),
9257 };
9258
9259 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9260 completion
9261 .with_animation(
9262 "loading-completion",
9263 Animation::new(Duration::from_secs(2))
9264 .repeat()
9265 .with_easing(pulsating_between(0.4, 0.8)),
9266 |label, delta| label.opacity(delta),
9267 )
9268 .into_any_element()
9269 } else {
9270 completion.into_any_element()
9271 };
9272
9273 let has_completion = self.active_edit_prediction.is_some();
9274
9275 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9276 Some(
9277 h_flex()
9278 .min_w(min_width)
9279 .max_w(max_width)
9280 .flex_1()
9281 .elevation_2(cx)
9282 .border_color(cx.theme().colors().border)
9283 .child(
9284 div()
9285 .flex_1()
9286 .py_1()
9287 .px_2()
9288 .overflow_hidden()
9289 .child(completion),
9290 )
9291 .when_some(accept_keystroke, |el, accept_keystroke| {
9292 if !accept_keystroke.modifiers.modified() {
9293 return el;
9294 }
9295
9296 el.child(
9297 h_flex()
9298 .h_full()
9299 .border_l_1()
9300 .rounded_r_lg()
9301 .border_color(cx.theme().colors().border)
9302 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9303 .gap_1()
9304 .py_1()
9305 .px_2()
9306 .child(
9307 h_flex()
9308 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9309 .when(is_platform_style_mac, |parent| parent.gap_1())
9310 .child(h_flex().children(ui::render_modifiers(
9311 &accept_keystroke.modifiers,
9312 PlatformStyle::platform(),
9313 Some(if !has_completion {
9314 Color::Muted
9315 } else {
9316 Color::Default
9317 }),
9318 None,
9319 false,
9320 ))),
9321 )
9322 .child(Label::new("Preview").into_any_element())
9323 .opacity(if has_completion { 1.0 } else { 0.4 }),
9324 )
9325 })
9326 .into_any(),
9327 )
9328 }
9329
9330 fn render_edit_prediction_cursor_popover_preview(
9331 &self,
9332 completion: &EditPredictionState,
9333 cursor_point: Point,
9334 style: &EditorStyle,
9335 cx: &mut Context<Editor>,
9336 ) -> Option<Div> {
9337 use text::ToPoint as _;
9338
9339 fn render_relative_row_jump(
9340 prefix: impl Into<String>,
9341 current_row: u32,
9342 target_row: u32,
9343 ) -> Div {
9344 let (row_diff, arrow) = if target_row < current_row {
9345 (current_row - target_row, IconName::ArrowUp)
9346 } else {
9347 (target_row - current_row, IconName::ArrowDown)
9348 };
9349
9350 h_flex()
9351 .child(
9352 Label::new(format!("{}{}", prefix.into(), row_diff))
9353 .color(Color::Muted)
9354 .size(LabelSize::Small),
9355 )
9356 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9357 }
9358
9359 let supports_jump = self
9360 .edit_prediction_provider
9361 .as_ref()
9362 .map(|provider| provider.provider.supports_jump_to_edit())
9363 .unwrap_or(true);
9364
9365 match &completion.completion {
9366 EditPrediction::Move {
9367 target, snapshot, ..
9368 } => {
9369 if !supports_jump {
9370 return None;
9371 }
9372
9373 Some(
9374 h_flex()
9375 .px_2()
9376 .gap_2()
9377 .flex_1()
9378 .child(
9379 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
9380 Icon::new(IconName::ZedPredictDown)
9381 } else {
9382 Icon::new(IconName::ZedPredictUp)
9383 },
9384 )
9385 .child(Label::new("Jump to Edit")),
9386 )
9387 }
9388
9389 EditPrediction::Edit {
9390 edits,
9391 edit_preview,
9392 snapshot,
9393 display_mode: _,
9394 } => {
9395 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
9396
9397 let (highlighted_edits, has_more_lines) =
9398 if let Some(edit_preview) = edit_preview.as_ref() {
9399 crate::edit_prediction_edit_text(&snapshot, &edits, edit_preview, true, cx)
9400 .first_line_preview()
9401 } else {
9402 crate::edit_prediction_fallback_text(&edits, cx).first_line_preview()
9403 };
9404
9405 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9406 .with_default_highlights(&style.text, highlighted_edits.highlights);
9407
9408 let preview = h_flex()
9409 .gap_1()
9410 .min_w_16()
9411 .child(styled_text)
9412 .when(has_more_lines, |parent| parent.child("…"));
9413
9414 let left = if supports_jump && first_edit_row != cursor_point.row {
9415 render_relative_row_jump("", cursor_point.row, first_edit_row)
9416 .into_any_element()
9417 } else {
9418 let icon_name =
9419 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9420 Icon::new(icon_name).into_any_element()
9421 };
9422
9423 Some(
9424 h_flex()
9425 .h_full()
9426 .flex_1()
9427 .gap_2()
9428 .pr_1()
9429 .overflow_x_hidden()
9430 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9431 .child(left)
9432 .child(preview),
9433 )
9434 }
9435 }
9436 }
9437
9438 pub fn render_context_menu(
9439 &self,
9440 style: &EditorStyle,
9441 max_height_in_lines: u32,
9442 window: &mut Window,
9443 cx: &mut Context<Editor>,
9444 ) -> Option<AnyElement> {
9445 let menu = self.context_menu.borrow();
9446 let menu = menu.as_ref()?;
9447 if !menu.visible() {
9448 return None;
9449 };
9450 Some(menu.render(style, max_height_in_lines, window, cx))
9451 }
9452
9453 fn render_context_menu_aside(
9454 &mut self,
9455 max_size: Size<Pixels>,
9456 window: &mut Window,
9457 cx: &mut Context<Editor>,
9458 ) -> Option<AnyElement> {
9459 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9460 if menu.visible() {
9461 menu.render_aside(max_size, window, cx)
9462 } else {
9463 None
9464 }
9465 })
9466 }
9467
9468 fn hide_context_menu(
9469 &mut self,
9470 window: &mut Window,
9471 cx: &mut Context<Self>,
9472 ) -> Option<CodeContextMenu> {
9473 cx.notify();
9474 self.completion_tasks.clear();
9475 let context_menu = self.context_menu.borrow_mut().take();
9476 self.stale_edit_prediction_in_menu.take();
9477 self.update_visible_edit_prediction(window, cx);
9478 if let Some(CodeContextMenu::Completions(_)) = &context_menu {
9479 if let Some(completion_provider) = &self.completion_provider {
9480 completion_provider.selection_changed(None, window, cx);
9481 }
9482 }
9483 context_menu
9484 }
9485
9486 fn show_snippet_choices(
9487 &mut self,
9488 choices: &Vec<String>,
9489 selection: Range<Anchor>,
9490 cx: &mut Context<Self>,
9491 ) {
9492 let buffer_id = match (&selection.start.buffer_id, &selection.end.buffer_id) {
9493 (Some(a), Some(b)) if a == b => a,
9494 _ => {
9495 log::error!("expected anchor range to have matching buffer IDs");
9496 return;
9497 }
9498 };
9499 let multi_buffer = self.buffer().read(cx);
9500 let Some(buffer) = multi_buffer.buffer(*buffer_id) else {
9501 return;
9502 };
9503
9504 let id = post_inc(&mut self.next_completion_id);
9505 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9506 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9507 CompletionsMenu::new_snippet_choices(
9508 id,
9509 true,
9510 choices,
9511 selection,
9512 buffer,
9513 snippet_sort_order,
9514 ),
9515 ));
9516 }
9517
9518 pub fn insert_snippet(
9519 &mut self,
9520 insertion_ranges: &[Range<usize>],
9521 snippet: Snippet,
9522 window: &mut Window,
9523 cx: &mut Context<Self>,
9524 ) -> Result<()> {
9525 struct Tabstop<T> {
9526 is_end_tabstop: bool,
9527 ranges: Vec<Range<T>>,
9528 choices: Option<Vec<String>>,
9529 }
9530
9531 let tabstops = self.buffer.update(cx, |buffer, cx| {
9532 let snippet_text: Arc<str> = snippet.text.clone().into();
9533 let edits = insertion_ranges
9534 .iter()
9535 .cloned()
9536 .map(|range| (range, snippet_text.clone()));
9537 let autoindent_mode = AutoindentMode::Block {
9538 original_indent_columns: Vec::new(),
9539 };
9540 buffer.edit(edits, Some(autoindent_mode), cx);
9541
9542 let snapshot = &*buffer.read(cx);
9543 let snippet = &snippet;
9544 snippet
9545 .tabstops
9546 .iter()
9547 .map(|tabstop| {
9548 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
9549 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9550 });
9551 let mut tabstop_ranges = tabstop
9552 .ranges
9553 .iter()
9554 .flat_map(|tabstop_range| {
9555 let mut delta = 0_isize;
9556 insertion_ranges.iter().map(move |insertion_range| {
9557 let insertion_start = insertion_range.start as isize + delta;
9558 delta +=
9559 snippet.text.len() as isize - insertion_range.len() as isize;
9560
9561 let start = ((insertion_start + tabstop_range.start) as usize)
9562 .min(snapshot.len());
9563 let end = ((insertion_start + tabstop_range.end) as usize)
9564 .min(snapshot.len());
9565 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9566 })
9567 })
9568 .collect::<Vec<_>>();
9569 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9570
9571 Tabstop {
9572 is_end_tabstop,
9573 ranges: tabstop_ranges,
9574 choices: tabstop.choices.clone(),
9575 }
9576 })
9577 .collect::<Vec<_>>()
9578 });
9579 if let Some(tabstop) = tabstops.first() {
9580 self.change_selections(Default::default(), window, cx, |s| {
9581 // Reverse order so that the first range is the newest created selection.
9582 // Completions will use it and autoscroll will prioritize it.
9583 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9584 });
9585
9586 if let Some(choices) = &tabstop.choices {
9587 if let Some(selection) = tabstop.ranges.first() {
9588 self.show_snippet_choices(choices, selection.clone(), cx)
9589 }
9590 }
9591
9592 // If we're already at the last tabstop and it's at the end of the snippet,
9593 // we're done, we don't need to keep the state around.
9594 if !tabstop.is_end_tabstop {
9595 let choices = tabstops
9596 .iter()
9597 .map(|tabstop| tabstop.choices.clone())
9598 .collect();
9599
9600 let ranges = tabstops
9601 .into_iter()
9602 .map(|tabstop| tabstop.ranges)
9603 .collect::<Vec<_>>();
9604
9605 self.snippet_stack.push(SnippetState {
9606 active_index: 0,
9607 ranges,
9608 choices,
9609 });
9610 }
9611
9612 // Check whether the just-entered snippet ends with an auto-closable bracket.
9613 if self.autoclose_regions.is_empty() {
9614 let snapshot = self.buffer.read(cx).snapshot(cx);
9615 let mut all_selections = self.selections.all::<Point>(cx);
9616 for selection in &mut all_selections {
9617 let selection_head = selection.head();
9618 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9619 continue;
9620 };
9621
9622 let mut bracket_pair = None;
9623 let max_lookup_length = scope
9624 .brackets()
9625 .map(|(pair, _)| {
9626 pair.start
9627 .as_str()
9628 .chars()
9629 .count()
9630 .max(pair.end.as_str().chars().count())
9631 })
9632 .max();
9633 if let Some(max_lookup_length) = max_lookup_length {
9634 let next_text = snapshot
9635 .chars_at(selection_head)
9636 .take(max_lookup_length)
9637 .collect::<String>();
9638 let prev_text = snapshot
9639 .reversed_chars_at(selection_head)
9640 .take(max_lookup_length)
9641 .collect::<String>();
9642
9643 for (pair, enabled) in scope.brackets() {
9644 if enabled
9645 && pair.close
9646 && prev_text.starts_with(pair.start.as_str())
9647 && next_text.starts_with(pair.end.as_str())
9648 {
9649 bracket_pair = Some(pair.clone());
9650 break;
9651 }
9652 }
9653 }
9654
9655 if let Some(pair) = bracket_pair {
9656 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9657 let autoclose_enabled =
9658 self.use_autoclose && snapshot_settings.use_autoclose;
9659 if autoclose_enabled {
9660 let start = snapshot.anchor_after(selection_head);
9661 let end = snapshot.anchor_after(selection_head);
9662 self.autoclose_regions.push(AutocloseRegion {
9663 selection_id: selection.id,
9664 range: start..end,
9665 pair,
9666 });
9667 }
9668 }
9669 }
9670 }
9671 }
9672 Ok(())
9673 }
9674
9675 pub fn move_to_next_snippet_tabstop(
9676 &mut self,
9677 window: &mut Window,
9678 cx: &mut Context<Self>,
9679 ) -> bool {
9680 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9681 }
9682
9683 pub fn move_to_prev_snippet_tabstop(
9684 &mut self,
9685 window: &mut Window,
9686 cx: &mut Context<Self>,
9687 ) -> bool {
9688 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9689 }
9690
9691 pub fn move_to_snippet_tabstop(
9692 &mut self,
9693 bias: Bias,
9694 window: &mut Window,
9695 cx: &mut Context<Self>,
9696 ) -> bool {
9697 if let Some(mut snippet) = self.snippet_stack.pop() {
9698 match bias {
9699 Bias::Left => {
9700 if snippet.active_index > 0 {
9701 snippet.active_index -= 1;
9702 } else {
9703 self.snippet_stack.push(snippet);
9704 return false;
9705 }
9706 }
9707 Bias::Right => {
9708 if snippet.active_index + 1 < snippet.ranges.len() {
9709 snippet.active_index += 1;
9710 } else {
9711 self.snippet_stack.push(snippet);
9712 return false;
9713 }
9714 }
9715 }
9716 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9717 self.change_selections(Default::default(), window, cx, |s| {
9718 // Reverse order so that the first range is the newest created selection.
9719 // Completions will use it and autoscroll will prioritize it.
9720 s.select_ranges(current_ranges.iter().rev().cloned())
9721 });
9722
9723 if let Some(choices) = &snippet.choices[snippet.active_index] {
9724 if let Some(selection) = current_ranges.first() {
9725 self.show_snippet_choices(&choices, selection.clone(), cx);
9726 }
9727 }
9728
9729 // If snippet state is not at the last tabstop, push it back on the stack
9730 if snippet.active_index + 1 < snippet.ranges.len() {
9731 self.snippet_stack.push(snippet);
9732 }
9733 return true;
9734 }
9735 }
9736
9737 false
9738 }
9739
9740 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9741 self.transact(window, cx, |this, window, cx| {
9742 this.select_all(&SelectAll, window, cx);
9743 this.insert("", window, cx);
9744 });
9745 }
9746
9747 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9748 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9749 self.transact(window, cx, |this, window, cx| {
9750 this.select_autoclose_pair(window, cx);
9751 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9752 if !this.linked_edit_ranges.is_empty() {
9753 let selections = this.selections.all::<MultiBufferPoint>(cx);
9754 let snapshot = this.buffer.read(cx).snapshot(cx);
9755
9756 for selection in selections.iter() {
9757 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9758 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9759 if selection_start.buffer_id != selection_end.buffer_id {
9760 continue;
9761 }
9762 if let Some(ranges) =
9763 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9764 {
9765 for (buffer, entries) in ranges {
9766 linked_ranges.entry(buffer).or_default().extend(entries);
9767 }
9768 }
9769 }
9770 }
9771
9772 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
9773 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9774 for selection in &mut selections {
9775 if selection.is_empty() {
9776 let old_head = selection.head();
9777 let mut new_head =
9778 movement::left(&display_map, old_head.to_display_point(&display_map))
9779 .to_point(&display_map);
9780 if let Some((buffer, line_buffer_range)) = display_map
9781 .buffer_snapshot
9782 .buffer_line_for_row(MultiBufferRow(old_head.row))
9783 {
9784 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9785 let indent_len = match indent_size.kind {
9786 IndentKind::Space => {
9787 buffer.settings_at(line_buffer_range.start, cx).tab_size
9788 }
9789 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9790 };
9791 if old_head.column <= indent_size.len && old_head.column > 0 {
9792 let indent_len = indent_len.get();
9793 new_head = cmp::min(
9794 new_head,
9795 MultiBufferPoint::new(
9796 old_head.row,
9797 ((old_head.column - 1) / indent_len) * indent_len,
9798 ),
9799 );
9800 }
9801 }
9802
9803 selection.set_head(new_head, SelectionGoal::None);
9804 }
9805 }
9806
9807 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9808 this.insert("", window, cx);
9809 let empty_str: Arc<str> = Arc::from("");
9810 for (buffer, edits) in linked_ranges {
9811 let snapshot = buffer.read(cx).snapshot();
9812 use text::ToPoint as TP;
9813
9814 let edits = edits
9815 .into_iter()
9816 .map(|range| {
9817 let end_point = TP::to_point(&range.end, &snapshot);
9818 let mut start_point = TP::to_point(&range.start, &snapshot);
9819
9820 if end_point == start_point {
9821 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9822 .saturating_sub(1);
9823 start_point =
9824 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9825 };
9826
9827 (start_point..end_point, empty_str.clone())
9828 })
9829 .sorted_by_key(|(range, _)| range.start)
9830 .collect::<Vec<_>>();
9831 buffer.update(cx, |this, cx| {
9832 this.edit(edits, None, cx);
9833 })
9834 }
9835 this.refresh_edit_prediction(true, false, window, cx);
9836 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
9837 });
9838 }
9839
9840 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9841 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9842 self.transact(window, cx, |this, window, cx| {
9843 this.change_selections(Default::default(), window, cx, |s| {
9844 s.move_with(|map, selection| {
9845 if selection.is_empty() {
9846 let cursor = movement::right(map, selection.head());
9847 selection.end = cursor;
9848 selection.reversed = true;
9849 selection.goal = SelectionGoal::None;
9850 }
9851 })
9852 });
9853 this.insert("", window, cx);
9854 this.refresh_edit_prediction(true, false, window, cx);
9855 });
9856 }
9857
9858 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9859 if self.mode.is_single_line() {
9860 cx.propagate();
9861 return;
9862 }
9863
9864 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9865 if self.move_to_prev_snippet_tabstop(window, cx) {
9866 return;
9867 }
9868 self.outdent(&Outdent, window, cx);
9869 }
9870
9871 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9872 if self.mode.is_single_line() {
9873 cx.propagate();
9874 return;
9875 }
9876
9877 if self.move_to_next_snippet_tabstop(window, cx) {
9878 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9879 return;
9880 }
9881 if self.read_only(cx) {
9882 return;
9883 }
9884 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9885 let mut selections = self.selections.all_adjusted(cx);
9886 let buffer = self.buffer.read(cx);
9887 let snapshot = buffer.snapshot(cx);
9888 let rows_iter = selections.iter().map(|s| s.head().row);
9889 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9890
9891 let has_some_cursor_in_whitespace = selections
9892 .iter()
9893 .filter(|selection| selection.is_empty())
9894 .any(|selection| {
9895 let cursor = selection.head();
9896 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9897 cursor.column < current_indent.len
9898 });
9899
9900 let mut edits = Vec::new();
9901 let mut prev_edited_row = 0;
9902 let mut row_delta = 0;
9903 for selection in &mut selections {
9904 if selection.start.row != prev_edited_row {
9905 row_delta = 0;
9906 }
9907 prev_edited_row = selection.end.row;
9908
9909 // If the selection is non-empty, then increase the indentation of the selected lines.
9910 if !selection.is_empty() {
9911 row_delta =
9912 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9913 continue;
9914 }
9915
9916 let cursor = selection.head();
9917 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9918 if let Some(suggested_indent) =
9919 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
9920 {
9921 // Don't do anything if already at suggested indent
9922 // and there is any other cursor which is not
9923 if has_some_cursor_in_whitespace
9924 && cursor.column == current_indent.len
9925 && current_indent.len == suggested_indent.len
9926 {
9927 continue;
9928 }
9929
9930 // Adjust line and move cursor to suggested indent
9931 // if cursor is not at suggested indent
9932 if cursor.column < suggested_indent.len
9933 && cursor.column <= current_indent.len
9934 && current_indent.len <= suggested_indent.len
9935 {
9936 selection.start = Point::new(cursor.row, suggested_indent.len);
9937 selection.end = selection.start;
9938 if row_delta == 0 {
9939 edits.extend(Buffer::edit_for_indent_size_adjustment(
9940 cursor.row,
9941 current_indent,
9942 suggested_indent,
9943 ));
9944 row_delta = suggested_indent.len - current_indent.len;
9945 }
9946 continue;
9947 }
9948
9949 // If current indent is more than suggested indent
9950 // only move cursor to current indent and skip indent
9951 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
9952 selection.start = Point::new(cursor.row, current_indent.len);
9953 selection.end = selection.start;
9954 continue;
9955 }
9956 }
9957
9958 // Otherwise, insert a hard or soft tab.
9959 let settings = buffer.language_settings_at(cursor, cx);
9960 let tab_size = if settings.hard_tabs {
9961 IndentSize::tab()
9962 } else {
9963 let tab_size = settings.tab_size.get();
9964 let indent_remainder = snapshot
9965 .text_for_range(Point::new(cursor.row, 0)..cursor)
9966 .flat_map(str::chars)
9967 .fold(row_delta % tab_size, |counter: u32, c| {
9968 if c == '\t' {
9969 0
9970 } else {
9971 (counter + 1) % tab_size
9972 }
9973 });
9974
9975 let chars_to_next_tab_stop = tab_size - indent_remainder;
9976 IndentSize::spaces(chars_to_next_tab_stop)
9977 };
9978 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
9979 selection.end = selection.start;
9980 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
9981 row_delta += tab_size.len;
9982 }
9983
9984 self.transact(window, cx, |this, window, cx| {
9985 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9986 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9987 this.refresh_edit_prediction(true, false, window, cx);
9988 });
9989 }
9990
9991 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
9992 if self.read_only(cx) {
9993 return;
9994 }
9995 if self.mode.is_single_line() {
9996 cx.propagate();
9997 return;
9998 }
9999
10000 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10001 let mut selections = self.selections.all::<Point>(cx);
10002 let mut prev_edited_row = 0;
10003 let mut row_delta = 0;
10004 let mut edits = Vec::new();
10005 let buffer = self.buffer.read(cx);
10006 let snapshot = buffer.snapshot(cx);
10007 for selection in &mut selections {
10008 if selection.start.row != prev_edited_row {
10009 row_delta = 0;
10010 }
10011 prev_edited_row = selection.end.row;
10012
10013 row_delta =
10014 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10015 }
10016
10017 self.transact(window, cx, |this, window, cx| {
10018 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10019 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10020 });
10021 }
10022
10023 fn indent_selection(
10024 buffer: &MultiBuffer,
10025 snapshot: &MultiBufferSnapshot,
10026 selection: &mut Selection<Point>,
10027 edits: &mut Vec<(Range<Point>, String)>,
10028 delta_for_start_row: u32,
10029 cx: &App,
10030 ) -> u32 {
10031 let settings = buffer.language_settings_at(selection.start, cx);
10032 let tab_size = settings.tab_size.get();
10033 let indent_kind = if settings.hard_tabs {
10034 IndentKind::Tab
10035 } else {
10036 IndentKind::Space
10037 };
10038 let mut start_row = selection.start.row;
10039 let mut end_row = selection.end.row + 1;
10040
10041 // If a selection ends at the beginning of a line, don't indent
10042 // that last line.
10043 if selection.end.column == 0 && selection.end.row > selection.start.row {
10044 end_row -= 1;
10045 }
10046
10047 // Avoid re-indenting a row that has already been indented by a
10048 // previous selection, but still update this selection's column
10049 // to reflect that indentation.
10050 if delta_for_start_row > 0 {
10051 start_row += 1;
10052 selection.start.column += delta_for_start_row;
10053 if selection.end.row == selection.start.row {
10054 selection.end.column += delta_for_start_row;
10055 }
10056 }
10057
10058 let mut delta_for_end_row = 0;
10059 let has_multiple_rows = start_row + 1 != end_row;
10060 for row in start_row..end_row {
10061 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10062 let indent_delta = match (current_indent.kind, indent_kind) {
10063 (IndentKind::Space, IndentKind::Space) => {
10064 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10065 IndentSize::spaces(columns_to_next_tab_stop)
10066 }
10067 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10068 (_, IndentKind::Tab) => IndentSize::tab(),
10069 };
10070
10071 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10072 0
10073 } else {
10074 selection.start.column
10075 };
10076 let row_start = Point::new(row, start);
10077 edits.push((
10078 row_start..row_start,
10079 indent_delta.chars().collect::<String>(),
10080 ));
10081
10082 // Update this selection's endpoints to reflect the indentation.
10083 if row == selection.start.row {
10084 selection.start.column += indent_delta.len;
10085 }
10086 if row == selection.end.row {
10087 selection.end.column += indent_delta.len;
10088 delta_for_end_row = indent_delta.len;
10089 }
10090 }
10091
10092 if selection.start.row == selection.end.row {
10093 delta_for_start_row + delta_for_end_row
10094 } else {
10095 delta_for_end_row
10096 }
10097 }
10098
10099 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10100 if self.read_only(cx) {
10101 return;
10102 }
10103 if self.mode.is_single_line() {
10104 cx.propagate();
10105 return;
10106 }
10107
10108 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10109 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10110 let selections = self.selections.all::<Point>(cx);
10111 let mut deletion_ranges = Vec::new();
10112 let mut last_outdent = None;
10113 {
10114 let buffer = self.buffer.read(cx);
10115 let snapshot = buffer.snapshot(cx);
10116 for selection in &selections {
10117 let settings = buffer.language_settings_at(selection.start, cx);
10118 let tab_size = settings.tab_size.get();
10119 let mut rows = selection.spanned_rows(false, &display_map);
10120
10121 // Avoid re-outdenting a row that has already been outdented by a
10122 // previous selection.
10123 if let Some(last_row) = last_outdent {
10124 if last_row == rows.start {
10125 rows.start = rows.start.next_row();
10126 }
10127 }
10128 let has_multiple_rows = rows.len() > 1;
10129 for row in rows.iter_rows() {
10130 let indent_size = snapshot.indent_size_for_line(row);
10131 if indent_size.len > 0 {
10132 let deletion_len = match indent_size.kind {
10133 IndentKind::Space => {
10134 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10135 if columns_to_prev_tab_stop == 0 {
10136 tab_size
10137 } else {
10138 columns_to_prev_tab_stop
10139 }
10140 }
10141 IndentKind::Tab => 1,
10142 };
10143 let start = if has_multiple_rows
10144 || deletion_len > selection.start.column
10145 || indent_size.len < selection.start.column
10146 {
10147 0
10148 } else {
10149 selection.start.column - deletion_len
10150 };
10151 deletion_ranges.push(
10152 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10153 );
10154 last_outdent = Some(row);
10155 }
10156 }
10157 }
10158 }
10159
10160 self.transact(window, cx, |this, window, cx| {
10161 this.buffer.update(cx, |buffer, cx| {
10162 let empty_str: Arc<str> = Arc::default();
10163 buffer.edit(
10164 deletion_ranges
10165 .into_iter()
10166 .map(|range| (range, empty_str.clone())),
10167 None,
10168 cx,
10169 );
10170 });
10171 let selections = this.selections.all::<usize>(cx);
10172 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10173 });
10174 }
10175
10176 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10177 if self.read_only(cx) {
10178 return;
10179 }
10180 if self.mode.is_single_line() {
10181 cx.propagate();
10182 return;
10183 }
10184
10185 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10186 let selections = self
10187 .selections
10188 .all::<usize>(cx)
10189 .into_iter()
10190 .map(|s| s.range());
10191
10192 self.transact(window, cx, |this, window, cx| {
10193 this.buffer.update(cx, |buffer, cx| {
10194 buffer.autoindent_ranges(selections, cx);
10195 });
10196 let selections = this.selections.all::<usize>(cx);
10197 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10198 });
10199 }
10200
10201 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10202 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10203 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10204 let selections = self.selections.all::<Point>(cx);
10205
10206 let mut new_cursors = Vec::new();
10207 let mut edit_ranges = Vec::new();
10208 let mut selections = selections.iter().peekable();
10209 while let Some(selection) = selections.next() {
10210 let mut rows = selection.spanned_rows(false, &display_map);
10211 let goal_display_column = selection.head().to_display_point(&display_map).column();
10212
10213 // Accumulate contiguous regions of rows that we want to delete.
10214 while let Some(next_selection) = selections.peek() {
10215 let next_rows = next_selection.spanned_rows(false, &display_map);
10216 if next_rows.start <= rows.end {
10217 rows.end = next_rows.end;
10218 selections.next().unwrap();
10219 } else {
10220 break;
10221 }
10222 }
10223
10224 let buffer = &display_map.buffer_snapshot;
10225 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
10226 let edit_end;
10227 let cursor_buffer_row;
10228 if buffer.max_point().row >= rows.end.0 {
10229 // If there's a line after the range, delete the \n from the end of the row range
10230 // and position the cursor on the next line.
10231 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
10232 cursor_buffer_row = rows.end;
10233 } else {
10234 // If there isn't a line after the range, delete the \n from the line before the
10235 // start of the row range and position the cursor there.
10236 edit_start = edit_start.saturating_sub(1);
10237 edit_end = buffer.len();
10238 cursor_buffer_row = rows.start.previous_row();
10239 }
10240
10241 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
10242 *cursor.column_mut() =
10243 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
10244
10245 new_cursors.push((
10246 selection.id,
10247 buffer.anchor_after(cursor.to_point(&display_map)),
10248 ));
10249 edit_ranges.push(edit_start..edit_end);
10250 }
10251
10252 self.transact(window, cx, |this, window, cx| {
10253 let buffer = this.buffer.update(cx, |buffer, cx| {
10254 let empty_str: Arc<str> = Arc::default();
10255 buffer.edit(
10256 edit_ranges
10257 .into_iter()
10258 .map(|range| (range, empty_str.clone())),
10259 None,
10260 cx,
10261 );
10262 buffer.snapshot(cx)
10263 });
10264 let new_selections = new_cursors
10265 .into_iter()
10266 .map(|(id, cursor)| {
10267 let cursor = cursor.to_point(&buffer);
10268 Selection {
10269 id,
10270 start: cursor,
10271 end: cursor,
10272 reversed: false,
10273 goal: SelectionGoal::None,
10274 }
10275 })
10276 .collect();
10277
10278 this.change_selections(Default::default(), window, cx, |s| {
10279 s.select(new_selections);
10280 });
10281 });
10282 }
10283
10284 pub fn join_lines_impl(
10285 &mut self,
10286 insert_whitespace: bool,
10287 window: &mut Window,
10288 cx: &mut Context<Self>,
10289 ) {
10290 if self.read_only(cx) {
10291 return;
10292 }
10293 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10294 for selection in self.selections.all::<Point>(cx) {
10295 let start = MultiBufferRow(selection.start.row);
10296 // Treat single line selections as if they include the next line. Otherwise this action
10297 // would do nothing for single line selections individual cursors.
10298 let end = if selection.start.row == selection.end.row {
10299 MultiBufferRow(selection.start.row + 1)
10300 } else {
10301 MultiBufferRow(selection.end.row)
10302 };
10303
10304 if let Some(last_row_range) = row_ranges.last_mut() {
10305 if start <= last_row_range.end {
10306 last_row_range.end = end;
10307 continue;
10308 }
10309 }
10310 row_ranges.push(start..end);
10311 }
10312
10313 let snapshot = self.buffer.read(cx).snapshot(cx);
10314 let mut cursor_positions = Vec::new();
10315 for row_range in &row_ranges {
10316 let anchor = snapshot.anchor_before(Point::new(
10317 row_range.end.previous_row().0,
10318 snapshot.line_len(row_range.end.previous_row()),
10319 ));
10320 cursor_positions.push(anchor..anchor);
10321 }
10322
10323 self.transact(window, cx, |this, window, cx| {
10324 for row_range in row_ranges.into_iter().rev() {
10325 for row in row_range.iter_rows().rev() {
10326 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10327 let next_line_row = row.next_row();
10328 let indent = snapshot.indent_size_for_line(next_line_row);
10329 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10330
10331 let replace =
10332 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10333 " "
10334 } else {
10335 ""
10336 };
10337
10338 this.buffer.update(cx, |buffer, cx| {
10339 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10340 });
10341 }
10342 }
10343
10344 this.change_selections(Default::default(), window, cx, |s| {
10345 s.select_anchor_ranges(cursor_positions)
10346 });
10347 });
10348 }
10349
10350 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10351 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10352 self.join_lines_impl(true, window, cx);
10353 }
10354
10355 pub fn sort_lines_case_sensitive(
10356 &mut self,
10357 _: &SortLinesCaseSensitive,
10358 window: &mut Window,
10359 cx: &mut Context<Self>,
10360 ) {
10361 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10362 }
10363
10364 pub fn sort_lines_by_length(
10365 &mut self,
10366 _: &SortLinesByLength,
10367 window: &mut Window,
10368 cx: &mut Context<Self>,
10369 ) {
10370 self.manipulate_immutable_lines(window, cx, |lines| {
10371 lines.sort_by_key(|&line| line.chars().count())
10372 })
10373 }
10374
10375 pub fn sort_lines_case_insensitive(
10376 &mut self,
10377 _: &SortLinesCaseInsensitive,
10378 window: &mut Window,
10379 cx: &mut Context<Self>,
10380 ) {
10381 self.manipulate_immutable_lines(window, cx, |lines| {
10382 lines.sort_by_key(|line| line.to_lowercase())
10383 })
10384 }
10385
10386 pub fn unique_lines_case_insensitive(
10387 &mut self,
10388 _: &UniqueLinesCaseInsensitive,
10389 window: &mut Window,
10390 cx: &mut Context<Self>,
10391 ) {
10392 self.manipulate_immutable_lines(window, cx, |lines| {
10393 let mut seen = HashSet::default();
10394 lines.retain(|line| seen.insert(line.to_lowercase()));
10395 })
10396 }
10397
10398 pub fn unique_lines_case_sensitive(
10399 &mut self,
10400 _: &UniqueLinesCaseSensitive,
10401 window: &mut Window,
10402 cx: &mut Context<Self>,
10403 ) {
10404 self.manipulate_immutable_lines(window, cx, |lines| {
10405 let mut seen = HashSet::default();
10406 lines.retain(|line| seen.insert(*line));
10407 })
10408 }
10409
10410 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10411 let Some(project) = self.project.clone() else {
10412 return;
10413 };
10414 self.reload(project, window, cx)
10415 .detach_and_notify_err(window, cx);
10416 }
10417
10418 pub fn restore_file(
10419 &mut self,
10420 _: &::git::RestoreFile,
10421 window: &mut Window,
10422 cx: &mut Context<Self>,
10423 ) {
10424 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10425 let mut buffer_ids = HashSet::default();
10426 let snapshot = self.buffer().read(cx).snapshot(cx);
10427 for selection in self.selections.all::<usize>(cx) {
10428 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10429 }
10430
10431 let buffer = self.buffer().read(cx);
10432 let ranges = buffer_ids
10433 .into_iter()
10434 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10435 .collect::<Vec<_>>();
10436
10437 self.restore_hunks_in_ranges(ranges, window, cx);
10438 }
10439
10440 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10441 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10442 let selections = self
10443 .selections
10444 .all(cx)
10445 .into_iter()
10446 .map(|s| s.range())
10447 .collect();
10448 self.restore_hunks_in_ranges(selections, window, cx);
10449 }
10450
10451 pub fn restore_hunks_in_ranges(
10452 &mut self,
10453 ranges: Vec<Range<Point>>,
10454 window: &mut Window,
10455 cx: &mut Context<Editor>,
10456 ) {
10457 let mut revert_changes = HashMap::default();
10458 let chunk_by = self
10459 .snapshot(window, cx)
10460 .hunks_for_ranges(ranges)
10461 .into_iter()
10462 .chunk_by(|hunk| hunk.buffer_id);
10463 for (buffer_id, hunks) in &chunk_by {
10464 let hunks = hunks.collect::<Vec<_>>();
10465 for hunk in &hunks {
10466 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10467 }
10468 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10469 }
10470 drop(chunk_by);
10471 if !revert_changes.is_empty() {
10472 self.transact(window, cx, |editor, window, cx| {
10473 editor.restore(revert_changes, window, cx);
10474 });
10475 }
10476 }
10477
10478 pub fn open_active_item_in_terminal(
10479 &mut self,
10480 _: &OpenInTerminal,
10481 window: &mut Window,
10482 cx: &mut Context<Self>,
10483 ) {
10484 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10485 let project_path = buffer.read(cx).project_path(cx)?;
10486 let project = self.project.as_ref()?.read(cx);
10487 let entry = project.entry_for_path(&project_path, cx)?;
10488 let parent = match &entry.canonical_path {
10489 Some(canonical_path) => canonical_path.to_path_buf(),
10490 None => project.absolute_path(&project_path, cx)?,
10491 }
10492 .parent()?
10493 .to_path_buf();
10494 Some(parent)
10495 }) {
10496 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10497 }
10498 }
10499
10500 fn set_breakpoint_context_menu(
10501 &mut self,
10502 display_row: DisplayRow,
10503 position: Option<Anchor>,
10504 clicked_point: gpui::Point<Pixels>,
10505 window: &mut Window,
10506 cx: &mut Context<Self>,
10507 ) {
10508 let source = self
10509 .buffer
10510 .read(cx)
10511 .snapshot(cx)
10512 .anchor_before(Point::new(display_row.0, 0u32));
10513
10514 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10515
10516 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10517 self,
10518 source,
10519 clicked_point,
10520 context_menu,
10521 window,
10522 cx,
10523 );
10524 }
10525
10526 fn add_edit_breakpoint_block(
10527 &mut self,
10528 anchor: Anchor,
10529 breakpoint: &Breakpoint,
10530 edit_action: BreakpointPromptEditAction,
10531 window: &mut Window,
10532 cx: &mut Context<Self>,
10533 ) {
10534 let weak_editor = cx.weak_entity();
10535 let bp_prompt = cx.new(|cx| {
10536 BreakpointPromptEditor::new(
10537 weak_editor,
10538 anchor,
10539 breakpoint.clone(),
10540 edit_action,
10541 window,
10542 cx,
10543 )
10544 });
10545
10546 let height = bp_prompt.update(cx, |this, cx| {
10547 this.prompt
10548 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10549 });
10550 let cloned_prompt = bp_prompt.clone();
10551 let blocks = vec![BlockProperties {
10552 style: BlockStyle::Sticky,
10553 placement: BlockPlacement::Above(anchor),
10554 height: Some(height),
10555 render: Arc::new(move |cx| {
10556 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10557 cloned_prompt.clone().into_any_element()
10558 }),
10559 priority: 0,
10560 }];
10561
10562 let focus_handle = bp_prompt.focus_handle(cx);
10563 window.focus(&focus_handle);
10564
10565 let block_ids = self.insert_blocks(blocks, None, cx);
10566 bp_prompt.update(cx, |prompt, _| {
10567 prompt.add_block_ids(block_ids);
10568 });
10569 }
10570
10571 pub(crate) fn breakpoint_at_row(
10572 &self,
10573 row: u32,
10574 window: &mut Window,
10575 cx: &mut Context<Self>,
10576 ) -> Option<(Anchor, Breakpoint)> {
10577 let snapshot = self.snapshot(window, cx);
10578 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
10579
10580 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10581 }
10582
10583 pub(crate) fn breakpoint_at_anchor(
10584 &self,
10585 breakpoint_position: Anchor,
10586 snapshot: &EditorSnapshot,
10587 cx: &mut Context<Self>,
10588 ) -> Option<(Anchor, Breakpoint)> {
10589 let project = self.project.clone()?;
10590
10591 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
10592 snapshot
10593 .buffer_snapshot
10594 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
10595 })?;
10596
10597 let enclosing_excerpt = breakpoint_position.excerpt_id;
10598 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
10599 let buffer_snapshot = buffer.read(cx).snapshot();
10600
10601 let row = buffer_snapshot
10602 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10603 .row;
10604
10605 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
10606 let anchor_end = snapshot
10607 .buffer_snapshot
10608 .anchor_after(Point::new(row, line_len));
10609
10610 let bp = self
10611 .breakpoint_store
10612 .as_ref()?
10613 .read_with(cx, |breakpoint_store, cx| {
10614 breakpoint_store
10615 .breakpoints(
10616 &buffer,
10617 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10618 &buffer_snapshot,
10619 cx,
10620 )
10621 .next()
10622 .and_then(|(bp, _)| {
10623 let breakpoint_row = buffer_snapshot
10624 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10625 .row;
10626
10627 if breakpoint_row == row {
10628 snapshot
10629 .buffer_snapshot
10630 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10631 .map(|position| (position, bp.bp.clone()))
10632 } else {
10633 None
10634 }
10635 })
10636 });
10637 bp
10638 }
10639
10640 pub fn edit_log_breakpoint(
10641 &mut self,
10642 _: &EditLogBreakpoint,
10643 window: &mut Window,
10644 cx: &mut Context<Self>,
10645 ) {
10646 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10647 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10648 message: None,
10649 state: BreakpointState::Enabled,
10650 condition: None,
10651 hit_condition: None,
10652 });
10653
10654 self.add_edit_breakpoint_block(
10655 anchor,
10656 &breakpoint,
10657 BreakpointPromptEditAction::Log,
10658 window,
10659 cx,
10660 );
10661 }
10662 }
10663
10664 fn breakpoints_at_cursors(
10665 &self,
10666 window: &mut Window,
10667 cx: &mut Context<Self>,
10668 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10669 let snapshot = self.snapshot(window, cx);
10670 let cursors = self
10671 .selections
10672 .disjoint_anchors()
10673 .into_iter()
10674 .map(|selection| {
10675 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
10676
10677 let breakpoint_position = self
10678 .breakpoint_at_row(cursor_position.row, window, cx)
10679 .map(|bp| bp.0)
10680 .unwrap_or_else(|| {
10681 snapshot
10682 .display_snapshot
10683 .buffer_snapshot
10684 .anchor_after(Point::new(cursor_position.row, 0))
10685 });
10686
10687 let breakpoint = self
10688 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10689 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10690
10691 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10692 })
10693 // 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.
10694 .collect::<HashMap<Anchor, _>>();
10695
10696 cursors.into_iter().collect()
10697 }
10698
10699 pub fn enable_breakpoint(
10700 &mut self,
10701 _: &crate::actions::EnableBreakpoint,
10702 window: &mut Window,
10703 cx: &mut Context<Self>,
10704 ) {
10705 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10706 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10707 continue;
10708 };
10709 self.edit_breakpoint_at_anchor(
10710 anchor,
10711 breakpoint,
10712 BreakpointEditAction::InvertState,
10713 cx,
10714 );
10715 }
10716 }
10717
10718 pub fn disable_breakpoint(
10719 &mut self,
10720 _: &crate::actions::DisableBreakpoint,
10721 window: &mut Window,
10722 cx: &mut Context<Self>,
10723 ) {
10724 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10725 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10726 continue;
10727 };
10728 self.edit_breakpoint_at_anchor(
10729 anchor,
10730 breakpoint,
10731 BreakpointEditAction::InvertState,
10732 cx,
10733 );
10734 }
10735 }
10736
10737 pub fn toggle_breakpoint(
10738 &mut self,
10739 _: &crate::actions::ToggleBreakpoint,
10740 window: &mut Window,
10741 cx: &mut Context<Self>,
10742 ) {
10743 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10744 if let Some(breakpoint) = breakpoint {
10745 self.edit_breakpoint_at_anchor(
10746 anchor,
10747 breakpoint,
10748 BreakpointEditAction::Toggle,
10749 cx,
10750 );
10751 } else {
10752 self.edit_breakpoint_at_anchor(
10753 anchor,
10754 Breakpoint::new_standard(),
10755 BreakpointEditAction::Toggle,
10756 cx,
10757 );
10758 }
10759 }
10760 }
10761
10762 pub fn edit_breakpoint_at_anchor(
10763 &mut self,
10764 breakpoint_position: Anchor,
10765 breakpoint: Breakpoint,
10766 edit_action: BreakpointEditAction,
10767 cx: &mut Context<Self>,
10768 ) {
10769 let Some(breakpoint_store) = &self.breakpoint_store else {
10770 return;
10771 };
10772
10773 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
10774 if breakpoint_position == Anchor::min() {
10775 self.buffer()
10776 .read(cx)
10777 .excerpt_buffer_ids()
10778 .into_iter()
10779 .next()
10780 } else {
10781 None
10782 }
10783 }) else {
10784 return;
10785 };
10786
10787 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
10788 return;
10789 };
10790
10791 breakpoint_store.update(cx, |breakpoint_store, cx| {
10792 breakpoint_store.toggle_breakpoint(
10793 buffer,
10794 BreakpointWithPosition {
10795 position: breakpoint_position.text_anchor,
10796 bp: breakpoint,
10797 },
10798 edit_action,
10799 cx,
10800 );
10801 });
10802
10803 cx.notify();
10804 }
10805
10806 #[cfg(any(test, feature = "test-support"))]
10807 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10808 self.breakpoint_store.clone()
10809 }
10810
10811 pub fn prepare_restore_change(
10812 &self,
10813 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10814 hunk: &MultiBufferDiffHunk,
10815 cx: &mut App,
10816 ) -> Option<()> {
10817 if hunk.is_created_file() {
10818 return None;
10819 }
10820 let buffer = self.buffer.read(cx);
10821 let diff = buffer.diff_for(hunk.buffer_id)?;
10822 let buffer = buffer.buffer(hunk.buffer_id)?;
10823 let buffer = buffer.read(cx);
10824 let original_text = diff
10825 .read(cx)
10826 .base_text()
10827 .as_rope()
10828 .slice(hunk.diff_base_byte_range.clone());
10829 let buffer_snapshot = buffer.snapshot();
10830 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
10831 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
10832 probe
10833 .0
10834 .start
10835 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
10836 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
10837 }) {
10838 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
10839 Some(())
10840 } else {
10841 None
10842 }
10843 }
10844
10845 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
10846 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
10847 }
10848
10849 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
10850 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
10851 }
10852
10853 fn manipulate_lines<M>(
10854 &mut self,
10855 window: &mut Window,
10856 cx: &mut Context<Self>,
10857 mut manipulate: M,
10858 ) where
10859 M: FnMut(&str) -> LineManipulationResult,
10860 {
10861 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10862
10863 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10864 let buffer = self.buffer.read(cx).snapshot(cx);
10865
10866 let mut edits = Vec::new();
10867
10868 let selections = self.selections.all::<Point>(cx);
10869 let mut selections = selections.iter().peekable();
10870 let mut contiguous_row_selections = Vec::new();
10871 let mut new_selections = Vec::new();
10872 let mut added_lines = 0;
10873 let mut removed_lines = 0;
10874
10875 while let Some(selection) = selections.next() {
10876 let (start_row, end_row) = consume_contiguous_rows(
10877 &mut contiguous_row_selections,
10878 selection,
10879 &display_map,
10880 &mut selections,
10881 );
10882
10883 let start_point = Point::new(start_row.0, 0);
10884 let end_point = Point::new(
10885 end_row.previous_row().0,
10886 buffer.line_len(end_row.previous_row()),
10887 );
10888 let text = buffer
10889 .text_for_range(start_point..end_point)
10890 .collect::<String>();
10891
10892 let LineManipulationResult {
10893 new_text,
10894 line_count_before,
10895 line_count_after,
10896 } = manipulate(&text);
10897
10898 edits.push((start_point..end_point, new_text));
10899
10900 // Selections must change based on added and removed line count
10901 let start_row =
10902 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
10903 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
10904 new_selections.push(Selection {
10905 id: selection.id,
10906 start: start_row,
10907 end: end_row,
10908 goal: SelectionGoal::None,
10909 reversed: selection.reversed,
10910 });
10911
10912 if line_count_after > line_count_before {
10913 added_lines += line_count_after - line_count_before;
10914 } else if line_count_before > line_count_after {
10915 removed_lines += line_count_before - line_count_after;
10916 }
10917 }
10918
10919 self.transact(window, cx, |this, window, cx| {
10920 let buffer = this.buffer.update(cx, |buffer, cx| {
10921 buffer.edit(edits, None, cx);
10922 buffer.snapshot(cx)
10923 });
10924
10925 // Recalculate offsets on newly edited buffer
10926 let new_selections = new_selections
10927 .iter()
10928 .map(|s| {
10929 let start_point = Point::new(s.start.0, 0);
10930 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
10931 Selection {
10932 id: s.id,
10933 start: buffer.point_to_offset(start_point),
10934 end: buffer.point_to_offset(end_point),
10935 goal: s.goal,
10936 reversed: s.reversed,
10937 }
10938 })
10939 .collect();
10940
10941 this.change_selections(Default::default(), window, cx, |s| {
10942 s.select(new_selections);
10943 });
10944
10945 this.request_autoscroll(Autoscroll::fit(), cx);
10946 });
10947 }
10948
10949 fn manipulate_immutable_lines<Fn>(
10950 &mut self,
10951 window: &mut Window,
10952 cx: &mut Context<Self>,
10953 mut callback: Fn,
10954 ) where
10955 Fn: FnMut(&mut Vec<&str>),
10956 {
10957 self.manipulate_lines(window, cx, |text| {
10958 let mut lines: Vec<&str> = text.split('\n').collect();
10959 let line_count_before = lines.len();
10960
10961 callback(&mut lines);
10962
10963 LineManipulationResult {
10964 new_text: lines.join("\n"),
10965 line_count_before,
10966 line_count_after: lines.len(),
10967 }
10968 });
10969 }
10970
10971 fn manipulate_mutable_lines<Fn>(
10972 &mut self,
10973 window: &mut Window,
10974 cx: &mut Context<Self>,
10975 mut callback: Fn,
10976 ) where
10977 Fn: FnMut(&mut Vec<Cow<'_, str>>),
10978 {
10979 self.manipulate_lines(window, cx, |text| {
10980 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
10981 let line_count_before = lines.len();
10982
10983 callback(&mut lines);
10984
10985 LineManipulationResult {
10986 new_text: lines.join("\n"),
10987 line_count_before,
10988 line_count_after: lines.len(),
10989 }
10990 });
10991 }
10992
10993 pub fn convert_indentation_to_spaces(
10994 &mut self,
10995 _: &ConvertIndentationToSpaces,
10996 window: &mut Window,
10997 cx: &mut Context<Self>,
10998 ) {
10999 let settings = self.buffer.read(cx).language_settings(cx);
11000 let tab_size = settings.tab_size.get() as usize;
11001
11002 self.manipulate_mutable_lines(window, cx, |lines| {
11003 // Allocates a reasonably sized scratch buffer once for the whole loop
11004 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11005 // Avoids recomputing spaces that could be inserted many times
11006 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11007 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11008 .collect();
11009
11010 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11011 let mut chars = line.as_ref().chars();
11012 let mut col = 0;
11013 let mut changed = false;
11014
11015 while let Some(ch) = chars.next() {
11016 match ch {
11017 ' ' => {
11018 reindented_line.push(' ');
11019 col += 1;
11020 }
11021 '\t' => {
11022 // \t are converted to spaces depending on the current column
11023 let spaces_len = tab_size - (col % tab_size);
11024 reindented_line.extend(&space_cache[spaces_len - 1]);
11025 col += spaces_len;
11026 changed = true;
11027 }
11028 _ => {
11029 // If we dont append before break, the character is consumed
11030 reindented_line.push(ch);
11031 break;
11032 }
11033 }
11034 }
11035
11036 if !changed {
11037 reindented_line.clear();
11038 continue;
11039 }
11040 // Append the rest of the line and replace old reference with new one
11041 reindented_line.extend(chars);
11042 *line = Cow::Owned(reindented_line.clone());
11043 reindented_line.clear();
11044 }
11045 });
11046 }
11047
11048 pub fn convert_indentation_to_tabs(
11049 &mut self,
11050 _: &ConvertIndentationToTabs,
11051 window: &mut Window,
11052 cx: &mut Context<Self>,
11053 ) {
11054 let settings = self.buffer.read(cx).language_settings(cx);
11055 let tab_size = settings.tab_size.get() as usize;
11056
11057 self.manipulate_mutable_lines(window, cx, |lines| {
11058 // Allocates a reasonably sized buffer once for the whole loop
11059 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11060 // Avoids recomputing spaces that could be inserted many times
11061 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11062 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11063 .collect();
11064
11065 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11066 let mut chars = line.chars();
11067 let mut spaces_count = 0;
11068 let mut first_non_indent_char = None;
11069 let mut changed = false;
11070
11071 while let Some(ch) = chars.next() {
11072 match ch {
11073 ' ' => {
11074 // Keep track of spaces. Append \t when we reach tab_size
11075 spaces_count += 1;
11076 changed = true;
11077 if spaces_count == tab_size {
11078 reindented_line.push('\t');
11079 spaces_count = 0;
11080 }
11081 }
11082 '\t' => {
11083 reindented_line.push('\t');
11084 spaces_count = 0;
11085 }
11086 _ => {
11087 // Dont append it yet, we might have remaining spaces
11088 first_non_indent_char = Some(ch);
11089 break;
11090 }
11091 }
11092 }
11093
11094 if !changed {
11095 reindented_line.clear();
11096 continue;
11097 }
11098 // Remaining spaces that didn't make a full tab stop
11099 if spaces_count > 0 {
11100 reindented_line.extend(&space_cache[spaces_count - 1]);
11101 }
11102 // If we consume an extra character that was not indentation, add it back
11103 if let Some(extra_char) = first_non_indent_char {
11104 reindented_line.push(extra_char);
11105 }
11106 // Append the rest of the line and replace old reference with new one
11107 reindented_line.extend(chars);
11108 *line = Cow::Owned(reindented_line.clone());
11109 reindented_line.clear();
11110 }
11111 });
11112 }
11113
11114 pub fn convert_to_upper_case(
11115 &mut self,
11116 _: &ConvertToUpperCase,
11117 window: &mut Window,
11118 cx: &mut Context<Self>,
11119 ) {
11120 self.manipulate_text(window, cx, |text| text.to_uppercase())
11121 }
11122
11123 pub fn convert_to_lower_case(
11124 &mut self,
11125 _: &ConvertToLowerCase,
11126 window: &mut Window,
11127 cx: &mut Context<Self>,
11128 ) {
11129 self.manipulate_text(window, cx, |text| text.to_lowercase())
11130 }
11131
11132 pub fn convert_to_title_case(
11133 &mut self,
11134 _: &ConvertToTitleCase,
11135 window: &mut Window,
11136 cx: &mut Context<Self>,
11137 ) {
11138 self.manipulate_text(window, cx, |text| {
11139 text.split('\n')
11140 .map(|line| line.to_case(Case::Title))
11141 .join("\n")
11142 })
11143 }
11144
11145 pub fn convert_to_snake_case(
11146 &mut self,
11147 _: &ConvertToSnakeCase,
11148 window: &mut Window,
11149 cx: &mut Context<Self>,
11150 ) {
11151 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11152 }
11153
11154 pub fn convert_to_kebab_case(
11155 &mut self,
11156 _: &ConvertToKebabCase,
11157 window: &mut Window,
11158 cx: &mut Context<Self>,
11159 ) {
11160 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11161 }
11162
11163 pub fn convert_to_upper_camel_case(
11164 &mut self,
11165 _: &ConvertToUpperCamelCase,
11166 window: &mut Window,
11167 cx: &mut Context<Self>,
11168 ) {
11169 self.manipulate_text(window, cx, |text| {
11170 text.split('\n')
11171 .map(|line| line.to_case(Case::UpperCamel))
11172 .join("\n")
11173 })
11174 }
11175
11176 pub fn convert_to_lower_camel_case(
11177 &mut self,
11178 _: &ConvertToLowerCamelCase,
11179 window: &mut Window,
11180 cx: &mut Context<Self>,
11181 ) {
11182 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11183 }
11184
11185 pub fn convert_to_opposite_case(
11186 &mut self,
11187 _: &ConvertToOppositeCase,
11188 window: &mut Window,
11189 cx: &mut Context<Self>,
11190 ) {
11191 self.manipulate_text(window, cx, |text| {
11192 text.chars()
11193 .fold(String::with_capacity(text.len()), |mut t, c| {
11194 if c.is_uppercase() {
11195 t.extend(c.to_lowercase());
11196 } else {
11197 t.extend(c.to_uppercase());
11198 }
11199 t
11200 })
11201 })
11202 }
11203
11204 pub fn convert_to_sentence_case(
11205 &mut self,
11206 _: &ConvertToSentenceCase,
11207 window: &mut Window,
11208 cx: &mut Context<Self>,
11209 ) {
11210 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11211 }
11212
11213 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11214 self.manipulate_text(window, cx, |text| {
11215 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11216 if has_upper_case_characters {
11217 text.to_lowercase()
11218 } else {
11219 text.to_uppercase()
11220 }
11221 })
11222 }
11223
11224 pub fn convert_to_rot13(
11225 &mut self,
11226 _: &ConvertToRot13,
11227 window: &mut Window,
11228 cx: &mut Context<Self>,
11229 ) {
11230 self.manipulate_text(window, cx, |text| {
11231 text.chars()
11232 .map(|c| match c {
11233 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11234 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11235 _ => c,
11236 })
11237 .collect()
11238 })
11239 }
11240
11241 pub fn convert_to_rot47(
11242 &mut self,
11243 _: &ConvertToRot47,
11244 window: &mut Window,
11245 cx: &mut Context<Self>,
11246 ) {
11247 self.manipulate_text(window, cx, |text| {
11248 text.chars()
11249 .map(|c| {
11250 let code_point = c as u32;
11251 if code_point >= 33 && code_point <= 126 {
11252 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11253 }
11254 c
11255 })
11256 .collect()
11257 })
11258 }
11259
11260 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11261 where
11262 Fn: FnMut(&str) -> String,
11263 {
11264 let buffer = self.buffer.read(cx).snapshot(cx);
11265
11266 let mut new_selections = Vec::new();
11267 let mut edits = Vec::new();
11268 let mut selection_adjustment = 0i32;
11269
11270 for selection in self.selections.all::<usize>(cx) {
11271 let selection_is_empty = selection.is_empty();
11272
11273 let (start, end) = if selection_is_empty {
11274 let (word_range, _) = buffer.surrounding_word(selection.start, false);
11275 (word_range.start, word_range.end)
11276 } else {
11277 (selection.start, selection.end)
11278 };
11279
11280 let text = buffer.text_for_range(start..end).collect::<String>();
11281 let old_length = text.len() as i32;
11282 let text = callback(&text);
11283
11284 new_selections.push(Selection {
11285 start: (start as i32 - selection_adjustment) as usize,
11286 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11287 goal: SelectionGoal::None,
11288 ..selection
11289 });
11290
11291 selection_adjustment += old_length - text.len() as i32;
11292
11293 edits.push((start..end, text));
11294 }
11295
11296 self.transact(window, cx, |this, window, cx| {
11297 this.buffer.update(cx, |buffer, cx| {
11298 buffer.edit(edits, None, cx);
11299 });
11300
11301 this.change_selections(Default::default(), window, cx, |s| {
11302 s.select(new_selections);
11303 });
11304
11305 this.request_autoscroll(Autoscroll::fit(), cx);
11306 });
11307 }
11308
11309 pub fn move_selection_on_drop(
11310 &mut self,
11311 selection: &Selection<Anchor>,
11312 target: DisplayPoint,
11313 is_cut: bool,
11314 window: &mut Window,
11315 cx: &mut Context<Self>,
11316 ) {
11317 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11318 let buffer = &display_map.buffer_snapshot;
11319 let mut edits = Vec::new();
11320 let insert_point = display_map
11321 .clip_point(target, Bias::Left)
11322 .to_point(&display_map);
11323 let text = buffer
11324 .text_for_range(selection.start..selection.end)
11325 .collect::<String>();
11326 if is_cut {
11327 edits.push(((selection.start..selection.end), String::new()));
11328 }
11329 let insert_anchor = buffer.anchor_before(insert_point);
11330 edits.push(((insert_anchor..insert_anchor), text));
11331 let last_edit_start = insert_anchor.bias_left(buffer);
11332 let last_edit_end = insert_anchor.bias_right(buffer);
11333 self.transact(window, cx, |this, window, cx| {
11334 this.buffer.update(cx, |buffer, cx| {
11335 buffer.edit(edits, None, cx);
11336 });
11337 this.change_selections(Default::default(), window, cx, |s| {
11338 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11339 });
11340 });
11341 }
11342
11343 pub fn clear_selection_drag_state(&mut self) {
11344 self.selection_drag_state = SelectionDragState::None;
11345 }
11346
11347 pub fn duplicate(
11348 &mut self,
11349 upwards: bool,
11350 whole_lines: bool,
11351 window: &mut Window,
11352 cx: &mut Context<Self>,
11353 ) {
11354 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11355
11356 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11357 let buffer = &display_map.buffer_snapshot;
11358 let selections = self.selections.all::<Point>(cx);
11359
11360 let mut edits = Vec::new();
11361 let mut selections_iter = selections.iter().peekable();
11362 while let Some(selection) = selections_iter.next() {
11363 let mut rows = selection.spanned_rows(false, &display_map);
11364 // duplicate line-wise
11365 if whole_lines || selection.start == selection.end {
11366 // Avoid duplicating the same lines twice.
11367 while let Some(next_selection) = selections_iter.peek() {
11368 let next_rows = next_selection.spanned_rows(false, &display_map);
11369 if next_rows.start < rows.end {
11370 rows.end = next_rows.end;
11371 selections_iter.next().unwrap();
11372 } else {
11373 break;
11374 }
11375 }
11376
11377 // Copy the text from the selected row region and splice it either at the start
11378 // or end of the region.
11379 let start = Point::new(rows.start.0, 0);
11380 let end = Point::new(
11381 rows.end.previous_row().0,
11382 buffer.line_len(rows.end.previous_row()),
11383 );
11384 let text = buffer
11385 .text_for_range(start..end)
11386 .chain(Some("\n"))
11387 .collect::<String>();
11388 let insert_location = if upwards {
11389 Point::new(rows.end.0, 0)
11390 } else {
11391 start
11392 };
11393 edits.push((insert_location..insert_location, text));
11394 } else {
11395 // duplicate character-wise
11396 let start = selection.start;
11397 let end = selection.end;
11398 let text = buffer.text_for_range(start..end).collect::<String>();
11399 edits.push((selection.end..selection.end, text));
11400 }
11401 }
11402
11403 self.transact(window, cx, |this, _, cx| {
11404 this.buffer.update(cx, |buffer, cx| {
11405 buffer.edit(edits, None, cx);
11406 });
11407
11408 this.request_autoscroll(Autoscroll::fit(), cx);
11409 });
11410 }
11411
11412 pub fn duplicate_line_up(
11413 &mut self,
11414 _: &DuplicateLineUp,
11415 window: &mut Window,
11416 cx: &mut Context<Self>,
11417 ) {
11418 self.duplicate(true, true, window, cx);
11419 }
11420
11421 pub fn duplicate_line_down(
11422 &mut self,
11423 _: &DuplicateLineDown,
11424 window: &mut Window,
11425 cx: &mut Context<Self>,
11426 ) {
11427 self.duplicate(false, true, window, cx);
11428 }
11429
11430 pub fn duplicate_selection(
11431 &mut self,
11432 _: &DuplicateSelection,
11433 window: &mut Window,
11434 cx: &mut Context<Self>,
11435 ) {
11436 self.duplicate(false, false, window, cx);
11437 }
11438
11439 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11440 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11441 if self.mode.is_single_line() {
11442 cx.propagate();
11443 return;
11444 }
11445
11446 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11447 let buffer = self.buffer.read(cx).snapshot(cx);
11448
11449 let mut edits = Vec::new();
11450 let mut unfold_ranges = Vec::new();
11451 let mut refold_creases = Vec::new();
11452
11453 let selections = self.selections.all::<Point>(cx);
11454 let mut selections = selections.iter().peekable();
11455 let mut contiguous_row_selections = Vec::new();
11456 let mut new_selections = Vec::new();
11457
11458 while let Some(selection) = selections.next() {
11459 // Find all the selections that span a contiguous row range
11460 let (start_row, end_row) = consume_contiguous_rows(
11461 &mut contiguous_row_selections,
11462 selection,
11463 &display_map,
11464 &mut selections,
11465 );
11466
11467 // Move the text spanned by the row range to be before the line preceding the row range
11468 if start_row.0 > 0 {
11469 let range_to_move = Point::new(
11470 start_row.previous_row().0,
11471 buffer.line_len(start_row.previous_row()),
11472 )
11473 ..Point::new(
11474 end_row.previous_row().0,
11475 buffer.line_len(end_row.previous_row()),
11476 );
11477 let insertion_point = display_map
11478 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11479 .0;
11480
11481 // Don't move lines across excerpts
11482 if buffer
11483 .excerpt_containing(insertion_point..range_to_move.end)
11484 .is_some()
11485 {
11486 let text = buffer
11487 .text_for_range(range_to_move.clone())
11488 .flat_map(|s| s.chars())
11489 .skip(1)
11490 .chain(['\n'])
11491 .collect::<String>();
11492
11493 edits.push((
11494 buffer.anchor_after(range_to_move.start)
11495 ..buffer.anchor_before(range_to_move.end),
11496 String::new(),
11497 ));
11498 let insertion_anchor = buffer.anchor_after(insertion_point);
11499 edits.push((insertion_anchor..insertion_anchor, text));
11500
11501 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11502
11503 // Move selections up
11504 new_selections.extend(contiguous_row_selections.drain(..).map(
11505 |mut selection| {
11506 selection.start.row -= row_delta;
11507 selection.end.row -= row_delta;
11508 selection
11509 },
11510 ));
11511
11512 // Move folds up
11513 unfold_ranges.push(range_to_move.clone());
11514 for fold in display_map.folds_in_range(
11515 buffer.anchor_before(range_to_move.start)
11516 ..buffer.anchor_after(range_to_move.end),
11517 ) {
11518 let mut start = fold.range.start.to_point(&buffer);
11519 let mut end = fold.range.end.to_point(&buffer);
11520 start.row -= row_delta;
11521 end.row -= row_delta;
11522 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11523 }
11524 }
11525 }
11526
11527 // If we didn't move line(s), preserve the existing selections
11528 new_selections.append(&mut contiguous_row_selections);
11529 }
11530
11531 self.transact(window, cx, |this, window, cx| {
11532 this.unfold_ranges(&unfold_ranges, true, true, cx);
11533 this.buffer.update(cx, |buffer, cx| {
11534 for (range, text) in edits {
11535 buffer.edit([(range, text)], None, cx);
11536 }
11537 });
11538 this.fold_creases(refold_creases, true, window, cx);
11539 this.change_selections(Default::default(), window, cx, |s| {
11540 s.select(new_selections);
11541 })
11542 });
11543 }
11544
11545 pub fn move_line_down(
11546 &mut self,
11547 _: &MoveLineDown,
11548 window: &mut Window,
11549 cx: &mut Context<Self>,
11550 ) {
11551 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11552 if self.mode.is_single_line() {
11553 cx.propagate();
11554 return;
11555 }
11556
11557 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11558 let buffer = self.buffer.read(cx).snapshot(cx);
11559
11560 let mut edits = Vec::new();
11561 let mut unfold_ranges = Vec::new();
11562 let mut refold_creases = Vec::new();
11563
11564 let selections = self.selections.all::<Point>(cx);
11565 let mut selections = selections.iter().peekable();
11566 let mut contiguous_row_selections = Vec::new();
11567 let mut new_selections = Vec::new();
11568
11569 while let Some(selection) = selections.next() {
11570 // Find all the selections that span a contiguous row range
11571 let (start_row, end_row) = consume_contiguous_rows(
11572 &mut contiguous_row_selections,
11573 selection,
11574 &display_map,
11575 &mut selections,
11576 );
11577
11578 // Move the text spanned by the row range to be after the last line of the row range
11579 if end_row.0 <= buffer.max_point().row {
11580 let range_to_move =
11581 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11582 let insertion_point = display_map
11583 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11584 .0;
11585
11586 // Don't move lines across excerpt boundaries
11587 if buffer
11588 .excerpt_containing(range_to_move.start..insertion_point)
11589 .is_some()
11590 {
11591 let mut text = String::from("\n");
11592 text.extend(buffer.text_for_range(range_to_move.clone()));
11593 text.pop(); // Drop trailing newline
11594 edits.push((
11595 buffer.anchor_after(range_to_move.start)
11596 ..buffer.anchor_before(range_to_move.end),
11597 String::new(),
11598 ));
11599 let insertion_anchor = buffer.anchor_after(insertion_point);
11600 edits.push((insertion_anchor..insertion_anchor, text));
11601
11602 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11603
11604 // Move selections down
11605 new_selections.extend(contiguous_row_selections.drain(..).map(
11606 |mut selection| {
11607 selection.start.row += row_delta;
11608 selection.end.row += row_delta;
11609 selection
11610 },
11611 ));
11612
11613 // Move folds down
11614 unfold_ranges.push(range_to_move.clone());
11615 for fold in display_map.folds_in_range(
11616 buffer.anchor_before(range_to_move.start)
11617 ..buffer.anchor_after(range_to_move.end),
11618 ) {
11619 let mut start = fold.range.start.to_point(&buffer);
11620 let mut end = fold.range.end.to_point(&buffer);
11621 start.row += row_delta;
11622 end.row += row_delta;
11623 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11624 }
11625 }
11626 }
11627
11628 // If we didn't move line(s), preserve the existing selections
11629 new_selections.append(&mut contiguous_row_selections);
11630 }
11631
11632 self.transact(window, cx, |this, window, cx| {
11633 this.unfold_ranges(&unfold_ranges, true, true, cx);
11634 this.buffer.update(cx, |buffer, cx| {
11635 for (range, text) in edits {
11636 buffer.edit([(range, text)], None, cx);
11637 }
11638 });
11639 this.fold_creases(refold_creases, true, window, cx);
11640 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
11641 });
11642 }
11643
11644 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11645 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11646 let text_layout_details = &self.text_layout_details(window);
11647 self.transact(window, cx, |this, window, cx| {
11648 let edits = this.change_selections(Default::default(), window, cx, |s| {
11649 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11650 s.move_with(|display_map, selection| {
11651 if !selection.is_empty() {
11652 return;
11653 }
11654
11655 let mut head = selection.head();
11656 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11657 if head.column() == display_map.line_len(head.row()) {
11658 transpose_offset = display_map
11659 .buffer_snapshot
11660 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11661 }
11662
11663 if transpose_offset == 0 {
11664 return;
11665 }
11666
11667 *head.column_mut() += 1;
11668 head = display_map.clip_point(head, Bias::Right);
11669 let goal = SelectionGoal::HorizontalPosition(
11670 display_map
11671 .x_for_display_point(head, text_layout_details)
11672 .into(),
11673 );
11674 selection.collapse_to(head, goal);
11675
11676 let transpose_start = display_map
11677 .buffer_snapshot
11678 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11679 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
11680 let transpose_end = display_map
11681 .buffer_snapshot
11682 .clip_offset(transpose_offset + 1, Bias::Right);
11683 if let Some(ch) =
11684 display_map.buffer_snapshot.chars_at(transpose_start).next()
11685 {
11686 edits.push((transpose_start..transpose_offset, String::new()));
11687 edits.push((transpose_end..transpose_end, ch.to_string()));
11688 }
11689 }
11690 });
11691 edits
11692 });
11693 this.buffer
11694 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11695 let selections = this.selections.all::<usize>(cx);
11696 this.change_selections(Default::default(), window, cx, |s| {
11697 s.select(selections);
11698 });
11699 });
11700 }
11701
11702 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
11703 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11704 if self.mode.is_single_line() {
11705 cx.propagate();
11706 return;
11707 }
11708
11709 self.rewrap_impl(RewrapOptions::default(), cx)
11710 }
11711
11712 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
11713 let buffer = self.buffer.read(cx).snapshot(cx);
11714 let selections = self.selections.all::<Point>(cx);
11715
11716 // Split selections to respect paragraph, indent, and comment prefix boundaries.
11717 let wrap_ranges = selections.into_iter().flat_map(|selection| {
11718 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
11719 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11720 .peekable();
11721
11722 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
11723 row
11724 } else {
11725 return Vec::new();
11726 };
11727
11728 let language_settings = buffer.language_settings_at(selection.head(), cx);
11729 let language_scope = buffer.language_scope_at(selection.head());
11730
11731 let indent_and_prefix_for_row =
11732 |row: u32| -> (IndentSize, Option<String>, Option<String>) {
11733 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
11734 let (comment_prefix, rewrap_prefix) =
11735 if let Some(language_scope) = &language_scope {
11736 let indent_end = Point::new(row, indent.len);
11737 let comment_prefix = language_scope
11738 .line_comment_prefixes()
11739 .iter()
11740 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11741 .map(|prefix| prefix.to_string());
11742 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11743 let line_text_after_indent = buffer
11744 .text_for_range(indent_end..line_end)
11745 .collect::<String>();
11746 let rewrap_prefix = language_scope
11747 .rewrap_prefixes()
11748 .iter()
11749 .find_map(|prefix_regex| {
11750 prefix_regex.find(&line_text_after_indent).map(|mat| {
11751 if mat.start() == 0 {
11752 Some(mat.as_str().to_string())
11753 } else {
11754 None
11755 }
11756 })
11757 })
11758 .flatten();
11759 (comment_prefix, rewrap_prefix)
11760 } else {
11761 (None, None)
11762 };
11763 (indent, comment_prefix, rewrap_prefix)
11764 };
11765
11766 let mut ranges = Vec::new();
11767 let from_empty_selection = selection.is_empty();
11768
11769 let mut current_range_start = first_row;
11770 let mut prev_row = first_row;
11771 let (
11772 mut current_range_indent,
11773 mut current_range_comment_prefix,
11774 mut current_range_rewrap_prefix,
11775 ) = indent_and_prefix_for_row(first_row);
11776
11777 for row in non_blank_rows_iter.skip(1) {
11778 let has_paragraph_break = row > prev_row + 1;
11779
11780 let (row_indent, row_comment_prefix, row_rewrap_prefix) =
11781 indent_and_prefix_for_row(row);
11782
11783 let has_indent_change = row_indent != current_range_indent;
11784 let has_comment_change = row_comment_prefix != current_range_comment_prefix;
11785
11786 let has_boundary_change = has_comment_change
11787 || row_rewrap_prefix.is_some()
11788 || (has_indent_change && current_range_comment_prefix.is_some());
11789
11790 if has_paragraph_break || has_boundary_change {
11791 ranges.push((
11792 language_settings.clone(),
11793 Point::new(current_range_start, 0)
11794 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
11795 current_range_indent,
11796 current_range_comment_prefix.clone(),
11797 current_range_rewrap_prefix.clone(),
11798 from_empty_selection,
11799 ));
11800 current_range_start = row;
11801 current_range_indent = row_indent;
11802 current_range_comment_prefix = row_comment_prefix;
11803 current_range_rewrap_prefix = row_rewrap_prefix;
11804 }
11805 prev_row = row;
11806 }
11807
11808 ranges.push((
11809 language_settings.clone(),
11810 Point::new(current_range_start, 0)
11811 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
11812 current_range_indent,
11813 current_range_comment_prefix,
11814 current_range_rewrap_prefix,
11815 from_empty_selection,
11816 ));
11817
11818 ranges
11819 });
11820
11821 let mut edits = Vec::new();
11822 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
11823
11824 for (
11825 language_settings,
11826 wrap_range,
11827 indent_size,
11828 comment_prefix,
11829 rewrap_prefix,
11830 from_empty_selection,
11831 ) in wrap_ranges
11832 {
11833 let mut start_row = wrap_range.start.row;
11834 let mut end_row = wrap_range.end.row;
11835
11836 // Skip selections that overlap with a range that has already been rewrapped.
11837 let selection_range = start_row..end_row;
11838 if rewrapped_row_ranges
11839 .iter()
11840 .any(|range| range.overlaps(&selection_range))
11841 {
11842 continue;
11843 }
11844
11845 let tab_size = language_settings.tab_size;
11846
11847 let indent_prefix = indent_size.chars().collect::<String>();
11848 let mut line_prefix = indent_prefix.clone();
11849 let mut inside_comment = false;
11850 if let Some(prefix) = &comment_prefix {
11851 line_prefix.push_str(prefix);
11852 inside_comment = true;
11853 }
11854 if let Some(prefix) = &rewrap_prefix {
11855 line_prefix.push_str(prefix);
11856 }
11857
11858 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
11859 RewrapBehavior::InComments => inside_comment,
11860 RewrapBehavior::InSelections => !wrap_range.is_empty(),
11861 RewrapBehavior::Anywhere => true,
11862 };
11863
11864 let should_rewrap = options.override_language_settings
11865 || allow_rewrap_based_on_language
11866 || self.hard_wrap.is_some();
11867 if !should_rewrap {
11868 continue;
11869 }
11870
11871 if from_empty_selection {
11872 'expand_upwards: while start_row > 0 {
11873 let prev_row = start_row - 1;
11874 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
11875 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
11876 && !buffer.is_line_blank(MultiBufferRow(prev_row))
11877 {
11878 start_row = prev_row;
11879 } else {
11880 break 'expand_upwards;
11881 }
11882 }
11883
11884 'expand_downwards: while end_row < buffer.max_point().row {
11885 let next_row = end_row + 1;
11886 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
11887 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
11888 && !buffer.is_line_blank(MultiBufferRow(next_row))
11889 {
11890 end_row = next_row;
11891 } else {
11892 break 'expand_downwards;
11893 }
11894 }
11895 }
11896
11897 let start = Point::new(start_row, 0);
11898 let start_offset = start.to_offset(&buffer);
11899 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
11900 let selection_text = buffer.text_for_range(start..end).collect::<String>();
11901 let Some(lines_without_prefixes) = selection_text
11902 .lines()
11903 .enumerate()
11904 .map(|(ix, line)| {
11905 let line_trimmed = line.trim_start();
11906 if rewrap_prefix.is_some() && ix > 0 {
11907 Ok(line_trimmed)
11908 } else {
11909 line_trimmed
11910 .strip_prefix(&line_prefix.trim_start())
11911 .with_context(|| {
11912 format!("line did not start with prefix {line_prefix:?}: {line:?}")
11913 })
11914 }
11915 })
11916 .collect::<Result<Vec<_>, _>>()
11917 .log_err()
11918 else {
11919 continue;
11920 };
11921
11922 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
11923 buffer
11924 .language_settings_at(Point::new(start_row, 0), cx)
11925 .preferred_line_length as usize
11926 });
11927
11928 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
11929 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
11930 } else {
11931 line_prefix.clone()
11932 };
11933
11934 let wrapped_text = wrap_with_prefix(
11935 line_prefix,
11936 subsequent_lines_prefix,
11937 lines_without_prefixes.join("\n"),
11938 wrap_column,
11939 tab_size,
11940 options.preserve_existing_whitespace,
11941 );
11942
11943 // TODO: should always use char-based diff while still supporting cursor behavior that
11944 // matches vim.
11945 let mut diff_options = DiffOptions::default();
11946 if options.override_language_settings {
11947 diff_options.max_word_diff_len = 0;
11948 diff_options.max_word_diff_line_count = 0;
11949 } else {
11950 diff_options.max_word_diff_len = usize::MAX;
11951 diff_options.max_word_diff_line_count = usize::MAX;
11952 }
11953
11954 for (old_range, new_text) in
11955 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
11956 {
11957 let edit_start = buffer.anchor_after(start_offset + old_range.start);
11958 let edit_end = buffer.anchor_after(start_offset + old_range.end);
11959 edits.push((edit_start..edit_end, new_text));
11960 }
11961
11962 rewrapped_row_ranges.push(start_row..=end_row);
11963 }
11964
11965 self.buffer
11966 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11967 }
11968
11969 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
11970 let mut text = String::new();
11971 let buffer = self.buffer.read(cx).snapshot(cx);
11972 let mut selections = self.selections.all::<Point>(cx);
11973 let mut clipboard_selections = Vec::with_capacity(selections.len());
11974 {
11975 let max_point = buffer.max_point();
11976 let mut is_first = true;
11977 for selection in &mut selections {
11978 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11979 if is_entire_line {
11980 selection.start = Point::new(selection.start.row, 0);
11981 if !selection.is_empty() && selection.end.column == 0 {
11982 selection.end = cmp::min(max_point, selection.end);
11983 } else {
11984 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
11985 }
11986 selection.goal = SelectionGoal::None;
11987 }
11988 if is_first {
11989 is_first = false;
11990 } else {
11991 text += "\n";
11992 }
11993 let mut len = 0;
11994 for chunk in buffer.text_for_range(selection.start..selection.end) {
11995 text.push_str(chunk);
11996 len += chunk.len();
11997 }
11998 clipboard_selections.push(ClipboardSelection {
11999 len,
12000 is_entire_line,
12001 first_line_indent: buffer
12002 .indent_size_for_line(MultiBufferRow(selection.start.row))
12003 .len,
12004 });
12005 }
12006 }
12007
12008 self.transact(window, cx, |this, window, cx| {
12009 this.change_selections(Default::default(), window, cx, |s| {
12010 s.select(selections);
12011 });
12012 this.insert("", window, cx);
12013 });
12014 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12015 }
12016
12017 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12018 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12019 let item = self.cut_common(window, cx);
12020 cx.write_to_clipboard(item);
12021 }
12022
12023 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12024 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12025 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12026 s.move_with(|snapshot, sel| {
12027 if sel.is_empty() {
12028 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
12029 }
12030 });
12031 });
12032 let item = self.cut_common(window, cx);
12033 cx.set_global(KillRing(item))
12034 }
12035
12036 pub fn kill_ring_yank(
12037 &mut self,
12038 _: &KillRingYank,
12039 window: &mut Window,
12040 cx: &mut Context<Self>,
12041 ) {
12042 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12043 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12044 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12045 (kill_ring.text().to_string(), kill_ring.metadata_json())
12046 } else {
12047 return;
12048 }
12049 } else {
12050 return;
12051 };
12052 self.do_paste(&text, metadata, false, window, cx);
12053 }
12054
12055 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12056 self.do_copy(true, cx);
12057 }
12058
12059 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12060 self.do_copy(false, cx);
12061 }
12062
12063 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12064 let selections = self.selections.all::<Point>(cx);
12065 let buffer = self.buffer.read(cx).read(cx);
12066 let mut text = String::new();
12067
12068 let mut clipboard_selections = Vec::with_capacity(selections.len());
12069 {
12070 let max_point = buffer.max_point();
12071 let mut is_first = true;
12072 for selection in &selections {
12073 let mut start = selection.start;
12074 let mut end = selection.end;
12075 let is_entire_line = selection.is_empty() || self.selections.line_mode;
12076 if is_entire_line {
12077 start = Point::new(start.row, 0);
12078 end = cmp::min(max_point, Point::new(end.row + 1, 0));
12079 }
12080
12081 let mut trimmed_selections = Vec::new();
12082 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12083 let row = MultiBufferRow(start.row);
12084 let first_indent = buffer.indent_size_for_line(row);
12085 if first_indent.len == 0 || start.column > first_indent.len {
12086 trimmed_selections.push(start..end);
12087 } else {
12088 trimmed_selections.push(
12089 Point::new(row.0, first_indent.len)
12090 ..Point::new(row.0, buffer.line_len(row)),
12091 );
12092 for row in start.row + 1..=end.row {
12093 let mut line_len = buffer.line_len(MultiBufferRow(row));
12094 if row == end.row {
12095 line_len = end.column;
12096 }
12097 if line_len == 0 {
12098 trimmed_selections
12099 .push(Point::new(row, 0)..Point::new(row, line_len));
12100 continue;
12101 }
12102 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12103 if row_indent_size.len >= first_indent.len {
12104 trimmed_selections.push(
12105 Point::new(row, first_indent.len)..Point::new(row, line_len),
12106 );
12107 } else {
12108 trimmed_selections.clear();
12109 trimmed_selections.push(start..end);
12110 break;
12111 }
12112 }
12113 }
12114 } else {
12115 trimmed_selections.push(start..end);
12116 }
12117
12118 for trimmed_range in trimmed_selections {
12119 if is_first {
12120 is_first = false;
12121 } else {
12122 text += "\n";
12123 }
12124 let mut len = 0;
12125 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12126 text.push_str(chunk);
12127 len += chunk.len();
12128 }
12129 clipboard_selections.push(ClipboardSelection {
12130 len,
12131 is_entire_line,
12132 first_line_indent: buffer
12133 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12134 .len,
12135 });
12136 }
12137 }
12138 }
12139
12140 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12141 text,
12142 clipboard_selections,
12143 ));
12144 }
12145
12146 pub fn do_paste(
12147 &mut self,
12148 text: &String,
12149 clipboard_selections: Option<Vec<ClipboardSelection>>,
12150 handle_entire_lines: bool,
12151 window: &mut Window,
12152 cx: &mut Context<Self>,
12153 ) {
12154 if self.read_only(cx) {
12155 return;
12156 }
12157
12158 let clipboard_text = Cow::Borrowed(text);
12159
12160 self.transact(window, cx, |this, window, cx| {
12161 if let Some(mut clipboard_selections) = clipboard_selections {
12162 let old_selections = this.selections.all::<usize>(cx);
12163 let all_selections_were_entire_line =
12164 clipboard_selections.iter().all(|s| s.is_entire_line);
12165 let first_selection_indent_column =
12166 clipboard_selections.first().map(|s| s.first_line_indent);
12167 if clipboard_selections.len() != old_selections.len() {
12168 clipboard_selections.drain(..);
12169 }
12170 let cursor_offset = this.selections.last::<usize>(cx).head();
12171 let mut auto_indent_on_paste = true;
12172
12173 this.buffer.update(cx, |buffer, cx| {
12174 let snapshot = buffer.read(cx);
12175 auto_indent_on_paste = snapshot
12176 .language_settings_at(cursor_offset, cx)
12177 .auto_indent_on_paste;
12178
12179 let mut start_offset = 0;
12180 let mut edits = Vec::new();
12181 let mut original_indent_columns = Vec::new();
12182 for (ix, selection) in old_selections.iter().enumerate() {
12183 let to_insert;
12184 let entire_line;
12185 let original_indent_column;
12186 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12187 let end_offset = start_offset + clipboard_selection.len;
12188 to_insert = &clipboard_text[start_offset..end_offset];
12189 entire_line = clipboard_selection.is_entire_line;
12190 start_offset = end_offset + 1;
12191 original_indent_column = Some(clipboard_selection.first_line_indent);
12192 } else {
12193 to_insert = clipboard_text.as_str();
12194 entire_line = all_selections_were_entire_line;
12195 original_indent_column = first_selection_indent_column
12196 }
12197
12198 // If the corresponding selection was empty when this slice of the
12199 // clipboard text was written, then the entire line containing the
12200 // selection was copied. If this selection is also currently empty,
12201 // then paste the line before the current line of the buffer.
12202 let range = if selection.is_empty() && handle_entire_lines && entire_line {
12203 let column = selection.start.to_point(&snapshot).column as usize;
12204 let line_start = selection.start - column;
12205 line_start..line_start
12206 } else {
12207 selection.range()
12208 };
12209
12210 edits.push((range, to_insert));
12211 original_indent_columns.push(original_indent_column);
12212 }
12213 drop(snapshot);
12214
12215 buffer.edit(
12216 edits,
12217 if auto_indent_on_paste {
12218 Some(AutoindentMode::Block {
12219 original_indent_columns,
12220 })
12221 } else {
12222 None
12223 },
12224 cx,
12225 );
12226 });
12227
12228 let selections = this.selections.all::<usize>(cx);
12229 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12230 } else {
12231 this.insert(&clipboard_text, window, cx);
12232 }
12233 });
12234 }
12235
12236 pub fn diff_clipboard_with_selection(
12237 &mut self,
12238 _: &DiffClipboardWithSelection,
12239 window: &mut Window,
12240 cx: &mut Context<Self>,
12241 ) {
12242 let selections = self.selections.all::<usize>(cx);
12243
12244 if selections.is_empty() {
12245 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12246 return;
12247 };
12248
12249 let clipboard_text = match cx.read_from_clipboard() {
12250 Some(item) => match item.entries().first() {
12251 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
12252 _ => None,
12253 },
12254 None => None,
12255 };
12256
12257 let Some(clipboard_text) = clipboard_text else {
12258 log::warn!("Clipboard doesn't contain text.");
12259 return;
12260 };
12261
12262 window.dispatch_action(
12263 Box::new(DiffClipboardWithSelectionData {
12264 clipboard_text,
12265 editor: cx.entity(),
12266 }),
12267 cx,
12268 );
12269 }
12270
12271 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12272 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12273 if let Some(item) = cx.read_from_clipboard() {
12274 let entries = item.entries();
12275
12276 match entries.first() {
12277 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12278 // of all the pasted entries.
12279 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12280 .do_paste(
12281 clipboard_string.text(),
12282 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12283 true,
12284 window,
12285 cx,
12286 ),
12287 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12288 }
12289 }
12290 }
12291
12292 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12293 if self.read_only(cx) {
12294 return;
12295 }
12296
12297 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12298
12299 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12300 if let Some((selections, _)) =
12301 self.selection_history.transaction(transaction_id).cloned()
12302 {
12303 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12304 s.select_anchors(selections.to_vec());
12305 });
12306 } else {
12307 log::error!(
12308 "No entry in selection_history found for undo. \
12309 This may correspond to a bug where undo does not update the selection. \
12310 If this is occurring, please add details to \
12311 https://github.com/zed-industries/zed/issues/22692"
12312 );
12313 }
12314 self.request_autoscroll(Autoscroll::fit(), cx);
12315 self.unmark_text(window, cx);
12316 self.refresh_edit_prediction(true, false, window, cx);
12317 cx.emit(EditorEvent::Edited { transaction_id });
12318 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12319 }
12320 }
12321
12322 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12323 if self.read_only(cx) {
12324 return;
12325 }
12326
12327 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12328
12329 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12330 if let Some((_, Some(selections))) =
12331 self.selection_history.transaction(transaction_id).cloned()
12332 {
12333 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12334 s.select_anchors(selections.to_vec());
12335 });
12336 } else {
12337 log::error!(
12338 "No entry in selection_history found for redo. \
12339 This may correspond to a bug where undo does not update the selection. \
12340 If this is occurring, please add details to \
12341 https://github.com/zed-industries/zed/issues/22692"
12342 );
12343 }
12344 self.request_autoscroll(Autoscroll::fit(), cx);
12345 self.unmark_text(window, cx);
12346 self.refresh_edit_prediction(true, false, window, cx);
12347 cx.emit(EditorEvent::Edited { transaction_id });
12348 }
12349 }
12350
12351 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12352 self.buffer
12353 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12354 }
12355
12356 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12357 self.buffer
12358 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12359 }
12360
12361 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
12362 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12363 self.change_selections(Default::default(), window, cx, |s| {
12364 s.move_with(|map, selection| {
12365 let cursor = if selection.is_empty() {
12366 movement::left(map, selection.start)
12367 } else {
12368 selection.start
12369 };
12370 selection.collapse_to(cursor, SelectionGoal::None);
12371 });
12372 })
12373 }
12374
12375 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12376 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12377 self.change_selections(Default::default(), window, cx, |s| {
12378 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12379 })
12380 }
12381
12382 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
12383 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12384 self.change_selections(Default::default(), window, cx, |s| {
12385 s.move_with(|map, selection| {
12386 let cursor = if selection.is_empty() {
12387 movement::right(map, selection.end)
12388 } else {
12389 selection.end
12390 };
12391 selection.collapse_to(cursor, SelectionGoal::None)
12392 });
12393 })
12394 }
12395
12396 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12397 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12398 self.change_selections(Default::default(), window, cx, |s| {
12399 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
12400 })
12401 }
12402
12403 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
12404 if self.take_rename(true, window, cx).is_some() {
12405 return;
12406 }
12407
12408 if self.mode.is_single_line() {
12409 cx.propagate();
12410 return;
12411 }
12412
12413 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12414
12415 let text_layout_details = &self.text_layout_details(window);
12416 let selection_count = self.selections.count();
12417 let first_selection = self.selections.first_anchor();
12418
12419 self.change_selections(Default::default(), window, cx, |s| {
12420 s.move_with(|map, selection| {
12421 if !selection.is_empty() {
12422 selection.goal = SelectionGoal::None;
12423 }
12424 let (cursor, goal) = movement::up(
12425 map,
12426 selection.start,
12427 selection.goal,
12428 false,
12429 text_layout_details,
12430 );
12431 selection.collapse_to(cursor, goal);
12432 });
12433 });
12434
12435 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12436 {
12437 cx.propagate();
12438 }
12439 }
12440
12441 pub fn move_up_by_lines(
12442 &mut self,
12443 action: &MoveUpByLines,
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::up_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 move_down_by_lines(
12479 &mut self,
12480 action: &MoveDownByLines,
12481 window: &mut Window,
12482 cx: &mut Context<Self>,
12483 ) {
12484 if self.take_rename(true, window, cx).is_some() {
12485 return;
12486 }
12487
12488 if self.mode.is_single_line() {
12489 cx.propagate();
12490 return;
12491 }
12492
12493 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12494
12495 let text_layout_details = &self.text_layout_details(window);
12496
12497 self.change_selections(Default::default(), window, cx, |s| {
12498 s.move_with(|map, selection| {
12499 if !selection.is_empty() {
12500 selection.goal = SelectionGoal::None;
12501 }
12502 let (cursor, goal) = movement::down_by_rows(
12503 map,
12504 selection.start,
12505 action.lines,
12506 selection.goal,
12507 false,
12508 text_layout_details,
12509 );
12510 selection.collapse_to(cursor, goal);
12511 });
12512 })
12513 }
12514
12515 pub fn select_down_by_lines(
12516 &mut self,
12517 action: &SelectDownByLines,
12518 window: &mut Window,
12519 cx: &mut Context<Self>,
12520 ) {
12521 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12522 let text_layout_details = &self.text_layout_details(window);
12523 self.change_selections(Default::default(), window, cx, |s| {
12524 s.move_heads_with(|map, head, goal| {
12525 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
12526 })
12527 })
12528 }
12529
12530 pub fn select_up_by_lines(
12531 &mut self,
12532 action: &SelectUpByLines,
12533 window: &mut Window,
12534 cx: &mut Context<Self>,
12535 ) {
12536 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12537 let text_layout_details = &self.text_layout_details(window);
12538 self.change_selections(Default::default(), window, cx, |s| {
12539 s.move_heads_with(|map, head, goal| {
12540 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
12541 })
12542 })
12543 }
12544
12545 pub fn select_page_up(
12546 &mut self,
12547 _: &SelectPageUp,
12548 window: &mut Window,
12549 cx: &mut Context<Self>,
12550 ) {
12551 let Some(row_count) = self.visible_row_count() else {
12552 return;
12553 };
12554
12555 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12556
12557 let text_layout_details = &self.text_layout_details(window);
12558
12559 self.change_selections(Default::default(), window, cx, |s| {
12560 s.move_heads_with(|map, head, goal| {
12561 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
12562 })
12563 })
12564 }
12565
12566 pub fn move_page_up(
12567 &mut self,
12568 action: &MovePageUp,
12569 window: &mut Window,
12570 cx: &mut Context<Self>,
12571 ) {
12572 if self.take_rename(true, window, cx).is_some() {
12573 return;
12574 }
12575
12576 if self
12577 .context_menu
12578 .borrow_mut()
12579 .as_mut()
12580 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
12581 .unwrap_or(false)
12582 {
12583 return;
12584 }
12585
12586 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12587 cx.propagate();
12588 return;
12589 }
12590
12591 let Some(row_count) = self.visible_row_count() else {
12592 return;
12593 };
12594
12595 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12596
12597 let effects = if action.center_cursor {
12598 SelectionEffects::scroll(Autoscroll::center())
12599 } else {
12600 SelectionEffects::default()
12601 };
12602
12603 let text_layout_details = &self.text_layout_details(window);
12604
12605 self.change_selections(effects, window, cx, |s| {
12606 s.move_with(|map, selection| {
12607 if !selection.is_empty() {
12608 selection.goal = SelectionGoal::None;
12609 }
12610 let (cursor, goal) = movement::up_by_rows(
12611 map,
12612 selection.end,
12613 row_count,
12614 selection.goal,
12615 false,
12616 text_layout_details,
12617 );
12618 selection.collapse_to(cursor, goal);
12619 });
12620 });
12621 }
12622
12623 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
12624 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12625 let text_layout_details = &self.text_layout_details(window);
12626 self.change_selections(Default::default(), window, cx, |s| {
12627 s.move_heads_with(|map, head, goal| {
12628 movement::up(map, head, goal, false, text_layout_details)
12629 })
12630 })
12631 }
12632
12633 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
12634 self.take_rename(true, window, cx);
12635
12636 if self.mode.is_single_line() {
12637 cx.propagate();
12638 return;
12639 }
12640
12641 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12642
12643 let text_layout_details = &self.text_layout_details(window);
12644 let selection_count = self.selections.count();
12645 let first_selection = self.selections.first_anchor();
12646
12647 self.change_selections(Default::default(), window, cx, |s| {
12648 s.move_with(|map, selection| {
12649 if !selection.is_empty() {
12650 selection.goal = SelectionGoal::None;
12651 }
12652 let (cursor, goal) = movement::down(
12653 map,
12654 selection.end,
12655 selection.goal,
12656 false,
12657 text_layout_details,
12658 );
12659 selection.collapse_to(cursor, goal);
12660 });
12661 });
12662
12663 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12664 {
12665 cx.propagate();
12666 }
12667 }
12668
12669 pub fn select_page_down(
12670 &mut self,
12671 _: &SelectPageDown,
12672 window: &mut Window,
12673 cx: &mut Context<Self>,
12674 ) {
12675 let Some(row_count) = self.visible_row_count() else {
12676 return;
12677 };
12678
12679 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12680
12681 let text_layout_details = &self.text_layout_details(window);
12682
12683 self.change_selections(Default::default(), window, cx, |s| {
12684 s.move_heads_with(|map, head, goal| {
12685 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
12686 })
12687 })
12688 }
12689
12690 pub fn move_page_down(
12691 &mut self,
12692 action: &MovePageDown,
12693 window: &mut Window,
12694 cx: &mut Context<Self>,
12695 ) {
12696 if self.take_rename(true, window, cx).is_some() {
12697 return;
12698 }
12699
12700 if self
12701 .context_menu
12702 .borrow_mut()
12703 .as_mut()
12704 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
12705 .unwrap_or(false)
12706 {
12707 return;
12708 }
12709
12710 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12711 cx.propagate();
12712 return;
12713 }
12714
12715 let Some(row_count) = self.visible_row_count() else {
12716 return;
12717 };
12718
12719 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12720
12721 let effects = if action.center_cursor {
12722 SelectionEffects::scroll(Autoscroll::center())
12723 } else {
12724 SelectionEffects::default()
12725 };
12726
12727 let text_layout_details = &self.text_layout_details(window);
12728 self.change_selections(effects, window, cx, |s| {
12729 s.move_with(|map, selection| {
12730 if !selection.is_empty() {
12731 selection.goal = SelectionGoal::None;
12732 }
12733 let (cursor, goal) = movement::down_by_rows(
12734 map,
12735 selection.end,
12736 row_count,
12737 selection.goal,
12738 false,
12739 text_layout_details,
12740 );
12741 selection.collapse_to(cursor, goal);
12742 });
12743 });
12744 }
12745
12746 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
12747 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12748 let text_layout_details = &self.text_layout_details(window);
12749 self.change_selections(Default::default(), window, cx, |s| {
12750 s.move_heads_with(|map, head, goal| {
12751 movement::down(map, head, goal, false, text_layout_details)
12752 })
12753 });
12754 }
12755
12756 pub fn context_menu_first(
12757 &mut self,
12758 _: &ContextMenuFirst,
12759 window: &mut Window,
12760 cx: &mut Context<Self>,
12761 ) {
12762 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12763 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
12764 }
12765 }
12766
12767 pub fn context_menu_prev(
12768 &mut self,
12769 _: &ContextMenuPrevious,
12770 window: &mut Window,
12771 cx: &mut Context<Self>,
12772 ) {
12773 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12774 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
12775 }
12776 }
12777
12778 pub fn context_menu_next(
12779 &mut self,
12780 _: &ContextMenuNext,
12781 window: &mut Window,
12782 cx: &mut Context<Self>,
12783 ) {
12784 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12785 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
12786 }
12787 }
12788
12789 pub fn context_menu_last(
12790 &mut self,
12791 _: &ContextMenuLast,
12792 window: &mut Window,
12793 cx: &mut Context<Self>,
12794 ) {
12795 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12796 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
12797 }
12798 }
12799
12800 pub fn signature_help_prev(
12801 &mut self,
12802 _: &SignatureHelpPrevious,
12803 _: &mut Window,
12804 cx: &mut Context<Self>,
12805 ) {
12806 if let Some(popover) = self.signature_help_state.popover_mut() {
12807 if popover.current_signature == 0 {
12808 popover.current_signature = popover.signatures.len() - 1;
12809 } else {
12810 popover.current_signature -= 1;
12811 }
12812 cx.notify();
12813 }
12814 }
12815
12816 pub fn signature_help_next(
12817 &mut self,
12818 _: &SignatureHelpNext,
12819 _: &mut Window,
12820 cx: &mut Context<Self>,
12821 ) {
12822 if let Some(popover) = self.signature_help_state.popover_mut() {
12823 if popover.current_signature + 1 == popover.signatures.len() {
12824 popover.current_signature = 0;
12825 } else {
12826 popover.current_signature += 1;
12827 }
12828 cx.notify();
12829 }
12830 }
12831
12832 pub fn move_to_previous_word_start(
12833 &mut self,
12834 _: &MoveToPreviousWordStart,
12835 window: &mut Window,
12836 cx: &mut Context<Self>,
12837 ) {
12838 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12839 self.change_selections(Default::default(), window, cx, |s| {
12840 s.move_cursors_with(|map, head, _| {
12841 (
12842 movement::previous_word_start(map, head),
12843 SelectionGoal::None,
12844 )
12845 });
12846 })
12847 }
12848
12849 pub fn move_to_previous_subword_start(
12850 &mut self,
12851 _: &MoveToPreviousSubwordStart,
12852 window: &mut Window,
12853 cx: &mut Context<Self>,
12854 ) {
12855 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12856 self.change_selections(Default::default(), window, cx, |s| {
12857 s.move_cursors_with(|map, head, _| {
12858 (
12859 movement::previous_subword_start(map, head),
12860 SelectionGoal::None,
12861 )
12862 });
12863 })
12864 }
12865
12866 pub fn select_to_previous_word_start(
12867 &mut self,
12868 _: &SelectToPreviousWordStart,
12869 window: &mut Window,
12870 cx: &mut Context<Self>,
12871 ) {
12872 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12873 self.change_selections(Default::default(), window, cx, |s| {
12874 s.move_heads_with(|map, head, _| {
12875 (
12876 movement::previous_word_start(map, head),
12877 SelectionGoal::None,
12878 )
12879 });
12880 })
12881 }
12882
12883 pub fn select_to_previous_subword_start(
12884 &mut self,
12885 _: &SelectToPreviousSubwordStart,
12886 window: &mut Window,
12887 cx: &mut Context<Self>,
12888 ) {
12889 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12890 self.change_selections(Default::default(), window, cx, |s| {
12891 s.move_heads_with(|map, head, _| {
12892 (
12893 movement::previous_subword_start(map, head),
12894 SelectionGoal::None,
12895 )
12896 });
12897 })
12898 }
12899
12900 pub fn delete_to_previous_word_start(
12901 &mut self,
12902 action: &DeleteToPreviousWordStart,
12903 window: &mut Window,
12904 cx: &mut Context<Self>,
12905 ) {
12906 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12907 self.transact(window, cx, |this, window, cx| {
12908 this.select_autoclose_pair(window, cx);
12909 this.change_selections(Default::default(), window, cx, |s| {
12910 s.move_with(|map, selection| {
12911 if selection.is_empty() {
12912 let cursor = if action.ignore_newlines {
12913 movement::previous_word_start(map, selection.head())
12914 } else {
12915 movement::previous_word_start_or_newline(map, selection.head())
12916 };
12917 selection.set_head(cursor, SelectionGoal::None);
12918 }
12919 });
12920 });
12921 this.insert("", window, cx);
12922 });
12923 }
12924
12925 pub fn delete_to_previous_subword_start(
12926 &mut self,
12927 _: &DeleteToPreviousSubwordStart,
12928 window: &mut Window,
12929 cx: &mut Context<Self>,
12930 ) {
12931 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12932 self.transact(window, cx, |this, window, cx| {
12933 this.select_autoclose_pair(window, cx);
12934 this.change_selections(Default::default(), window, cx, |s| {
12935 s.move_with(|map, selection| {
12936 if selection.is_empty() {
12937 let cursor = movement::previous_subword_start(map, selection.head());
12938 selection.set_head(cursor, SelectionGoal::None);
12939 }
12940 });
12941 });
12942 this.insert("", window, cx);
12943 });
12944 }
12945
12946 pub fn move_to_next_word_end(
12947 &mut self,
12948 _: &MoveToNextWordEnd,
12949 window: &mut Window,
12950 cx: &mut Context<Self>,
12951 ) {
12952 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12953 self.change_selections(Default::default(), window, cx, |s| {
12954 s.move_cursors_with(|map, head, _| {
12955 (movement::next_word_end(map, head), SelectionGoal::None)
12956 });
12957 })
12958 }
12959
12960 pub fn move_to_next_subword_end(
12961 &mut self,
12962 _: &MoveToNextSubwordEnd,
12963 window: &mut Window,
12964 cx: &mut Context<Self>,
12965 ) {
12966 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12967 self.change_selections(Default::default(), window, cx, |s| {
12968 s.move_cursors_with(|map, head, _| {
12969 (movement::next_subword_end(map, head), SelectionGoal::None)
12970 });
12971 })
12972 }
12973
12974 pub fn select_to_next_word_end(
12975 &mut self,
12976 _: &SelectToNextWordEnd,
12977 window: &mut Window,
12978 cx: &mut Context<Self>,
12979 ) {
12980 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12981 self.change_selections(Default::default(), window, cx, |s| {
12982 s.move_heads_with(|map, head, _| {
12983 (movement::next_word_end(map, head), SelectionGoal::None)
12984 });
12985 })
12986 }
12987
12988 pub fn select_to_next_subword_end(
12989 &mut self,
12990 _: &SelectToNextSubwordEnd,
12991 window: &mut Window,
12992 cx: &mut Context<Self>,
12993 ) {
12994 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12995 self.change_selections(Default::default(), window, cx, |s| {
12996 s.move_heads_with(|map, head, _| {
12997 (movement::next_subword_end(map, head), SelectionGoal::None)
12998 });
12999 })
13000 }
13001
13002 pub fn delete_to_next_word_end(
13003 &mut self,
13004 action: &DeleteToNextWordEnd,
13005 window: &mut Window,
13006 cx: &mut Context<Self>,
13007 ) {
13008 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13009 self.transact(window, cx, |this, window, cx| {
13010 this.change_selections(Default::default(), window, cx, |s| {
13011 s.move_with(|map, selection| {
13012 if selection.is_empty() {
13013 let cursor = if action.ignore_newlines {
13014 movement::next_word_end(map, selection.head())
13015 } else {
13016 movement::next_word_end_or_newline(map, selection.head())
13017 };
13018 selection.set_head(cursor, SelectionGoal::None);
13019 }
13020 });
13021 });
13022 this.insert("", window, cx);
13023 });
13024 }
13025
13026 pub fn delete_to_next_subword_end(
13027 &mut self,
13028 _: &DeleteToNextSubwordEnd,
13029 window: &mut Window,
13030 cx: &mut Context<Self>,
13031 ) {
13032 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13033 self.transact(window, cx, |this, window, cx| {
13034 this.change_selections(Default::default(), window, cx, |s| {
13035 s.move_with(|map, selection| {
13036 if selection.is_empty() {
13037 let cursor = movement::next_subword_end(map, selection.head());
13038 selection.set_head(cursor, SelectionGoal::None);
13039 }
13040 });
13041 });
13042 this.insert("", window, cx);
13043 });
13044 }
13045
13046 pub fn move_to_beginning_of_line(
13047 &mut self,
13048 action: &MoveToBeginningOfLine,
13049 window: &mut Window,
13050 cx: &mut Context<Self>,
13051 ) {
13052 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13053 self.change_selections(Default::default(), window, cx, |s| {
13054 s.move_cursors_with(|map, head, _| {
13055 (
13056 movement::indented_line_beginning(
13057 map,
13058 head,
13059 action.stop_at_soft_wraps,
13060 action.stop_at_indent,
13061 ),
13062 SelectionGoal::None,
13063 )
13064 });
13065 })
13066 }
13067
13068 pub fn select_to_beginning_of_line(
13069 &mut self,
13070 action: &SelectToBeginningOfLine,
13071 window: &mut Window,
13072 cx: &mut Context<Self>,
13073 ) {
13074 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13075 self.change_selections(Default::default(), window, cx, |s| {
13076 s.move_heads_with(|map, head, _| {
13077 (
13078 movement::indented_line_beginning(
13079 map,
13080 head,
13081 action.stop_at_soft_wraps,
13082 action.stop_at_indent,
13083 ),
13084 SelectionGoal::None,
13085 )
13086 });
13087 });
13088 }
13089
13090 pub fn delete_to_beginning_of_line(
13091 &mut self,
13092 action: &DeleteToBeginningOfLine,
13093 window: &mut Window,
13094 cx: &mut Context<Self>,
13095 ) {
13096 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13097 self.transact(window, cx, |this, window, cx| {
13098 this.change_selections(Default::default(), window, cx, |s| {
13099 s.move_with(|_, selection| {
13100 selection.reversed = true;
13101 });
13102 });
13103
13104 this.select_to_beginning_of_line(
13105 &SelectToBeginningOfLine {
13106 stop_at_soft_wraps: false,
13107 stop_at_indent: action.stop_at_indent,
13108 },
13109 window,
13110 cx,
13111 );
13112 this.backspace(&Backspace, window, cx);
13113 });
13114 }
13115
13116 pub fn move_to_end_of_line(
13117 &mut self,
13118 action: &MoveToEndOfLine,
13119 window: &mut Window,
13120 cx: &mut Context<Self>,
13121 ) {
13122 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13123 self.change_selections(Default::default(), window, cx, |s| {
13124 s.move_cursors_with(|map, head, _| {
13125 (
13126 movement::line_end(map, head, action.stop_at_soft_wraps),
13127 SelectionGoal::None,
13128 )
13129 });
13130 })
13131 }
13132
13133 pub fn select_to_end_of_line(
13134 &mut self,
13135 action: &SelectToEndOfLine,
13136 window: &mut Window,
13137 cx: &mut Context<Self>,
13138 ) {
13139 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13140 self.change_selections(Default::default(), window, cx, |s| {
13141 s.move_heads_with(|map, head, _| {
13142 (
13143 movement::line_end(map, head, action.stop_at_soft_wraps),
13144 SelectionGoal::None,
13145 )
13146 });
13147 })
13148 }
13149
13150 pub fn delete_to_end_of_line(
13151 &mut self,
13152 _: &DeleteToEndOfLine,
13153 window: &mut Window,
13154 cx: &mut Context<Self>,
13155 ) {
13156 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13157 self.transact(window, cx, |this, window, cx| {
13158 this.select_to_end_of_line(
13159 &SelectToEndOfLine {
13160 stop_at_soft_wraps: false,
13161 },
13162 window,
13163 cx,
13164 );
13165 this.delete(&Delete, window, cx);
13166 });
13167 }
13168
13169 pub fn cut_to_end_of_line(
13170 &mut self,
13171 _: &CutToEndOfLine,
13172 window: &mut Window,
13173 cx: &mut Context<Self>,
13174 ) {
13175 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13176 self.transact(window, cx, |this, window, cx| {
13177 this.select_to_end_of_line(
13178 &SelectToEndOfLine {
13179 stop_at_soft_wraps: false,
13180 },
13181 window,
13182 cx,
13183 );
13184 this.cut(&Cut, window, cx);
13185 });
13186 }
13187
13188 pub fn move_to_start_of_paragraph(
13189 &mut self,
13190 _: &MoveToStartOfParagraph,
13191 window: &mut Window,
13192 cx: &mut Context<Self>,
13193 ) {
13194 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13195 cx.propagate();
13196 return;
13197 }
13198 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13199 self.change_selections(Default::default(), window, cx, |s| {
13200 s.move_with(|map, selection| {
13201 selection.collapse_to(
13202 movement::start_of_paragraph(map, selection.head(), 1),
13203 SelectionGoal::None,
13204 )
13205 });
13206 })
13207 }
13208
13209 pub fn move_to_end_of_paragraph(
13210 &mut self,
13211 _: &MoveToEndOfParagraph,
13212 window: &mut Window,
13213 cx: &mut Context<Self>,
13214 ) {
13215 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13216 cx.propagate();
13217 return;
13218 }
13219 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13220 self.change_selections(Default::default(), window, cx, |s| {
13221 s.move_with(|map, selection| {
13222 selection.collapse_to(
13223 movement::end_of_paragraph(map, selection.head(), 1),
13224 SelectionGoal::None,
13225 )
13226 });
13227 })
13228 }
13229
13230 pub fn select_to_start_of_paragraph(
13231 &mut self,
13232 _: &SelectToStartOfParagraph,
13233 window: &mut Window,
13234 cx: &mut Context<Self>,
13235 ) {
13236 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13237 cx.propagate();
13238 return;
13239 }
13240 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13241 self.change_selections(Default::default(), window, cx, |s| {
13242 s.move_heads_with(|map, head, _| {
13243 (
13244 movement::start_of_paragraph(map, head, 1),
13245 SelectionGoal::None,
13246 )
13247 });
13248 })
13249 }
13250
13251 pub fn select_to_end_of_paragraph(
13252 &mut self,
13253 _: &SelectToEndOfParagraph,
13254 window: &mut Window,
13255 cx: &mut Context<Self>,
13256 ) {
13257 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13258 cx.propagate();
13259 return;
13260 }
13261 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13262 self.change_selections(Default::default(), window, cx, |s| {
13263 s.move_heads_with(|map, head, _| {
13264 (
13265 movement::end_of_paragraph(map, head, 1),
13266 SelectionGoal::None,
13267 )
13268 });
13269 })
13270 }
13271
13272 pub fn move_to_start_of_excerpt(
13273 &mut self,
13274 _: &MoveToStartOfExcerpt,
13275 window: &mut Window,
13276 cx: &mut Context<Self>,
13277 ) {
13278 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13279 cx.propagate();
13280 return;
13281 }
13282 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13283 self.change_selections(Default::default(), window, cx, |s| {
13284 s.move_with(|map, selection| {
13285 selection.collapse_to(
13286 movement::start_of_excerpt(
13287 map,
13288 selection.head(),
13289 workspace::searchable::Direction::Prev,
13290 ),
13291 SelectionGoal::None,
13292 )
13293 });
13294 })
13295 }
13296
13297 pub fn move_to_start_of_next_excerpt(
13298 &mut self,
13299 _: &MoveToStartOfNextExcerpt,
13300 window: &mut Window,
13301 cx: &mut Context<Self>,
13302 ) {
13303 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13304 cx.propagate();
13305 return;
13306 }
13307
13308 self.change_selections(Default::default(), window, cx, |s| {
13309 s.move_with(|map, selection| {
13310 selection.collapse_to(
13311 movement::start_of_excerpt(
13312 map,
13313 selection.head(),
13314 workspace::searchable::Direction::Next,
13315 ),
13316 SelectionGoal::None,
13317 )
13318 });
13319 })
13320 }
13321
13322 pub fn move_to_end_of_excerpt(
13323 &mut self,
13324 _: &MoveToEndOfExcerpt,
13325 window: &mut Window,
13326 cx: &mut Context<Self>,
13327 ) {
13328 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13329 cx.propagate();
13330 return;
13331 }
13332 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13333 self.change_selections(Default::default(), window, cx, |s| {
13334 s.move_with(|map, selection| {
13335 selection.collapse_to(
13336 movement::end_of_excerpt(
13337 map,
13338 selection.head(),
13339 workspace::searchable::Direction::Next,
13340 ),
13341 SelectionGoal::None,
13342 )
13343 });
13344 })
13345 }
13346
13347 pub fn move_to_end_of_previous_excerpt(
13348 &mut self,
13349 _: &MoveToEndOfPreviousExcerpt,
13350 window: &mut Window,
13351 cx: &mut Context<Self>,
13352 ) {
13353 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13354 cx.propagate();
13355 return;
13356 }
13357 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13358 self.change_selections(Default::default(), window, cx, |s| {
13359 s.move_with(|map, selection| {
13360 selection.collapse_to(
13361 movement::end_of_excerpt(
13362 map,
13363 selection.head(),
13364 workspace::searchable::Direction::Prev,
13365 ),
13366 SelectionGoal::None,
13367 )
13368 });
13369 })
13370 }
13371
13372 pub fn select_to_start_of_excerpt(
13373 &mut self,
13374 _: &SelectToStartOfExcerpt,
13375 window: &mut Window,
13376 cx: &mut Context<Self>,
13377 ) {
13378 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13379 cx.propagate();
13380 return;
13381 }
13382 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13383 self.change_selections(Default::default(), window, cx, |s| {
13384 s.move_heads_with(|map, head, _| {
13385 (
13386 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13387 SelectionGoal::None,
13388 )
13389 });
13390 })
13391 }
13392
13393 pub fn select_to_start_of_next_excerpt(
13394 &mut self,
13395 _: &SelectToStartOfNextExcerpt,
13396 window: &mut Window,
13397 cx: &mut Context<Self>,
13398 ) {
13399 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13400 cx.propagate();
13401 return;
13402 }
13403 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13404 self.change_selections(Default::default(), window, cx, |s| {
13405 s.move_heads_with(|map, head, _| {
13406 (
13407 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
13408 SelectionGoal::None,
13409 )
13410 });
13411 })
13412 }
13413
13414 pub fn select_to_end_of_excerpt(
13415 &mut self,
13416 _: &SelectToEndOfExcerpt,
13417 window: &mut Window,
13418 cx: &mut Context<Self>,
13419 ) {
13420 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13421 cx.propagate();
13422 return;
13423 }
13424 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13425 self.change_selections(Default::default(), window, cx, |s| {
13426 s.move_heads_with(|map, head, _| {
13427 (
13428 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
13429 SelectionGoal::None,
13430 )
13431 });
13432 })
13433 }
13434
13435 pub fn select_to_end_of_previous_excerpt(
13436 &mut self,
13437 _: &SelectToEndOfPreviousExcerpt,
13438 window: &mut Window,
13439 cx: &mut Context<Self>,
13440 ) {
13441 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13442 cx.propagate();
13443 return;
13444 }
13445 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13446 self.change_selections(Default::default(), window, cx, |s| {
13447 s.move_heads_with(|map, head, _| {
13448 (
13449 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13450 SelectionGoal::None,
13451 )
13452 });
13453 })
13454 }
13455
13456 pub fn move_to_beginning(
13457 &mut self,
13458 _: &MoveToBeginning,
13459 window: &mut Window,
13460 cx: &mut Context<Self>,
13461 ) {
13462 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13463 cx.propagate();
13464 return;
13465 }
13466 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13467 self.change_selections(Default::default(), window, cx, |s| {
13468 s.select_ranges(vec![0..0]);
13469 });
13470 }
13471
13472 pub fn select_to_beginning(
13473 &mut self,
13474 _: &SelectToBeginning,
13475 window: &mut Window,
13476 cx: &mut Context<Self>,
13477 ) {
13478 let mut selection = self.selections.last::<Point>(cx);
13479 selection.set_head(Point::zero(), SelectionGoal::None);
13480 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13481 self.change_selections(Default::default(), window, cx, |s| {
13482 s.select(vec![selection]);
13483 });
13484 }
13485
13486 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
13487 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13488 cx.propagate();
13489 return;
13490 }
13491 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13492 let cursor = self.buffer.read(cx).read(cx).len();
13493 self.change_selections(Default::default(), window, cx, |s| {
13494 s.select_ranges(vec![cursor..cursor])
13495 });
13496 }
13497
13498 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
13499 self.nav_history = nav_history;
13500 }
13501
13502 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
13503 self.nav_history.as_ref()
13504 }
13505
13506 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
13507 self.push_to_nav_history(
13508 self.selections.newest_anchor().head(),
13509 None,
13510 false,
13511 true,
13512 cx,
13513 );
13514 }
13515
13516 fn push_to_nav_history(
13517 &mut self,
13518 cursor_anchor: Anchor,
13519 new_position: Option<Point>,
13520 is_deactivate: bool,
13521 always: bool,
13522 cx: &mut Context<Self>,
13523 ) {
13524 if let Some(nav_history) = self.nav_history.as_mut() {
13525 let buffer = self.buffer.read(cx).read(cx);
13526 let cursor_position = cursor_anchor.to_point(&buffer);
13527 let scroll_state = self.scroll_manager.anchor();
13528 let scroll_top_row = scroll_state.top_row(&buffer);
13529 drop(buffer);
13530
13531 if let Some(new_position) = new_position {
13532 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
13533 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
13534 return;
13535 }
13536 }
13537
13538 nav_history.push(
13539 Some(NavigationData {
13540 cursor_anchor,
13541 cursor_position,
13542 scroll_anchor: scroll_state,
13543 scroll_top_row,
13544 }),
13545 cx,
13546 );
13547 cx.emit(EditorEvent::PushedToNavHistory {
13548 anchor: cursor_anchor,
13549 is_deactivate,
13550 })
13551 }
13552 }
13553
13554 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
13555 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13556 let buffer = self.buffer.read(cx).snapshot(cx);
13557 let mut selection = self.selections.first::<usize>(cx);
13558 selection.set_head(buffer.len(), SelectionGoal::None);
13559 self.change_selections(Default::default(), window, cx, |s| {
13560 s.select(vec![selection]);
13561 });
13562 }
13563
13564 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
13565 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13566 let end = self.buffer.read(cx).read(cx).len();
13567 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13568 s.select_ranges(vec![0..end]);
13569 });
13570 }
13571
13572 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
13573 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13574 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13575 let mut selections = self.selections.all::<Point>(cx);
13576 let max_point = display_map.buffer_snapshot.max_point();
13577 for selection in &mut selections {
13578 let rows = selection.spanned_rows(true, &display_map);
13579 selection.start = Point::new(rows.start.0, 0);
13580 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
13581 selection.reversed = false;
13582 }
13583 self.change_selections(Default::default(), window, cx, |s| {
13584 s.select(selections);
13585 });
13586 }
13587
13588 pub fn split_selection_into_lines(
13589 &mut self,
13590 _: &SplitSelectionIntoLines,
13591 window: &mut Window,
13592 cx: &mut Context<Self>,
13593 ) {
13594 let selections = self
13595 .selections
13596 .all::<Point>(cx)
13597 .into_iter()
13598 .map(|selection| selection.start..selection.end)
13599 .collect::<Vec<_>>();
13600 self.unfold_ranges(&selections, true, true, cx);
13601
13602 let mut new_selection_ranges = Vec::new();
13603 {
13604 let buffer = self.buffer.read(cx).read(cx);
13605 for selection in selections {
13606 for row in selection.start.row..selection.end.row {
13607 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
13608 new_selection_ranges.push(cursor..cursor);
13609 }
13610
13611 let is_multiline_selection = selection.start.row != selection.end.row;
13612 // Don't insert last one if it's a multi-line selection ending at the start of a line,
13613 // so this action feels more ergonomic when paired with other selection operations
13614 let should_skip_last = is_multiline_selection && selection.end.column == 0;
13615 if !should_skip_last {
13616 new_selection_ranges.push(selection.end..selection.end);
13617 }
13618 }
13619 }
13620 self.change_selections(Default::default(), window, cx, |s| {
13621 s.select_ranges(new_selection_ranges);
13622 });
13623 }
13624
13625 pub fn add_selection_above(
13626 &mut self,
13627 _: &AddSelectionAbove,
13628 window: &mut Window,
13629 cx: &mut Context<Self>,
13630 ) {
13631 self.add_selection(true, window, cx);
13632 }
13633
13634 pub fn add_selection_below(
13635 &mut self,
13636 _: &AddSelectionBelow,
13637 window: &mut Window,
13638 cx: &mut Context<Self>,
13639 ) {
13640 self.add_selection(false, window, cx);
13641 }
13642
13643 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
13644 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13645
13646 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13647 let all_selections = self.selections.all::<Point>(cx);
13648 let text_layout_details = self.text_layout_details(window);
13649
13650 let (mut columnar_selections, new_selections_to_columnarize) = {
13651 if let Some(state) = self.add_selections_state.as_ref() {
13652 let columnar_selection_ids: HashSet<_> = state
13653 .groups
13654 .iter()
13655 .flat_map(|group| group.stack.iter())
13656 .copied()
13657 .collect();
13658
13659 all_selections
13660 .into_iter()
13661 .partition(|s| columnar_selection_ids.contains(&s.id))
13662 } else {
13663 (Vec::new(), all_selections)
13664 }
13665 };
13666
13667 let mut state = self
13668 .add_selections_state
13669 .take()
13670 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
13671
13672 for selection in new_selections_to_columnarize {
13673 let range = selection.display_range(&display_map).sorted();
13674 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
13675 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
13676 let positions = start_x.min(end_x)..start_x.max(end_x);
13677 let mut stack = Vec::new();
13678 for row in range.start.row().0..=range.end.row().0 {
13679 if let Some(selection) = self.selections.build_columnar_selection(
13680 &display_map,
13681 DisplayRow(row),
13682 &positions,
13683 selection.reversed,
13684 &text_layout_details,
13685 ) {
13686 stack.push(selection.id);
13687 columnar_selections.push(selection);
13688 }
13689 }
13690 if !stack.is_empty() {
13691 if above {
13692 stack.reverse();
13693 }
13694 state.groups.push(AddSelectionsGroup { above, stack });
13695 }
13696 }
13697
13698 let mut final_selections = Vec::new();
13699 let end_row = if above {
13700 DisplayRow(0)
13701 } else {
13702 display_map.max_point().row()
13703 };
13704
13705 let mut last_added_item_per_group = HashMap::default();
13706 for group in state.groups.iter_mut() {
13707 if let Some(last_id) = group.stack.last() {
13708 last_added_item_per_group.insert(*last_id, group);
13709 }
13710 }
13711
13712 for selection in columnar_selections {
13713 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
13714 if above == group.above {
13715 let range = selection.display_range(&display_map).sorted();
13716 debug_assert_eq!(range.start.row(), range.end.row());
13717 let mut row = range.start.row();
13718 let positions =
13719 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
13720 px(start)..px(end)
13721 } else {
13722 let start_x =
13723 display_map.x_for_display_point(range.start, &text_layout_details);
13724 let end_x =
13725 display_map.x_for_display_point(range.end, &text_layout_details);
13726 start_x.min(end_x)..start_x.max(end_x)
13727 };
13728
13729 let mut maybe_new_selection = None;
13730 while row != end_row {
13731 if above {
13732 row.0 -= 1;
13733 } else {
13734 row.0 += 1;
13735 }
13736 if let Some(new_selection) = self.selections.build_columnar_selection(
13737 &display_map,
13738 row,
13739 &positions,
13740 selection.reversed,
13741 &text_layout_details,
13742 ) {
13743 maybe_new_selection = Some(new_selection);
13744 break;
13745 }
13746 }
13747
13748 if let Some(new_selection) = maybe_new_selection {
13749 group.stack.push(new_selection.id);
13750 if above {
13751 final_selections.push(new_selection);
13752 final_selections.push(selection);
13753 } else {
13754 final_selections.push(selection);
13755 final_selections.push(new_selection);
13756 }
13757 } else {
13758 final_selections.push(selection);
13759 }
13760 } else {
13761 group.stack.pop();
13762 }
13763 } else {
13764 final_selections.push(selection);
13765 }
13766 }
13767
13768 self.change_selections(Default::default(), window, cx, |s| {
13769 s.select(final_selections);
13770 });
13771
13772 let final_selection_ids: HashSet<_> = self
13773 .selections
13774 .all::<Point>(cx)
13775 .iter()
13776 .map(|s| s.id)
13777 .collect();
13778 state.groups.retain_mut(|group| {
13779 // selections might get merged above so we remove invalid items from stacks
13780 group.stack.retain(|id| final_selection_ids.contains(id));
13781
13782 // single selection in stack can be treated as initial state
13783 group.stack.len() > 1
13784 });
13785
13786 if !state.groups.is_empty() {
13787 self.add_selections_state = Some(state);
13788 }
13789 }
13790
13791 fn select_match_ranges(
13792 &mut self,
13793 range: Range<usize>,
13794 reversed: bool,
13795 replace_newest: bool,
13796 auto_scroll: Option<Autoscroll>,
13797 window: &mut Window,
13798 cx: &mut Context<Editor>,
13799 ) {
13800 self.unfold_ranges(
13801 std::slice::from_ref(&range),
13802 false,
13803 auto_scroll.is_some(),
13804 cx,
13805 );
13806 let effects = if let Some(scroll) = auto_scroll {
13807 SelectionEffects::scroll(scroll)
13808 } else {
13809 SelectionEffects::no_scroll()
13810 };
13811 self.change_selections(effects, window, cx, |s| {
13812 if replace_newest {
13813 s.delete(s.newest_anchor().id);
13814 }
13815 if reversed {
13816 s.insert_range(range.end..range.start);
13817 } else {
13818 s.insert_range(range);
13819 }
13820 });
13821 }
13822
13823 pub fn select_next_match_internal(
13824 &mut self,
13825 display_map: &DisplaySnapshot,
13826 replace_newest: bool,
13827 autoscroll: Option<Autoscroll>,
13828 window: &mut Window,
13829 cx: &mut Context<Self>,
13830 ) -> Result<()> {
13831 let buffer = &display_map.buffer_snapshot;
13832 let mut selections = self.selections.all::<usize>(cx);
13833 if let Some(mut select_next_state) = self.select_next_state.take() {
13834 let query = &select_next_state.query;
13835 if !select_next_state.done {
13836 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13837 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13838 let mut next_selected_range = None;
13839
13840 let bytes_after_last_selection =
13841 buffer.bytes_in_range(last_selection.end..buffer.len());
13842 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
13843 let query_matches = query
13844 .stream_find_iter(bytes_after_last_selection)
13845 .map(|result| (last_selection.end, result))
13846 .chain(
13847 query
13848 .stream_find_iter(bytes_before_first_selection)
13849 .map(|result| (0, result)),
13850 );
13851
13852 for (start_offset, query_match) in query_matches {
13853 let query_match = query_match.unwrap(); // can only fail due to I/O
13854 let offset_range =
13855 start_offset + query_match.start()..start_offset + query_match.end();
13856
13857 if !select_next_state.wordwise
13858 || (!buffer.is_inside_word(offset_range.start, false)
13859 && !buffer.is_inside_word(offset_range.end, false))
13860 {
13861 // TODO: This is n^2, because we might check all the selections
13862 if !selections
13863 .iter()
13864 .any(|selection| selection.range().overlaps(&offset_range))
13865 {
13866 next_selected_range = Some(offset_range);
13867 break;
13868 }
13869 }
13870 }
13871
13872 if let Some(next_selected_range) = next_selected_range {
13873 self.select_match_ranges(
13874 next_selected_range,
13875 last_selection.reversed,
13876 replace_newest,
13877 autoscroll,
13878 window,
13879 cx,
13880 );
13881 } else {
13882 select_next_state.done = true;
13883 }
13884 }
13885
13886 self.select_next_state = Some(select_next_state);
13887 } else {
13888 let mut only_carets = true;
13889 let mut same_text_selected = true;
13890 let mut selected_text = None;
13891
13892 let mut selections_iter = selections.iter().peekable();
13893 while let Some(selection) = selections_iter.next() {
13894 if selection.start != selection.end {
13895 only_carets = false;
13896 }
13897
13898 if same_text_selected {
13899 if selected_text.is_none() {
13900 selected_text =
13901 Some(buffer.text_for_range(selection.range()).collect::<String>());
13902 }
13903
13904 if let Some(next_selection) = selections_iter.peek() {
13905 if next_selection.range().len() == selection.range().len() {
13906 let next_selected_text = buffer
13907 .text_for_range(next_selection.range())
13908 .collect::<String>();
13909 if Some(next_selected_text) != selected_text {
13910 same_text_selected = false;
13911 selected_text = None;
13912 }
13913 } else {
13914 same_text_selected = false;
13915 selected_text = None;
13916 }
13917 }
13918 }
13919 }
13920
13921 if only_carets {
13922 for selection in &mut selections {
13923 let (word_range, _) = buffer.surrounding_word(selection.start, false);
13924 selection.start = word_range.start;
13925 selection.end = word_range.end;
13926 selection.goal = SelectionGoal::None;
13927 selection.reversed = false;
13928 self.select_match_ranges(
13929 selection.start..selection.end,
13930 selection.reversed,
13931 replace_newest,
13932 autoscroll,
13933 window,
13934 cx,
13935 );
13936 }
13937
13938 if selections.len() == 1 {
13939 let selection = selections
13940 .last()
13941 .expect("ensured that there's only one selection");
13942 let query = buffer
13943 .text_for_range(selection.start..selection.end)
13944 .collect::<String>();
13945 let is_empty = query.is_empty();
13946 let select_state = SelectNextState {
13947 query: AhoCorasick::new(&[query])?,
13948 wordwise: true,
13949 done: is_empty,
13950 };
13951 self.select_next_state = Some(select_state);
13952 } else {
13953 self.select_next_state = None;
13954 }
13955 } else if let Some(selected_text) = selected_text {
13956 self.select_next_state = Some(SelectNextState {
13957 query: AhoCorasick::new(&[selected_text])?,
13958 wordwise: false,
13959 done: false,
13960 });
13961 self.select_next_match_internal(
13962 display_map,
13963 replace_newest,
13964 autoscroll,
13965 window,
13966 cx,
13967 )?;
13968 }
13969 }
13970 Ok(())
13971 }
13972
13973 pub fn select_all_matches(
13974 &mut self,
13975 _action: &SelectAllMatches,
13976 window: &mut Window,
13977 cx: &mut Context<Self>,
13978 ) -> Result<()> {
13979 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13980
13981 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13982
13983 self.select_next_match_internal(&display_map, false, None, window, cx)?;
13984 let Some(select_next_state) = self.select_next_state.as_mut() else {
13985 return Ok(());
13986 };
13987 if select_next_state.done {
13988 return Ok(());
13989 }
13990
13991 let mut new_selections = Vec::new();
13992
13993 let reversed = self.selections.oldest::<usize>(cx).reversed;
13994 let buffer = &display_map.buffer_snapshot;
13995 let query_matches = select_next_state
13996 .query
13997 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
13998
13999 for query_match in query_matches.into_iter() {
14000 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
14001 let offset_range = if reversed {
14002 query_match.end()..query_match.start()
14003 } else {
14004 query_match.start()..query_match.end()
14005 };
14006
14007 if !select_next_state.wordwise
14008 || (!buffer.is_inside_word(offset_range.start, false)
14009 && !buffer.is_inside_word(offset_range.end, false))
14010 {
14011 new_selections.push(offset_range.start..offset_range.end);
14012 }
14013 }
14014
14015 select_next_state.done = true;
14016
14017 if new_selections.is_empty() {
14018 log::error!("bug: new_selections is empty in select_all_matches");
14019 return Ok(());
14020 }
14021
14022 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14023 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14024 selections.select_ranges(new_selections)
14025 });
14026
14027 Ok(())
14028 }
14029
14030 pub fn select_next(
14031 &mut self,
14032 action: &SelectNext,
14033 window: &mut Window,
14034 cx: &mut Context<Self>,
14035 ) -> Result<()> {
14036 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14037 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14038 self.select_next_match_internal(
14039 &display_map,
14040 action.replace_newest,
14041 Some(Autoscroll::newest()),
14042 window,
14043 cx,
14044 )?;
14045 Ok(())
14046 }
14047
14048 pub fn select_previous(
14049 &mut self,
14050 action: &SelectPrevious,
14051 window: &mut Window,
14052 cx: &mut Context<Self>,
14053 ) -> Result<()> {
14054 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14055 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14056 let buffer = &display_map.buffer_snapshot;
14057 let mut selections = self.selections.all::<usize>(cx);
14058 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14059 let query = &select_prev_state.query;
14060 if !select_prev_state.done {
14061 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14062 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14063 let mut next_selected_range = None;
14064 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14065 let bytes_before_last_selection =
14066 buffer.reversed_bytes_in_range(0..last_selection.start);
14067 let bytes_after_first_selection =
14068 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14069 let query_matches = query
14070 .stream_find_iter(bytes_before_last_selection)
14071 .map(|result| (last_selection.start, result))
14072 .chain(
14073 query
14074 .stream_find_iter(bytes_after_first_selection)
14075 .map(|result| (buffer.len(), result)),
14076 );
14077 for (end_offset, query_match) in query_matches {
14078 let query_match = query_match.unwrap(); // can only fail due to I/O
14079 let offset_range =
14080 end_offset - query_match.end()..end_offset - query_match.start();
14081
14082 if !select_prev_state.wordwise
14083 || (!buffer.is_inside_word(offset_range.start, false)
14084 && !buffer.is_inside_word(offset_range.end, false))
14085 {
14086 next_selected_range = Some(offset_range);
14087 break;
14088 }
14089 }
14090
14091 if let Some(next_selected_range) = next_selected_range {
14092 self.select_match_ranges(
14093 next_selected_range,
14094 last_selection.reversed,
14095 action.replace_newest,
14096 Some(Autoscroll::newest()),
14097 window,
14098 cx,
14099 );
14100 } else {
14101 select_prev_state.done = true;
14102 }
14103 }
14104
14105 self.select_prev_state = Some(select_prev_state);
14106 } else {
14107 let mut only_carets = true;
14108 let mut same_text_selected = true;
14109 let mut selected_text = None;
14110
14111 let mut selections_iter = selections.iter().peekable();
14112 while let Some(selection) = selections_iter.next() {
14113 if selection.start != selection.end {
14114 only_carets = false;
14115 }
14116
14117 if same_text_selected {
14118 if selected_text.is_none() {
14119 selected_text =
14120 Some(buffer.text_for_range(selection.range()).collect::<String>());
14121 }
14122
14123 if let Some(next_selection) = selections_iter.peek() {
14124 if next_selection.range().len() == selection.range().len() {
14125 let next_selected_text = buffer
14126 .text_for_range(next_selection.range())
14127 .collect::<String>();
14128 if Some(next_selected_text) != selected_text {
14129 same_text_selected = false;
14130 selected_text = None;
14131 }
14132 } else {
14133 same_text_selected = false;
14134 selected_text = None;
14135 }
14136 }
14137 }
14138 }
14139
14140 if only_carets {
14141 for selection in &mut selections {
14142 let (word_range, _) = buffer.surrounding_word(selection.start, false);
14143 selection.start = word_range.start;
14144 selection.end = word_range.end;
14145 selection.goal = SelectionGoal::None;
14146 selection.reversed = false;
14147 self.select_match_ranges(
14148 selection.start..selection.end,
14149 selection.reversed,
14150 action.replace_newest,
14151 Some(Autoscroll::newest()),
14152 window,
14153 cx,
14154 );
14155 }
14156 if selections.len() == 1 {
14157 let selection = selections
14158 .last()
14159 .expect("ensured that there's only one selection");
14160 let query = buffer
14161 .text_for_range(selection.start..selection.end)
14162 .collect::<String>();
14163 let is_empty = query.is_empty();
14164 let select_state = SelectNextState {
14165 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
14166 wordwise: true,
14167 done: is_empty,
14168 };
14169 self.select_prev_state = Some(select_state);
14170 } else {
14171 self.select_prev_state = None;
14172 }
14173 } else if let Some(selected_text) = selected_text {
14174 self.select_prev_state = Some(SelectNextState {
14175 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
14176 wordwise: false,
14177 done: false,
14178 });
14179 self.select_previous(action, window, cx)?;
14180 }
14181 }
14182 Ok(())
14183 }
14184
14185 pub fn find_next_match(
14186 &mut self,
14187 _: &FindNextMatch,
14188 window: &mut Window,
14189 cx: &mut Context<Self>,
14190 ) -> Result<()> {
14191 let selections = self.selections.disjoint_anchors();
14192 match selections.first() {
14193 Some(first) if selections.len() >= 2 => {
14194 self.change_selections(Default::default(), window, cx, |s| {
14195 s.select_ranges([first.range()]);
14196 });
14197 }
14198 _ => self.select_next(
14199 &SelectNext {
14200 replace_newest: true,
14201 },
14202 window,
14203 cx,
14204 )?,
14205 }
14206 Ok(())
14207 }
14208
14209 pub fn find_previous_match(
14210 &mut self,
14211 _: &FindPreviousMatch,
14212 window: &mut Window,
14213 cx: &mut Context<Self>,
14214 ) -> Result<()> {
14215 let selections = self.selections.disjoint_anchors();
14216 match selections.last() {
14217 Some(last) if selections.len() >= 2 => {
14218 self.change_selections(Default::default(), window, cx, |s| {
14219 s.select_ranges([last.range()]);
14220 });
14221 }
14222 _ => self.select_previous(
14223 &SelectPrevious {
14224 replace_newest: true,
14225 },
14226 window,
14227 cx,
14228 )?,
14229 }
14230 Ok(())
14231 }
14232
14233 pub fn toggle_comments(
14234 &mut self,
14235 action: &ToggleComments,
14236 window: &mut Window,
14237 cx: &mut Context<Self>,
14238 ) {
14239 if self.read_only(cx) {
14240 return;
14241 }
14242 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14243 let text_layout_details = &self.text_layout_details(window);
14244 self.transact(window, cx, |this, window, cx| {
14245 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
14246 let mut edits = Vec::new();
14247 let mut selection_edit_ranges = Vec::new();
14248 let mut last_toggled_row = None;
14249 let snapshot = this.buffer.read(cx).read(cx);
14250 let empty_str: Arc<str> = Arc::default();
14251 let mut suffixes_inserted = Vec::new();
14252 let ignore_indent = action.ignore_indent;
14253
14254 fn comment_prefix_range(
14255 snapshot: &MultiBufferSnapshot,
14256 row: MultiBufferRow,
14257 comment_prefix: &str,
14258 comment_prefix_whitespace: &str,
14259 ignore_indent: bool,
14260 ) -> Range<Point> {
14261 let indent_size = if ignore_indent {
14262 0
14263 } else {
14264 snapshot.indent_size_for_line(row).len
14265 };
14266
14267 let start = Point::new(row.0, indent_size);
14268
14269 let mut line_bytes = snapshot
14270 .bytes_in_range(start..snapshot.max_point())
14271 .flatten()
14272 .copied();
14273
14274 // If this line currently begins with the line comment prefix, then record
14275 // the range containing the prefix.
14276 if line_bytes
14277 .by_ref()
14278 .take(comment_prefix.len())
14279 .eq(comment_prefix.bytes())
14280 {
14281 // Include any whitespace that matches the comment prefix.
14282 let matching_whitespace_len = line_bytes
14283 .zip(comment_prefix_whitespace.bytes())
14284 .take_while(|(a, b)| a == b)
14285 .count() as u32;
14286 let end = Point::new(
14287 start.row,
14288 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14289 );
14290 start..end
14291 } else {
14292 start..start
14293 }
14294 }
14295
14296 fn comment_suffix_range(
14297 snapshot: &MultiBufferSnapshot,
14298 row: MultiBufferRow,
14299 comment_suffix: &str,
14300 comment_suffix_has_leading_space: bool,
14301 ) -> Range<Point> {
14302 let end = Point::new(row.0, snapshot.line_len(row));
14303 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14304
14305 let mut line_end_bytes = snapshot
14306 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14307 .flatten()
14308 .copied();
14309
14310 let leading_space_len = if suffix_start_column > 0
14311 && line_end_bytes.next() == Some(b' ')
14312 && comment_suffix_has_leading_space
14313 {
14314 1
14315 } else {
14316 0
14317 };
14318
14319 // If this line currently begins with the line comment prefix, then record
14320 // the range containing the prefix.
14321 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
14322 let start = Point::new(end.row, suffix_start_column - leading_space_len);
14323 start..end
14324 } else {
14325 end..end
14326 }
14327 }
14328
14329 // TODO: Handle selections that cross excerpts
14330 for selection in &mut selections {
14331 let start_column = snapshot
14332 .indent_size_for_line(MultiBufferRow(selection.start.row))
14333 .len;
14334 let language = if let Some(language) =
14335 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
14336 {
14337 language
14338 } else {
14339 continue;
14340 };
14341
14342 selection_edit_ranges.clear();
14343
14344 // If multiple selections contain a given row, avoid processing that
14345 // row more than once.
14346 let mut start_row = MultiBufferRow(selection.start.row);
14347 if last_toggled_row == Some(start_row) {
14348 start_row = start_row.next_row();
14349 }
14350 let end_row =
14351 if selection.end.row > selection.start.row && selection.end.column == 0 {
14352 MultiBufferRow(selection.end.row - 1)
14353 } else {
14354 MultiBufferRow(selection.end.row)
14355 };
14356 last_toggled_row = Some(end_row);
14357
14358 if start_row > end_row {
14359 continue;
14360 }
14361
14362 // If the language has line comments, toggle those.
14363 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
14364
14365 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
14366 if ignore_indent {
14367 full_comment_prefixes = full_comment_prefixes
14368 .into_iter()
14369 .map(|s| Arc::from(s.trim_end()))
14370 .collect();
14371 }
14372
14373 if !full_comment_prefixes.is_empty() {
14374 let first_prefix = full_comment_prefixes
14375 .first()
14376 .expect("prefixes is non-empty");
14377 let prefix_trimmed_lengths = full_comment_prefixes
14378 .iter()
14379 .map(|p| p.trim_end_matches(' ').len())
14380 .collect::<SmallVec<[usize; 4]>>();
14381
14382 let mut all_selection_lines_are_comments = true;
14383
14384 for row in start_row.0..=end_row.0 {
14385 let row = MultiBufferRow(row);
14386 if start_row < end_row && snapshot.is_line_blank(row) {
14387 continue;
14388 }
14389
14390 let prefix_range = full_comment_prefixes
14391 .iter()
14392 .zip(prefix_trimmed_lengths.iter().copied())
14393 .map(|(prefix, trimmed_prefix_len)| {
14394 comment_prefix_range(
14395 snapshot.deref(),
14396 row,
14397 &prefix[..trimmed_prefix_len],
14398 &prefix[trimmed_prefix_len..],
14399 ignore_indent,
14400 )
14401 })
14402 .max_by_key(|range| range.end.column - range.start.column)
14403 .expect("prefixes is non-empty");
14404
14405 if prefix_range.is_empty() {
14406 all_selection_lines_are_comments = false;
14407 }
14408
14409 selection_edit_ranges.push(prefix_range);
14410 }
14411
14412 if all_selection_lines_are_comments {
14413 edits.extend(
14414 selection_edit_ranges
14415 .iter()
14416 .cloned()
14417 .map(|range| (range, empty_str.clone())),
14418 );
14419 } else {
14420 let min_column = selection_edit_ranges
14421 .iter()
14422 .map(|range| range.start.column)
14423 .min()
14424 .unwrap_or(0);
14425 edits.extend(selection_edit_ranges.iter().map(|range| {
14426 let position = Point::new(range.start.row, min_column);
14427 (position..position, first_prefix.clone())
14428 }));
14429 }
14430 } else if let Some(BlockCommentConfig {
14431 start: full_comment_prefix,
14432 end: comment_suffix,
14433 ..
14434 }) = language.block_comment()
14435 {
14436 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
14437 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
14438 let prefix_range = comment_prefix_range(
14439 snapshot.deref(),
14440 start_row,
14441 comment_prefix,
14442 comment_prefix_whitespace,
14443 ignore_indent,
14444 );
14445 let suffix_range = comment_suffix_range(
14446 snapshot.deref(),
14447 end_row,
14448 comment_suffix.trim_start_matches(' '),
14449 comment_suffix.starts_with(' '),
14450 );
14451
14452 if prefix_range.is_empty() || suffix_range.is_empty() {
14453 edits.push((
14454 prefix_range.start..prefix_range.start,
14455 full_comment_prefix.clone(),
14456 ));
14457 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
14458 suffixes_inserted.push((end_row, comment_suffix.len()));
14459 } else {
14460 edits.push((prefix_range, empty_str.clone()));
14461 edits.push((suffix_range, empty_str.clone()));
14462 }
14463 } else {
14464 continue;
14465 }
14466 }
14467
14468 drop(snapshot);
14469 this.buffer.update(cx, |buffer, cx| {
14470 buffer.edit(edits, None, cx);
14471 });
14472
14473 // Adjust selections so that they end before any comment suffixes that
14474 // were inserted.
14475 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
14476 let mut selections = this.selections.all::<Point>(cx);
14477 let snapshot = this.buffer.read(cx).read(cx);
14478 for selection in &mut selections {
14479 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
14480 match row.cmp(&MultiBufferRow(selection.end.row)) {
14481 Ordering::Less => {
14482 suffixes_inserted.next();
14483 continue;
14484 }
14485 Ordering::Greater => break,
14486 Ordering::Equal => {
14487 if selection.end.column == snapshot.line_len(row) {
14488 if selection.is_empty() {
14489 selection.start.column -= suffix_len as u32;
14490 }
14491 selection.end.column -= suffix_len as u32;
14492 }
14493 break;
14494 }
14495 }
14496 }
14497 }
14498
14499 drop(snapshot);
14500 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
14501
14502 let selections = this.selections.all::<Point>(cx);
14503 let selections_on_single_row = selections.windows(2).all(|selections| {
14504 selections[0].start.row == selections[1].start.row
14505 && selections[0].end.row == selections[1].end.row
14506 && selections[0].start.row == selections[0].end.row
14507 });
14508 let selections_selecting = selections
14509 .iter()
14510 .any(|selection| selection.start != selection.end);
14511 let advance_downwards = action.advance_downwards
14512 && selections_on_single_row
14513 && !selections_selecting
14514 && !matches!(this.mode, EditorMode::SingleLine { .. });
14515
14516 if advance_downwards {
14517 let snapshot = this.buffer.read(cx).snapshot(cx);
14518
14519 this.change_selections(Default::default(), window, cx, |s| {
14520 s.move_cursors_with(|display_snapshot, display_point, _| {
14521 let mut point = display_point.to_point(display_snapshot);
14522 point.row += 1;
14523 point = snapshot.clip_point(point, Bias::Left);
14524 let display_point = point.to_display_point(display_snapshot);
14525 let goal = SelectionGoal::HorizontalPosition(
14526 display_snapshot
14527 .x_for_display_point(display_point, text_layout_details)
14528 .into(),
14529 );
14530 (display_point, goal)
14531 })
14532 });
14533 }
14534 });
14535 }
14536
14537 pub fn select_enclosing_symbol(
14538 &mut self,
14539 _: &SelectEnclosingSymbol,
14540 window: &mut Window,
14541 cx: &mut Context<Self>,
14542 ) {
14543 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14544
14545 let buffer = self.buffer.read(cx).snapshot(cx);
14546 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
14547
14548 fn update_selection(
14549 selection: &Selection<usize>,
14550 buffer_snap: &MultiBufferSnapshot,
14551 ) -> Option<Selection<usize>> {
14552 let cursor = selection.head();
14553 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
14554 for symbol in symbols.iter().rev() {
14555 let start = symbol.range.start.to_offset(buffer_snap);
14556 let end = symbol.range.end.to_offset(buffer_snap);
14557 let new_range = start..end;
14558 if start < selection.start || end > selection.end {
14559 return Some(Selection {
14560 id: selection.id,
14561 start: new_range.start,
14562 end: new_range.end,
14563 goal: SelectionGoal::None,
14564 reversed: selection.reversed,
14565 });
14566 }
14567 }
14568 None
14569 }
14570
14571 let mut selected_larger_symbol = false;
14572 let new_selections = old_selections
14573 .iter()
14574 .map(|selection| match update_selection(selection, &buffer) {
14575 Some(new_selection) => {
14576 if new_selection.range() != selection.range() {
14577 selected_larger_symbol = true;
14578 }
14579 new_selection
14580 }
14581 None => selection.clone(),
14582 })
14583 .collect::<Vec<_>>();
14584
14585 if selected_larger_symbol {
14586 self.change_selections(Default::default(), window, cx, |s| {
14587 s.select(new_selections);
14588 });
14589 }
14590 }
14591
14592 pub fn select_larger_syntax_node(
14593 &mut self,
14594 _: &SelectLargerSyntaxNode,
14595 window: &mut Window,
14596 cx: &mut Context<Self>,
14597 ) {
14598 let Some(visible_row_count) = self.visible_row_count() else {
14599 return;
14600 };
14601 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
14602 if old_selections.is_empty() {
14603 return;
14604 }
14605
14606 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14607
14608 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14609 let buffer = self.buffer.read(cx).snapshot(cx);
14610
14611 let mut selected_larger_node = false;
14612 let mut new_selections = old_selections
14613 .iter()
14614 .map(|selection| {
14615 let old_range = selection.start..selection.end;
14616
14617 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
14618 // manually select word at selection
14619 if ["string_content", "inline"].contains(&node.kind()) {
14620 let (word_range, _) = buffer.surrounding_word(old_range.start, false);
14621 // ignore if word is already selected
14622 if !word_range.is_empty() && old_range != word_range {
14623 let (last_word_range, _) =
14624 buffer.surrounding_word(old_range.end, false);
14625 // only select word if start and end point belongs to same word
14626 if word_range == last_word_range {
14627 selected_larger_node = true;
14628 return Selection {
14629 id: selection.id,
14630 start: word_range.start,
14631 end: word_range.end,
14632 goal: SelectionGoal::None,
14633 reversed: selection.reversed,
14634 };
14635 }
14636 }
14637 }
14638 }
14639
14640 let mut new_range = old_range.clone();
14641 while let Some((_node, containing_range)) =
14642 buffer.syntax_ancestor(new_range.clone())
14643 {
14644 new_range = match containing_range {
14645 MultiOrSingleBufferOffsetRange::Single(_) => break,
14646 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14647 };
14648 if !display_map.intersects_fold(new_range.start)
14649 && !display_map.intersects_fold(new_range.end)
14650 {
14651 break;
14652 }
14653 }
14654
14655 selected_larger_node |= new_range != old_range;
14656 Selection {
14657 id: selection.id,
14658 start: new_range.start,
14659 end: new_range.end,
14660 goal: SelectionGoal::None,
14661 reversed: selection.reversed,
14662 }
14663 })
14664 .collect::<Vec<_>>();
14665
14666 if !selected_larger_node {
14667 return; // don't put this call in the history
14668 }
14669
14670 // scroll based on transformation done to the last selection created by the user
14671 let (last_old, last_new) = old_selections
14672 .last()
14673 .zip(new_selections.last().cloned())
14674 .expect("old_selections isn't empty");
14675
14676 // revert selection
14677 let is_selection_reversed = {
14678 let should_newest_selection_be_reversed = last_old.start != last_new.start;
14679 new_selections.last_mut().expect("checked above").reversed =
14680 should_newest_selection_be_reversed;
14681 should_newest_selection_be_reversed
14682 };
14683
14684 if selected_larger_node {
14685 self.select_syntax_node_history.disable_clearing = true;
14686 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14687 s.select(new_selections.clone());
14688 });
14689 self.select_syntax_node_history.disable_clearing = false;
14690 }
14691
14692 let start_row = last_new.start.to_display_point(&display_map).row().0;
14693 let end_row = last_new.end.to_display_point(&display_map).row().0;
14694 let selection_height = end_row - start_row + 1;
14695 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
14696
14697 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
14698 let scroll_behavior = if fits_on_the_screen {
14699 self.request_autoscroll(Autoscroll::fit(), cx);
14700 SelectSyntaxNodeScrollBehavior::FitSelection
14701 } else if is_selection_reversed {
14702 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14703 SelectSyntaxNodeScrollBehavior::CursorTop
14704 } else {
14705 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14706 SelectSyntaxNodeScrollBehavior::CursorBottom
14707 };
14708
14709 self.select_syntax_node_history.push((
14710 old_selections,
14711 scroll_behavior,
14712 is_selection_reversed,
14713 ));
14714 }
14715
14716 pub fn select_smaller_syntax_node(
14717 &mut self,
14718 _: &SelectSmallerSyntaxNode,
14719 window: &mut Window,
14720 cx: &mut Context<Self>,
14721 ) {
14722 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14723
14724 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
14725 self.select_syntax_node_history.pop()
14726 {
14727 if let Some(selection) = selections.last_mut() {
14728 selection.reversed = is_selection_reversed;
14729 }
14730
14731 self.select_syntax_node_history.disable_clearing = true;
14732 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14733 s.select(selections.to_vec());
14734 });
14735 self.select_syntax_node_history.disable_clearing = false;
14736
14737 match scroll_behavior {
14738 SelectSyntaxNodeScrollBehavior::CursorTop => {
14739 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14740 }
14741 SelectSyntaxNodeScrollBehavior::FitSelection => {
14742 self.request_autoscroll(Autoscroll::fit(), cx);
14743 }
14744 SelectSyntaxNodeScrollBehavior::CursorBottom => {
14745 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14746 }
14747 }
14748 }
14749 }
14750
14751 pub fn unwrap_syntax_node(
14752 &mut self,
14753 _: &UnwrapSyntaxNode,
14754 window: &mut Window,
14755 cx: &mut Context<Self>,
14756 ) {
14757 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14758
14759 let buffer = self.buffer.read(cx).snapshot(cx);
14760 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
14761
14762 let edits = old_selections
14763 .iter()
14764 // only consider the first selection for now
14765 .take(1)
14766 .map(|selection| {
14767 // Only requires two branches once if-let-chains stabilize (#53667)
14768 let selection_range = if !selection.is_empty() {
14769 selection.range()
14770 } else if let Some((_, ancestor_range)) =
14771 buffer.syntax_ancestor(selection.start..selection.end)
14772 {
14773 match ancestor_range {
14774 MultiOrSingleBufferOffsetRange::Single(range) => range,
14775 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14776 }
14777 } else {
14778 selection.range()
14779 };
14780
14781 let mut new_range = selection_range.clone();
14782 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(new_range.clone()) {
14783 new_range = match ancestor_range {
14784 MultiOrSingleBufferOffsetRange::Single(range) => range,
14785 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14786 };
14787 if new_range.start < selection_range.start
14788 || new_range.end > selection_range.end
14789 {
14790 break;
14791 }
14792 }
14793
14794 (selection, selection_range, new_range)
14795 })
14796 .collect::<Vec<_>>();
14797
14798 self.transact(window, cx, |editor, window, cx| {
14799 for (_, child, parent) in &edits {
14800 let text = buffer.text_for_range(child.clone()).collect::<String>();
14801 editor.replace_text_in_range(Some(parent.clone()), &text, window, cx);
14802 }
14803
14804 editor.change_selections(
14805 SelectionEffects::scroll(Autoscroll::fit()),
14806 window,
14807 cx,
14808 |s| {
14809 s.select(
14810 edits
14811 .iter()
14812 .map(|(s, old, new)| Selection {
14813 id: s.id,
14814 start: new.start,
14815 end: new.start + old.len(),
14816 goal: SelectionGoal::None,
14817 reversed: s.reversed,
14818 })
14819 .collect(),
14820 );
14821 },
14822 );
14823 });
14824 }
14825
14826 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
14827 if !EditorSettings::get_global(cx).gutter.runnables {
14828 self.clear_tasks();
14829 return Task::ready(());
14830 }
14831 let project = self.project.as_ref().map(Entity::downgrade);
14832 let task_sources = self.lsp_task_sources(cx);
14833 let multi_buffer = self.buffer.downgrade();
14834 cx.spawn_in(window, async move |editor, cx| {
14835 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
14836 let Some(project) = project.and_then(|p| p.upgrade()) else {
14837 return;
14838 };
14839 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
14840 this.display_map.update(cx, |map, cx| map.snapshot(cx))
14841 }) else {
14842 return;
14843 };
14844
14845 let hide_runnables = project
14846 .update(cx, |project, cx| {
14847 // Do not display any test indicators in non-dev server remote projects.
14848 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
14849 })
14850 .unwrap_or(true);
14851 if hide_runnables {
14852 return;
14853 }
14854 let new_rows =
14855 cx.background_spawn({
14856 let snapshot = display_snapshot.clone();
14857 async move {
14858 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
14859 }
14860 })
14861 .await;
14862 let Ok(lsp_tasks) =
14863 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
14864 else {
14865 return;
14866 };
14867 let lsp_tasks = lsp_tasks.await;
14868
14869 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
14870 lsp_tasks
14871 .into_iter()
14872 .flat_map(|(kind, tasks)| {
14873 tasks.into_iter().filter_map(move |(location, task)| {
14874 Some((kind.clone(), location?, task))
14875 })
14876 })
14877 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
14878 let buffer = location.target.buffer;
14879 let buffer_snapshot = buffer.read(cx).snapshot();
14880 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
14881 |(excerpt_id, snapshot, _)| {
14882 if snapshot.remote_id() == buffer_snapshot.remote_id() {
14883 display_snapshot
14884 .buffer_snapshot
14885 .anchor_in_excerpt(excerpt_id, location.target.range.start)
14886 } else {
14887 None
14888 }
14889 },
14890 );
14891 if let Some(offset) = offset {
14892 let task_buffer_range =
14893 location.target.range.to_point(&buffer_snapshot);
14894 let context_buffer_range =
14895 task_buffer_range.to_offset(&buffer_snapshot);
14896 let context_range = BufferOffset(context_buffer_range.start)
14897 ..BufferOffset(context_buffer_range.end);
14898
14899 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
14900 .or_insert_with(|| RunnableTasks {
14901 templates: Vec::new(),
14902 offset,
14903 column: task_buffer_range.start.column,
14904 extra_variables: HashMap::default(),
14905 context_range,
14906 })
14907 .templates
14908 .push((kind, task.original_task().clone()));
14909 }
14910
14911 acc
14912 })
14913 }) else {
14914 return;
14915 };
14916
14917 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
14918 buffer.language_settings(cx).tasks.prefer_lsp
14919 }) else {
14920 return;
14921 };
14922
14923 let rows = Self::runnable_rows(
14924 project,
14925 display_snapshot,
14926 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
14927 new_rows,
14928 cx.clone(),
14929 )
14930 .await;
14931 editor
14932 .update(cx, |editor, _| {
14933 editor.clear_tasks();
14934 for (key, mut value) in rows {
14935 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
14936 value.templates.extend(lsp_tasks.templates);
14937 }
14938
14939 editor.insert_tasks(key, value);
14940 }
14941 for (key, value) in lsp_tasks_by_rows {
14942 editor.insert_tasks(key, value);
14943 }
14944 })
14945 .ok();
14946 })
14947 }
14948 fn fetch_runnable_ranges(
14949 snapshot: &DisplaySnapshot,
14950 range: Range<Anchor>,
14951 ) -> Vec<language::RunnableRange> {
14952 snapshot.buffer_snapshot.runnable_ranges(range).collect()
14953 }
14954
14955 fn runnable_rows(
14956 project: Entity<Project>,
14957 snapshot: DisplaySnapshot,
14958 prefer_lsp: bool,
14959 runnable_ranges: Vec<RunnableRange>,
14960 cx: AsyncWindowContext,
14961 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
14962 cx.spawn(async move |cx| {
14963 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
14964 for mut runnable in runnable_ranges {
14965 let Some(tasks) = cx
14966 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
14967 .ok()
14968 else {
14969 continue;
14970 };
14971 let mut tasks = tasks.await;
14972
14973 if prefer_lsp {
14974 tasks.retain(|(task_kind, _)| {
14975 !matches!(task_kind, TaskSourceKind::Language { .. })
14976 });
14977 }
14978 if tasks.is_empty() {
14979 continue;
14980 }
14981
14982 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
14983 let Some(row) = snapshot
14984 .buffer_snapshot
14985 .buffer_line_for_row(MultiBufferRow(point.row))
14986 .map(|(_, range)| range.start.row)
14987 else {
14988 continue;
14989 };
14990
14991 let context_range =
14992 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
14993 runnable_rows.push((
14994 (runnable.buffer_id, row),
14995 RunnableTasks {
14996 templates: tasks,
14997 offset: snapshot
14998 .buffer_snapshot
14999 .anchor_before(runnable.run_range.start),
15000 context_range,
15001 column: point.column,
15002 extra_variables: runnable.extra_captures,
15003 },
15004 ));
15005 }
15006 runnable_rows
15007 })
15008 }
15009
15010 fn templates_with_tags(
15011 project: &Entity<Project>,
15012 runnable: &mut Runnable,
15013 cx: &mut App,
15014 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
15015 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
15016 let (worktree_id, file) = project
15017 .buffer_for_id(runnable.buffer, cx)
15018 .and_then(|buffer| buffer.read(cx).file())
15019 .map(|file| (file.worktree_id(cx), file.clone()))
15020 .unzip();
15021
15022 (
15023 project.task_store().read(cx).task_inventory().cloned(),
15024 worktree_id,
15025 file,
15026 )
15027 });
15028
15029 let tags = mem::take(&mut runnable.tags);
15030 let language = runnable.language.clone();
15031 cx.spawn(async move |cx| {
15032 let mut templates_with_tags = Vec::new();
15033 if let Some(inventory) = inventory {
15034 for RunnableTag(tag) in tags {
15035 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
15036 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
15037 }) else {
15038 return templates_with_tags;
15039 };
15040 templates_with_tags.extend(new_tasks.await.into_iter().filter(
15041 move |(_, template)| {
15042 template.tags.iter().any(|source_tag| source_tag == &tag)
15043 },
15044 ));
15045 }
15046 }
15047 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
15048
15049 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
15050 // Strongest source wins; if we have worktree tag binding, prefer that to
15051 // global and language bindings;
15052 // if we have a global binding, prefer that to language binding.
15053 let first_mismatch = templates_with_tags
15054 .iter()
15055 .position(|(tag_source, _)| tag_source != leading_tag_source);
15056 if let Some(index) = first_mismatch {
15057 templates_with_tags.truncate(index);
15058 }
15059 }
15060
15061 templates_with_tags
15062 })
15063 }
15064
15065 pub fn move_to_enclosing_bracket(
15066 &mut self,
15067 _: &MoveToEnclosingBracket,
15068 window: &mut Window,
15069 cx: &mut Context<Self>,
15070 ) {
15071 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15072 self.change_selections(Default::default(), window, cx, |s| {
15073 s.move_offsets_with(|snapshot, selection| {
15074 let Some(enclosing_bracket_ranges) =
15075 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
15076 else {
15077 return;
15078 };
15079
15080 let mut best_length = usize::MAX;
15081 let mut best_inside = false;
15082 let mut best_in_bracket_range = false;
15083 let mut best_destination = None;
15084 for (open, close) in enclosing_bracket_ranges {
15085 let close = close.to_inclusive();
15086 let length = close.end() - open.start;
15087 let inside = selection.start >= open.end && selection.end <= *close.start();
15088 let in_bracket_range = open.to_inclusive().contains(&selection.head())
15089 || close.contains(&selection.head());
15090
15091 // If best is next to a bracket and current isn't, skip
15092 if !in_bracket_range && best_in_bracket_range {
15093 continue;
15094 }
15095
15096 // Prefer smaller lengths unless best is inside and current isn't
15097 if length > best_length && (best_inside || !inside) {
15098 continue;
15099 }
15100
15101 best_length = length;
15102 best_inside = inside;
15103 best_in_bracket_range = in_bracket_range;
15104 best_destination = Some(
15105 if close.contains(&selection.start) && close.contains(&selection.end) {
15106 if inside { open.end } else { open.start }
15107 } else if inside {
15108 *close.start()
15109 } else {
15110 *close.end()
15111 },
15112 );
15113 }
15114
15115 if let Some(destination) = best_destination {
15116 selection.collapse_to(destination, SelectionGoal::None);
15117 }
15118 })
15119 });
15120 }
15121
15122 pub fn undo_selection(
15123 &mut self,
15124 _: &UndoSelection,
15125 window: &mut Window,
15126 cx: &mut Context<Self>,
15127 ) {
15128 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15129 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
15130 self.selection_history.mode = SelectionHistoryMode::Undoing;
15131 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15132 this.end_selection(window, cx);
15133 this.change_selections(
15134 SelectionEffects::scroll(Autoscroll::newest()),
15135 window,
15136 cx,
15137 |s| s.select_anchors(entry.selections.to_vec()),
15138 );
15139 });
15140 self.selection_history.mode = SelectionHistoryMode::Normal;
15141
15142 self.select_next_state = entry.select_next_state;
15143 self.select_prev_state = entry.select_prev_state;
15144 self.add_selections_state = entry.add_selections_state;
15145 }
15146 }
15147
15148 pub fn redo_selection(
15149 &mut self,
15150 _: &RedoSelection,
15151 window: &mut Window,
15152 cx: &mut Context<Self>,
15153 ) {
15154 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15155 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
15156 self.selection_history.mode = SelectionHistoryMode::Redoing;
15157 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15158 this.end_selection(window, cx);
15159 this.change_selections(
15160 SelectionEffects::scroll(Autoscroll::newest()),
15161 window,
15162 cx,
15163 |s| s.select_anchors(entry.selections.to_vec()),
15164 );
15165 });
15166 self.selection_history.mode = SelectionHistoryMode::Normal;
15167
15168 self.select_next_state = entry.select_next_state;
15169 self.select_prev_state = entry.select_prev_state;
15170 self.add_selections_state = entry.add_selections_state;
15171 }
15172 }
15173
15174 pub fn expand_excerpts(
15175 &mut self,
15176 action: &ExpandExcerpts,
15177 _: &mut Window,
15178 cx: &mut Context<Self>,
15179 ) {
15180 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
15181 }
15182
15183 pub fn expand_excerpts_down(
15184 &mut self,
15185 action: &ExpandExcerptsDown,
15186 _: &mut Window,
15187 cx: &mut Context<Self>,
15188 ) {
15189 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
15190 }
15191
15192 pub fn expand_excerpts_up(
15193 &mut self,
15194 action: &ExpandExcerptsUp,
15195 _: &mut Window,
15196 cx: &mut Context<Self>,
15197 ) {
15198 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
15199 }
15200
15201 pub fn expand_excerpts_for_direction(
15202 &mut self,
15203 lines: u32,
15204 direction: ExpandExcerptDirection,
15205
15206 cx: &mut Context<Self>,
15207 ) {
15208 let selections = self.selections.disjoint_anchors();
15209
15210 let lines = if lines == 0 {
15211 EditorSettings::get_global(cx).expand_excerpt_lines
15212 } else {
15213 lines
15214 };
15215
15216 self.buffer.update(cx, |buffer, cx| {
15217 let snapshot = buffer.snapshot(cx);
15218 let mut excerpt_ids = selections
15219 .iter()
15220 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
15221 .collect::<Vec<_>>();
15222 excerpt_ids.sort();
15223 excerpt_ids.dedup();
15224 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
15225 })
15226 }
15227
15228 pub fn expand_excerpt(
15229 &mut self,
15230 excerpt: ExcerptId,
15231 direction: ExpandExcerptDirection,
15232 window: &mut Window,
15233 cx: &mut Context<Self>,
15234 ) {
15235 let current_scroll_position = self.scroll_position(cx);
15236 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
15237 let mut should_scroll_up = false;
15238
15239 if direction == ExpandExcerptDirection::Down {
15240 let multi_buffer = self.buffer.read(cx);
15241 let snapshot = multi_buffer.snapshot(cx);
15242 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
15243 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
15244 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
15245 let buffer_snapshot = buffer.read(cx).snapshot();
15246 let excerpt_end_row =
15247 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
15248 let last_row = buffer_snapshot.max_point().row;
15249 let lines_below = last_row.saturating_sub(excerpt_end_row);
15250 should_scroll_up = lines_below >= lines_to_expand;
15251 }
15252 }
15253 }
15254 }
15255
15256 self.buffer.update(cx, |buffer, cx| {
15257 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
15258 });
15259
15260 if should_scroll_up {
15261 let new_scroll_position =
15262 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
15263 self.set_scroll_position(new_scroll_position, window, cx);
15264 }
15265 }
15266
15267 pub fn go_to_singleton_buffer_point(
15268 &mut self,
15269 point: Point,
15270 window: &mut Window,
15271 cx: &mut Context<Self>,
15272 ) {
15273 self.go_to_singleton_buffer_range(point..point, window, cx);
15274 }
15275
15276 pub fn go_to_singleton_buffer_range(
15277 &mut self,
15278 range: Range<Point>,
15279 window: &mut Window,
15280 cx: &mut Context<Self>,
15281 ) {
15282 let multibuffer = self.buffer().read(cx);
15283 let Some(buffer) = multibuffer.as_singleton() else {
15284 return;
15285 };
15286 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
15287 return;
15288 };
15289 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
15290 return;
15291 };
15292 self.change_selections(
15293 SelectionEffects::default().nav_history(true),
15294 window,
15295 cx,
15296 |s| s.select_anchor_ranges([start..end]),
15297 );
15298 }
15299
15300 pub fn go_to_diagnostic(
15301 &mut self,
15302 action: &GoToDiagnostic,
15303 window: &mut Window,
15304 cx: &mut Context<Self>,
15305 ) {
15306 if !self.diagnostics_enabled() {
15307 return;
15308 }
15309 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15310 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
15311 }
15312
15313 pub fn go_to_prev_diagnostic(
15314 &mut self,
15315 action: &GoToPreviousDiagnostic,
15316 window: &mut Window,
15317 cx: &mut Context<Self>,
15318 ) {
15319 if !self.diagnostics_enabled() {
15320 return;
15321 }
15322 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15323 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
15324 }
15325
15326 pub fn go_to_diagnostic_impl(
15327 &mut self,
15328 direction: Direction,
15329 severity: GoToDiagnosticSeverityFilter,
15330 window: &mut Window,
15331 cx: &mut Context<Self>,
15332 ) {
15333 let buffer = self.buffer.read(cx).snapshot(cx);
15334 let selection = self.selections.newest::<usize>(cx);
15335
15336 let mut active_group_id = None;
15337 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
15338 if active_group.active_range.start.to_offset(&buffer) == selection.start {
15339 active_group_id = Some(active_group.group_id);
15340 }
15341 }
15342
15343 fn filtered(
15344 snapshot: EditorSnapshot,
15345 severity: GoToDiagnosticSeverityFilter,
15346 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
15347 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
15348 diagnostics
15349 .filter(move |entry| severity.matches(entry.diagnostic.severity))
15350 .filter(|entry| entry.range.start != entry.range.end)
15351 .filter(|entry| !entry.diagnostic.is_unnecessary)
15352 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
15353 }
15354
15355 let snapshot = self.snapshot(window, cx);
15356 let before = filtered(
15357 snapshot.clone(),
15358 severity,
15359 buffer
15360 .diagnostics_in_range(0..selection.start)
15361 .filter(|entry| entry.range.start <= selection.start),
15362 );
15363 let after = filtered(
15364 snapshot,
15365 severity,
15366 buffer
15367 .diagnostics_in_range(selection.start..buffer.len())
15368 .filter(|entry| entry.range.start >= selection.start),
15369 );
15370
15371 let mut found: Option<DiagnosticEntry<usize>> = None;
15372 if direction == Direction::Prev {
15373 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
15374 {
15375 for diagnostic in prev_diagnostics.into_iter().rev() {
15376 if diagnostic.range.start != selection.start
15377 || active_group_id
15378 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
15379 {
15380 found = Some(diagnostic);
15381 break 'outer;
15382 }
15383 }
15384 }
15385 } else {
15386 for diagnostic in after.chain(before) {
15387 if diagnostic.range.start != selection.start
15388 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
15389 {
15390 found = Some(diagnostic);
15391 break;
15392 }
15393 }
15394 }
15395 let Some(next_diagnostic) = found else {
15396 return;
15397 };
15398
15399 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
15400 return;
15401 };
15402 self.change_selections(Default::default(), window, cx, |s| {
15403 s.select_ranges(vec![
15404 next_diagnostic.range.start..next_diagnostic.range.start,
15405 ])
15406 });
15407 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
15408 self.refresh_edit_prediction(false, true, window, cx);
15409 }
15410
15411 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
15412 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15413 let snapshot = self.snapshot(window, cx);
15414 let selection = self.selections.newest::<Point>(cx);
15415 self.go_to_hunk_before_or_after_position(
15416 &snapshot,
15417 selection.head(),
15418 Direction::Next,
15419 window,
15420 cx,
15421 );
15422 }
15423
15424 pub fn go_to_hunk_before_or_after_position(
15425 &mut self,
15426 snapshot: &EditorSnapshot,
15427 position: Point,
15428 direction: Direction,
15429 window: &mut Window,
15430 cx: &mut Context<Editor>,
15431 ) {
15432 let row = if direction == Direction::Next {
15433 self.hunk_after_position(snapshot, position)
15434 .map(|hunk| hunk.row_range.start)
15435 } else {
15436 self.hunk_before_position(snapshot, position)
15437 };
15438
15439 if let Some(row) = row {
15440 let destination = Point::new(row.0, 0);
15441 let autoscroll = Autoscroll::center();
15442
15443 self.unfold_ranges(&[destination..destination], false, false, cx);
15444 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
15445 s.select_ranges([destination..destination]);
15446 });
15447 }
15448 }
15449
15450 fn hunk_after_position(
15451 &mut self,
15452 snapshot: &EditorSnapshot,
15453 position: Point,
15454 ) -> Option<MultiBufferDiffHunk> {
15455 snapshot
15456 .buffer_snapshot
15457 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
15458 .find(|hunk| hunk.row_range.start.0 > position.row)
15459 .or_else(|| {
15460 snapshot
15461 .buffer_snapshot
15462 .diff_hunks_in_range(Point::zero()..position)
15463 .find(|hunk| hunk.row_range.end.0 < position.row)
15464 })
15465 }
15466
15467 fn go_to_prev_hunk(
15468 &mut self,
15469 _: &GoToPreviousHunk,
15470 window: &mut Window,
15471 cx: &mut Context<Self>,
15472 ) {
15473 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15474 let snapshot = self.snapshot(window, cx);
15475 let selection = self.selections.newest::<Point>(cx);
15476 self.go_to_hunk_before_or_after_position(
15477 &snapshot,
15478 selection.head(),
15479 Direction::Prev,
15480 window,
15481 cx,
15482 );
15483 }
15484
15485 fn hunk_before_position(
15486 &mut self,
15487 snapshot: &EditorSnapshot,
15488 position: Point,
15489 ) -> Option<MultiBufferRow> {
15490 snapshot
15491 .buffer_snapshot
15492 .diff_hunk_before(position)
15493 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
15494 }
15495
15496 fn go_to_next_change(
15497 &mut self,
15498 _: &GoToNextChange,
15499 window: &mut Window,
15500 cx: &mut Context<Self>,
15501 ) {
15502 if let Some(selections) = self
15503 .change_list
15504 .next_change(1, Direction::Next)
15505 .map(|s| s.to_vec())
15506 {
15507 self.change_selections(Default::default(), window, cx, |s| {
15508 let map = s.display_map();
15509 s.select_display_ranges(selections.iter().map(|a| {
15510 let point = a.to_display_point(&map);
15511 point..point
15512 }))
15513 })
15514 }
15515 }
15516
15517 fn go_to_previous_change(
15518 &mut self,
15519 _: &GoToPreviousChange,
15520 window: &mut Window,
15521 cx: &mut Context<Self>,
15522 ) {
15523 if let Some(selections) = self
15524 .change_list
15525 .next_change(1, Direction::Prev)
15526 .map(|s| s.to_vec())
15527 {
15528 self.change_selections(Default::default(), window, cx, |s| {
15529 let map = s.display_map();
15530 s.select_display_ranges(selections.iter().map(|a| {
15531 let point = a.to_display_point(&map);
15532 point..point
15533 }))
15534 })
15535 }
15536 }
15537
15538 fn go_to_line<T: 'static>(
15539 &mut self,
15540 position: Anchor,
15541 highlight_color: Option<Hsla>,
15542 window: &mut Window,
15543 cx: &mut Context<Self>,
15544 ) {
15545 let snapshot = self.snapshot(window, cx).display_snapshot;
15546 let position = position.to_point(&snapshot.buffer_snapshot);
15547 let start = snapshot
15548 .buffer_snapshot
15549 .clip_point(Point::new(position.row, 0), Bias::Left);
15550 let end = start + Point::new(1, 0);
15551 let start = snapshot.buffer_snapshot.anchor_before(start);
15552 let end = snapshot.buffer_snapshot.anchor_before(end);
15553
15554 self.highlight_rows::<T>(
15555 start..end,
15556 highlight_color
15557 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
15558 Default::default(),
15559 cx,
15560 );
15561
15562 if self.buffer.read(cx).is_singleton() {
15563 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
15564 }
15565 }
15566
15567 pub fn go_to_definition(
15568 &mut self,
15569 _: &GoToDefinition,
15570 window: &mut Window,
15571 cx: &mut Context<Self>,
15572 ) -> Task<Result<Navigated>> {
15573 let definition =
15574 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
15575 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
15576 cx.spawn_in(window, async move |editor, cx| {
15577 if definition.await? == Navigated::Yes {
15578 return Ok(Navigated::Yes);
15579 }
15580 match fallback_strategy {
15581 GoToDefinitionFallback::None => Ok(Navigated::No),
15582 GoToDefinitionFallback::FindAllReferences => {
15583 match editor.update_in(cx, |editor, window, cx| {
15584 editor.find_all_references(&FindAllReferences, window, cx)
15585 })? {
15586 Some(references) => references.await,
15587 None => Ok(Navigated::No),
15588 }
15589 }
15590 }
15591 })
15592 }
15593
15594 pub fn go_to_declaration(
15595 &mut self,
15596 _: &GoToDeclaration,
15597 window: &mut Window,
15598 cx: &mut Context<Self>,
15599 ) -> Task<Result<Navigated>> {
15600 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
15601 }
15602
15603 pub fn go_to_declaration_split(
15604 &mut self,
15605 _: &GoToDeclaration,
15606 window: &mut Window,
15607 cx: &mut Context<Self>,
15608 ) -> Task<Result<Navigated>> {
15609 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
15610 }
15611
15612 pub fn go_to_implementation(
15613 &mut self,
15614 _: &GoToImplementation,
15615 window: &mut Window,
15616 cx: &mut Context<Self>,
15617 ) -> Task<Result<Navigated>> {
15618 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
15619 }
15620
15621 pub fn go_to_implementation_split(
15622 &mut self,
15623 _: &GoToImplementationSplit,
15624 window: &mut Window,
15625 cx: &mut Context<Self>,
15626 ) -> Task<Result<Navigated>> {
15627 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
15628 }
15629
15630 pub fn go_to_type_definition(
15631 &mut self,
15632 _: &GoToTypeDefinition,
15633 window: &mut Window,
15634 cx: &mut Context<Self>,
15635 ) -> Task<Result<Navigated>> {
15636 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
15637 }
15638
15639 pub fn go_to_definition_split(
15640 &mut self,
15641 _: &GoToDefinitionSplit,
15642 window: &mut Window,
15643 cx: &mut Context<Self>,
15644 ) -> Task<Result<Navigated>> {
15645 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
15646 }
15647
15648 pub fn go_to_type_definition_split(
15649 &mut self,
15650 _: &GoToTypeDefinitionSplit,
15651 window: &mut Window,
15652 cx: &mut Context<Self>,
15653 ) -> Task<Result<Navigated>> {
15654 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
15655 }
15656
15657 fn go_to_definition_of_kind(
15658 &mut self,
15659 kind: GotoDefinitionKind,
15660 split: bool,
15661 window: &mut Window,
15662 cx: &mut Context<Self>,
15663 ) -> Task<Result<Navigated>> {
15664 let Some(provider) = self.semantics_provider.clone() else {
15665 return Task::ready(Ok(Navigated::No));
15666 };
15667 let head = self.selections.newest::<usize>(cx).head();
15668 let buffer = self.buffer.read(cx);
15669 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
15670 text_anchor
15671 } else {
15672 return Task::ready(Ok(Navigated::No));
15673 };
15674
15675 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
15676 return Task::ready(Ok(Navigated::No));
15677 };
15678
15679 cx.spawn_in(window, async move |editor, cx| {
15680 let definitions = definitions.await?;
15681 let navigated = editor
15682 .update_in(cx, |editor, window, cx| {
15683 editor.navigate_to_hover_links(
15684 Some(kind),
15685 definitions
15686 .into_iter()
15687 .filter(|location| {
15688 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
15689 })
15690 .map(HoverLink::Text)
15691 .collect::<Vec<_>>(),
15692 split,
15693 window,
15694 cx,
15695 )
15696 })?
15697 .await?;
15698 anyhow::Ok(navigated)
15699 })
15700 }
15701
15702 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
15703 let selection = self.selections.newest_anchor();
15704 let head = selection.head();
15705 let tail = selection.tail();
15706
15707 let Some((buffer, start_position)) =
15708 self.buffer.read(cx).text_anchor_for_position(head, cx)
15709 else {
15710 return;
15711 };
15712
15713 let end_position = if head != tail {
15714 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
15715 return;
15716 };
15717 Some(pos)
15718 } else {
15719 None
15720 };
15721
15722 let url_finder = cx.spawn_in(window, async move |editor, cx| {
15723 let url = if let Some(end_pos) = end_position {
15724 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
15725 } else {
15726 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
15727 };
15728
15729 if let Some(url) = url {
15730 editor.update(cx, |_, cx| {
15731 cx.open_url(&url);
15732 })
15733 } else {
15734 Ok(())
15735 }
15736 });
15737
15738 url_finder.detach();
15739 }
15740
15741 pub fn open_selected_filename(
15742 &mut self,
15743 _: &OpenSelectedFilename,
15744 window: &mut Window,
15745 cx: &mut Context<Self>,
15746 ) {
15747 let Some(workspace) = self.workspace() else {
15748 return;
15749 };
15750
15751 let position = self.selections.newest_anchor().head();
15752
15753 let Some((buffer, buffer_position)) =
15754 self.buffer.read(cx).text_anchor_for_position(position, cx)
15755 else {
15756 return;
15757 };
15758
15759 let project = self.project.clone();
15760
15761 cx.spawn_in(window, async move |_, cx| {
15762 let result = find_file(&buffer, project, buffer_position, cx).await;
15763
15764 if let Some((_, path)) = result {
15765 workspace
15766 .update_in(cx, |workspace, window, cx| {
15767 workspace.open_resolved_path(path, window, cx)
15768 })?
15769 .await?;
15770 }
15771 anyhow::Ok(())
15772 })
15773 .detach();
15774 }
15775
15776 pub(crate) fn navigate_to_hover_links(
15777 &mut self,
15778 kind: Option<GotoDefinitionKind>,
15779 mut definitions: Vec<HoverLink>,
15780 split: bool,
15781 window: &mut Window,
15782 cx: &mut Context<Editor>,
15783 ) -> Task<Result<Navigated>> {
15784 // If there is one definition, just open it directly
15785 if definitions.len() == 1 {
15786 let definition = definitions.pop().unwrap();
15787
15788 enum TargetTaskResult {
15789 Location(Option<Location>),
15790 AlreadyNavigated,
15791 }
15792
15793 let target_task = match definition {
15794 HoverLink::Text(link) => {
15795 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
15796 }
15797 HoverLink::InlayHint(lsp_location, server_id) => {
15798 let computation =
15799 self.compute_target_location(lsp_location, server_id, window, cx);
15800 cx.background_spawn(async move {
15801 let location = computation.await?;
15802 Ok(TargetTaskResult::Location(location))
15803 })
15804 }
15805 HoverLink::Url(url) => {
15806 cx.open_url(&url);
15807 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
15808 }
15809 HoverLink::File(path) => {
15810 if let Some(workspace) = self.workspace() {
15811 cx.spawn_in(window, async move |_, cx| {
15812 workspace
15813 .update_in(cx, |workspace, window, cx| {
15814 workspace.open_resolved_path(path, window, cx)
15815 })?
15816 .await
15817 .map(|_| TargetTaskResult::AlreadyNavigated)
15818 })
15819 } else {
15820 Task::ready(Ok(TargetTaskResult::Location(None)))
15821 }
15822 }
15823 };
15824 cx.spawn_in(window, async move |editor, cx| {
15825 let target = match target_task.await.context("target resolution task")? {
15826 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
15827 TargetTaskResult::Location(None) => return Ok(Navigated::No),
15828 TargetTaskResult::Location(Some(target)) => target,
15829 };
15830
15831 editor.update_in(cx, |editor, window, cx| {
15832 let Some(workspace) = editor.workspace() else {
15833 return Navigated::No;
15834 };
15835 let pane = workspace.read(cx).active_pane().clone();
15836
15837 let range = target.range.to_point(target.buffer.read(cx));
15838 let range = editor.range_for_match(&range);
15839 let range = collapse_multiline_range(range);
15840
15841 if !split
15842 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
15843 {
15844 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
15845 } else {
15846 window.defer(cx, move |window, cx| {
15847 let target_editor: Entity<Self> =
15848 workspace.update(cx, |workspace, cx| {
15849 let pane = if split {
15850 workspace.adjacent_pane(window, cx)
15851 } else {
15852 workspace.active_pane().clone()
15853 };
15854
15855 workspace.open_project_item(
15856 pane,
15857 target.buffer.clone(),
15858 true,
15859 true,
15860 window,
15861 cx,
15862 )
15863 });
15864 target_editor.update(cx, |target_editor, cx| {
15865 // When selecting a definition in a different buffer, disable the nav history
15866 // to avoid creating a history entry at the previous cursor location.
15867 pane.update(cx, |pane, _| pane.disable_history());
15868 target_editor.go_to_singleton_buffer_range(range, window, cx);
15869 pane.update(cx, |pane, _| pane.enable_history());
15870 });
15871 });
15872 }
15873 Navigated::Yes
15874 })
15875 })
15876 } else if !definitions.is_empty() {
15877 cx.spawn_in(window, async move |editor, cx| {
15878 let (title, location_tasks, workspace) = editor
15879 .update_in(cx, |editor, window, cx| {
15880 let tab_kind = match kind {
15881 Some(GotoDefinitionKind::Implementation) => "Implementations",
15882 _ => "Definitions",
15883 };
15884 let title = definitions
15885 .iter()
15886 .find_map(|definition| match definition {
15887 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
15888 let buffer = origin.buffer.read(cx);
15889 format!(
15890 "{} for {}",
15891 tab_kind,
15892 buffer
15893 .text_for_range(origin.range.clone())
15894 .collect::<String>()
15895 )
15896 }),
15897 HoverLink::InlayHint(_, _) => None,
15898 HoverLink::Url(_) => None,
15899 HoverLink::File(_) => None,
15900 })
15901 .unwrap_or(tab_kind.to_string());
15902 let location_tasks = definitions
15903 .into_iter()
15904 .map(|definition| match definition {
15905 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
15906 HoverLink::InlayHint(lsp_location, server_id) => editor
15907 .compute_target_location(lsp_location, server_id, window, cx),
15908 HoverLink::Url(_) => Task::ready(Ok(None)),
15909 HoverLink::File(_) => Task::ready(Ok(None)),
15910 })
15911 .collect::<Vec<_>>();
15912 (title, location_tasks, editor.workspace().clone())
15913 })
15914 .context("location tasks preparation")?;
15915
15916 let locations: Vec<Location> = future::join_all(location_tasks)
15917 .await
15918 .into_iter()
15919 .filter_map(|location| location.transpose())
15920 .collect::<Result<_>>()
15921 .context("location tasks")?;
15922
15923 if locations.is_empty() {
15924 return Ok(Navigated::No);
15925 }
15926
15927 let Some(workspace) = workspace else {
15928 return Ok(Navigated::No);
15929 };
15930
15931 let opened = workspace
15932 .update_in(cx, |workspace, window, cx| {
15933 Self::open_locations_in_multibuffer(
15934 workspace,
15935 locations,
15936 title,
15937 split,
15938 MultibufferSelectionMode::First,
15939 window,
15940 cx,
15941 )
15942 })
15943 .ok();
15944
15945 anyhow::Ok(Navigated::from_bool(opened.is_some()))
15946 })
15947 } else {
15948 Task::ready(Ok(Navigated::No))
15949 }
15950 }
15951
15952 fn compute_target_location(
15953 &self,
15954 lsp_location: lsp::Location,
15955 server_id: LanguageServerId,
15956 window: &mut Window,
15957 cx: &mut Context<Self>,
15958 ) -> Task<anyhow::Result<Option<Location>>> {
15959 let Some(project) = self.project.clone() else {
15960 return Task::ready(Ok(None));
15961 };
15962
15963 cx.spawn_in(window, async move |editor, cx| {
15964 let location_task = editor.update(cx, |_, cx| {
15965 project.update(cx, |project, cx| {
15966 let language_server_name = project
15967 .language_server_statuses(cx)
15968 .find(|(id, _)| server_id == *id)
15969 .map(|(_, status)| status.name.clone());
15970 language_server_name.map(|language_server_name| {
15971 project.open_local_buffer_via_lsp(
15972 lsp_location.uri.clone(),
15973 server_id,
15974 language_server_name,
15975 cx,
15976 )
15977 })
15978 })
15979 })?;
15980 let location = match location_task {
15981 Some(task) => Some({
15982 let target_buffer_handle = task.await.context("open local buffer")?;
15983 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
15984 let target_start = target_buffer
15985 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
15986 let target_end = target_buffer
15987 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
15988 target_buffer.anchor_after(target_start)
15989 ..target_buffer.anchor_before(target_end)
15990 })?;
15991 Location {
15992 buffer: target_buffer_handle,
15993 range,
15994 }
15995 }),
15996 None => None,
15997 };
15998 Ok(location)
15999 })
16000 }
16001
16002 pub fn find_all_references(
16003 &mut self,
16004 _: &FindAllReferences,
16005 window: &mut Window,
16006 cx: &mut Context<Self>,
16007 ) -> Option<Task<Result<Navigated>>> {
16008 let selection = self.selections.newest::<usize>(cx);
16009 let multi_buffer = self.buffer.read(cx);
16010 let head = selection.head();
16011
16012 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16013 let head_anchor = multi_buffer_snapshot.anchor_at(
16014 head,
16015 if head < selection.tail() {
16016 Bias::Right
16017 } else {
16018 Bias::Left
16019 },
16020 );
16021
16022 match self
16023 .find_all_references_task_sources
16024 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16025 {
16026 Ok(_) => {
16027 log::info!(
16028 "Ignoring repeated FindAllReferences invocation with the position of already running task"
16029 );
16030 return None;
16031 }
16032 Err(i) => {
16033 self.find_all_references_task_sources.insert(i, head_anchor);
16034 }
16035 }
16036
16037 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
16038 let workspace = self.workspace()?;
16039 let project = workspace.read(cx).project().clone();
16040 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
16041 Some(cx.spawn_in(window, async move |editor, cx| {
16042 let _cleanup = cx.on_drop(&editor, move |editor, _| {
16043 if let Ok(i) = editor
16044 .find_all_references_task_sources
16045 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16046 {
16047 editor.find_all_references_task_sources.remove(i);
16048 }
16049 });
16050
16051 let locations = references.await?;
16052 if locations.is_empty() {
16053 return anyhow::Ok(Navigated::No);
16054 }
16055
16056 workspace.update_in(cx, |workspace, window, cx| {
16057 let title = locations
16058 .first()
16059 .as_ref()
16060 .map(|location| {
16061 let buffer = location.buffer.read(cx);
16062 format!(
16063 "References to `{}`",
16064 buffer
16065 .text_for_range(location.range.clone())
16066 .collect::<String>()
16067 )
16068 })
16069 .unwrap();
16070 Self::open_locations_in_multibuffer(
16071 workspace,
16072 locations,
16073 title,
16074 false,
16075 MultibufferSelectionMode::First,
16076 window,
16077 cx,
16078 );
16079 Navigated::Yes
16080 })
16081 }))
16082 }
16083
16084 /// Opens a multibuffer with the given project locations in it
16085 pub fn open_locations_in_multibuffer(
16086 workspace: &mut Workspace,
16087 mut locations: Vec<Location>,
16088 title: String,
16089 split: bool,
16090 multibuffer_selection_mode: MultibufferSelectionMode,
16091 window: &mut Window,
16092 cx: &mut Context<Workspace>,
16093 ) {
16094 if locations.is_empty() {
16095 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
16096 return;
16097 }
16098
16099 // If there are multiple definitions, open them in a multibuffer
16100 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
16101 let mut locations = locations.into_iter().peekable();
16102 let mut ranges: Vec<Range<Anchor>> = Vec::new();
16103 let capability = workspace.project().read(cx).capability();
16104
16105 let excerpt_buffer = cx.new(|cx| {
16106 let mut multibuffer = MultiBuffer::new(capability);
16107 while let Some(location) = locations.next() {
16108 let buffer = location.buffer.read(cx);
16109 let mut ranges_for_buffer = Vec::new();
16110 let range = location.range.to_point(buffer);
16111 ranges_for_buffer.push(range.clone());
16112
16113 while let Some(next_location) = locations.peek() {
16114 if next_location.buffer == location.buffer {
16115 ranges_for_buffer.push(next_location.range.to_point(buffer));
16116 locations.next();
16117 } else {
16118 break;
16119 }
16120 }
16121
16122 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
16123 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
16124 PathKey::for_buffer(&location.buffer, cx),
16125 location.buffer.clone(),
16126 ranges_for_buffer,
16127 DEFAULT_MULTIBUFFER_CONTEXT,
16128 cx,
16129 );
16130 ranges.extend(new_ranges)
16131 }
16132
16133 multibuffer.with_title(title)
16134 });
16135
16136 let editor = cx.new(|cx| {
16137 Editor::for_multibuffer(
16138 excerpt_buffer,
16139 Some(workspace.project().clone()),
16140 window,
16141 cx,
16142 )
16143 });
16144 editor.update(cx, |editor, cx| {
16145 match multibuffer_selection_mode {
16146 MultibufferSelectionMode::First => {
16147 if let Some(first_range) = ranges.first() {
16148 editor.change_selections(
16149 SelectionEffects::no_scroll(),
16150 window,
16151 cx,
16152 |selections| {
16153 selections.clear_disjoint();
16154 selections
16155 .select_anchor_ranges(std::iter::once(first_range.clone()));
16156 },
16157 );
16158 }
16159 editor.highlight_background::<Self>(
16160 &ranges,
16161 |theme| theme.colors().editor_highlighted_line_background,
16162 cx,
16163 );
16164 }
16165 MultibufferSelectionMode::All => {
16166 editor.change_selections(
16167 SelectionEffects::no_scroll(),
16168 window,
16169 cx,
16170 |selections| {
16171 selections.clear_disjoint();
16172 selections.select_anchor_ranges(ranges);
16173 },
16174 );
16175 }
16176 }
16177 editor.register_buffers_with_language_servers(cx);
16178 });
16179
16180 let item = Box::new(editor);
16181 let item_id = item.item_id();
16182
16183 if split {
16184 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
16185 } else {
16186 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
16187 let (preview_item_id, preview_item_idx) =
16188 workspace.active_pane().read_with(cx, |pane, _| {
16189 (pane.preview_item_id(), pane.preview_item_idx())
16190 });
16191
16192 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
16193
16194 if let Some(preview_item_id) = preview_item_id {
16195 workspace.active_pane().update(cx, |pane, cx| {
16196 pane.remove_item(preview_item_id, false, false, window, cx);
16197 });
16198 }
16199 } else {
16200 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
16201 }
16202 }
16203 workspace.active_pane().update(cx, |pane, cx| {
16204 pane.set_preview_item_id(Some(item_id), cx);
16205 });
16206 }
16207
16208 pub fn rename(
16209 &mut self,
16210 _: &Rename,
16211 window: &mut Window,
16212 cx: &mut Context<Self>,
16213 ) -> Option<Task<Result<()>>> {
16214 use language::ToOffset as _;
16215
16216 let provider = self.semantics_provider.clone()?;
16217 let selection = self.selections.newest_anchor().clone();
16218 let (cursor_buffer, cursor_buffer_position) = self
16219 .buffer
16220 .read(cx)
16221 .text_anchor_for_position(selection.head(), cx)?;
16222 let (tail_buffer, cursor_buffer_position_end) = self
16223 .buffer
16224 .read(cx)
16225 .text_anchor_for_position(selection.tail(), cx)?;
16226 if tail_buffer != cursor_buffer {
16227 return None;
16228 }
16229
16230 let snapshot = cursor_buffer.read(cx).snapshot();
16231 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
16232 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
16233 let prepare_rename = provider
16234 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
16235 .unwrap_or_else(|| Task::ready(Ok(None)));
16236 drop(snapshot);
16237
16238 Some(cx.spawn_in(window, async move |this, cx| {
16239 let rename_range = if let Some(range) = prepare_rename.await? {
16240 Some(range)
16241 } else {
16242 this.update(cx, |this, cx| {
16243 let buffer = this.buffer.read(cx).snapshot(cx);
16244 let mut buffer_highlights = this
16245 .document_highlights_for_position(selection.head(), &buffer)
16246 .filter(|highlight| {
16247 highlight.start.excerpt_id == selection.head().excerpt_id
16248 && highlight.end.excerpt_id == selection.head().excerpt_id
16249 });
16250 buffer_highlights
16251 .next()
16252 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
16253 })?
16254 };
16255 if let Some(rename_range) = rename_range {
16256 this.update_in(cx, |this, window, cx| {
16257 let snapshot = cursor_buffer.read(cx).snapshot();
16258 let rename_buffer_range = rename_range.to_offset(&snapshot);
16259 let cursor_offset_in_rename_range =
16260 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
16261 let cursor_offset_in_rename_range_end =
16262 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
16263
16264 this.take_rename(false, window, cx);
16265 let buffer = this.buffer.read(cx).read(cx);
16266 let cursor_offset = selection.head().to_offset(&buffer);
16267 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
16268 let rename_end = rename_start + rename_buffer_range.len();
16269 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
16270 let mut old_highlight_id = None;
16271 let old_name: Arc<str> = buffer
16272 .chunks(rename_start..rename_end, true)
16273 .map(|chunk| {
16274 if old_highlight_id.is_none() {
16275 old_highlight_id = chunk.syntax_highlight_id;
16276 }
16277 chunk.text
16278 })
16279 .collect::<String>()
16280 .into();
16281
16282 drop(buffer);
16283
16284 // Position the selection in the rename editor so that it matches the current selection.
16285 this.show_local_selections = false;
16286 let rename_editor = cx.new(|cx| {
16287 let mut editor = Editor::single_line(window, cx);
16288 editor.buffer.update(cx, |buffer, cx| {
16289 buffer.edit([(0..0, old_name.clone())], None, cx)
16290 });
16291 let rename_selection_range = match cursor_offset_in_rename_range
16292 .cmp(&cursor_offset_in_rename_range_end)
16293 {
16294 Ordering::Equal => {
16295 editor.select_all(&SelectAll, window, cx);
16296 return editor;
16297 }
16298 Ordering::Less => {
16299 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
16300 }
16301 Ordering::Greater => {
16302 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
16303 }
16304 };
16305 if rename_selection_range.end > old_name.len() {
16306 editor.select_all(&SelectAll, window, cx);
16307 } else {
16308 editor.change_selections(Default::default(), window, cx, |s| {
16309 s.select_ranges([rename_selection_range]);
16310 });
16311 }
16312 editor
16313 });
16314 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
16315 if e == &EditorEvent::Focused {
16316 cx.emit(EditorEvent::FocusedIn)
16317 }
16318 })
16319 .detach();
16320
16321 let write_highlights =
16322 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
16323 let read_highlights =
16324 this.clear_background_highlights::<DocumentHighlightRead>(cx);
16325 let ranges = write_highlights
16326 .iter()
16327 .flat_map(|(_, ranges)| ranges.iter())
16328 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
16329 .cloned()
16330 .collect();
16331
16332 this.highlight_text::<Rename>(
16333 ranges,
16334 HighlightStyle {
16335 fade_out: Some(0.6),
16336 ..Default::default()
16337 },
16338 cx,
16339 );
16340 let rename_focus_handle = rename_editor.focus_handle(cx);
16341 window.focus(&rename_focus_handle);
16342 let block_id = this.insert_blocks(
16343 [BlockProperties {
16344 style: BlockStyle::Flex,
16345 placement: BlockPlacement::Below(range.start),
16346 height: Some(1),
16347 render: Arc::new({
16348 let rename_editor = rename_editor.clone();
16349 move |cx: &mut BlockContext| {
16350 let mut text_style = cx.editor_style.text.clone();
16351 if let Some(highlight_style) = old_highlight_id
16352 .and_then(|h| h.style(&cx.editor_style.syntax))
16353 {
16354 text_style = text_style.highlight(highlight_style);
16355 }
16356 div()
16357 .block_mouse_except_scroll()
16358 .pl(cx.anchor_x)
16359 .child(EditorElement::new(
16360 &rename_editor,
16361 EditorStyle {
16362 background: cx.theme().system().transparent,
16363 local_player: cx.editor_style.local_player,
16364 text: text_style,
16365 scrollbar_width: cx.editor_style.scrollbar_width,
16366 syntax: cx.editor_style.syntax.clone(),
16367 status: cx.editor_style.status.clone(),
16368 inlay_hints_style: HighlightStyle {
16369 font_weight: Some(FontWeight::BOLD),
16370 ..make_inlay_hints_style(cx.app)
16371 },
16372 edit_prediction_styles: make_suggestion_styles(
16373 cx.app,
16374 ),
16375 ..EditorStyle::default()
16376 },
16377 ))
16378 .into_any_element()
16379 }
16380 }),
16381 priority: 0,
16382 }],
16383 Some(Autoscroll::fit()),
16384 cx,
16385 )[0];
16386 this.pending_rename = Some(RenameState {
16387 range,
16388 old_name,
16389 editor: rename_editor,
16390 block_id,
16391 });
16392 })?;
16393 }
16394
16395 Ok(())
16396 }))
16397 }
16398
16399 pub fn confirm_rename(
16400 &mut self,
16401 _: &ConfirmRename,
16402 window: &mut Window,
16403 cx: &mut Context<Self>,
16404 ) -> Option<Task<Result<()>>> {
16405 let rename = self.take_rename(false, window, cx)?;
16406 let workspace = self.workspace()?.downgrade();
16407 let (buffer, start) = self
16408 .buffer
16409 .read(cx)
16410 .text_anchor_for_position(rename.range.start, cx)?;
16411 let (end_buffer, _) = self
16412 .buffer
16413 .read(cx)
16414 .text_anchor_for_position(rename.range.end, cx)?;
16415 if buffer != end_buffer {
16416 return None;
16417 }
16418
16419 let old_name = rename.old_name;
16420 let new_name = rename.editor.read(cx).text(cx);
16421
16422 let rename = self.semantics_provider.as_ref()?.perform_rename(
16423 &buffer,
16424 start,
16425 new_name.clone(),
16426 cx,
16427 )?;
16428
16429 Some(cx.spawn_in(window, async move |editor, cx| {
16430 let project_transaction = rename.await?;
16431 Self::open_project_transaction(
16432 &editor,
16433 workspace,
16434 project_transaction,
16435 format!("Rename: {} → {}", old_name, new_name),
16436 cx,
16437 )
16438 .await?;
16439
16440 editor.update(cx, |editor, cx| {
16441 editor.refresh_document_highlights(cx);
16442 })?;
16443 Ok(())
16444 }))
16445 }
16446
16447 fn take_rename(
16448 &mut self,
16449 moving_cursor: bool,
16450 window: &mut Window,
16451 cx: &mut Context<Self>,
16452 ) -> Option<RenameState> {
16453 let rename = self.pending_rename.take()?;
16454 if rename.editor.focus_handle(cx).is_focused(window) {
16455 window.focus(&self.focus_handle);
16456 }
16457
16458 self.remove_blocks(
16459 [rename.block_id].into_iter().collect(),
16460 Some(Autoscroll::fit()),
16461 cx,
16462 );
16463 self.clear_highlights::<Rename>(cx);
16464 self.show_local_selections = true;
16465
16466 if moving_cursor {
16467 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
16468 editor.selections.newest::<usize>(cx).head()
16469 });
16470
16471 // Update the selection to match the position of the selection inside
16472 // the rename editor.
16473 let snapshot = self.buffer.read(cx).read(cx);
16474 let rename_range = rename.range.to_offset(&snapshot);
16475 let cursor_in_editor = snapshot
16476 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
16477 .min(rename_range.end);
16478 drop(snapshot);
16479
16480 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16481 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
16482 });
16483 } else {
16484 self.refresh_document_highlights(cx);
16485 }
16486
16487 Some(rename)
16488 }
16489
16490 pub fn pending_rename(&self) -> Option<&RenameState> {
16491 self.pending_rename.as_ref()
16492 }
16493
16494 fn format(
16495 &mut self,
16496 _: &Format,
16497 window: &mut Window,
16498 cx: &mut Context<Self>,
16499 ) -> Option<Task<Result<()>>> {
16500 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16501
16502 let project = match &self.project {
16503 Some(project) => project.clone(),
16504 None => return None,
16505 };
16506
16507 Some(self.perform_format(
16508 project,
16509 FormatTrigger::Manual,
16510 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
16511 window,
16512 cx,
16513 ))
16514 }
16515
16516 fn format_selections(
16517 &mut self,
16518 _: &FormatSelections,
16519 window: &mut Window,
16520 cx: &mut Context<Self>,
16521 ) -> Option<Task<Result<()>>> {
16522 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16523
16524 let project = match &self.project {
16525 Some(project) => project.clone(),
16526 None => return None,
16527 };
16528
16529 let ranges = self
16530 .selections
16531 .all_adjusted(cx)
16532 .into_iter()
16533 .map(|selection| selection.range())
16534 .collect_vec();
16535
16536 Some(self.perform_format(
16537 project,
16538 FormatTrigger::Manual,
16539 FormatTarget::Ranges(ranges),
16540 window,
16541 cx,
16542 ))
16543 }
16544
16545 fn perform_format(
16546 &mut self,
16547 project: Entity<Project>,
16548 trigger: FormatTrigger,
16549 target: FormatTarget,
16550 window: &mut Window,
16551 cx: &mut Context<Self>,
16552 ) -> Task<Result<()>> {
16553 let buffer = self.buffer.clone();
16554 let (buffers, target) = match target {
16555 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
16556 FormatTarget::Ranges(selection_ranges) => {
16557 let multi_buffer = buffer.read(cx);
16558 let snapshot = multi_buffer.read(cx);
16559 let mut buffers = HashSet::default();
16560 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
16561 BTreeMap::new();
16562 for selection_range in selection_ranges {
16563 for (buffer, buffer_range, _) in
16564 snapshot.range_to_buffer_ranges(selection_range)
16565 {
16566 let buffer_id = buffer.remote_id();
16567 let start = buffer.anchor_before(buffer_range.start);
16568 let end = buffer.anchor_after(buffer_range.end);
16569 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
16570 buffer_id_to_ranges
16571 .entry(buffer_id)
16572 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
16573 .or_insert_with(|| vec![start..end]);
16574 }
16575 }
16576 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
16577 }
16578 };
16579
16580 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
16581 let selections_prev = transaction_id_prev
16582 .and_then(|transaction_id_prev| {
16583 // default to selections as they were after the last edit, if we have them,
16584 // instead of how they are now.
16585 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
16586 // will take you back to where you made the last edit, instead of staying where you scrolled
16587 self.selection_history
16588 .transaction(transaction_id_prev)
16589 .map(|t| t.0.clone())
16590 })
16591 .unwrap_or_else(|| {
16592 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
16593 self.selections.disjoint_anchors()
16594 });
16595
16596 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
16597 let format = project.update(cx, |project, cx| {
16598 project.format(buffers, target, true, trigger, cx)
16599 });
16600
16601 cx.spawn_in(window, async move |editor, cx| {
16602 let transaction = futures::select_biased! {
16603 transaction = format.log_err().fuse() => transaction,
16604 () = timeout => {
16605 log::warn!("timed out waiting for formatting");
16606 None
16607 }
16608 };
16609
16610 buffer
16611 .update(cx, |buffer, cx| {
16612 if let Some(transaction) = transaction {
16613 if !buffer.is_singleton() {
16614 buffer.push_transaction(&transaction.0, cx);
16615 }
16616 }
16617 cx.notify();
16618 })
16619 .ok();
16620
16621 if let Some(transaction_id_now) =
16622 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
16623 {
16624 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
16625 if has_new_transaction {
16626 _ = editor.update(cx, |editor, _| {
16627 editor
16628 .selection_history
16629 .insert_transaction(transaction_id_now, selections_prev);
16630 });
16631 }
16632 }
16633
16634 Ok(())
16635 })
16636 }
16637
16638 fn organize_imports(
16639 &mut self,
16640 _: &OrganizeImports,
16641 window: &mut Window,
16642 cx: &mut Context<Self>,
16643 ) -> Option<Task<Result<()>>> {
16644 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16645 let project = match &self.project {
16646 Some(project) => project.clone(),
16647 None => return None,
16648 };
16649 Some(self.perform_code_action_kind(
16650 project,
16651 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
16652 window,
16653 cx,
16654 ))
16655 }
16656
16657 fn perform_code_action_kind(
16658 &mut self,
16659 project: Entity<Project>,
16660 kind: CodeActionKind,
16661 window: &mut Window,
16662 cx: &mut Context<Self>,
16663 ) -> Task<Result<()>> {
16664 let buffer = self.buffer.clone();
16665 let buffers = buffer.read(cx).all_buffers();
16666 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
16667 let apply_action = project.update(cx, |project, cx| {
16668 project.apply_code_action_kind(buffers, kind, true, cx)
16669 });
16670 cx.spawn_in(window, async move |_, cx| {
16671 let transaction = futures::select_biased! {
16672 () = timeout => {
16673 log::warn!("timed out waiting for executing code action");
16674 None
16675 }
16676 transaction = apply_action.log_err().fuse() => transaction,
16677 };
16678 buffer
16679 .update(cx, |buffer, cx| {
16680 // check if we need this
16681 if let Some(transaction) = transaction {
16682 if !buffer.is_singleton() {
16683 buffer.push_transaction(&transaction.0, cx);
16684 }
16685 }
16686 cx.notify();
16687 })
16688 .ok();
16689 Ok(())
16690 })
16691 }
16692
16693 pub fn restart_language_server(
16694 &mut self,
16695 _: &RestartLanguageServer,
16696 _: &mut Window,
16697 cx: &mut Context<Self>,
16698 ) {
16699 if let Some(project) = self.project.clone() {
16700 self.buffer.update(cx, |multi_buffer, cx| {
16701 project.update(cx, |project, cx| {
16702 project.restart_language_servers_for_buffers(
16703 multi_buffer.all_buffers().into_iter().collect(),
16704 HashSet::default(),
16705 cx,
16706 );
16707 });
16708 })
16709 }
16710 }
16711
16712 pub fn stop_language_server(
16713 &mut self,
16714 _: &StopLanguageServer,
16715 _: &mut Window,
16716 cx: &mut Context<Self>,
16717 ) {
16718 if let Some(project) = self.project.clone() {
16719 self.buffer.update(cx, |multi_buffer, cx| {
16720 project.update(cx, |project, cx| {
16721 project.stop_language_servers_for_buffers(
16722 multi_buffer.all_buffers().into_iter().collect(),
16723 HashSet::default(),
16724 cx,
16725 );
16726 cx.emit(project::Event::RefreshInlayHints);
16727 });
16728 });
16729 }
16730 }
16731
16732 fn cancel_language_server_work(
16733 workspace: &mut Workspace,
16734 _: &actions::CancelLanguageServerWork,
16735 _: &mut Window,
16736 cx: &mut Context<Workspace>,
16737 ) {
16738 let project = workspace.project();
16739 let buffers = workspace
16740 .active_item(cx)
16741 .and_then(|item| item.act_as::<Editor>(cx))
16742 .map_or(HashSet::default(), |editor| {
16743 editor.read(cx).buffer.read(cx).all_buffers()
16744 });
16745 project.update(cx, |project, cx| {
16746 project.cancel_language_server_work_for_buffers(buffers, cx);
16747 });
16748 }
16749
16750 fn show_character_palette(
16751 &mut self,
16752 _: &ShowCharacterPalette,
16753 window: &mut Window,
16754 _: &mut Context<Self>,
16755 ) {
16756 window.show_character_palette();
16757 }
16758
16759 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
16760 if !self.diagnostics_enabled() {
16761 return;
16762 }
16763
16764 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
16765 let buffer = self.buffer.read(cx).snapshot(cx);
16766 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
16767 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
16768 let is_valid = buffer
16769 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
16770 .any(|entry| {
16771 entry.diagnostic.is_primary
16772 && !entry.range.is_empty()
16773 && entry.range.start == primary_range_start
16774 && entry.diagnostic.message == active_diagnostics.active_message
16775 });
16776
16777 if !is_valid {
16778 self.dismiss_diagnostics(cx);
16779 }
16780 }
16781 }
16782
16783 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
16784 match &self.active_diagnostics {
16785 ActiveDiagnostic::Group(group) => Some(group),
16786 _ => None,
16787 }
16788 }
16789
16790 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
16791 if !self.diagnostics_enabled() {
16792 return;
16793 }
16794 self.dismiss_diagnostics(cx);
16795 self.active_diagnostics = ActiveDiagnostic::All;
16796 }
16797
16798 fn activate_diagnostics(
16799 &mut self,
16800 buffer_id: BufferId,
16801 diagnostic: DiagnosticEntry<usize>,
16802 window: &mut Window,
16803 cx: &mut Context<Self>,
16804 ) {
16805 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16806 return;
16807 }
16808 self.dismiss_diagnostics(cx);
16809 let snapshot = self.snapshot(window, cx);
16810 let buffer = self.buffer.read(cx).snapshot(cx);
16811 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
16812 return;
16813 };
16814
16815 let diagnostic_group = buffer
16816 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
16817 .collect::<Vec<_>>();
16818
16819 let blocks =
16820 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
16821
16822 let blocks = self.display_map.update(cx, |display_map, cx| {
16823 display_map.insert_blocks(blocks, cx).into_iter().collect()
16824 });
16825 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
16826 active_range: buffer.anchor_before(diagnostic.range.start)
16827 ..buffer.anchor_after(diagnostic.range.end),
16828 active_message: diagnostic.diagnostic.message.clone(),
16829 group_id: diagnostic.diagnostic.group_id,
16830 blocks,
16831 });
16832 cx.notify();
16833 }
16834
16835 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
16836 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16837 return;
16838 };
16839
16840 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
16841 if let ActiveDiagnostic::Group(group) = prev {
16842 self.display_map.update(cx, |display_map, cx| {
16843 display_map.remove_blocks(group.blocks, cx);
16844 });
16845 cx.notify();
16846 }
16847 }
16848
16849 /// Disable inline diagnostics rendering for this editor.
16850 pub fn disable_inline_diagnostics(&mut self) {
16851 self.inline_diagnostics_enabled = false;
16852 self.inline_diagnostics_update = Task::ready(());
16853 self.inline_diagnostics.clear();
16854 }
16855
16856 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
16857 self.diagnostics_enabled = false;
16858 self.dismiss_diagnostics(cx);
16859 self.inline_diagnostics_update = Task::ready(());
16860 self.inline_diagnostics.clear();
16861 }
16862
16863 pub fn diagnostics_enabled(&self) -> bool {
16864 self.diagnostics_enabled && self.mode.is_full()
16865 }
16866
16867 pub fn inline_diagnostics_enabled(&self) -> bool {
16868 self.inline_diagnostics_enabled && self.diagnostics_enabled()
16869 }
16870
16871 pub fn show_inline_diagnostics(&self) -> bool {
16872 self.show_inline_diagnostics
16873 }
16874
16875 pub fn toggle_inline_diagnostics(
16876 &mut self,
16877 _: &ToggleInlineDiagnostics,
16878 window: &mut Window,
16879 cx: &mut Context<Editor>,
16880 ) {
16881 self.show_inline_diagnostics = !self.show_inline_diagnostics;
16882 self.refresh_inline_diagnostics(false, window, cx);
16883 }
16884
16885 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
16886 self.diagnostics_max_severity = severity;
16887 self.display_map.update(cx, |display_map, _| {
16888 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
16889 });
16890 }
16891
16892 pub fn toggle_diagnostics(
16893 &mut self,
16894 _: &ToggleDiagnostics,
16895 window: &mut Window,
16896 cx: &mut Context<Editor>,
16897 ) {
16898 if !self.diagnostics_enabled() {
16899 return;
16900 }
16901
16902 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16903 EditorSettings::get_global(cx)
16904 .diagnostics_max_severity
16905 .filter(|severity| severity != &DiagnosticSeverity::Off)
16906 .unwrap_or(DiagnosticSeverity::Hint)
16907 } else {
16908 DiagnosticSeverity::Off
16909 };
16910 self.set_max_diagnostics_severity(new_severity, cx);
16911 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16912 self.active_diagnostics = ActiveDiagnostic::None;
16913 self.inline_diagnostics_update = Task::ready(());
16914 self.inline_diagnostics.clear();
16915 } else {
16916 self.refresh_inline_diagnostics(false, window, cx);
16917 }
16918
16919 cx.notify();
16920 }
16921
16922 pub fn toggle_minimap(
16923 &mut self,
16924 _: &ToggleMinimap,
16925 window: &mut Window,
16926 cx: &mut Context<Editor>,
16927 ) {
16928 if self.supports_minimap(cx) {
16929 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
16930 }
16931 }
16932
16933 fn refresh_inline_diagnostics(
16934 &mut self,
16935 debounce: bool,
16936 window: &mut Window,
16937 cx: &mut Context<Self>,
16938 ) {
16939 let max_severity = ProjectSettings::get_global(cx)
16940 .diagnostics
16941 .inline
16942 .max_severity
16943 .unwrap_or(self.diagnostics_max_severity);
16944
16945 if !self.inline_diagnostics_enabled()
16946 || !self.show_inline_diagnostics
16947 || max_severity == DiagnosticSeverity::Off
16948 {
16949 self.inline_diagnostics_update = Task::ready(());
16950 self.inline_diagnostics.clear();
16951 return;
16952 }
16953
16954 let debounce_ms = ProjectSettings::get_global(cx)
16955 .diagnostics
16956 .inline
16957 .update_debounce_ms;
16958 let debounce = if debounce && debounce_ms > 0 {
16959 Some(Duration::from_millis(debounce_ms))
16960 } else {
16961 None
16962 };
16963 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
16964 if let Some(debounce) = debounce {
16965 cx.background_executor().timer(debounce).await;
16966 }
16967 let Some(snapshot) = editor.upgrade().and_then(|editor| {
16968 editor
16969 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
16970 .ok()
16971 }) else {
16972 return;
16973 };
16974
16975 let new_inline_diagnostics = cx
16976 .background_spawn(async move {
16977 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
16978 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
16979 let message = diagnostic_entry
16980 .diagnostic
16981 .message
16982 .split_once('\n')
16983 .map(|(line, _)| line)
16984 .map(SharedString::new)
16985 .unwrap_or_else(|| {
16986 SharedString::from(diagnostic_entry.diagnostic.message)
16987 });
16988 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
16989 let (Ok(i) | Err(i)) = inline_diagnostics
16990 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
16991 inline_diagnostics.insert(
16992 i,
16993 (
16994 start_anchor,
16995 InlineDiagnostic {
16996 message,
16997 group_id: diagnostic_entry.diagnostic.group_id,
16998 start: diagnostic_entry.range.start.to_point(&snapshot),
16999 is_primary: diagnostic_entry.diagnostic.is_primary,
17000 severity: diagnostic_entry.diagnostic.severity,
17001 },
17002 ),
17003 );
17004 }
17005 inline_diagnostics
17006 })
17007 .await;
17008
17009 editor
17010 .update(cx, |editor, cx| {
17011 editor.inline_diagnostics = new_inline_diagnostics;
17012 cx.notify();
17013 })
17014 .ok();
17015 });
17016 }
17017
17018 fn pull_diagnostics(
17019 &mut self,
17020 buffer_id: Option<BufferId>,
17021 window: &Window,
17022 cx: &mut Context<Self>,
17023 ) -> Option<()> {
17024 if !self.mode().is_full() {
17025 return None;
17026 }
17027 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
17028 .diagnostics
17029 .lsp_pull_diagnostics;
17030 if !pull_diagnostics_settings.enabled {
17031 return None;
17032 }
17033 let project = self.project.as_ref()?.downgrade();
17034 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
17035 let mut buffers = self.buffer.read(cx).all_buffers();
17036 if let Some(buffer_id) = buffer_id {
17037 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
17038 }
17039
17040 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
17041 cx.background_executor().timer(debounce).await;
17042
17043 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
17044 buffers
17045 .into_iter()
17046 .filter_map(|buffer| {
17047 project
17048 .update(cx, |project, cx| {
17049 project.lsp_store().update(cx, |lsp_store, cx| {
17050 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
17051 })
17052 })
17053 .ok()
17054 })
17055 .collect::<FuturesUnordered<_>>()
17056 }) else {
17057 return;
17058 };
17059
17060 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
17061 match pull_task {
17062 Ok(()) => {
17063 if editor
17064 .update_in(cx, |editor, window, cx| {
17065 editor.update_diagnostics_state(window, cx);
17066 })
17067 .is_err()
17068 {
17069 return;
17070 }
17071 }
17072 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
17073 }
17074 }
17075 });
17076
17077 Some(())
17078 }
17079
17080 pub fn set_selections_from_remote(
17081 &mut self,
17082 selections: Vec<Selection<Anchor>>,
17083 pending_selection: Option<Selection<Anchor>>,
17084 window: &mut Window,
17085 cx: &mut Context<Self>,
17086 ) {
17087 let old_cursor_position = self.selections.newest_anchor().head();
17088 self.selections.change_with(cx, |s| {
17089 s.select_anchors(selections);
17090 if let Some(pending_selection) = pending_selection {
17091 s.set_pending(pending_selection, SelectMode::Character);
17092 } else {
17093 s.clear_pending();
17094 }
17095 });
17096 self.selections_did_change(
17097 false,
17098 &old_cursor_position,
17099 SelectionEffects::default(),
17100 window,
17101 cx,
17102 );
17103 }
17104
17105 pub fn transact(
17106 &mut self,
17107 window: &mut Window,
17108 cx: &mut Context<Self>,
17109 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
17110 ) -> Option<TransactionId> {
17111 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17112 this.start_transaction_at(Instant::now(), window, cx);
17113 update(this, window, cx);
17114 this.end_transaction_at(Instant::now(), cx)
17115 })
17116 }
17117
17118 pub fn start_transaction_at(
17119 &mut self,
17120 now: Instant,
17121 window: &mut Window,
17122 cx: &mut Context<Self>,
17123 ) -> Option<TransactionId> {
17124 self.end_selection(window, cx);
17125 if let Some(tx_id) = self
17126 .buffer
17127 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
17128 {
17129 self.selection_history
17130 .insert_transaction(tx_id, self.selections.disjoint_anchors());
17131 cx.emit(EditorEvent::TransactionBegun {
17132 transaction_id: tx_id,
17133 });
17134 Some(tx_id)
17135 } else {
17136 None
17137 }
17138 }
17139
17140 pub fn end_transaction_at(
17141 &mut self,
17142 now: Instant,
17143 cx: &mut Context<Self>,
17144 ) -> Option<TransactionId> {
17145 if let Some(transaction_id) = self
17146 .buffer
17147 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
17148 {
17149 if let Some((_, end_selections)) =
17150 self.selection_history.transaction_mut(transaction_id)
17151 {
17152 *end_selections = Some(self.selections.disjoint_anchors());
17153 } else {
17154 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
17155 }
17156
17157 cx.emit(EditorEvent::Edited { transaction_id });
17158 Some(transaction_id)
17159 } else {
17160 None
17161 }
17162 }
17163
17164 pub fn modify_transaction_selection_history(
17165 &mut self,
17166 transaction_id: TransactionId,
17167 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
17168 ) -> bool {
17169 self.selection_history
17170 .transaction_mut(transaction_id)
17171 .map(modify)
17172 .is_some()
17173 }
17174
17175 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
17176 if self.selection_mark_mode {
17177 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17178 s.move_with(|_, sel| {
17179 sel.collapse_to(sel.head(), SelectionGoal::None);
17180 });
17181 })
17182 }
17183 self.selection_mark_mode = true;
17184 cx.notify();
17185 }
17186
17187 pub fn swap_selection_ends(
17188 &mut self,
17189 _: &actions::SwapSelectionEnds,
17190 window: &mut Window,
17191 cx: &mut Context<Self>,
17192 ) {
17193 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17194 s.move_with(|_, sel| {
17195 if sel.start != sel.end {
17196 sel.reversed = !sel.reversed
17197 }
17198 });
17199 });
17200 self.request_autoscroll(Autoscroll::newest(), cx);
17201 cx.notify();
17202 }
17203
17204 pub fn toggle_focus(
17205 workspace: &mut Workspace,
17206 _: &actions::ToggleFocus,
17207 window: &mut Window,
17208 cx: &mut Context<Workspace>,
17209 ) {
17210 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
17211 return;
17212 };
17213 workspace.activate_item(&item, true, true, window, cx);
17214 }
17215
17216 pub fn toggle_fold(
17217 &mut self,
17218 _: &actions::ToggleFold,
17219 window: &mut Window,
17220 cx: &mut Context<Self>,
17221 ) {
17222 if self.is_singleton(cx) {
17223 let selection = self.selections.newest::<Point>(cx);
17224
17225 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17226 let range = if selection.is_empty() {
17227 let point = selection.head().to_display_point(&display_map);
17228 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17229 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17230 .to_point(&display_map);
17231 start..end
17232 } else {
17233 selection.range()
17234 };
17235 if display_map.folds_in_range(range).next().is_some() {
17236 self.unfold_lines(&Default::default(), window, cx)
17237 } else {
17238 self.fold(&Default::default(), window, cx)
17239 }
17240 } else {
17241 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17242 let buffer_ids: HashSet<_> = self
17243 .selections
17244 .disjoint_anchor_ranges()
17245 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17246 .collect();
17247
17248 let should_unfold = buffer_ids
17249 .iter()
17250 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17251
17252 for buffer_id in buffer_ids {
17253 if should_unfold {
17254 self.unfold_buffer(buffer_id, cx);
17255 } else {
17256 self.fold_buffer(buffer_id, cx);
17257 }
17258 }
17259 }
17260 }
17261
17262 pub fn toggle_fold_recursive(
17263 &mut self,
17264 _: &actions::ToggleFoldRecursive,
17265 window: &mut Window,
17266 cx: &mut Context<Self>,
17267 ) {
17268 let selection = self.selections.newest::<Point>(cx);
17269
17270 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17271 let range = if selection.is_empty() {
17272 let point = selection.head().to_display_point(&display_map);
17273 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17274 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17275 .to_point(&display_map);
17276 start..end
17277 } else {
17278 selection.range()
17279 };
17280 if display_map.folds_in_range(range).next().is_some() {
17281 self.unfold_recursive(&Default::default(), window, cx)
17282 } else {
17283 self.fold_recursive(&Default::default(), window, cx)
17284 }
17285 }
17286
17287 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
17288 if self.is_singleton(cx) {
17289 let mut to_fold = Vec::new();
17290 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17291 let selections = self.selections.all_adjusted(cx);
17292
17293 for selection in selections {
17294 let range = selection.range().sorted();
17295 let buffer_start_row = range.start.row;
17296
17297 if range.start.row != range.end.row {
17298 let mut found = false;
17299 let mut row = range.start.row;
17300 while row <= range.end.row {
17301 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
17302 {
17303 found = true;
17304 row = crease.range().end.row + 1;
17305 to_fold.push(crease);
17306 } else {
17307 row += 1
17308 }
17309 }
17310 if found {
17311 continue;
17312 }
17313 }
17314
17315 for row in (0..=range.start.row).rev() {
17316 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17317 if crease.range().end.row >= buffer_start_row {
17318 to_fold.push(crease);
17319 if row <= range.start.row {
17320 break;
17321 }
17322 }
17323 }
17324 }
17325 }
17326
17327 self.fold_creases(to_fold, true, window, cx);
17328 } else {
17329 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17330 let buffer_ids = self
17331 .selections
17332 .disjoint_anchor_ranges()
17333 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17334 .collect::<HashSet<_>>();
17335 for buffer_id in buffer_ids {
17336 self.fold_buffer(buffer_id, cx);
17337 }
17338 }
17339 }
17340
17341 pub fn toggle_fold_all(
17342 &mut self,
17343 _: &actions::ToggleFoldAll,
17344 window: &mut Window,
17345 cx: &mut Context<Self>,
17346 ) {
17347 if self.buffer.read(cx).is_singleton() {
17348 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17349 let has_folds = display_map
17350 .folds_in_range(0..display_map.buffer_snapshot.len())
17351 .next()
17352 .is_some();
17353
17354 if has_folds {
17355 self.unfold_all(&actions::UnfoldAll, window, cx);
17356 } else {
17357 self.fold_all(&actions::FoldAll, window, cx);
17358 }
17359 } else {
17360 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
17361 let should_unfold = buffer_ids
17362 .iter()
17363 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17364
17365 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17366 editor
17367 .update_in(cx, |editor, _, cx| {
17368 for buffer_id in buffer_ids {
17369 if should_unfold {
17370 editor.unfold_buffer(buffer_id, cx);
17371 } else {
17372 editor.fold_buffer(buffer_id, cx);
17373 }
17374 }
17375 })
17376 .ok();
17377 });
17378 }
17379 }
17380
17381 fn fold_at_level(
17382 &mut self,
17383 fold_at: &FoldAtLevel,
17384 window: &mut Window,
17385 cx: &mut Context<Self>,
17386 ) {
17387 if !self.buffer.read(cx).is_singleton() {
17388 return;
17389 }
17390
17391 let fold_at_level = fold_at.0;
17392 let snapshot = self.buffer.read(cx).snapshot(cx);
17393 let mut to_fold = Vec::new();
17394 let mut stack = vec![(0, snapshot.max_row().0, 1)];
17395
17396 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
17397 while start_row < end_row {
17398 match self
17399 .snapshot(window, cx)
17400 .crease_for_buffer_row(MultiBufferRow(start_row))
17401 {
17402 Some(crease) => {
17403 let nested_start_row = crease.range().start.row + 1;
17404 let nested_end_row = crease.range().end.row;
17405
17406 if current_level < fold_at_level {
17407 stack.push((nested_start_row, nested_end_row, current_level + 1));
17408 } else if current_level == fold_at_level {
17409 to_fold.push(crease);
17410 }
17411
17412 start_row = nested_end_row + 1;
17413 }
17414 None => start_row += 1,
17415 }
17416 }
17417 }
17418
17419 self.fold_creases(to_fold, true, window, cx);
17420 }
17421
17422 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
17423 if self.buffer.read(cx).is_singleton() {
17424 let mut fold_ranges = Vec::new();
17425 let snapshot = self.buffer.read(cx).snapshot(cx);
17426
17427 for row in 0..snapshot.max_row().0 {
17428 if let Some(foldable_range) = self
17429 .snapshot(window, cx)
17430 .crease_for_buffer_row(MultiBufferRow(row))
17431 {
17432 fold_ranges.push(foldable_range);
17433 }
17434 }
17435
17436 self.fold_creases(fold_ranges, true, window, cx);
17437 } else {
17438 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17439 editor
17440 .update_in(cx, |editor, _, cx| {
17441 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17442 editor.fold_buffer(buffer_id, cx);
17443 }
17444 })
17445 .ok();
17446 });
17447 }
17448 }
17449
17450 pub fn fold_function_bodies(
17451 &mut self,
17452 _: &actions::FoldFunctionBodies,
17453 window: &mut Window,
17454 cx: &mut Context<Self>,
17455 ) {
17456 let snapshot = self.buffer.read(cx).snapshot(cx);
17457
17458 let ranges = snapshot
17459 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
17460 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
17461 .collect::<Vec<_>>();
17462
17463 let creases = ranges
17464 .into_iter()
17465 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
17466 .collect();
17467
17468 self.fold_creases(creases, true, window, cx);
17469 }
17470
17471 pub fn fold_recursive(
17472 &mut self,
17473 _: &actions::FoldRecursive,
17474 window: &mut Window,
17475 cx: &mut Context<Self>,
17476 ) {
17477 let mut to_fold = Vec::new();
17478 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17479 let selections = self.selections.all_adjusted(cx);
17480
17481 for selection in selections {
17482 let range = selection.range().sorted();
17483 let buffer_start_row = range.start.row;
17484
17485 if range.start.row != range.end.row {
17486 let mut found = false;
17487 for row in range.start.row..=range.end.row {
17488 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17489 found = true;
17490 to_fold.push(crease);
17491 }
17492 }
17493 if found {
17494 continue;
17495 }
17496 }
17497
17498 for row in (0..=range.start.row).rev() {
17499 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17500 if crease.range().end.row >= buffer_start_row {
17501 to_fold.push(crease);
17502 } else {
17503 break;
17504 }
17505 }
17506 }
17507 }
17508
17509 self.fold_creases(to_fold, true, window, cx);
17510 }
17511
17512 pub fn fold_at(
17513 &mut self,
17514 buffer_row: MultiBufferRow,
17515 window: &mut Window,
17516 cx: &mut Context<Self>,
17517 ) {
17518 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17519
17520 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
17521 let autoscroll = self
17522 .selections
17523 .all::<Point>(cx)
17524 .iter()
17525 .any(|selection| crease.range().overlaps(&selection.range()));
17526
17527 self.fold_creases(vec![crease], autoscroll, window, cx);
17528 }
17529 }
17530
17531 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
17532 if self.is_singleton(cx) {
17533 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17534 let buffer = &display_map.buffer_snapshot;
17535 let selections = self.selections.all::<Point>(cx);
17536 let ranges = selections
17537 .iter()
17538 .map(|s| {
17539 let range = s.display_range(&display_map).sorted();
17540 let mut start = range.start.to_point(&display_map);
17541 let mut end = range.end.to_point(&display_map);
17542 start.column = 0;
17543 end.column = buffer.line_len(MultiBufferRow(end.row));
17544 start..end
17545 })
17546 .collect::<Vec<_>>();
17547
17548 self.unfold_ranges(&ranges, true, true, cx);
17549 } else {
17550 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17551 let buffer_ids = self
17552 .selections
17553 .disjoint_anchor_ranges()
17554 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17555 .collect::<HashSet<_>>();
17556 for buffer_id in buffer_ids {
17557 self.unfold_buffer(buffer_id, cx);
17558 }
17559 }
17560 }
17561
17562 pub fn unfold_recursive(
17563 &mut self,
17564 _: &UnfoldRecursive,
17565 _window: &mut Window,
17566 cx: &mut Context<Self>,
17567 ) {
17568 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17569 let selections = self.selections.all::<Point>(cx);
17570 let ranges = selections
17571 .iter()
17572 .map(|s| {
17573 let mut range = s.display_range(&display_map).sorted();
17574 *range.start.column_mut() = 0;
17575 *range.end.column_mut() = display_map.line_len(range.end.row());
17576 let start = range.start.to_point(&display_map);
17577 let end = range.end.to_point(&display_map);
17578 start..end
17579 })
17580 .collect::<Vec<_>>();
17581
17582 self.unfold_ranges(&ranges, true, true, cx);
17583 }
17584
17585 pub fn unfold_at(
17586 &mut self,
17587 buffer_row: MultiBufferRow,
17588 _window: &mut Window,
17589 cx: &mut Context<Self>,
17590 ) {
17591 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17592
17593 let intersection_range = Point::new(buffer_row.0, 0)
17594 ..Point::new(
17595 buffer_row.0,
17596 display_map.buffer_snapshot.line_len(buffer_row),
17597 );
17598
17599 let autoscroll = self
17600 .selections
17601 .all::<Point>(cx)
17602 .iter()
17603 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
17604
17605 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
17606 }
17607
17608 pub fn unfold_all(
17609 &mut self,
17610 _: &actions::UnfoldAll,
17611 _window: &mut Window,
17612 cx: &mut Context<Self>,
17613 ) {
17614 if self.buffer.read(cx).is_singleton() {
17615 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17616 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
17617 } else {
17618 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
17619 editor
17620 .update(cx, |editor, cx| {
17621 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17622 editor.unfold_buffer(buffer_id, cx);
17623 }
17624 })
17625 .ok();
17626 });
17627 }
17628 }
17629
17630 pub fn fold_selected_ranges(
17631 &mut self,
17632 _: &FoldSelectedRanges,
17633 window: &mut Window,
17634 cx: &mut Context<Self>,
17635 ) {
17636 let selections = self.selections.all_adjusted(cx);
17637 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17638 let ranges = selections
17639 .into_iter()
17640 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
17641 .collect::<Vec<_>>();
17642 self.fold_creases(ranges, true, window, cx);
17643 }
17644
17645 pub fn fold_ranges<T: ToOffset + Clone>(
17646 &mut self,
17647 ranges: Vec<Range<T>>,
17648 auto_scroll: bool,
17649 window: &mut Window,
17650 cx: &mut Context<Self>,
17651 ) {
17652 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17653 let ranges = ranges
17654 .into_iter()
17655 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
17656 .collect::<Vec<_>>();
17657 self.fold_creases(ranges, auto_scroll, window, cx);
17658 }
17659
17660 pub fn fold_creases<T: ToOffset + Clone>(
17661 &mut self,
17662 creases: Vec<Crease<T>>,
17663 auto_scroll: bool,
17664 _window: &mut Window,
17665 cx: &mut Context<Self>,
17666 ) {
17667 if creases.is_empty() {
17668 return;
17669 }
17670
17671 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
17672
17673 if auto_scroll {
17674 self.request_autoscroll(Autoscroll::fit(), cx);
17675 }
17676
17677 cx.notify();
17678
17679 self.scrollbar_marker_state.dirty = true;
17680 self.folds_did_change(cx);
17681 }
17682
17683 /// Removes any folds whose ranges intersect any of the given ranges.
17684 pub fn unfold_ranges<T: ToOffset + Clone>(
17685 &mut self,
17686 ranges: &[Range<T>],
17687 inclusive: bool,
17688 auto_scroll: bool,
17689 cx: &mut Context<Self>,
17690 ) {
17691 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17692 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
17693 });
17694 self.folds_did_change(cx);
17695 }
17696
17697 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17698 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
17699 return;
17700 }
17701 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17702 self.display_map.update(cx, |display_map, cx| {
17703 display_map.fold_buffers([buffer_id], cx)
17704 });
17705 cx.emit(EditorEvent::BufferFoldToggled {
17706 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
17707 folded: true,
17708 });
17709 cx.notify();
17710 }
17711
17712 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17713 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
17714 return;
17715 }
17716 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17717 self.display_map.update(cx, |display_map, cx| {
17718 display_map.unfold_buffers([buffer_id], cx);
17719 });
17720 cx.emit(EditorEvent::BufferFoldToggled {
17721 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
17722 folded: false,
17723 });
17724 cx.notify();
17725 }
17726
17727 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
17728 self.display_map.read(cx).is_buffer_folded(buffer)
17729 }
17730
17731 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
17732 self.display_map.read(cx).folded_buffers()
17733 }
17734
17735 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17736 self.display_map.update(cx, |display_map, cx| {
17737 display_map.disable_header_for_buffer(buffer_id, cx);
17738 });
17739 cx.notify();
17740 }
17741
17742 /// Removes any folds with the given ranges.
17743 pub fn remove_folds_with_type<T: ToOffset + Clone>(
17744 &mut self,
17745 ranges: &[Range<T>],
17746 type_id: TypeId,
17747 auto_scroll: bool,
17748 cx: &mut Context<Self>,
17749 ) {
17750 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17751 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
17752 });
17753 self.folds_did_change(cx);
17754 }
17755
17756 fn remove_folds_with<T: ToOffset + Clone>(
17757 &mut self,
17758 ranges: &[Range<T>],
17759 auto_scroll: bool,
17760 cx: &mut Context<Self>,
17761 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
17762 ) {
17763 if ranges.is_empty() {
17764 return;
17765 }
17766
17767 let mut buffers_affected = HashSet::default();
17768 let multi_buffer = self.buffer().read(cx);
17769 for range in ranges {
17770 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
17771 buffers_affected.insert(buffer.read(cx).remote_id());
17772 };
17773 }
17774
17775 self.display_map.update(cx, update);
17776
17777 if auto_scroll {
17778 self.request_autoscroll(Autoscroll::fit(), cx);
17779 }
17780
17781 cx.notify();
17782 self.scrollbar_marker_state.dirty = true;
17783 self.active_indent_guides_state.dirty = true;
17784 }
17785
17786 pub fn update_renderer_widths(
17787 &mut self,
17788 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
17789 cx: &mut Context<Self>,
17790 ) -> bool {
17791 self.display_map
17792 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
17793 }
17794
17795 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
17796 self.display_map.read(cx).fold_placeholder.clone()
17797 }
17798
17799 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
17800 self.buffer.update(cx, |buffer, cx| {
17801 buffer.set_all_diff_hunks_expanded(cx);
17802 });
17803 }
17804
17805 pub fn expand_all_diff_hunks(
17806 &mut self,
17807 _: &ExpandAllDiffHunks,
17808 _window: &mut Window,
17809 cx: &mut Context<Self>,
17810 ) {
17811 self.buffer.update(cx, |buffer, cx| {
17812 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
17813 });
17814 }
17815
17816 pub fn toggle_selected_diff_hunks(
17817 &mut self,
17818 _: &ToggleSelectedDiffHunks,
17819 _window: &mut Window,
17820 cx: &mut Context<Self>,
17821 ) {
17822 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17823 self.toggle_diff_hunks_in_ranges(ranges, cx);
17824 }
17825
17826 pub fn diff_hunks_in_ranges<'a>(
17827 &'a self,
17828 ranges: &'a [Range<Anchor>],
17829 buffer: &'a MultiBufferSnapshot,
17830 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
17831 ranges.iter().flat_map(move |range| {
17832 let end_excerpt_id = range.end.excerpt_id;
17833 let range = range.to_point(buffer);
17834 let mut peek_end = range.end;
17835 if range.end.row < buffer.max_row().0 {
17836 peek_end = Point::new(range.end.row + 1, 0);
17837 }
17838 buffer
17839 .diff_hunks_in_range(range.start..peek_end)
17840 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
17841 })
17842 }
17843
17844 pub fn has_stageable_diff_hunks_in_ranges(
17845 &self,
17846 ranges: &[Range<Anchor>],
17847 snapshot: &MultiBufferSnapshot,
17848 ) -> bool {
17849 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
17850 hunks.any(|hunk| hunk.status().has_secondary_hunk())
17851 }
17852
17853 pub fn toggle_staged_selected_diff_hunks(
17854 &mut self,
17855 _: &::git::ToggleStaged,
17856 _: &mut Window,
17857 cx: &mut Context<Self>,
17858 ) {
17859 let snapshot = self.buffer.read(cx).snapshot(cx);
17860 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17861 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
17862 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17863 }
17864
17865 pub fn set_render_diff_hunk_controls(
17866 &mut self,
17867 render_diff_hunk_controls: RenderDiffHunkControlsFn,
17868 cx: &mut Context<Self>,
17869 ) {
17870 self.render_diff_hunk_controls = render_diff_hunk_controls;
17871 cx.notify();
17872 }
17873
17874 pub fn stage_and_next(
17875 &mut self,
17876 _: &::git::StageAndNext,
17877 window: &mut Window,
17878 cx: &mut Context<Self>,
17879 ) {
17880 self.do_stage_or_unstage_and_next(true, window, cx);
17881 }
17882
17883 pub fn unstage_and_next(
17884 &mut self,
17885 _: &::git::UnstageAndNext,
17886 window: &mut Window,
17887 cx: &mut Context<Self>,
17888 ) {
17889 self.do_stage_or_unstage_and_next(false, window, cx);
17890 }
17891
17892 pub fn stage_or_unstage_diff_hunks(
17893 &mut self,
17894 stage: bool,
17895 ranges: Vec<Range<Anchor>>,
17896 cx: &mut Context<Self>,
17897 ) {
17898 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
17899 cx.spawn(async move |this, cx| {
17900 task.await?;
17901 this.update(cx, |this, cx| {
17902 let snapshot = this.buffer.read(cx).snapshot(cx);
17903 let chunk_by = this
17904 .diff_hunks_in_ranges(&ranges, &snapshot)
17905 .chunk_by(|hunk| hunk.buffer_id);
17906 for (buffer_id, hunks) in &chunk_by {
17907 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
17908 }
17909 })
17910 })
17911 .detach_and_log_err(cx);
17912 }
17913
17914 fn save_buffers_for_ranges_if_needed(
17915 &mut self,
17916 ranges: &[Range<Anchor>],
17917 cx: &mut Context<Editor>,
17918 ) -> Task<Result<()>> {
17919 let multibuffer = self.buffer.read(cx);
17920 let snapshot = multibuffer.read(cx);
17921 let buffer_ids: HashSet<_> = ranges
17922 .iter()
17923 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
17924 .collect();
17925 drop(snapshot);
17926
17927 let mut buffers = HashSet::default();
17928 for buffer_id in buffer_ids {
17929 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
17930 let buffer = buffer_entity.read(cx);
17931 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
17932 {
17933 buffers.insert(buffer_entity);
17934 }
17935 }
17936 }
17937
17938 if let Some(project) = &self.project {
17939 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
17940 } else {
17941 Task::ready(Ok(()))
17942 }
17943 }
17944
17945 fn do_stage_or_unstage_and_next(
17946 &mut self,
17947 stage: bool,
17948 window: &mut Window,
17949 cx: &mut Context<Self>,
17950 ) {
17951 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
17952
17953 if ranges.iter().any(|range| range.start != range.end) {
17954 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17955 return;
17956 }
17957
17958 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17959 let snapshot = self.snapshot(window, cx);
17960 let position = self.selections.newest::<Point>(cx).head();
17961 let mut row = snapshot
17962 .buffer_snapshot
17963 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
17964 .find(|hunk| hunk.row_range.start.0 > position.row)
17965 .map(|hunk| hunk.row_range.start);
17966
17967 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
17968 // Outside of the project diff editor, wrap around to the beginning.
17969 if !all_diff_hunks_expanded {
17970 row = row.or_else(|| {
17971 snapshot
17972 .buffer_snapshot
17973 .diff_hunks_in_range(Point::zero()..position)
17974 .find(|hunk| hunk.row_range.end.0 < position.row)
17975 .map(|hunk| hunk.row_range.start)
17976 });
17977 }
17978
17979 if let Some(row) = row {
17980 let destination = Point::new(row.0, 0);
17981 let autoscroll = Autoscroll::center();
17982
17983 self.unfold_ranges(&[destination..destination], false, false, cx);
17984 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17985 s.select_ranges([destination..destination]);
17986 });
17987 }
17988 }
17989
17990 fn do_stage_or_unstage(
17991 &self,
17992 stage: bool,
17993 buffer_id: BufferId,
17994 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
17995 cx: &mut App,
17996 ) -> Option<()> {
17997 let project = self.project.as_ref()?;
17998 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
17999 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
18000 let buffer_snapshot = buffer.read(cx).snapshot();
18001 let file_exists = buffer_snapshot
18002 .file()
18003 .is_some_and(|file| file.disk_state().exists());
18004 diff.update(cx, |diff, cx| {
18005 diff.stage_or_unstage_hunks(
18006 stage,
18007 &hunks
18008 .map(|hunk| buffer_diff::DiffHunk {
18009 buffer_range: hunk.buffer_range,
18010 diff_base_byte_range: hunk.diff_base_byte_range,
18011 secondary_status: hunk.secondary_status,
18012 range: Point::zero()..Point::zero(), // unused
18013 })
18014 .collect::<Vec<_>>(),
18015 &buffer_snapshot,
18016 file_exists,
18017 cx,
18018 )
18019 });
18020 None
18021 }
18022
18023 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
18024 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
18025 self.buffer
18026 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
18027 }
18028
18029 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
18030 self.buffer.update(cx, |buffer, cx| {
18031 let ranges = vec![Anchor::min()..Anchor::max()];
18032 if !buffer.all_diff_hunks_expanded()
18033 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
18034 {
18035 buffer.collapse_diff_hunks(ranges, cx);
18036 true
18037 } else {
18038 false
18039 }
18040 })
18041 }
18042
18043 fn toggle_diff_hunks_in_ranges(
18044 &mut self,
18045 ranges: Vec<Range<Anchor>>,
18046 cx: &mut Context<Editor>,
18047 ) {
18048 self.buffer.update(cx, |buffer, cx| {
18049 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
18050 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
18051 })
18052 }
18053
18054 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
18055 self.buffer.update(cx, |buffer, cx| {
18056 let snapshot = buffer.snapshot(cx);
18057 let excerpt_id = range.end.excerpt_id;
18058 let point_range = range.to_point(&snapshot);
18059 let expand = !buffer.single_hunk_is_expanded(range, cx);
18060 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
18061 })
18062 }
18063
18064 pub(crate) fn apply_all_diff_hunks(
18065 &mut self,
18066 _: &ApplyAllDiffHunks,
18067 window: &mut Window,
18068 cx: &mut Context<Self>,
18069 ) {
18070 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18071
18072 let buffers = self.buffer.read(cx).all_buffers();
18073 for branch_buffer in buffers {
18074 branch_buffer.update(cx, |branch_buffer, cx| {
18075 branch_buffer.merge_into_base(Vec::new(), cx);
18076 });
18077 }
18078
18079 if let Some(project) = self.project.clone() {
18080 self.save(
18081 SaveOptions {
18082 format: true,
18083 autosave: false,
18084 },
18085 project,
18086 window,
18087 cx,
18088 )
18089 .detach_and_log_err(cx);
18090 }
18091 }
18092
18093 pub(crate) fn apply_selected_diff_hunks(
18094 &mut self,
18095 _: &ApplyDiffHunk,
18096 window: &mut Window,
18097 cx: &mut Context<Self>,
18098 ) {
18099 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18100 let snapshot = self.snapshot(window, cx);
18101 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
18102 let mut ranges_by_buffer = HashMap::default();
18103 self.transact(window, cx, |editor, _window, cx| {
18104 for hunk in hunks {
18105 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
18106 ranges_by_buffer
18107 .entry(buffer.clone())
18108 .or_insert_with(Vec::new)
18109 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
18110 }
18111 }
18112
18113 for (buffer, ranges) in ranges_by_buffer {
18114 buffer.update(cx, |buffer, cx| {
18115 buffer.merge_into_base(ranges, cx);
18116 });
18117 }
18118 });
18119
18120 if let Some(project) = self.project.clone() {
18121 self.save(
18122 SaveOptions {
18123 format: true,
18124 autosave: false,
18125 },
18126 project,
18127 window,
18128 cx,
18129 )
18130 .detach_and_log_err(cx);
18131 }
18132 }
18133
18134 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
18135 if hovered != self.gutter_hovered {
18136 self.gutter_hovered = hovered;
18137 cx.notify();
18138 }
18139 }
18140
18141 pub fn insert_blocks(
18142 &mut self,
18143 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
18144 autoscroll: Option<Autoscroll>,
18145 cx: &mut Context<Self>,
18146 ) -> Vec<CustomBlockId> {
18147 let blocks = self
18148 .display_map
18149 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
18150 if let Some(autoscroll) = autoscroll {
18151 self.request_autoscroll(autoscroll, cx);
18152 }
18153 cx.notify();
18154 blocks
18155 }
18156
18157 pub fn resize_blocks(
18158 &mut self,
18159 heights: HashMap<CustomBlockId, u32>,
18160 autoscroll: Option<Autoscroll>,
18161 cx: &mut Context<Self>,
18162 ) {
18163 self.display_map
18164 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
18165 if let Some(autoscroll) = autoscroll {
18166 self.request_autoscroll(autoscroll, cx);
18167 }
18168 cx.notify();
18169 }
18170
18171 pub fn replace_blocks(
18172 &mut self,
18173 renderers: HashMap<CustomBlockId, RenderBlock>,
18174 autoscroll: Option<Autoscroll>,
18175 cx: &mut Context<Self>,
18176 ) {
18177 self.display_map
18178 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
18179 if let Some(autoscroll) = autoscroll {
18180 self.request_autoscroll(autoscroll, cx);
18181 }
18182 cx.notify();
18183 }
18184
18185 pub fn remove_blocks(
18186 &mut self,
18187 block_ids: HashSet<CustomBlockId>,
18188 autoscroll: Option<Autoscroll>,
18189 cx: &mut Context<Self>,
18190 ) {
18191 self.display_map.update(cx, |display_map, cx| {
18192 display_map.remove_blocks(block_ids, cx)
18193 });
18194 if let Some(autoscroll) = autoscroll {
18195 self.request_autoscroll(autoscroll, cx);
18196 }
18197 cx.notify();
18198 }
18199
18200 pub fn row_for_block(
18201 &self,
18202 block_id: CustomBlockId,
18203 cx: &mut Context<Self>,
18204 ) -> Option<DisplayRow> {
18205 self.display_map
18206 .update(cx, |map, cx| map.row_for_block(block_id, cx))
18207 }
18208
18209 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
18210 self.focused_block = Some(focused_block);
18211 }
18212
18213 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
18214 self.focused_block.take()
18215 }
18216
18217 pub fn insert_creases(
18218 &mut self,
18219 creases: impl IntoIterator<Item = Crease<Anchor>>,
18220 cx: &mut Context<Self>,
18221 ) -> Vec<CreaseId> {
18222 self.display_map
18223 .update(cx, |map, cx| map.insert_creases(creases, cx))
18224 }
18225
18226 pub fn remove_creases(
18227 &mut self,
18228 ids: impl IntoIterator<Item = CreaseId>,
18229 cx: &mut Context<Self>,
18230 ) -> Vec<(CreaseId, Range<Anchor>)> {
18231 self.display_map
18232 .update(cx, |map, cx| map.remove_creases(ids, cx))
18233 }
18234
18235 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
18236 self.display_map
18237 .update(cx, |map, cx| map.snapshot(cx))
18238 .longest_row()
18239 }
18240
18241 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
18242 self.display_map
18243 .update(cx, |map, cx| map.snapshot(cx))
18244 .max_point()
18245 }
18246
18247 pub fn text(&self, cx: &App) -> String {
18248 self.buffer.read(cx).read(cx).text()
18249 }
18250
18251 pub fn is_empty(&self, cx: &App) -> bool {
18252 self.buffer.read(cx).read(cx).is_empty()
18253 }
18254
18255 pub fn text_option(&self, cx: &App) -> Option<String> {
18256 let text = self.text(cx);
18257 let text = text.trim();
18258
18259 if text.is_empty() {
18260 return None;
18261 }
18262
18263 Some(text.to_string())
18264 }
18265
18266 pub fn set_text(
18267 &mut self,
18268 text: impl Into<Arc<str>>,
18269 window: &mut Window,
18270 cx: &mut Context<Self>,
18271 ) {
18272 self.transact(window, cx, |this, _, cx| {
18273 this.buffer
18274 .read(cx)
18275 .as_singleton()
18276 .expect("you can only call set_text on editors for singleton buffers")
18277 .update(cx, |buffer, cx| buffer.set_text(text, cx));
18278 });
18279 }
18280
18281 pub fn display_text(&self, cx: &mut App) -> String {
18282 self.display_map
18283 .update(cx, |map, cx| map.snapshot(cx))
18284 .text()
18285 }
18286
18287 fn create_minimap(
18288 &self,
18289 minimap_settings: MinimapSettings,
18290 window: &mut Window,
18291 cx: &mut Context<Self>,
18292 ) -> Option<Entity<Self>> {
18293 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
18294 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
18295 }
18296
18297 fn initialize_new_minimap(
18298 &self,
18299 minimap_settings: MinimapSettings,
18300 window: &mut Window,
18301 cx: &mut Context<Self>,
18302 ) -> Entity<Self> {
18303 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
18304
18305 let mut minimap = Editor::new_internal(
18306 EditorMode::Minimap {
18307 parent: cx.weak_entity(),
18308 },
18309 self.buffer.clone(),
18310 None,
18311 Some(self.display_map.clone()),
18312 window,
18313 cx,
18314 );
18315 minimap.scroll_manager.clone_state(&self.scroll_manager);
18316 minimap.set_text_style_refinement(TextStyleRefinement {
18317 font_size: Some(MINIMAP_FONT_SIZE),
18318 font_weight: Some(MINIMAP_FONT_WEIGHT),
18319 ..Default::default()
18320 });
18321 minimap.update_minimap_configuration(minimap_settings, cx);
18322 cx.new(|_| minimap)
18323 }
18324
18325 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
18326 let current_line_highlight = minimap_settings
18327 .current_line_highlight
18328 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
18329 self.set_current_line_highlight(Some(current_line_highlight));
18330 }
18331
18332 pub fn minimap(&self) -> Option<&Entity<Self>> {
18333 self.minimap
18334 .as_ref()
18335 .filter(|_| self.minimap_visibility.visible())
18336 }
18337
18338 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
18339 let mut wrap_guides = smallvec![];
18340
18341 if self.show_wrap_guides == Some(false) {
18342 return wrap_guides;
18343 }
18344
18345 let settings = self.buffer.read(cx).language_settings(cx);
18346 if settings.show_wrap_guides {
18347 match self.soft_wrap_mode(cx) {
18348 SoftWrap::Column(soft_wrap) => {
18349 wrap_guides.push((soft_wrap as usize, true));
18350 }
18351 SoftWrap::Bounded(soft_wrap) => {
18352 wrap_guides.push((soft_wrap as usize, true));
18353 }
18354 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
18355 }
18356 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
18357 }
18358
18359 wrap_guides
18360 }
18361
18362 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
18363 let settings = self.buffer.read(cx).language_settings(cx);
18364 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
18365 match mode {
18366 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
18367 SoftWrap::None
18368 }
18369 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
18370 language_settings::SoftWrap::PreferredLineLength => {
18371 SoftWrap::Column(settings.preferred_line_length)
18372 }
18373 language_settings::SoftWrap::Bounded => {
18374 SoftWrap::Bounded(settings.preferred_line_length)
18375 }
18376 }
18377 }
18378
18379 pub fn set_soft_wrap_mode(
18380 &mut self,
18381 mode: language_settings::SoftWrap,
18382
18383 cx: &mut Context<Self>,
18384 ) {
18385 self.soft_wrap_mode_override = Some(mode);
18386 cx.notify();
18387 }
18388
18389 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
18390 self.hard_wrap = hard_wrap;
18391 cx.notify();
18392 }
18393
18394 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
18395 self.text_style_refinement = Some(style);
18396 }
18397
18398 /// called by the Element so we know what style we were most recently rendered with.
18399 pub(crate) fn set_style(
18400 &mut self,
18401 style: EditorStyle,
18402 window: &mut Window,
18403 cx: &mut Context<Self>,
18404 ) {
18405 // We intentionally do not inform the display map about the minimap style
18406 // so that wrapping is not recalculated and stays consistent for the editor
18407 // and its linked minimap.
18408 if !self.mode.is_minimap() {
18409 let rem_size = window.rem_size();
18410 self.display_map.update(cx, |map, cx| {
18411 map.set_font(
18412 style.text.font(),
18413 style.text.font_size.to_pixels(rem_size),
18414 cx,
18415 )
18416 });
18417 }
18418 self.style = Some(style);
18419 }
18420
18421 pub fn style(&self) -> Option<&EditorStyle> {
18422 self.style.as_ref()
18423 }
18424
18425 // Called by the element. This method is not designed to be called outside of the editor
18426 // element's layout code because it does not notify when rewrapping is computed synchronously.
18427 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
18428 self.display_map
18429 .update(cx, |map, cx| map.set_wrap_width(width, cx))
18430 }
18431
18432 pub fn set_soft_wrap(&mut self) {
18433 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
18434 }
18435
18436 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
18437 if self.soft_wrap_mode_override.is_some() {
18438 self.soft_wrap_mode_override.take();
18439 } else {
18440 let soft_wrap = match self.soft_wrap_mode(cx) {
18441 SoftWrap::GitDiff => return,
18442 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
18443 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
18444 language_settings::SoftWrap::None
18445 }
18446 };
18447 self.soft_wrap_mode_override = Some(soft_wrap);
18448 }
18449 cx.notify();
18450 }
18451
18452 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
18453 let Some(workspace) = self.workspace() else {
18454 return;
18455 };
18456 let fs = workspace.read(cx).app_state().fs.clone();
18457 let current_show = TabBarSettings::get_global(cx).show;
18458 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
18459 setting.show = Some(!current_show);
18460 });
18461 }
18462
18463 pub fn toggle_indent_guides(
18464 &mut self,
18465 _: &ToggleIndentGuides,
18466 _: &mut Window,
18467 cx: &mut Context<Self>,
18468 ) {
18469 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
18470 self.buffer
18471 .read(cx)
18472 .language_settings(cx)
18473 .indent_guides
18474 .enabled
18475 });
18476 self.show_indent_guides = Some(!currently_enabled);
18477 cx.notify();
18478 }
18479
18480 fn should_show_indent_guides(&self) -> Option<bool> {
18481 self.show_indent_guides
18482 }
18483
18484 pub fn toggle_line_numbers(
18485 &mut self,
18486 _: &ToggleLineNumbers,
18487 _: &mut Window,
18488 cx: &mut Context<Self>,
18489 ) {
18490 let mut editor_settings = EditorSettings::get_global(cx).clone();
18491 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
18492 EditorSettings::override_global(editor_settings, cx);
18493 }
18494
18495 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
18496 if let Some(show_line_numbers) = self.show_line_numbers {
18497 return show_line_numbers;
18498 }
18499 EditorSettings::get_global(cx).gutter.line_numbers
18500 }
18501
18502 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
18503 self.use_relative_line_numbers
18504 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
18505 }
18506
18507 pub fn toggle_relative_line_numbers(
18508 &mut self,
18509 _: &ToggleRelativeLineNumbers,
18510 _: &mut Window,
18511 cx: &mut Context<Self>,
18512 ) {
18513 let is_relative = self.should_use_relative_line_numbers(cx);
18514 self.set_relative_line_number(Some(!is_relative), cx)
18515 }
18516
18517 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
18518 self.use_relative_line_numbers = is_relative;
18519 cx.notify();
18520 }
18521
18522 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
18523 self.show_gutter = show_gutter;
18524 cx.notify();
18525 }
18526
18527 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
18528 self.show_scrollbars = ScrollbarAxes {
18529 horizontal: show,
18530 vertical: show,
18531 };
18532 cx.notify();
18533 }
18534
18535 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18536 self.show_scrollbars.vertical = show;
18537 cx.notify();
18538 }
18539
18540 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18541 self.show_scrollbars.horizontal = show;
18542 cx.notify();
18543 }
18544
18545 pub fn set_minimap_visibility(
18546 &mut self,
18547 minimap_visibility: MinimapVisibility,
18548 window: &mut Window,
18549 cx: &mut Context<Self>,
18550 ) {
18551 if self.minimap_visibility != minimap_visibility {
18552 if minimap_visibility.visible() && self.minimap.is_none() {
18553 let minimap_settings = EditorSettings::get_global(cx).minimap;
18554 self.minimap =
18555 self.create_minimap(minimap_settings.with_show_override(), window, cx);
18556 }
18557 self.minimap_visibility = minimap_visibility;
18558 cx.notify();
18559 }
18560 }
18561
18562 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18563 self.set_show_scrollbars(false, cx);
18564 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
18565 }
18566
18567 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18568 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
18569 }
18570
18571 /// Normally the text in full mode and auto height editors is padded on the
18572 /// left side by roughly half a character width for improved hit testing.
18573 ///
18574 /// Use this method to disable this for cases where this is not wanted (e.g.
18575 /// if you want to align the editor text with some other text above or below)
18576 /// or if you want to add this padding to single-line editors.
18577 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
18578 self.offset_content = offset_content;
18579 cx.notify();
18580 }
18581
18582 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
18583 self.show_line_numbers = Some(show_line_numbers);
18584 cx.notify();
18585 }
18586
18587 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
18588 self.disable_expand_excerpt_buttons = true;
18589 cx.notify();
18590 }
18591
18592 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
18593 self.show_git_diff_gutter = Some(show_git_diff_gutter);
18594 cx.notify();
18595 }
18596
18597 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
18598 self.show_code_actions = Some(show_code_actions);
18599 cx.notify();
18600 }
18601
18602 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
18603 self.show_runnables = Some(show_runnables);
18604 cx.notify();
18605 }
18606
18607 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
18608 self.show_breakpoints = Some(show_breakpoints);
18609 cx.notify();
18610 }
18611
18612 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
18613 if self.display_map.read(cx).masked != masked {
18614 self.display_map.update(cx, |map, _| map.masked = masked);
18615 }
18616 cx.notify()
18617 }
18618
18619 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
18620 self.show_wrap_guides = Some(show_wrap_guides);
18621 cx.notify();
18622 }
18623
18624 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
18625 self.show_indent_guides = Some(show_indent_guides);
18626 cx.notify();
18627 }
18628
18629 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
18630 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
18631 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
18632 if let Some(dir) = file.abs_path(cx).parent() {
18633 return Some(dir.to_owned());
18634 }
18635 }
18636
18637 if let Some(project_path) = buffer.read(cx).project_path(cx) {
18638 return Some(project_path.path.to_path_buf());
18639 }
18640 }
18641
18642 None
18643 }
18644
18645 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
18646 self.active_excerpt(cx)?
18647 .1
18648 .read(cx)
18649 .file()
18650 .and_then(|f| f.as_local())
18651 }
18652
18653 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18654 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18655 let buffer = buffer.read(cx);
18656 if let Some(project_path) = buffer.project_path(cx) {
18657 let project = self.project.as_ref()?.read(cx);
18658 project.absolute_path(&project_path, cx)
18659 } else {
18660 buffer
18661 .file()
18662 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
18663 }
18664 })
18665 }
18666
18667 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18668 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18669 let project_path = buffer.read(cx).project_path(cx)?;
18670 let project = self.project.as_ref()?.read(cx);
18671 let entry = project.entry_for_path(&project_path, cx)?;
18672 let path = entry.path.to_path_buf();
18673 Some(path)
18674 })
18675 }
18676
18677 pub fn reveal_in_finder(
18678 &mut self,
18679 _: &RevealInFileManager,
18680 _window: &mut Window,
18681 cx: &mut Context<Self>,
18682 ) {
18683 if let Some(target) = self.target_file(cx) {
18684 cx.reveal_path(&target.abs_path(cx));
18685 }
18686 }
18687
18688 pub fn copy_path(
18689 &mut self,
18690 _: &zed_actions::workspace::CopyPath,
18691 _window: &mut Window,
18692 cx: &mut Context<Self>,
18693 ) {
18694 if let Some(path) = self.target_file_abs_path(cx) {
18695 if let Some(path) = path.to_str() {
18696 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18697 }
18698 }
18699 }
18700
18701 pub fn copy_relative_path(
18702 &mut self,
18703 _: &zed_actions::workspace::CopyRelativePath,
18704 _window: &mut Window,
18705 cx: &mut Context<Self>,
18706 ) {
18707 if let Some(path) = self.target_file_path(cx) {
18708 if let Some(path) = path.to_str() {
18709 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18710 }
18711 }
18712 }
18713
18714 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
18715 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
18716 buffer.read(cx).project_path(cx)
18717 } else {
18718 None
18719 }
18720 }
18721
18722 // Returns true if the editor handled a go-to-line request
18723 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
18724 maybe!({
18725 let breakpoint_store = self.breakpoint_store.as_ref()?;
18726
18727 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
18728 else {
18729 self.clear_row_highlights::<ActiveDebugLine>();
18730 return None;
18731 };
18732
18733 let position = active_stack_frame.position;
18734 let buffer_id = position.buffer_id?;
18735 let snapshot = self
18736 .project
18737 .as_ref()?
18738 .read(cx)
18739 .buffer_for_id(buffer_id, cx)?
18740 .read(cx)
18741 .snapshot();
18742
18743 let mut handled = false;
18744 for (id, ExcerptRange { context, .. }) in
18745 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
18746 {
18747 if context.start.cmp(&position, &snapshot).is_ge()
18748 || context.end.cmp(&position, &snapshot).is_lt()
18749 {
18750 continue;
18751 }
18752 let snapshot = self.buffer.read(cx).snapshot(cx);
18753 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
18754
18755 handled = true;
18756 self.clear_row_highlights::<ActiveDebugLine>();
18757
18758 self.go_to_line::<ActiveDebugLine>(
18759 multibuffer_anchor,
18760 Some(cx.theme().colors().editor_debugger_active_line_background),
18761 window,
18762 cx,
18763 );
18764
18765 cx.notify();
18766 }
18767
18768 handled.then_some(())
18769 })
18770 .is_some()
18771 }
18772
18773 pub fn copy_file_name_without_extension(
18774 &mut self,
18775 _: &CopyFileNameWithoutExtension,
18776 _: &mut Window,
18777 cx: &mut Context<Self>,
18778 ) {
18779 if let Some(file) = self.target_file(cx) {
18780 if let Some(file_stem) = file.path().file_stem() {
18781 if let Some(name) = file_stem.to_str() {
18782 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18783 }
18784 }
18785 }
18786 }
18787
18788 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
18789 if let Some(file) = self.target_file(cx) {
18790 if let Some(file_name) = file.path().file_name() {
18791 if let Some(name) = file_name.to_str() {
18792 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18793 }
18794 }
18795 }
18796 }
18797
18798 pub fn toggle_git_blame(
18799 &mut self,
18800 _: &::git::Blame,
18801 window: &mut Window,
18802 cx: &mut Context<Self>,
18803 ) {
18804 self.show_git_blame_gutter = !self.show_git_blame_gutter;
18805
18806 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
18807 self.start_git_blame(true, window, cx);
18808 }
18809
18810 cx.notify();
18811 }
18812
18813 pub fn toggle_git_blame_inline(
18814 &mut self,
18815 _: &ToggleGitBlameInline,
18816 window: &mut Window,
18817 cx: &mut Context<Self>,
18818 ) {
18819 self.toggle_git_blame_inline_internal(true, window, cx);
18820 cx.notify();
18821 }
18822
18823 pub fn open_git_blame_commit(
18824 &mut self,
18825 _: &OpenGitBlameCommit,
18826 window: &mut Window,
18827 cx: &mut Context<Self>,
18828 ) {
18829 self.open_git_blame_commit_internal(window, cx);
18830 }
18831
18832 fn open_git_blame_commit_internal(
18833 &mut self,
18834 window: &mut Window,
18835 cx: &mut Context<Self>,
18836 ) -> Option<()> {
18837 let blame = self.blame.as_ref()?;
18838 let snapshot = self.snapshot(window, cx);
18839 let cursor = self.selections.newest::<Point>(cx).head();
18840 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
18841 let blame_entry = blame
18842 .update(cx, |blame, cx| {
18843 blame
18844 .blame_for_rows(
18845 &[RowInfo {
18846 buffer_id: Some(buffer.remote_id()),
18847 buffer_row: Some(point.row),
18848 ..Default::default()
18849 }],
18850 cx,
18851 )
18852 .next()
18853 })
18854 .flatten()?;
18855 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
18856 let repo = blame.read(cx).repository(cx)?;
18857 let workspace = self.workspace()?.downgrade();
18858 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
18859 None
18860 }
18861
18862 pub fn git_blame_inline_enabled(&self) -> bool {
18863 self.git_blame_inline_enabled
18864 }
18865
18866 pub fn toggle_selection_menu(
18867 &mut self,
18868 _: &ToggleSelectionMenu,
18869 _: &mut Window,
18870 cx: &mut Context<Self>,
18871 ) {
18872 self.show_selection_menu = self
18873 .show_selection_menu
18874 .map(|show_selections_menu| !show_selections_menu)
18875 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
18876
18877 cx.notify();
18878 }
18879
18880 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
18881 self.show_selection_menu
18882 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
18883 }
18884
18885 fn start_git_blame(
18886 &mut self,
18887 user_triggered: bool,
18888 window: &mut Window,
18889 cx: &mut Context<Self>,
18890 ) {
18891 if let Some(project) = self.project.as_ref() {
18892 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
18893 return;
18894 };
18895
18896 if buffer.read(cx).file().is_none() {
18897 return;
18898 }
18899
18900 let focused = self.focus_handle(cx).contains_focused(window, cx);
18901
18902 let project = project.clone();
18903 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
18904 self.blame_subscription =
18905 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
18906 self.blame = Some(blame);
18907 }
18908 }
18909
18910 fn toggle_git_blame_inline_internal(
18911 &mut self,
18912 user_triggered: bool,
18913 window: &mut Window,
18914 cx: &mut Context<Self>,
18915 ) {
18916 if self.git_blame_inline_enabled {
18917 self.git_blame_inline_enabled = false;
18918 self.show_git_blame_inline = false;
18919 self.show_git_blame_inline_delay_task.take();
18920 } else {
18921 self.git_blame_inline_enabled = true;
18922 self.start_git_blame_inline(user_triggered, window, cx);
18923 }
18924
18925 cx.notify();
18926 }
18927
18928 fn start_git_blame_inline(
18929 &mut self,
18930 user_triggered: bool,
18931 window: &mut Window,
18932 cx: &mut Context<Self>,
18933 ) {
18934 self.start_git_blame(user_triggered, window, cx);
18935
18936 if ProjectSettings::get_global(cx)
18937 .git
18938 .inline_blame_delay()
18939 .is_some()
18940 {
18941 self.start_inline_blame_timer(window, cx);
18942 } else {
18943 self.show_git_blame_inline = true
18944 }
18945 }
18946
18947 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
18948 self.blame.as_ref()
18949 }
18950
18951 pub fn show_git_blame_gutter(&self) -> bool {
18952 self.show_git_blame_gutter
18953 }
18954
18955 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
18956 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
18957 }
18958
18959 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
18960 self.show_git_blame_inline
18961 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
18962 && !self.newest_selection_head_on_empty_line(cx)
18963 && self.has_blame_entries(cx)
18964 }
18965
18966 fn has_blame_entries(&self, cx: &App) -> bool {
18967 self.blame()
18968 .map_or(false, |blame| blame.read(cx).has_generated_entries())
18969 }
18970
18971 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
18972 let cursor_anchor = self.selections.newest_anchor().head();
18973
18974 let snapshot = self.buffer.read(cx).snapshot(cx);
18975 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
18976
18977 snapshot.line_len(buffer_row) == 0
18978 }
18979
18980 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
18981 let buffer_and_selection = maybe!({
18982 let selection = self.selections.newest::<Point>(cx);
18983 let selection_range = selection.range();
18984
18985 let multi_buffer = self.buffer().read(cx);
18986 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18987 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
18988
18989 let (buffer, range, _) = if selection.reversed {
18990 buffer_ranges.first()
18991 } else {
18992 buffer_ranges.last()
18993 }?;
18994
18995 let selection = text::ToPoint::to_point(&range.start, &buffer).row
18996 ..text::ToPoint::to_point(&range.end, &buffer).row;
18997 Some((
18998 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
18999 selection,
19000 ))
19001 });
19002
19003 let Some((buffer, selection)) = buffer_and_selection else {
19004 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
19005 };
19006
19007 let Some(project) = self.project.as_ref() else {
19008 return Task::ready(Err(anyhow!("editor does not have project")));
19009 };
19010
19011 project.update(cx, |project, cx| {
19012 project.get_permalink_to_line(&buffer, selection, cx)
19013 })
19014 }
19015
19016 pub fn copy_permalink_to_line(
19017 &mut self,
19018 _: &CopyPermalinkToLine,
19019 window: &mut Window,
19020 cx: &mut Context<Self>,
19021 ) {
19022 let permalink_task = self.get_permalink_to_line(cx);
19023 let workspace = self.workspace();
19024
19025 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19026 Ok(permalink) => {
19027 cx.update(|_, cx| {
19028 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
19029 })
19030 .ok();
19031 }
19032 Err(err) => {
19033 let message = format!("Failed to copy permalink: {err}");
19034
19035 anyhow::Result::<()>::Err(err).log_err();
19036
19037 if let Some(workspace) = workspace {
19038 workspace
19039 .update_in(cx, |workspace, _, cx| {
19040 struct CopyPermalinkToLine;
19041
19042 workspace.show_toast(
19043 Toast::new(
19044 NotificationId::unique::<CopyPermalinkToLine>(),
19045 message,
19046 ),
19047 cx,
19048 )
19049 })
19050 .ok();
19051 }
19052 }
19053 })
19054 .detach();
19055 }
19056
19057 pub fn copy_file_location(
19058 &mut self,
19059 _: &CopyFileLocation,
19060 _: &mut Window,
19061 cx: &mut Context<Self>,
19062 ) {
19063 let selection = self.selections.newest::<Point>(cx).start.row + 1;
19064 if let Some(file) = self.target_file(cx) {
19065 if let Some(path) = file.path().to_str() {
19066 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
19067 }
19068 }
19069 }
19070
19071 pub fn open_permalink_to_line(
19072 &mut self,
19073 _: &OpenPermalinkToLine,
19074 window: &mut Window,
19075 cx: &mut Context<Self>,
19076 ) {
19077 let permalink_task = self.get_permalink_to_line(cx);
19078 let workspace = self.workspace();
19079
19080 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19081 Ok(permalink) => {
19082 cx.update(|_, cx| {
19083 cx.open_url(permalink.as_ref());
19084 })
19085 .ok();
19086 }
19087 Err(err) => {
19088 let message = format!("Failed to open permalink: {err}");
19089
19090 anyhow::Result::<()>::Err(err).log_err();
19091
19092 if let Some(workspace) = workspace {
19093 workspace
19094 .update(cx, |workspace, cx| {
19095 struct OpenPermalinkToLine;
19096
19097 workspace.show_toast(
19098 Toast::new(
19099 NotificationId::unique::<OpenPermalinkToLine>(),
19100 message,
19101 ),
19102 cx,
19103 )
19104 })
19105 .ok();
19106 }
19107 }
19108 })
19109 .detach();
19110 }
19111
19112 pub fn insert_uuid_v4(
19113 &mut self,
19114 _: &InsertUuidV4,
19115 window: &mut Window,
19116 cx: &mut Context<Self>,
19117 ) {
19118 self.insert_uuid(UuidVersion::V4, window, cx);
19119 }
19120
19121 pub fn insert_uuid_v7(
19122 &mut self,
19123 _: &InsertUuidV7,
19124 window: &mut Window,
19125 cx: &mut Context<Self>,
19126 ) {
19127 self.insert_uuid(UuidVersion::V7, window, cx);
19128 }
19129
19130 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
19131 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19132 self.transact(window, cx, |this, window, cx| {
19133 let edits = this
19134 .selections
19135 .all::<Point>(cx)
19136 .into_iter()
19137 .map(|selection| {
19138 let uuid = match version {
19139 UuidVersion::V4 => uuid::Uuid::new_v4(),
19140 UuidVersion::V7 => uuid::Uuid::now_v7(),
19141 };
19142
19143 (selection.range(), uuid.to_string())
19144 });
19145 this.edit(edits, cx);
19146 this.refresh_edit_prediction(true, false, window, cx);
19147 });
19148 }
19149
19150 pub fn open_selections_in_multibuffer(
19151 &mut self,
19152 _: &OpenSelectionsInMultibuffer,
19153 window: &mut Window,
19154 cx: &mut Context<Self>,
19155 ) {
19156 let multibuffer = self.buffer.read(cx);
19157
19158 let Some(buffer) = multibuffer.as_singleton() else {
19159 return;
19160 };
19161
19162 let Some(workspace) = self.workspace() else {
19163 return;
19164 };
19165
19166 let title = multibuffer.title(cx).to_string();
19167
19168 let locations = self
19169 .selections
19170 .all_anchors(cx)
19171 .into_iter()
19172 .map(|selection| Location {
19173 buffer: buffer.clone(),
19174 range: selection.start.text_anchor..selection.end.text_anchor,
19175 })
19176 .collect::<Vec<_>>();
19177
19178 cx.spawn_in(window, async move |_, cx| {
19179 workspace.update_in(cx, |workspace, window, cx| {
19180 Self::open_locations_in_multibuffer(
19181 workspace,
19182 locations,
19183 format!("Selections for '{title}'"),
19184 false,
19185 MultibufferSelectionMode::All,
19186 window,
19187 cx,
19188 );
19189 })
19190 })
19191 .detach();
19192 }
19193
19194 /// Adds a row highlight for the given range. If a row has multiple highlights, the
19195 /// last highlight added will be used.
19196 ///
19197 /// If the range ends at the beginning of a line, then that line will not be highlighted.
19198 pub fn highlight_rows<T: 'static>(
19199 &mut self,
19200 range: Range<Anchor>,
19201 color: Hsla,
19202 options: RowHighlightOptions,
19203 cx: &mut Context<Self>,
19204 ) {
19205 let snapshot = self.buffer().read(cx).snapshot(cx);
19206 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19207 let ix = row_highlights.binary_search_by(|highlight| {
19208 Ordering::Equal
19209 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
19210 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
19211 });
19212
19213 if let Err(mut ix) = ix {
19214 let index = post_inc(&mut self.highlight_order);
19215
19216 // If this range intersects with the preceding highlight, then merge it with
19217 // the preceding highlight. Otherwise insert a new highlight.
19218 let mut merged = false;
19219 if ix > 0 {
19220 let prev_highlight = &mut row_highlights[ix - 1];
19221 if prev_highlight
19222 .range
19223 .end
19224 .cmp(&range.start, &snapshot)
19225 .is_ge()
19226 {
19227 ix -= 1;
19228 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
19229 prev_highlight.range.end = range.end;
19230 }
19231 merged = true;
19232 prev_highlight.index = index;
19233 prev_highlight.color = color;
19234 prev_highlight.options = options;
19235 }
19236 }
19237
19238 if !merged {
19239 row_highlights.insert(
19240 ix,
19241 RowHighlight {
19242 range: range.clone(),
19243 index,
19244 color,
19245 options,
19246 type_id: TypeId::of::<T>(),
19247 },
19248 );
19249 }
19250
19251 // If any of the following highlights intersect with this one, merge them.
19252 while let Some(next_highlight) = row_highlights.get(ix + 1) {
19253 let highlight = &row_highlights[ix];
19254 if next_highlight
19255 .range
19256 .start
19257 .cmp(&highlight.range.end, &snapshot)
19258 .is_le()
19259 {
19260 if next_highlight
19261 .range
19262 .end
19263 .cmp(&highlight.range.end, &snapshot)
19264 .is_gt()
19265 {
19266 row_highlights[ix].range.end = next_highlight.range.end;
19267 }
19268 row_highlights.remove(ix + 1);
19269 } else {
19270 break;
19271 }
19272 }
19273 }
19274 }
19275
19276 /// Remove any highlighted row ranges of the given type that intersect the
19277 /// given ranges.
19278 pub fn remove_highlighted_rows<T: 'static>(
19279 &mut self,
19280 ranges_to_remove: Vec<Range<Anchor>>,
19281 cx: &mut Context<Self>,
19282 ) {
19283 let snapshot = self.buffer().read(cx).snapshot(cx);
19284 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19285 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19286 row_highlights.retain(|highlight| {
19287 while let Some(range_to_remove) = ranges_to_remove.peek() {
19288 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
19289 Ordering::Less | Ordering::Equal => {
19290 ranges_to_remove.next();
19291 }
19292 Ordering::Greater => {
19293 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
19294 Ordering::Less | Ordering::Equal => {
19295 return false;
19296 }
19297 Ordering::Greater => break,
19298 }
19299 }
19300 }
19301 }
19302
19303 true
19304 })
19305 }
19306
19307 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
19308 pub fn clear_row_highlights<T: 'static>(&mut self) {
19309 self.highlighted_rows.remove(&TypeId::of::<T>());
19310 }
19311
19312 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
19313 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
19314 self.highlighted_rows
19315 .get(&TypeId::of::<T>())
19316 .map_or(&[] as &[_], |vec| vec.as_slice())
19317 .iter()
19318 .map(|highlight| (highlight.range.clone(), highlight.color))
19319 }
19320
19321 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
19322 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
19323 /// Allows to ignore certain kinds of highlights.
19324 pub fn highlighted_display_rows(
19325 &self,
19326 window: &mut Window,
19327 cx: &mut App,
19328 ) -> BTreeMap<DisplayRow, LineHighlight> {
19329 let snapshot = self.snapshot(window, cx);
19330 let mut used_highlight_orders = HashMap::default();
19331 self.highlighted_rows
19332 .iter()
19333 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
19334 .fold(
19335 BTreeMap::<DisplayRow, LineHighlight>::new(),
19336 |mut unique_rows, highlight| {
19337 let start = highlight.range.start.to_display_point(&snapshot);
19338 let end = highlight.range.end.to_display_point(&snapshot);
19339 let start_row = start.row().0;
19340 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
19341 && end.column() == 0
19342 {
19343 end.row().0.saturating_sub(1)
19344 } else {
19345 end.row().0
19346 };
19347 for row in start_row..=end_row {
19348 let used_index =
19349 used_highlight_orders.entry(row).or_insert(highlight.index);
19350 if highlight.index >= *used_index {
19351 *used_index = highlight.index;
19352 unique_rows.insert(
19353 DisplayRow(row),
19354 LineHighlight {
19355 include_gutter: highlight.options.include_gutter,
19356 border: None,
19357 background: highlight.color.into(),
19358 type_id: Some(highlight.type_id),
19359 },
19360 );
19361 }
19362 }
19363 unique_rows
19364 },
19365 )
19366 }
19367
19368 pub fn highlighted_display_row_for_autoscroll(
19369 &self,
19370 snapshot: &DisplaySnapshot,
19371 ) -> Option<DisplayRow> {
19372 self.highlighted_rows
19373 .values()
19374 .flat_map(|highlighted_rows| highlighted_rows.iter())
19375 .filter_map(|highlight| {
19376 if highlight.options.autoscroll {
19377 Some(highlight.range.start.to_display_point(snapshot).row())
19378 } else {
19379 None
19380 }
19381 })
19382 .min()
19383 }
19384
19385 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
19386 self.highlight_background::<SearchWithinRange>(
19387 ranges,
19388 |colors| colors.colors().editor_document_highlight_read_background,
19389 cx,
19390 )
19391 }
19392
19393 pub fn set_breadcrumb_header(&mut self, new_header: String) {
19394 self.breadcrumb_header = Some(new_header);
19395 }
19396
19397 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
19398 self.clear_background_highlights::<SearchWithinRange>(cx);
19399 }
19400
19401 pub fn highlight_background<T: 'static>(
19402 &mut self,
19403 ranges: &[Range<Anchor>],
19404 color_fetcher: fn(&Theme) -> Hsla,
19405 cx: &mut Context<Self>,
19406 ) {
19407 self.background_highlights.insert(
19408 HighlightKey::Type(TypeId::of::<T>()),
19409 (color_fetcher, Arc::from(ranges)),
19410 );
19411 self.scrollbar_marker_state.dirty = true;
19412 cx.notify();
19413 }
19414
19415 pub fn highlight_background_key<T: 'static>(
19416 &mut self,
19417 key: usize,
19418 ranges: &[Range<Anchor>],
19419 color_fetcher: fn(&Theme) -> Hsla,
19420 cx: &mut Context<Self>,
19421 ) {
19422 self.background_highlights.insert(
19423 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19424 (color_fetcher, Arc::from(ranges)),
19425 );
19426 self.scrollbar_marker_state.dirty = true;
19427 cx.notify();
19428 }
19429
19430 pub fn clear_background_highlights<T: 'static>(
19431 &mut self,
19432 cx: &mut Context<Self>,
19433 ) -> Option<BackgroundHighlight> {
19434 let text_highlights = self
19435 .background_highlights
19436 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
19437 if !text_highlights.1.is_empty() {
19438 self.scrollbar_marker_state.dirty = true;
19439 cx.notify();
19440 }
19441 Some(text_highlights)
19442 }
19443
19444 pub fn highlight_gutter<T: 'static>(
19445 &mut self,
19446 ranges: impl Into<Vec<Range<Anchor>>>,
19447 color_fetcher: fn(&App) -> Hsla,
19448 cx: &mut Context<Self>,
19449 ) {
19450 self.gutter_highlights
19451 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
19452 cx.notify();
19453 }
19454
19455 pub fn clear_gutter_highlights<T: 'static>(
19456 &mut self,
19457 cx: &mut Context<Self>,
19458 ) -> Option<GutterHighlight> {
19459 cx.notify();
19460 self.gutter_highlights.remove(&TypeId::of::<T>())
19461 }
19462
19463 pub fn insert_gutter_highlight<T: 'static>(
19464 &mut self,
19465 range: Range<Anchor>,
19466 color_fetcher: fn(&App) -> Hsla,
19467 cx: &mut Context<Self>,
19468 ) {
19469 let snapshot = self.buffer().read(cx).snapshot(cx);
19470 let mut highlights = self
19471 .gutter_highlights
19472 .remove(&TypeId::of::<T>())
19473 .map(|(_, highlights)| highlights)
19474 .unwrap_or_default();
19475 let ix = highlights.binary_search_by(|highlight| {
19476 Ordering::Equal
19477 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
19478 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
19479 });
19480 if let Err(ix) = ix {
19481 highlights.insert(ix, range);
19482 }
19483 self.gutter_highlights
19484 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
19485 }
19486
19487 pub fn remove_gutter_highlights<T: 'static>(
19488 &mut self,
19489 ranges_to_remove: Vec<Range<Anchor>>,
19490 cx: &mut Context<Self>,
19491 ) {
19492 let snapshot = self.buffer().read(cx).snapshot(cx);
19493 let Some((color_fetcher, mut gutter_highlights)) =
19494 self.gutter_highlights.remove(&TypeId::of::<T>())
19495 else {
19496 return;
19497 };
19498 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19499 gutter_highlights.retain(|highlight| {
19500 while let Some(range_to_remove) = ranges_to_remove.peek() {
19501 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
19502 Ordering::Less | Ordering::Equal => {
19503 ranges_to_remove.next();
19504 }
19505 Ordering::Greater => {
19506 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
19507 Ordering::Less | Ordering::Equal => {
19508 return false;
19509 }
19510 Ordering::Greater => break,
19511 }
19512 }
19513 }
19514 }
19515
19516 true
19517 });
19518 self.gutter_highlights
19519 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
19520 }
19521
19522 #[cfg(feature = "test-support")]
19523 pub fn all_text_highlights(
19524 &self,
19525 window: &mut Window,
19526 cx: &mut Context<Self>,
19527 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
19528 let snapshot = self.snapshot(window, cx);
19529 self.display_map.update(cx, |display_map, _| {
19530 display_map
19531 .all_text_highlights()
19532 .map(|highlight| {
19533 let (style, ranges) = highlight.as_ref();
19534 (
19535 *style,
19536 ranges
19537 .iter()
19538 .map(|range| range.clone().to_display_points(&snapshot))
19539 .collect(),
19540 )
19541 })
19542 .collect()
19543 })
19544 }
19545
19546 #[cfg(feature = "test-support")]
19547 pub fn all_text_background_highlights(
19548 &self,
19549 window: &mut Window,
19550 cx: &mut Context<Self>,
19551 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19552 let snapshot = self.snapshot(window, cx);
19553 let buffer = &snapshot.buffer_snapshot;
19554 let start = buffer.anchor_before(0);
19555 let end = buffer.anchor_after(buffer.len());
19556 self.background_highlights_in_range(start..end, &snapshot, cx.theme())
19557 }
19558
19559 #[cfg(feature = "test-support")]
19560 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
19561 let snapshot = self.buffer().read(cx).snapshot(cx);
19562
19563 let highlights = self
19564 .background_highlights
19565 .get(&HighlightKey::Type(TypeId::of::<
19566 items::BufferSearchHighlights,
19567 >()));
19568
19569 if let Some((_color, ranges)) = highlights {
19570 ranges
19571 .iter()
19572 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
19573 .collect_vec()
19574 } else {
19575 vec![]
19576 }
19577 }
19578
19579 fn document_highlights_for_position<'a>(
19580 &'a self,
19581 position: Anchor,
19582 buffer: &'a MultiBufferSnapshot,
19583 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
19584 let read_highlights = self
19585 .background_highlights
19586 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
19587 .map(|h| &h.1);
19588 let write_highlights = self
19589 .background_highlights
19590 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
19591 .map(|h| &h.1);
19592 let left_position = position.bias_left(buffer);
19593 let right_position = position.bias_right(buffer);
19594 read_highlights
19595 .into_iter()
19596 .chain(write_highlights)
19597 .flat_map(move |ranges| {
19598 let start_ix = match ranges.binary_search_by(|probe| {
19599 let cmp = probe.end.cmp(&left_position, buffer);
19600 if cmp.is_ge() {
19601 Ordering::Greater
19602 } else {
19603 Ordering::Less
19604 }
19605 }) {
19606 Ok(i) | Err(i) => i,
19607 };
19608
19609 ranges[start_ix..]
19610 .iter()
19611 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
19612 })
19613 }
19614
19615 pub fn has_background_highlights<T: 'static>(&self) -> bool {
19616 self.background_highlights
19617 .get(&HighlightKey::Type(TypeId::of::<T>()))
19618 .map_or(false, |(_, highlights)| !highlights.is_empty())
19619 }
19620
19621 pub fn background_highlights_in_range(
19622 &self,
19623 search_range: Range<Anchor>,
19624 display_snapshot: &DisplaySnapshot,
19625 theme: &Theme,
19626 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19627 let mut results = Vec::new();
19628 for (color_fetcher, ranges) in self.background_highlights.values() {
19629 let color = color_fetcher(theme);
19630 let start_ix = match ranges.binary_search_by(|probe| {
19631 let cmp = probe
19632 .end
19633 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19634 if cmp.is_gt() {
19635 Ordering::Greater
19636 } else {
19637 Ordering::Less
19638 }
19639 }) {
19640 Ok(i) | Err(i) => i,
19641 };
19642 for range in &ranges[start_ix..] {
19643 if range
19644 .start
19645 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19646 .is_ge()
19647 {
19648 break;
19649 }
19650
19651 let start = range.start.to_display_point(display_snapshot);
19652 let end = range.end.to_display_point(display_snapshot);
19653 results.push((start..end, color))
19654 }
19655 }
19656 results
19657 }
19658
19659 pub fn background_highlight_row_ranges<T: 'static>(
19660 &self,
19661 search_range: Range<Anchor>,
19662 display_snapshot: &DisplaySnapshot,
19663 count: usize,
19664 ) -> Vec<RangeInclusive<DisplayPoint>> {
19665 let mut results = Vec::new();
19666 let Some((_, ranges)) = self
19667 .background_highlights
19668 .get(&HighlightKey::Type(TypeId::of::<T>()))
19669 else {
19670 return vec![];
19671 };
19672
19673 let start_ix = match ranges.binary_search_by(|probe| {
19674 let cmp = probe
19675 .end
19676 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19677 if cmp.is_gt() {
19678 Ordering::Greater
19679 } else {
19680 Ordering::Less
19681 }
19682 }) {
19683 Ok(i) | Err(i) => i,
19684 };
19685 let mut push_region = |start: Option<Point>, end: Option<Point>| {
19686 if let (Some(start_display), Some(end_display)) = (start, end) {
19687 results.push(
19688 start_display.to_display_point(display_snapshot)
19689 ..=end_display.to_display_point(display_snapshot),
19690 );
19691 }
19692 };
19693 let mut start_row: Option<Point> = None;
19694 let mut end_row: Option<Point> = None;
19695 if ranges.len() > count {
19696 return Vec::new();
19697 }
19698 for range in &ranges[start_ix..] {
19699 if range
19700 .start
19701 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19702 .is_ge()
19703 {
19704 break;
19705 }
19706 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
19707 if let Some(current_row) = &end_row {
19708 if end.row == current_row.row {
19709 continue;
19710 }
19711 }
19712 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
19713 if start_row.is_none() {
19714 assert_eq!(end_row, None);
19715 start_row = Some(start);
19716 end_row = Some(end);
19717 continue;
19718 }
19719 if let Some(current_end) = end_row.as_mut() {
19720 if start.row > current_end.row + 1 {
19721 push_region(start_row, end_row);
19722 start_row = Some(start);
19723 end_row = Some(end);
19724 } else {
19725 // Merge two hunks.
19726 *current_end = end;
19727 }
19728 } else {
19729 unreachable!();
19730 }
19731 }
19732 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
19733 push_region(start_row, end_row);
19734 results
19735 }
19736
19737 pub fn gutter_highlights_in_range(
19738 &self,
19739 search_range: Range<Anchor>,
19740 display_snapshot: &DisplaySnapshot,
19741 cx: &App,
19742 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19743 let mut results = Vec::new();
19744 for (color_fetcher, ranges) in self.gutter_highlights.values() {
19745 let color = color_fetcher(cx);
19746 let start_ix = match ranges.binary_search_by(|probe| {
19747 let cmp = probe
19748 .end
19749 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19750 if cmp.is_gt() {
19751 Ordering::Greater
19752 } else {
19753 Ordering::Less
19754 }
19755 }) {
19756 Ok(i) | Err(i) => i,
19757 };
19758 for range in &ranges[start_ix..] {
19759 if range
19760 .start
19761 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19762 .is_ge()
19763 {
19764 break;
19765 }
19766
19767 let start = range.start.to_display_point(display_snapshot);
19768 let end = range.end.to_display_point(display_snapshot);
19769 results.push((start..end, color))
19770 }
19771 }
19772 results
19773 }
19774
19775 /// Get the text ranges corresponding to the redaction query
19776 pub fn redacted_ranges(
19777 &self,
19778 search_range: Range<Anchor>,
19779 display_snapshot: &DisplaySnapshot,
19780 cx: &App,
19781 ) -> Vec<Range<DisplayPoint>> {
19782 display_snapshot
19783 .buffer_snapshot
19784 .redacted_ranges(search_range, |file| {
19785 if let Some(file) = file {
19786 file.is_private()
19787 && EditorSettings::get(
19788 Some(SettingsLocation {
19789 worktree_id: file.worktree_id(cx),
19790 path: file.path().as_ref(),
19791 }),
19792 cx,
19793 )
19794 .redact_private_values
19795 } else {
19796 false
19797 }
19798 })
19799 .map(|range| {
19800 range.start.to_display_point(display_snapshot)
19801 ..range.end.to_display_point(display_snapshot)
19802 })
19803 .collect()
19804 }
19805
19806 pub fn highlight_text_key<T: 'static>(
19807 &mut self,
19808 key: usize,
19809 ranges: Vec<Range<Anchor>>,
19810 style: HighlightStyle,
19811 cx: &mut Context<Self>,
19812 ) {
19813 self.display_map.update(cx, |map, _| {
19814 map.highlight_text(
19815 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19816 ranges,
19817 style,
19818 );
19819 });
19820 cx.notify();
19821 }
19822
19823 pub fn highlight_text<T: 'static>(
19824 &mut self,
19825 ranges: Vec<Range<Anchor>>,
19826 style: HighlightStyle,
19827 cx: &mut Context<Self>,
19828 ) {
19829 self.display_map.update(cx, |map, _| {
19830 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
19831 });
19832 cx.notify();
19833 }
19834
19835 pub(crate) fn highlight_inlays<T: 'static>(
19836 &mut self,
19837 highlights: Vec<InlayHighlight>,
19838 style: HighlightStyle,
19839 cx: &mut Context<Self>,
19840 ) {
19841 self.display_map.update(cx, |map, _| {
19842 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
19843 });
19844 cx.notify();
19845 }
19846
19847 pub fn text_highlights<'a, T: 'static>(
19848 &'a self,
19849 cx: &'a App,
19850 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
19851 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
19852 }
19853
19854 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
19855 let cleared = self
19856 .display_map
19857 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
19858 if cleared {
19859 cx.notify();
19860 }
19861 }
19862
19863 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
19864 (self.read_only(cx) || self.blink_manager.read(cx).visible())
19865 && self.focus_handle.is_focused(window)
19866 }
19867
19868 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
19869 self.show_cursor_when_unfocused = is_enabled;
19870 cx.notify();
19871 }
19872
19873 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
19874 cx.notify();
19875 }
19876
19877 fn on_debug_session_event(
19878 &mut self,
19879 _session: Entity<Session>,
19880 event: &SessionEvent,
19881 cx: &mut Context<Self>,
19882 ) {
19883 match event {
19884 SessionEvent::InvalidateInlineValue => {
19885 self.refresh_inline_values(cx);
19886 }
19887 _ => {}
19888 }
19889 }
19890
19891 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
19892 let Some(project) = self.project.clone() else {
19893 return;
19894 };
19895
19896 if !self.inline_value_cache.enabled {
19897 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
19898 self.splice_inlays(&inlays, Vec::new(), cx);
19899 return;
19900 }
19901
19902 let current_execution_position = self
19903 .highlighted_rows
19904 .get(&TypeId::of::<ActiveDebugLine>())
19905 .and_then(|lines| lines.last().map(|line| line.range.end));
19906
19907 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
19908 let inline_values = editor
19909 .update(cx, |editor, cx| {
19910 let Some(current_execution_position) = current_execution_position else {
19911 return Some(Task::ready(Ok(Vec::new())));
19912 };
19913
19914 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
19915 let snapshot = buffer.snapshot(cx);
19916
19917 let excerpt = snapshot.excerpt_containing(
19918 current_execution_position..current_execution_position,
19919 )?;
19920
19921 editor.buffer.read(cx).buffer(excerpt.buffer_id())
19922 })?;
19923
19924 let range =
19925 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
19926
19927 project.inline_values(buffer, range, cx)
19928 })
19929 .ok()
19930 .flatten()?
19931 .await
19932 .context("refreshing debugger inlays")
19933 .log_err()?;
19934
19935 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
19936
19937 for (buffer_id, inline_value) in inline_values
19938 .into_iter()
19939 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
19940 {
19941 buffer_inline_values
19942 .entry(buffer_id)
19943 .or_default()
19944 .push(inline_value);
19945 }
19946
19947 editor
19948 .update(cx, |editor, cx| {
19949 let snapshot = editor.buffer.read(cx).snapshot(cx);
19950 let mut new_inlays = Vec::default();
19951
19952 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
19953 let buffer_id = buffer_snapshot.remote_id();
19954 buffer_inline_values
19955 .get(&buffer_id)
19956 .into_iter()
19957 .flatten()
19958 .for_each(|hint| {
19959 let inlay = Inlay::debugger(
19960 post_inc(&mut editor.next_inlay_id),
19961 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
19962 hint.text(),
19963 );
19964 if !inlay.text.chars().contains(&'\n') {
19965 new_inlays.push(inlay);
19966 }
19967 });
19968 }
19969
19970 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
19971 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
19972
19973 editor.splice_inlays(&inlay_ids, new_inlays, cx);
19974 })
19975 .ok()?;
19976 Some(())
19977 });
19978 }
19979
19980 fn on_buffer_event(
19981 &mut self,
19982 multibuffer: &Entity<MultiBuffer>,
19983 event: &multi_buffer::Event,
19984 window: &mut Window,
19985 cx: &mut Context<Self>,
19986 ) {
19987 match event {
19988 multi_buffer::Event::Edited {
19989 singleton_buffer_edited,
19990 edited_buffer,
19991 } => {
19992 self.scrollbar_marker_state.dirty = true;
19993 self.active_indent_guides_state.dirty = true;
19994 self.refresh_active_diagnostics(cx);
19995 self.refresh_code_actions(window, cx);
19996 self.refresh_selected_text_highlights(true, window, cx);
19997 self.refresh_single_line_folds(window, cx);
19998 refresh_matching_bracket_highlights(self, window, cx);
19999 if self.has_active_edit_prediction() {
20000 self.update_visible_edit_prediction(window, cx);
20001 }
20002 if let Some(project) = self.project.as_ref() {
20003 if let Some(edited_buffer) = edited_buffer {
20004 project.update(cx, |project, cx| {
20005 self.registered_buffers
20006 .entry(edited_buffer.read(cx).remote_id())
20007 .or_insert_with(|| {
20008 project
20009 .register_buffer_with_language_servers(&edited_buffer, cx)
20010 });
20011 });
20012 }
20013 }
20014 cx.emit(EditorEvent::BufferEdited);
20015 cx.emit(SearchEvent::MatchesInvalidated);
20016
20017 if let Some(buffer) = edited_buffer {
20018 self.update_lsp_data(false, Some(buffer.read(cx).remote_id()), window, cx);
20019 }
20020
20021 if *singleton_buffer_edited {
20022 if let Some(buffer) = edited_buffer {
20023 if buffer.read(cx).file().is_none() {
20024 cx.emit(EditorEvent::TitleChanged);
20025 }
20026 }
20027 if let Some(project) = &self.project {
20028 #[allow(clippy::mutable_key_type)]
20029 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
20030 multibuffer
20031 .all_buffers()
20032 .into_iter()
20033 .filter_map(|buffer| {
20034 buffer.update(cx, |buffer, cx| {
20035 let language = buffer.language()?;
20036 let should_discard = project.update(cx, |project, cx| {
20037 project.is_local()
20038 && !project.has_language_servers_for(buffer, cx)
20039 });
20040 should_discard.not().then_some(language.clone())
20041 })
20042 })
20043 .collect::<HashSet<_>>()
20044 });
20045 if !languages_affected.is_empty() {
20046 self.refresh_inlay_hints(
20047 InlayHintRefreshReason::BufferEdited(languages_affected),
20048 cx,
20049 );
20050 }
20051 }
20052 }
20053
20054 let Some(project) = &self.project else { return };
20055 let (telemetry, is_via_ssh) = {
20056 let project = project.read(cx);
20057 let telemetry = project.client().telemetry().clone();
20058 let is_via_ssh = project.is_via_ssh();
20059 (telemetry, is_via_ssh)
20060 };
20061 refresh_linked_ranges(self, window, cx);
20062 telemetry.log_edit_event("editor", is_via_ssh);
20063 }
20064 multi_buffer::Event::ExcerptsAdded {
20065 buffer,
20066 predecessor,
20067 excerpts,
20068 } => {
20069 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20070 let buffer_id = buffer.read(cx).remote_id();
20071 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
20072 if let Some(project) = &self.project {
20073 update_uncommitted_diff_for_buffer(
20074 cx.entity(),
20075 project,
20076 [buffer.clone()],
20077 self.buffer.clone(),
20078 cx,
20079 )
20080 .detach();
20081 }
20082 }
20083 self.update_lsp_data(false, Some(buffer_id), window, cx);
20084 cx.emit(EditorEvent::ExcerptsAdded {
20085 buffer: buffer.clone(),
20086 predecessor: *predecessor,
20087 excerpts: excerpts.clone(),
20088 });
20089 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20090 }
20091 multi_buffer::Event::ExcerptsRemoved {
20092 ids,
20093 removed_buffer_ids,
20094 } => {
20095 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
20096 let buffer = self.buffer.read(cx);
20097 self.registered_buffers
20098 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
20099 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20100 cx.emit(EditorEvent::ExcerptsRemoved {
20101 ids: ids.clone(),
20102 removed_buffer_ids: removed_buffer_ids.clone(),
20103 });
20104 }
20105 multi_buffer::Event::ExcerptsEdited {
20106 excerpt_ids,
20107 buffer_ids,
20108 } => {
20109 self.display_map.update(cx, |map, cx| {
20110 map.unfold_buffers(buffer_ids.iter().copied(), cx)
20111 });
20112 cx.emit(EditorEvent::ExcerptsEdited {
20113 ids: excerpt_ids.clone(),
20114 });
20115 }
20116 multi_buffer::Event::ExcerptsExpanded { ids } => {
20117 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20118 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
20119 }
20120 multi_buffer::Event::Reparsed(buffer_id) => {
20121 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20122 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20123
20124 cx.emit(EditorEvent::Reparsed(*buffer_id));
20125 }
20126 multi_buffer::Event::DiffHunksToggled => {
20127 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20128 }
20129 multi_buffer::Event::LanguageChanged(buffer_id) => {
20130 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
20131 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20132 cx.emit(EditorEvent::Reparsed(*buffer_id));
20133 cx.notify();
20134 }
20135 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
20136 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
20137 multi_buffer::Event::FileHandleChanged
20138 | multi_buffer::Event::Reloaded
20139 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
20140 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
20141 multi_buffer::Event::DiagnosticsUpdated => {
20142 self.update_diagnostics_state(window, cx);
20143 }
20144 _ => {}
20145 };
20146 }
20147
20148 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
20149 if !self.diagnostics_enabled() {
20150 return;
20151 }
20152 self.refresh_active_diagnostics(cx);
20153 self.refresh_inline_diagnostics(true, window, cx);
20154 self.scrollbar_marker_state.dirty = true;
20155 cx.notify();
20156 }
20157
20158 pub fn start_temporary_diff_override(&mut self) {
20159 self.load_diff_task.take();
20160 self.temporary_diff_override = true;
20161 }
20162
20163 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
20164 self.temporary_diff_override = false;
20165 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
20166 self.buffer.update(cx, |buffer, cx| {
20167 buffer.set_all_diff_hunks_collapsed(cx);
20168 });
20169
20170 if let Some(project) = self.project.clone() {
20171 self.load_diff_task = Some(
20172 update_uncommitted_diff_for_buffer(
20173 cx.entity(),
20174 &project,
20175 self.buffer.read(cx).all_buffers(),
20176 self.buffer.clone(),
20177 cx,
20178 )
20179 .shared(),
20180 );
20181 }
20182 }
20183
20184 fn on_display_map_changed(
20185 &mut self,
20186 _: Entity<DisplayMap>,
20187 _: &mut Window,
20188 cx: &mut Context<Self>,
20189 ) {
20190 cx.notify();
20191 }
20192
20193 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20194 if self.diagnostics_enabled() {
20195 let new_severity = EditorSettings::get_global(cx)
20196 .diagnostics_max_severity
20197 .unwrap_or(DiagnosticSeverity::Hint);
20198 self.set_max_diagnostics_severity(new_severity, cx);
20199 }
20200 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20201 self.update_edit_prediction_settings(cx);
20202 self.refresh_edit_prediction(true, false, window, cx);
20203 self.refresh_inline_values(cx);
20204 self.refresh_inlay_hints(
20205 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
20206 self.selections.newest_anchor().head(),
20207 &self.buffer.read(cx).snapshot(cx),
20208 cx,
20209 )),
20210 cx,
20211 );
20212
20213 let old_cursor_shape = self.cursor_shape;
20214
20215 {
20216 let editor_settings = EditorSettings::get_global(cx);
20217 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
20218 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
20219 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
20220 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
20221 }
20222
20223 if old_cursor_shape != self.cursor_shape {
20224 cx.emit(EditorEvent::CursorShapeChanged);
20225 }
20226
20227 let project_settings = ProjectSettings::get_global(cx);
20228 self.serialize_dirty_buffers =
20229 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
20230
20231 if self.mode.is_full() {
20232 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
20233 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
20234 if self.show_inline_diagnostics != show_inline_diagnostics {
20235 self.show_inline_diagnostics = show_inline_diagnostics;
20236 self.refresh_inline_diagnostics(false, window, cx);
20237 }
20238
20239 if self.git_blame_inline_enabled != inline_blame_enabled {
20240 self.toggle_git_blame_inline_internal(false, window, cx);
20241 }
20242
20243 let minimap_settings = EditorSettings::get_global(cx).minimap;
20244 if self.minimap_visibility != MinimapVisibility::Disabled {
20245 if self.minimap_visibility.settings_visibility()
20246 != minimap_settings.minimap_enabled()
20247 {
20248 self.set_minimap_visibility(
20249 MinimapVisibility::for_mode(self.mode(), cx),
20250 window,
20251 cx,
20252 );
20253 } else if let Some(minimap_entity) = self.minimap.as_ref() {
20254 minimap_entity.update(cx, |minimap_editor, cx| {
20255 minimap_editor.update_minimap_configuration(minimap_settings, cx)
20256 })
20257 }
20258 }
20259 }
20260
20261 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
20262 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
20263 }) {
20264 if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
20265 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
20266 }
20267 self.refresh_colors(false, None, window, cx);
20268 }
20269
20270 cx.notify();
20271 }
20272
20273 pub fn set_searchable(&mut self, searchable: bool) {
20274 self.searchable = searchable;
20275 }
20276
20277 pub fn searchable(&self) -> bool {
20278 self.searchable
20279 }
20280
20281 fn open_proposed_changes_editor(
20282 &mut self,
20283 _: &OpenProposedChangesEditor,
20284 window: &mut Window,
20285 cx: &mut Context<Self>,
20286 ) {
20287 let Some(workspace) = self.workspace() else {
20288 cx.propagate();
20289 return;
20290 };
20291
20292 let selections = self.selections.all::<usize>(cx);
20293 let multi_buffer = self.buffer.read(cx);
20294 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20295 let mut new_selections_by_buffer = HashMap::default();
20296 for selection in selections {
20297 for (buffer, range, _) in
20298 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
20299 {
20300 let mut range = range.to_point(buffer);
20301 range.start.column = 0;
20302 range.end.column = buffer.line_len(range.end.row);
20303 new_selections_by_buffer
20304 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
20305 .or_insert(Vec::new())
20306 .push(range)
20307 }
20308 }
20309
20310 let proposed_changes_buffers = new_selections_by_buffer
20311 .into_iter()
20312 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
20313 .collect::<Vec<_>>();
20314 let proposed_changes_editor = cx.new(|cx| {
20315 ProposedChangesEditor::new(
20316 "Proposed changes",
20317 proposed_changes_buffers,
20318 self.project.clone(),
20319 window,
20320 cx,
20321 )
20322 });
20323
20324 window.defer(cx, move |window, cx| {
20325 workspace.update(cx, |workspace, cx| {
20326 workspace.active_pane().update(cx, |pane, cx| {
20327 pane.add_item(
20328 Box::new(proposed_changes_editor),
20329 true,
20330 true,
20331 None,
20332 window,
20333 cx,
20334 );
20335 });
20336 });
20337 });
20338 }
20339
20340 pub fn open_excerpts_in_split(
20341 &mut self,
20342 _: &OpenExcerptsSplit,
20343 window: &mut Window,
20344 cx: &mut Context<Self>,
20345 ) {
20346 self.open_excerpts_common(None, true, window, cx)
20347 }
20348
20349 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
20350 self.open_excerpts_common(None, false, window, cx)
20351 }
20352
20353 fn open_excerpts_common(
20354 &mut self,
20355 jump_data: Option<JumpData>,
20356 split: bool,
20357 window: &mut Window,
20358 cx: &mut Context<Self>,
20359 ) {
20360 let Some(workspace) = self.workspace() else {
20361 cx.propagate();
20362 return;
20363 };
20364
20365 if self.buffer.read(cx).is_singleton() {
20366 cx.propagate();
20367 return;
20368 }
20369
20370 let mut new_selections_by_buffer = HashMap::default();
20371 match &jump_data {
20372 Some(JumpData::MultiBufferPoint {
20373 excerpt_id,
20374 position,
20375 anchor,
20376 line_offset_from_top,
20377 }) => {
20378 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20379 if let Some(buffer) = multi_buffer_snapshot
20380 .buffer_id_for_excerpt(*excerpt_id)
20381 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
20382 {
20383 let buffer_snapshot = buffer.read(cx).snapshot();
20384 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
20385 language::ToPoint::to_point(anchor, &buffer_snapshot)
20386 } else {
20387 buffer_snapshot.clip_point(*position, Bias::Left)
20388 };
20389 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
20390 new_selections_by_buffer.insert(
20391 buffer,
20392 (
20393 vec![jump_to_offset..jump_to_offset],
20394 Some(*line_offset_from_top),
20395 ),
20396 );
20397 }
20398 }
20399 Some(JumpData::MultiBufferRow {
20400 row,
20401 line_offset_from_top,
20402 }) => {
20403 let point = MultiBufferPoint::new(row.0, 0);
20404 if let Some((buffer, buffer_point, _)) =
20405 self.buffer.read(cx).point_to_buffer_point(point, cx)
20406 {
20407 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
20408 new_selections_by_buffer
20409 .entry(buffer)
20410 .or_insert((Vec::new(), Some(*line_offset_from_top)))
20411 .0
20412 .push(buffer_offset..buffer_offset)
20413 }
20414 }
20415 None => {
20416 let selections = self.selections.all::<usize>(cx);
20417 let multi_buffer = self.buffer.read(cx);
20418 for selection in selections {
20419 for (snapshot, range, _, anchor) in multi_buffer
20420 .snapshot(cx)
20421 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
20422 {
20423 if let Some(anchor) = anchor {
20424 // selection is in a deleted hunk
20425 let Some(buffer_id) = anchor.buffer_id else {
20426 continue;
20427 };
20428 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
20429 continue;
20430 };
20431 let offset = text::ToOffset::to_offset(
20432 &anchor.text_anchor,
20433 &buffer_handle.read(cx).snapshot(),
20434 );
20435 let range = offset..offset;
20436 new_selections_by_buffer
20437 .entry(buffer_handle)
20438 .or_insert((Vec::new(), None))
20439 .0
20440 .push(range)
20441 } else {
20442 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
20443 else {
20444 continue;
20445 };
20446 new_selections_by_buffer
20447 .entry(buffer_handle)
20448 .or_insert((Vec::new(), None))
20449 .0
20450 .push(range)
20451 }
20452 }
20453 }
20454 }
20455 }
20456
20457 new_selections_by_buffer
20458 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
20459
20460 if new_selections_by_buffer.is_empty() {
20461 return;
20462 }
20463
20464 // We defer the pane interaction because we ourselves are a workspace item
20465 // and activating a new item causes the pane to call a method on us reentrantly,
20466 // which panics if we're on the stack.
20467 window.defer(cx, move |window, cx| {
20468 workspace.update(cx, |workspace, cx| {
20469 let pane = if split {
20470 workspace.adjacent_pane(window, cx)
20471 } else {
20472 workspace.active_pane().clone()
20473 };
20474
20475 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
20476 let editor = buffer
20477 .read(cx)
20478 .file()
20479 .is_none()
20480 .then(|| {
20481 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
20482 // so `workspace.open_project_item` will never find them, always opening a new editor.
20483 // Instead, we try to activate the existing editor in the pane first.
20484 let (editor, pane_item_index) =
20485 pane.read(cx).items().enumerate().find_map(|(i, item)| {
20486 let editor = item.downcast::<Editor>()?;
20487 let singleton_buffer =
20488 editor.read(cx).buffer().read(cx).as_singleton()?;
20489 if singleton_buffer == buffer {
20490 Some((editor, i))
20491 } else {
20492 None
20493 }
20494 })?;
20495 pane.update(cx, |pane, cx| {
20496 pane.activate_item(pane_item_index, true, true, window, cx)
20497 });
20498 Some(editor)
20499 })
20500 .flatten()
20501 .unwrap_or_else(|| {
20502 workspace.open_project_item::<Self>(
20503 pane.clone(),
20504 buffer,
20505 true,
20506 true,
20507 window,
20508 cx,
20509 )
20510 });
20511
20512 editor.update(cx, |editor, cx| {
20513 let autoscroll = match scroll_offset {
20514 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
20515 None => Autoscroll::newest(),
20516 };
20517 let nav_history = editor.nav_history.take();
20518 editor.change_selections(
20519 SelectionEffects::scroll(autoscroll),
20520 window,
20521 cx,
20522 |s| {
20523 s.select_ranges(ranges);
20524 },
20525 );
20526 editor.nav_history = nav_history;
20527 });
20528 }
20529 })
20530 });
20531 }
20532
20533 // For now, don't allow opening excerpts in buffers that aren't backed by
20534 // regular project files.
20535 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
20536 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
20537 }
20538
20539 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
20540 let snapshot = self.buffer.read(cx).read(cx);
20541 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
20542 Some(
20543 ranges
20544 .iter()
20545 .map(move |range| {
20546 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
20547 })
20548 .collect(),
20549 )
20550 }
20551
20552 fn selection_replacement_ranges(
20553 &self,
20554 range: Range<OffsetUtf16>,
20555 cx: &mut App,
20556 ) -> Vec<Range<OffsetUtf16>> {
20557 let selections = self.selections.all::<OffsetUtf16>(cx);
20558 let newest_selection = selections
20559 .iter()
20560 .max_by_key(|selection| selection.id)
20561 .unwrap();
20562 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
20563 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
20564 let snapshot = self.buffer.read(cx).read(cx);
20565 selections
20566 .into_iter()
20567 .map(|mut selection| {
20568 selection.start.0 =
20569 (selection.start.0 as isize).saturating_add(start_delta) as usize;
20570 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
20571 snapshot.clip_offset_utf16(selection.start, Bias::Left)
20572 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
20573 })
20574 .collect()
20575 }
20576
20577 fn report_editor_event(
20578 &self,
20579 event_type: &'static str,
20580 file_extension: Option<String>,
20581 cx: &App,
20582 ) {
20583 if cfg!(any(test, feature = "test-support")) {
20584 return;
20585 }
20586
20587 let Some(project) = &self.project else { return };
20588
20589 // If None, we are in a file without an extension
20590 let file = self
20591 .buffer
20592 .read(cx)
20593 .as_singleton()
20594 .and_then(|b| b.read(cx).file());
20595 let file_extension = file_extension.or(file
20596 .as_ref()
20597 .and_then(|file| Path::new(file.file_name(cx)).extension())
20598 .and_then(|e| e.to_str())
20599 .map(|a| a.to_string()));
20600
20601 let vim_mode = vim_enabled(cx);
20602
20603 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
20604 let copilot_enabled = edit_predictions_provider
20605 == language::language_settings::EditPredictionProvider::Copilot;
20606 let copilot_enabled_for_language = self
20607 .buffer
20608 .read(cx)
20609 .language_settings(cx)
20610 .show_edit_predictions;
20611
20612 let project = project.read(cx);
20613 telemetry::event!(
20614 event_type,
20615 file_extension,
20616 vim_mode,
20617 copilot_enabled,
20618 copilot_enabled_for_language,
20619 edit_predictions_provider,
20620 is_via_ssh = project.is_via_ssh(),
20621 );
20622 }
20623
20624 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
20625 /// with each line being an array of {text, highlight} objects.
20626 fn copy_highlight_json(
20627 &mut self,
20628 _: &CopyHighlightJson,
20629 window: &mut Window,
20630 cx: &mut Context<Self>,
20631 ) {
20632 #[derive(Serialize)]
20633 struct Chunk<'a> {
20634 text: String,
20635 highlight: Option<&'a str>,
20636 }
20637
20638 let snapshot = self.buffer.read(cx).snapshot(cx);
20639 let range = self
20640 .selected_text_range(false, window, cx)
20641 .and_then(|selection| {
20642 if selection.range.is_empty() {
20643 None
20644 } else {
20645 Some(selection.range)
20646 }
20647 })
20648 .unwrap_or_else(|| 0..snapshot.len());
20649
20650 let chunks = snapshot.chunks(range, true);
20651 let mut lines = Vec::new();
20652 let mut line: VecDeque<Chunk> = VecDeque::new();
20653
20654 let Some(style) = self.style.as_ref() else {
20655 return;
20656 };
20657
20658 for chunk in chunks {
20659 let highlight = chunk
20660 .syntax_highlight_id
20661 .and_then(|id| id.name(&style.syntax));
20662 let mut chunk_lines = chunk.text.split('\n').peekable();
20663 while let Some(text) = chunk_lines.next() {
20664 let mut merged_with_last_token = false;
20665 if let Some(last_token) = line.back_mut() {
20666 if last_token.highlight == highlight {
20667 last_token.text.push_str(text);
20668 merged_with_last_token = true;
20669 }
20670 }
20671
20672 if !merged_with_last_token {
20673 line.push_back(Chunk {
20674 text: text.into(),
20675 highlight,
20676 });
20677 }
20678
20679 if chunk_lines.peek().is_some() {
20680 if line.len() > 1 && line.front().unwrap().text.is_empty() {
20681 line.pop_front();
20682 }
20683 if line.len() > 1 && line.back().unwrap().text.is_empty() {
20684 line.pop_back();
20685 }
20686
20687 lines.push(mem::take(&mut line));
20688 }
20689 }
20690 }
20691
20692 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
20693 return;
20694 };
20695 cx.write_to_clipboard(ClipboardItem::new_string(lines));
20696 }
20697
20698 pub fn open_context_menu(
20699 &mut self,
20700 _: &OpenContextMenu,
20701 window: &mut Window,
20702 cx: &mut Context<Self>,
20703 ) {
20704 self.request_autoscroll(Autoscroll::newest(), cx);
20705 let position = self.selections.newest_display(cx).start;
20706 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
20707 }
20708
20709 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
20710 &self.inlay_hint_cache
20711 }
20712
20713 pub fn replay_insert_event(
20714 &mut self,
20715 text: &str,
20716 relative_utf16_range: Option<Range<isize>>,
20717 window: &mut Window,
20718 cx: &mut Context<Self>,
20719 ) {
20720 if !self.input_enabled {
20721 cx.emit(EditorEvent::InputIgnored { text: text.into() });
20722 return;
20723 }
20724 if let Some(relative_utf16_range) = relative_utf16_range {
20725 let selections = self.selections.all::<OffsetUtf16>(cx);
20726 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20727 let new_ranges = selections.into_iter().map(|range| {
20728 let start = OffsetUtf16(
20729 range
20730 .head()
20731 .0
20732 .saturating_add_signed(relative_utf16_range.start),
20733 );
20734 let end = OffsetUtf16(
20735 range
20736 .head()
20737 .0
20738 .saturating_add_signed(relative_utf16_range.end),
20739 );
20740 start..end
20741 });
20742 s.select_ranges(new_ranges);
20743 });
20744 }
20745
20746 self.handle_input(text, window, cx);
20747 }
20748
20749 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
20750 let Some(provider) = self.semantics_provider.as_ref() else {
20751 return false;
20752 };
20753
20754 let mut supports = false;
20755 self.buffer().update(cx, |this, cx| {
20756 this.for_each_buffer(|buffer| {
20757 supports |= provider.supports_inlay_hints(buffer, cx);
20758 });
20759 });
20760
20761 supports
20762 }
20763
20764 pub fn is_focused(&self, window: &Window) -> bool {
20765 self.focus_handle.is_focused(window)
20766 }
20767
20768 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20769 cx.emit(EditorEvent::Focused);
20770
20771 if let Some(descendant) = self
20772 .last_focused_descendant
20773 .take()
20774 .and_then(|descendant| descendant.upgrade())
20775 {
20776 window.focus(&descendant);
20777 } else {
20778 if let Some(blame) = self.blame.as_ref() {
20779 blame.update(cx, GitBlame::focus)
20780 }
20781
20782 self.blink_manager.update(cx, BlinkManager::enable);
20783 self.show_cursor_names(window, cx);
20784 self.buffer.update(cx, |buffer, cx| {
20785 buffer.finalize_last_transaction(cx);
20786 if self.leader_id.is_none() {
20787 buffer.set_active_selections(
20788 &self.selections.disjoint_anchors(),
20789 self.selections.line_mode,
20790 self.cursor_shape,
20791 cx,
20792 );
20793 }
20794 });
20795 }
20796 }
20797
20798 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
20799 cx.emit(EditorEvent::FocusedIn)
20800 }
20801
20802 fn handle_focus_out(
20803 &mut self,
20804 event: FocusOutEvent,
20805 _window: &mut Window,
20806 cx: &mut Context<Self>,
20807 ) {
20808 if event.blurred != self.focus_handle {
20809 self.last_focused_descendant = Some(event.blurred);
20810 }
20811 self.selection_drag_state = SelectionDragState::None;
20812 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
20813 }
20814
20815 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20816 self.blink_manager.update(cx, BlinkManager::disable);
20817 self.buffer
20818 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
20819
20820 if let Some(blame) = self.blame.as_ref() {
20821 blame.update(cx, GitBlame::blur)
20822 }
20823 if !self.hover_state.focused(window, cx) {
20824 hide_hover(self, cx);
20825 }
20826 if !self
20827 .context_menu
20828 .borrow()
20829 .as_ref()
20830 .is_some_and(|context_menu| context_menu.focused(window, cx))
20831 {
20832 self.hide_context_menu(window, cx);
20833 }
20834 self.discard_edit_prediction(false, cx);
20835 cx.emit(EditorEvent::Blurred);
20836 cx.notify();
20837 }
20838
20839 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20840 let mut pending: String = window
20841 .pending_input_keystrokes()
20842 .into_iter()
20843 .flatten()
20844 .filter_map(|keystroke| {
20845 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
20846 keystroke.key_char.clone()
20847 } else {
20848 None
20849 }
20850 })
20851 .collect();
20852
20853 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
20854 pending = "".to_string();
20855 }
20856
20857 let existing_pending = self
20858 .text_highlights::<PendingInput>(cx)
20859 .map(|(_, ranges)| ranges.iter().cloned().collect::<Vec<_>>());
20860 if existing_pending.is_none() && pending.is_empty() {
20861 return;
20862 }
20863 let transaction =
20864 self.transact(window, cx, |this, window, cx| {
20865 let selections = this.selections.all::<usize>(cx);
20866 let edits = selections
20867 .iter()
20868 .map(|selection| (selection.end..selection.end, pending.clone()));
20869 this.edit(edits, cx);
20870 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20871 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
20872 sel.start + ix * pending.len()..sel.end + ix * pending.len()
20873 }));
20874 });
20875 if let Some(existing_ranges) = existing_pending {
20876 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
20877 this.edit(edits, cx);
20878 }
20879 });
20880
20881 let snapshot = self.snapshot(window, cx);
20882 let ranges = self
20883 .selections
20884 .all::<usize>(cx)
20885 .into_iter()
20886 .map(|selection| {
20887 snapshot.buffer_snapshot.anchor_after(selection.end)
20888 ..snapshot
20889 .buffer_snapshot
20890 .anchor_before(selection.end + pending.len())
20891 })
20892 .collect();
20893
20894 if pending.is_empty() {
20895 self.clear_highlights::<PendingInput>(cx);
20896 } else {
20897 self.highlight_text::<PendingInput>(
20898 ranges,
20899 HighlightStyle {
20900 underline: Some(UnderlineStyle {
20901 thickness: px(1.),
20902 color: None,
20903 wavy: false,
20904 }),
20905 ..Default::default()
20906 },
20907 cx,
20908 );
20909 }
20910
20911 self.ime_transaction = self.ime_transaction.or(transaction);
20912 if let Some(transaction) = self.ime_transaction {
20913 self.buffer.update(cx, |buffer, cx| {
20914 buffer.group_until_transaction(transaction, cx);
20915 });
20916 }
20917
20918 if self.text_highlights::<PendingInput>(cx).is_none() {
20919 self.ime_transaction.take();
20920 }
20921 }
20922
20923 pub fn register_action_renderer(
20924 &mut self,
20925 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
20926 ) -> Subscription {
20927 let id = self.next_editor_action_id.post_inc();
20928 self.editor_actions
20929 .borrow_mut()
20930 .insert(id, Box::new(listener));
20931
20932 let editor_actions = self.editor_actions.clone();
20933 Subscription::new(move || {
20934 editor_actions.borrow_mut().remove(&id);
20935 })
20936 }
20937
20938 pub fn register_action<A: Action>(
20939 &mut self,
20940 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
20941 ) -> Subscription {
20942 let id = self.next_editor_action_id.post_inc();
20943 let listener = Arc::new(listener);
20944 self.editor_actions.borrow_mut().insert(
20945 id,
20946 Box::new(move |_, window, _| {
20947 let listener = listener.clone();
20948 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
20949 let action = action.downcast_ref().unwrap();
20950 if phase == DispatchPhase::Bubble {
20951 listener(action, window, cx)
20952 }
20953 })
20954 }),
20955 );
20956
20957 let editor_actions = self.editor_actions.clone();
20958 Subscription::new(move || {
20959 editor_actions.borrow_mut().remove(&id);
20960 })
20961 }
20962
20963 pub fn file_header_size(&self) -> u32 {
20964 FILE_HEADER_HEIGHT
20965 }
20966
20967 pub fn restore(
20968 &mut self,
20969 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
20970 window: &mut Window,
20971 cx: &mut Context<Self>,
20972 ) {
20973 let workspace = self.workspace();
20974 let project = self.project.as_ref();
20975 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
20976 let mut tasks = Vec::new();
20977 for (buffer_id, changes) in revert_changes {
20978 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
20979 buffer.update(cx, |buffer, cx| {
20980 buffer.edit(
20981 changes
20982 .into_iter()
20983 .map(|(range, text)| (range, text.to_string())),
20984 None,
20985 cx,
20986 );
20987 });
20988
20989 if let Some(project) =
20990 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
20991 {
20992 project.update(cx, |project, cx| {
20993 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
20994 })
20995 }
20996 }
20997 }
20998 tasks
20999 });
21000 cx.spawn_in(window, async move |_, cx| {
21001 for (buffer, task) in save_tasks {
21002 let result = task.await;
21003 if result.is_err() {
21004 let Some(path) = buffer
21005 .read_with(cx, |buffer, cx| buffer.project_path(cx))
21006 .ok()
21007 else {
21008 continue;
21009 };
21010 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
21011 let Some(task) = cx
21012 .update_window_entity(&workspace, |workspace, window, cx| {
21013 workspace
21014 .open_path_preview(path, None, false, false, false, window, cx)
21015 })
21016 .ok()
21017 else {
21018 continue;
21019 };
21020 task.await.log_err();
21021 }
21022 }
21023 }
21024 })
21025 .detach();
21026 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21027 selections.refresh()
21028 });
21029 }
21030
21031 pub fn to_pixel_point(
21032 &self,
21033 source: multi_buffer::Anchor,
21034 editor_snapshot: &EditorSnapshot,
21035 window: &mut Window,
21036 ) -> Option<gpui::Point<Pixels>> {
21037 let source_point = source.to_display_point(editor_snapshot);
21038 self.display_to_pixel_point(source_point, editor_snapshot, window)
21039 }
21040
21041 pub fn display_to_pixel_point(
21042 &self,
21043 source: DisplayPoint,
21044 editor_snapshot: &EditorSnapshot,
21045 window: &mut Window,
21046 ) -> Option<gpui::Point<Pixels>> {
21047 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
21048 let text_layout_details = self.text_layout_details(window);
21049 let scroll_top = text_layout_details
21050 .scroll_anchor
21051 .scroll_position(editor_snapshot)
21052 .y;
21053
21054 if source.row().as_f32() < scroll_top.floor() {
21055 return None;
21056 }
21057 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
21058 let source_y = line_height * (source.row().as_f32() - scroll_top);
21059 Some(gpui::Point::new(source_x, source_y))
21060 }
21061
21062 pub fn has_visible_completions_menu(&self) -> bool {
21063 !self.edit_prediction_preview_is_active()
21064 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
21065 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
21066 })
21067 }
21068
21069 pub fn register_addon<T: Addon>(&mut self, instance: T) {
21070 if self.mode.is_minimap() {
21071 return;
21072 }
21073 self.addons
21074 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
21075 }
21076
21077 pub fn unregister_addon<T: Addon>(&mut self) {
21078 self.addons.remove(&std::any::TypeId::of::<T>());
21079 }
21080
21081 pub fn addon<T: Addon>(&self) -> Option<&T> {
21082 let type_id = std::any::TypeId::of::<T>();
21083 self.addons
21084 .get(&type_id)
21085 .and_then(|item| item.to_any().downcast_ref::<T>())
21086 }
21087
21088 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
21089 let type_id = std::any::TypeId::of::<T>();
21090 self.addons
21091 .get_mut(&type_id)
21092 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
21093 }
21094
21095 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
21096 let text_layout_details = self.text_layout_details(window);
21097 let style = &text_layout_details.editor_style;
21098 let font_id = window.text_system().resolve_font(&style.text.font());
21099 let font_size = style.text.font_size.to_pixels(window.rem_size());
21100 let line_height = style.text.line_height_in_pixels(window.rem_size());
21101 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
21102 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
21103
21104 CharacterDimensions {
21105 em_width,
21106 em_advance,
21107 line_height,
21108 }
21109 }
21110
21111 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
21112 self.load_diff_task.clone()
21113 }
21114
21115 fn read_metadata_from_db(
21116 &mut self,
21117 item_id: u64,
21118 workspace_id: WorkspaceId,
21119 window: &mut Window,
21120 cx: &mut Context<Editor>,
21121 ) {
21122 if self.is_singleton(cx)
21123 && !self.mode.is_minimap()
21124 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
21125 {
21126 let buffer_snapshot = OnceCell::new();
21127
21128 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
21129 if !folds.is_empty() {
21130 let snapshot =
21131 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21132 self.fold_ranges(
21133 folds
21134 .into_iter()
21135 .map(|(start, end)| {
21136 snapshot.clip_offset(start, Bias::Left)
21137 ..snapshot.clip_offset(end, Bias::Right)
21138 })
21139 .collect(),
21140 false,
21141 window,
21142 cx,
21143 );
21144 }
21145 }
21146
21147 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
21148 if !selections.is_empty() {
21149 let snapshot =
21150 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21151 // skip adding the initial selection to selection history
21152 self.selection_history.mode = SelectionHistoryMode::Skipping;
21153 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21154 s.select_ranges(selections.into_iter().map(|(start, end)| {
21155 snapshot.clip_offset(start, Bias::Left)
21156 ..snapshot.clip_offset(end, Bias::Right)
21157 }));
21158 });
21159 self.selection_history.mode = SelectionHistoryMode::Normal;
21160 }
21161 };
21162 }
21163
21164 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
21165 }
21166
21167 fn update_lsp_data(
21168 &mut self,
21169 ignore_cache: bool,
21170 for_buffer: Option<BufferId>,
21171 window: &mut Window,
21172 cx: &mut Context<'_, Self>,
21173 ) {
21174 self.pull_diagnostics(for_buffer, window, cx);
21175 self.refresh_colors(ignore_cache, for_buffer, window, cx);
21176 }
21177}
21178
21179fn vim_enabled(cx: &App) -> bool {
21180 cx.global::<SettingsStore>()
21181 .raw_user_settings()
21182 .get("vim_mode")
21183 == Some(&serde_json::Value::Bool(true))
21184}
21185
21186fn process_completion_for_edit(
21187 completion: &Completion,
21188 intent: CompletionIntent,
21189 buffer: &Entity<Buffer>,
21190 cursor_position: &text::Anchor,
21191 cx: &mut Context<Editor>,
21192) -> CompletionEdit {
21193 let buffer = buffer.read(cx);
21194 let buffer_snapshot = buffer.snapshot();
21195 let (snippet, new_text) = if completion.is_snippet() {
21196 // Workaround for typescript language server issues so that methods don't expand within
21197 // strings and functions with type expressions. The previous point is used because the query
21198 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
21199 let mut snippet_source = completion.new_text.clone();
21200 let mut previous_point = text::ToPoint::to_point(cursor_position, buffer);
21201 previous_point.column = previous_point.column.saturating_sub(1);
21202 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point) {
21203 if scope.prefers_label_for_snippet_in_completion() {
21204 if let Some(label) = completion.label() {
21205 if matches!(
21206 completion.kind(),
21207 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
21208 ) {
21209 snippet_source = label;
21210 }
21211 }
21212 }
21213 }
21214 match Snippet::parse(&snippet_source).log_err() {
21215 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
21216 None => (None, completion.new_text.clone()),
21217 }
21218 } else {
21219 (None, completion.new_text.clone())
21220 };
21221
21222 let mut range_to_replace = {
21223 let replace_range = &completion.replace_range;
21224 if let CompletionSource::Lsp {
21225 insert_range: Some(insert_range),
21226 ..
21227 } = &completion.source
21228 {
21229 debug_assert_eq!(
21230 insert_range.start, replace_range.start,
21231 "insert_range and replace_range should start at the same position"
21232 );
21233 debug_assert!(
21234 insert_range
21235 .start
21236 .cmp(&cursor_position, &buffer_snapshot)
21237 .is_le(),
21238 "insert_range should start before or at cursor position"
21239 );
21240 debug_assert!(
21241 replace_range
21242 .start
21243 .cmp(&cursor_position, &buffer_snapshot)
21244 .is_le(),
21245 "replace_range should start before or at cursor position"
21246 );
21247
21248 let should_replace = match intent {
21249 CompletionIntent::CompleteWithInsert => false,
21250 CompletionIntent::CompleteWithReplace => true,
21251 CompletionIntent::Complete | CompletionIntent::Compose => {
21252 let insert_mode =
21253 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
21254 .completions
21255 .lsp_insert_mode;
21256 match insert_mode {
21257 LspInsertMode::Insert => false,
21258 LspInsertMode::Replace => true,
21259 LspInsertMode::ReplaceSubsequence => {
21260 let mut text_to_replace = buffer.chars_for_range(
21261 buffer.anchor_before(replace_range.start)
21262 ..buffer.anchor_after(replace_range.end),
21263 );
21264 let mut current_needle = text_to_replace.next();
21265 for haystack_ch in completion.label.text.chars() {
21266 if let Some(needle_ch) = current_needle {
21267 if haystack_ch.eq_ignore_ascii_case(&needle_ch) {
21268 current_needle = text_to_replace.next();
21269 }
21270 }
21271 }
21272 current_needle.is_none()
21273 }
21274 LspInsertMode::ReplaceSuffix => {
21275 if replace_range
21276 .end
21277 .cmp(&cursor_position, &buffer_snapshot)
21278 .is_gt()
21279 {
21280 let range_after_cursor = *cursor_position..replace_range.end;
21281 let text_after_cursor = buffer
21282 .text_for_range(
21283 buffer.anchor_before(range_after_cursor.start)
21284 ..buffer.anchor_after(range_after_cursor.end),
21285 )
21286 .collect::<String>()
21287 .to_ascii_lowercase();
21288 completion
21289 .label
21290 .text
21291 .to_ascii_lowercase()
21292 .ends_with(&text_after_cursor)
21293 } else {
21294 true
21295 }
21296 }
21297 }
21298 }
21299 };
21300
21301 if should_replace {
21302 replace_range.clone()
21303 } else {
21304 insert_range.clone()
21305 }
21306 } else {
21307 replace_range.clone()
21308 }
21309 };
21310
21311 if range_to_replace
21312 .end
21313 .cmp(&cursor_position, &buffer_snapshot)
21314 .is_lt()
21315 {
21316 range_to_replace.end = *cursor_position;
21317 }
21318
21319 CompletionEdit {
21320 new_text,
21321 replace_range: range_to_replace.to_offset(&buffer),
21322 snippet,
21323 }
21324}
21325
21326struct CompletionEdit {
21327 new_text: String,
21328 replace_range: Range<usize>,
21329 snippet: Option<Snippet>,
21330}
21331
21332fn insert_extra_newline_brackets(
21333 buffer: &MultiBufferSnapshot,
21334 range: Range<usize>,
21335 language: &language::LanguageScope,
21336) -> bool {
21337 let leading_whitespace_len = buffer
21338 .reversed_chars_at(range.start)
21339 .take_while(|c| c.is_whitespace() && *c != '\n')
21340 .map(|c| c.len_utf8())
21341 .sum::<usize>();
21342 let trailing_whitespace_len = buffer
21343 .chars_at(range.end)
21344 .take_while(|c| c.is_whitespace() && *c != '\n')
21345 .map(|c| c.len_utf8())
21346 .sum::<usize>();
21347 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
21348
21349 language.brackets().any(|(pair, enabled)| {
21350 let pair_start = pair.start.trim_end();
21351 let pair_end = pair.end.trim_start();
21352
21353 enabled
21354 && pair.newline
21355 && buffer.contains_str_at(range.end, pair_end)
21356 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
21357 })
21358}
21359
21360fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
21361 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
21362 [(buffer, range, _)] => (*buffer, range.clone()),
21363 _ => return false,
21364 };
21365 let pair = {
21366 let mut result: Option<BracketMatch> = None;
21367
21368 for pair in buffer
21369 .all_bracket_ranges(range.clone())
21370 .filter(move |pair| {
21371 pair.open_range.start <= range.start && pair.close_range.end >= range.end
21372 })
21373 {
21374 let len = pair.close_range.end - pair.open_range.start;
21375
21376 if let Some(existing) = &result {
21377 let existing_len = existing.close_range.end - existing.open_range.start;
21378 if len > existing_len {
21379 continue;
21380 }
21381 }
21382
21383 result = Some(pair);
21384 }
21385
21386 result
21387 };
21388 let Some(pair) = pair else {
21389 return false;
21390 };
21391 pair.newline_only
21392 && buffer
21393 .chars_for_range(pair.open_range.end..range.start)
21394 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
21395 .all(|c| c.is_whitespace() && c != '\n')
21396}
21397
21398fn update_uncommitted_diff_for_buffer(
21399 editor: Entity<Editor>,
21400 project: &Entity<Project>,
21401 buffers: impl IntoIterator<Item = Entity<Buffer>>,
21402 buffer: Entity<MultiBuffer>,
21403 cx: &mut App,
21404) -> Task<()> {
21405 let mut tasks = Vec::new();
21406 project.update(cx, |project, cx| {
21407 for buffer in buffers {
21408 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
21409 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
21410 }
21411 }
21412 });
21413 cx.spawn(async move |cx| {
21414 let diffs = future::join_all(tasks).await;
21415 if editor
21416 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
21417 .unwrap_or(false)
21418 {
21419 return;
21420 }
21421
21422 buffer
21423 .update(cx, |buffer, cx| {
21424 for diff in diffs.into_iter().flatten() {
21425 buffer.add_diff(diff, cx);
21426 }
21427 })
21428 .ok();
21429 })
21430}
21431
21432fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
21433 let tab_size = tab_size.get() as usize;
21434 let mut width = offset;
21435
21436 for ch in text.chars() {
21437 width += if ch == '\t' {
21438 tab_size - (width % tab_size)
21439 } else {
21440 1
21441 };
21442 }
21443
21444 width - offset
21445}
21446
21447#[cfg(test)]
21448mod tests {
21449 use super::*;
21450
21451 #[test]
21452 fn test_string_size_with_expanded_tabs() {
21453 let nz = |val| NonZeroU32::new(val).unwrap();
21454 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
21455 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
21456 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
21457 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
21458 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
21459 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
21460 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
21461 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
21462 }
21463}
21464
21465/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
21466struct WordBreakingTokenizer<'a> {
21467 input: &'a str,
21468}
21469
21470impl<'a> WordBreakingTokenizer<'a> {
21471 fn new(input: &'a str) -> Self {
21472 Self { input }
21473 }
21474}
21475
21476fn is_char_ideographic(ch: char) -> bool {
21477 use unicode_script::Script::*;
21478 use unicode_script::UnicodeScript;
21479 matches!(ch.script(), Han | Tangut | Yi)
21480}
21481
21482fn is_grapheme_ideographic(text: &str) -> bool {
21483 text.chars().any(is_char_ideographic)
21484}
21485
21486fn is_grapheme_whitespace(text: &str) -> bool {
21487 text.chars().any(|x| x.is_whitespace())
21488}
21489
21490fn should_stay_with_preceding_ideograph(text: &str) -> bool {
21491 text.chars().next().map_or(false, |ch| {
21492 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
21493 })
21494}
21495
21496#[derive(PartialEq, Eq, Debug, Clone, Copy)]
21497enum WordBreakToken<'a> {
21498 Word { token: &'a str, grapheme_len: usize },
21499 InlineWhitespace { token: &'a str, grapheme_len: usize },
21500 Newline,
21501}
21502
21503impl<'a> Iterator for WordBreakingTokenizer<'a> {
21504 /// Yields a span, the count of graphemes in the token, and whether it was
21505 /// whitespace. Note that it also breaks at word boundaries.
21506 type Item = WordBreakToken<'a>;
21507
21508 fn next(&mut self) -> Option<Self::Item> {
21509 use unicode_segmentation::UnicodeSegmentation;
21510 if self.input.is_empty() {
21511 return None;
21512 }
21513
21514 let mut iter = self.input.graphemes(true).peekable();
21515 let mut offset = 0;
21516 let mut grapheme_len = 0;
21517 if let Some(first_grapheme) = iter.next() {
21518 let is_newline = first_grapheme == "\n";
21519 let is_whitespace = is_grapheme_whitespace(first_grapheme);
21520 offset += first_grapheme.len();
21521 grapheme_len += 1;
21522 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
21523 if let Some(grapheme) = iter.peek().copied() {
21524 if should_stay_with_preceding_ideograph(grapheme) {
21525 offset += grapheme.len();
21526 grapheme_len += 1;
21527 }
21528 }
21529 } else {
21530 let mut words = self.input[offset..].split_word_bound_indices().peekable();
21531 let mut next_word_bound = words.peek().copied();
21532 if next_word_bound.map_or(false, |(i, _)| i == 0) {
21533 next_word_bound = words.next();
21534 }
21535 while let Some(grapheme) = iter.peek().copied() {
21536 if next_word_bound.map_or(false, |(i, _)| i == offset) {
21537 break;
21538 };
21539 if is_grapheme_whitespace(grapheme) != is_whitespace
21540 || (grapheme == "\n") != is_newline
21541 {
21542 break;
21543 };
21544 offset += grapheme.len();
21545 grapheme_len += 1;
21546 iter.next();
21547 }
21548 }
21549 let token = &self.input[..offset];
21550 self.input = &self.input[offset..];
21551 if token == "\n" {
21552 Some(WordBreakToken::Newline)
21553 } else if is_whitespace {
21554 Some(WordBreakToken::InlineWhitespace {
21555 token,
21556 grapheme_len,
21557 })
21558 } else {
21559 Some(WordBreakToken::Word {
21560 token,
21561 grapheme_len,
21562 })
21563 }
21564 } else {
21565 None
21566 }
21567 }
21568}
21569
21570#[test]
21571fn test_word_breaking_tokenizer() {
21572 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
21573 ("", &[]),
21574 (" ", &[whitespace(" ", 2)]),
21575 ("Ʒ", &[word("Ʒ", 1)]),
21576 ("Ǽ", &[word("Ǽ", 1)]),
21577 ("⋑", &[word("⋑", 1)]),
21578 ("⋑⋑", &[word("⋑⋑", 2)]),
21579 (
21580 "原理,进而",
21581 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
21582 ),
21583 (
21584 "hello world",
21585 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
21586 ),
21587 (
21588 "hello, world",
21589 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
21590 ),
21591 (
21592 " hello world",
21593 &[
21594 whitespace(" ", 2),
21595 word("hello", 5),
21596 whitespace(" ", 1),
21597 word("world", 5),
21598 ],
21599 ),
21600 (
21601 "这是什么 \n 钢笔",
21602 &[
21603 word("这", 1),
21604 word("是", 1),
21605 word("什", 1),
21606 word("么", 1),
21607 whitespace(" ", 1),
21608 newline(),
21609 whitespace(" ", 1),
21610 word("钢", 1),
21611 word("笔", 1),
21612 ],
21613 ),
21614 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
21615 ];
21616
21617 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21618 WordBreakToken::Word {
21619 token,
21620 grapheme_len,
21621 }
21622 }
21623
21624 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21625 WordBreakToken::InlineWhitespace {
21626 token,
21627 grapheme_len,
21628 }
21629 }
21630
21631 fn newline() -> WordBreakToken<'static> {
21632 WordBreakToken::Newline
21633 }
21634
21635 for (input, result) in tests {
21636 assert_eq!(
21637 WordBreakingTokenizer::new(input)
21638 .collect::<Vec<_>>()
21639 .as_slice(),
21640 *result,
21641 );
21642 }
21643}
21644
21645fn wrap_with_prefix(
21646 first_line_prefix: String,
21647 subsequent_lines_prefix: String,
21648 unwrapped_text: String,
21649 wrap_column: usize,
21650 tab_size: NonZeroU32,
21651 preserve_existing_whitespace: bool,
21652) -> String {
21653 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
21654 let subsequent_lines_prefix_len =
21655 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
21656 let mut wrapped_text = String::new();
21657 let mut current_line = first_line_prefix.clone();
21658 let mut is_first_line = true;
21659
21660 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
21661 let mut current_line_len = first_line_prefix_len;
21662 let mut in_whitespace = false;
21663 for token in tokenizer {
21664 let have_preceding_whitespace = in_whitespace;
21665 match token {
21666 WordBreakToken::Word {
21667 token,
21668 grapheme_len,
21669 } => {
21670 in_whitespace = false;
21671 let current_prefix_len = if is_first_line {
21672 first_line_prefix_len
21673 } else {
21674 subsequent_lines_prefix_len
21675 };
21676 if current_line_len + grapheme_len > wrap_column
21677 && current_line_len != current_prefix_len
21678 {
21679 wrapped_text.push_str(current_line.trim_end());
21680 wrapped_text.push('\n');
21681 is_first_line = false;
21682 current_line = subsequent_lines_prefix.clone();
21683 current_line_len = subsequent_lines_prefix_len;
21684 }
21685 current_line.push_str(token);
21686 current_line_len += grapheme_len;
21687 }
21688 WordBreakToken::InlineWhitespace {
21689 mut token,
21690 mut grapheme_len,
21691 } => {
21692 in_whitespace = true;
21693 if have_preceding_whitespace && !preserve_existing_whitespace {
21694 continue;
21695 }
21696 if !preserve_existing_whitespace {
21697 token = " ";
21698 grapheme_len = 1;
21699 }
21700 let current_prefix_len = if is_first_line {
21701 first_line_prefix_len
21702 } else {
21703 subsequent_lines_prefix_len
21704 };
21705 if current_line_len + grapheme_len > wrap_column {
21706 wrapped_text.push_str(current_line.trim_end());
21707 wrapped_text.push('\n');
21708 is_first_line = false;
21709 current_line = subsequent_lines_prefix.clone();
21710 current_line_len = subsequent_lines_prefix_len;
21711 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
21712 current_line.push_str(token);
21713 current_line_len += grapheme_len;
21714 }
21715 }
21716 WordBreakToken::Newline => {
21717 in_whitespace = true;
21718 let current_prefix_len = if is_first_line {
21719 first_line_prefix_len
21720 } else {
21721 subsequent_lines_prefix_len
21722 };
21723 if preserve_existing_whitespace {
21724 wrapped_text.push_str(current_line.trim_end());
21725 wrapped_text.push('\n');
21726 is_first_line = false;
21727 current_line = subsequent_lines_prefix.clone();
21728 current_line_len = subsequent_lines_prefix_len;
21729 } else if have_preceding_whitespace {
21730 continue;
21731 } else if current_line_len + 1 > wrap_column
21732 && current_line_len != current_prefix_len
21733 {
21734 wrapped_text.push_str(current_line.trim_end());
21735 wrapped_text.push('\n');
21736 is_first_line = false;
21737 current_line = subsequent_lines_prefix.clone();
21738 current_line_len = subsequent_lines_prefix_len;
21739 } else if current_line_len != current_prefix_len {
21740 current_line.push(' ');
21741 current_line_len += 1;
21742 }
21743 }
21744 }
21745 }
21746
21747 if !current_line.is_empty() {
21748 wrapped_text.push_str(¤t_line);
21749 }
21750 wrapped_text
21751}
21752
21753#[test]
21754fn test_wrap_with_prefix() {
21755 assert_eq!(
21756 wrap_with_prefix(
21757 "# ".to_string(),
21758 "# ".to_string(),
21759 "abcdefg".to_string(),
21760 4,
21761 NonZeroU32::new(4).unwrap(),
21762 false,
21763 ),
21764 "# abcdefg"
21765 );
21766 assert_eq!(
21767 wrap_with_prefix(
21768 "".to_string(),
21769 "".to_string(),
21770 "\thello world".to_string(),
21771 8,
21772 NonZeroU32::new(4).unwrap(),
21773 false,
21774 ),
21775 "hello\nworld"
21776 );
21777 assert_eq!(
21778 wrap_with_prefix(
21779 "// ".to_string(),
21780 "// ".to_string(),
21781 "xx \nyy zz aa bb cc".to_string(),
21782 12,
21783 NonZeroU32::new(4).unwrap(),
21784 false,
21785 ),
21786 "// xx yy zz\n// aa bb cc"
21787 );
21788 assert_eq!(
21789 wrap_with_prefix(
21790 String::new(),
21791 String::new(),
21792 "这是什么 \n 钢笔".to_string(),
21793 3,
21794 NonZeroU32::new(4).unwrap(),
21795 false,
21796 ),
21797 "这是什\n么 钢\n笔"
21798 );
21799}
21800
21801pub trait CollaborationHub {
21802 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
21803 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
21804 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
21805}
21806
21807impl CollaborationHub for Entity<Project> {
21808 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
21809 self.read(cx).collaborators()
21810 }
21811
21812 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
21813 self.read(cx).user_store().read(cx).participant_indices()
21814 }
21815
21816 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
21817 let this = self.read(cx);
21818 let user_ids = this.collaborators().values().map(|c| c.user_id);
21819 this.user_store().read(cx).participant_names(user_ids, cx)
21820 }
21821}
21822
21823pub trait SemanticsProvider {
21824 fn hover(
21825 &self,
21826 buffer: &Entity<Buffer>,
21827 position: text::Anchor,
21828 cx: &mut App,
21829 ) -> Option<Task<Vec<project::Hover>>>;
21830
21831 fn inline_values(
21832 &self,
21833 buffer_handle: Entity<Buffer>,
21834 range: Range<text::Anchor>,
21835 cx: &mut App,
21836 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21837
21838 fn inlay_hints(
21839 &self,
21840 buffer_handle: Entity<Buffer>,
21841 range: Range<text::Anchor>,
21842 cx: &mut App,
21843 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21844
21845 fn resolve_inlay_hint(
21846 &self,
21847 hint: InlayHint,
21848 buffer_handle: Entity<Buffer>,
21849 server_id: LanguageServerId,
21850 cx: &mut App,
21851 ) -> Option<Task<anyhow::Result<InlayHint>>>;
21852
21853 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
21854
21855 fn document_highlights(
21856 &self,
21857 buffer: &Entity<Buffer>,
21858 position: text::Anchor,
21859 cx: &mut App,
21860 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
21861
21862 fn definitions(
21863 &self,
21864 buffer: &Entity<Buffer>,
21865 position: text::Anchor,
21866 kind: GotoDefinitionKind,
21867 cx: &mut App,
21868 ) -> Option<Task<Result<Vec<LocationLink>>>>;
21869
21870 fn range_for_rename(
21871 &self,
21872 buffer: &Entity<Buffer>,
21873 position: text::Anchor,
21874 cx: &mut App,
21875 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
21876
21877 fn perform_rename(
21878 &self,
21879 buffer: &Entity<Buffer>,
21880 position: text::Anchor,
21881 new_name: String,
21882 cx: &mut App,
21883 ) -> Option<Task<Result<ProjectTransaction>>>;
21884}
21885
21886pub trait CompletionProvider {
21887 fn completions(
21888 &self,
21889 excerpt_id: ExcerptId,
21890 buffer: &Entity<Buffer>,
21891 buffer_position: text::Anchor,
21892 trigger: CompletionContext,
21893 window: &mut Window,
21894 cx: &mut Context<Editor>,
21895 ) -> Task<Result<Vec<CompletionResponse>>>;
21896
21897 fn resolve_completions(
21898 &self,
21899 _buffer: Entity<Buffer>,
21900 _completion_indices: Vec<usize>,
21901 _completions: Rc<RefCell<Box<[Completion]>>>,
21902 _cx: &mut Context<Editor>,
21903 ) -> Task<Result<bool>> {
21904 Task::ready(Ok(false))
21905 }
21906
21907 fn apply_additional_edits_for_completion(
21908 &self,
21909 _buffer: Entity<Buffer>,
21910 _completions: Rc<RefCell<Box<[Completion]>>>,
21911 _completion_index: usize,
21912 _push_to_history: bool,
21913 _cx: &mut Context<Editor>,
21914 ) -> Task<Result<Option<language::Transaction>>> {
21915 Task::ready(Ok(None))
21916 }
21917
21918 fn is_completion_trigger(
21919 &self,
21920 buffer: &Entity<Buffer>,
21921 position: language::Anchor,
21922 text: &str,
21923 trigger_in_words: bool,
21924 menu_is_open: bool,
21925 cx: &mut Context<Editor>,
21926 ) -> bool;
21927
21928 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
21929
21930 fn sort_completions(&self) -> bool {
21931 true
21932 }
21933
21934 fn filter_completions(&self) -> bool {
21935 true
21936 }
21937}
21938
21939pub trait CodeActionProvider {
21940 fn id(&self) -> Arc<str>;
21941
21942 fn code_actions(
21943 &self,
21944 buffer: &Entity<Buffer>,
21945 range: Range<text::Anchor>,
21946 window: &mut Window,
21947 cx: &mut App,
21948 ) -> Task<Result<Vec<CodeAction>>>;
21949
21950 fn apply_code_action(
21951 &self,
21952 buffer_handle: Entity<Buffer>,
21953 action: CodeAction,
21954 excerpt_id: ExcerptId,
21955 push_to_history: bool,
21956 window: &mut Window,
21957 cx: &mut App,
21958 ) -> Task<Result<ProjectTransaction>>;
21959}
21960
21961impl CodeActionProvider for Entity<Project> {
21962 fn id(&self) -> Arc<str> {
21963 "project".into()
21964 }
21965
21966 fn code_actions(
21967 &self,
21968 buffer: &Entity<Buffer>,
21969 range: Range<text::Anchor>,
21970 _window: &mut Window,
21971 cx: &mut App,
21972 ) -> Task<Result<Vec<CodeAction>>> {
21973 self.update(cx, |project, cx| {
21974 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
21975 let code_actions = project.code_actions(buffer, range, None, cx);
21976 cx.background_spawn(async move {
21977 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
21978 Ok(code_lens_actions
21979 .context("code lens fetch")?
21980 .into_iter()
21981 .chain(code_actions.context("code action fetch")?)
21982 .collect())
21983 })
21984 })
21985 }
21986
21987 fn apply_code_action(
21988 &self,
21989 buffer_handle: Entity<Buffer>,
21990 action: CodeAction,
21991 _excerpt_id: ExcerptId,
21992 push_to_history: bool,
21993 _window: &mut Window,
21994 cx: &mut App,
21995 ) -> Task<Result<ProjectTransaction>> {
21996 self.update(cx, |project, cx| {
21997 project.apply_code_action(buffer_handle, action, push_to_history, cx)
21998 })
21999 }
22000}
22001
22002fn snippet_completions(
22003 project: &Project,
22004 buffer: &Entity<Buffer>,
22005 buffer_position: text::Anchor,
22006 cx: &mut App,
22007) -> Task<Result<CompletionResponse>> {
22008 let languages = buffer.read(cx).languages_at(buffer_position);
22009 let snippet_store = project.snippets().read(cx);
22010
22011 let scopes: Vec<_> = languages
22012 .iter()
22013 .filter_map(|language| {
22014 let language_name = language.lsp_id();
22015 let snippets = snippet_store.snippets_for(Some(language_name), cx);
22016
22017 if snippets.is_empty() {
22018 None
22019 } else {
22020 Some((language.default_scope(), snippets))
22021 }
22022 })
22023 .collect();
22024
22025 if scopes.is_empty() {
22026 return Task::ready(Ok(CompletionResponse {
22027 completions: vec![],
22028 is_incomplete: false,
22029 }));
22030 }
22031
22032 let snapshot = buffer.read(cx).text_snapshot();
22033 let chars: String = snapshot
22034 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
22035 .collect();
22036 let executor = cx.background_executor().clone();
22037
22038 cx.background_spawn(async move {
22039 let mut is_incomplete = false;
22040 let mut completions: Vec<Completion> = Vec::new();
22041 for (scope, snippets) in scopes.into_iter() {
22042 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
22043 let mut last_word = chars
22044 .chars()
22045 .take_while(|c| classifier.is_word(*c))
22046 .collect::<String>();
22047 last_word = last_word.chars().rev().collect();
22048
22049 if last_word.is_empty() {
22050 return Ok(CompletionResponse {
22051 completions: vec![],
22052 is_incomplete: true,
22053 });
22054 }
22055
22056 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
22057 let to_lsp = |point: &text::Anchor| {
22058 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
22059 point_to_lsp(end)
22060 };
22061 let lsp_end = to_lsp(&buffer_position);
22062
22063 let candidates = snippets
22064 .iter()
22065 .enumerate()
22066 .flat_map(|(ix, snippet)| {
22067 snippet
22068 .prefix
22069 .iter()
22070 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
22071 })
22072 .collect::<Vec<StringMatchCandidate>>();
22073
22074 const MAX_RESULTS: usize = 100;
22075 let mut matches = fuzzy::match_strings(
22076 &candidates,
22077 &last_word,
22078 last_word.chars().any(|c| c.is_uppercase()),
22079 true,
22080 MAX_RESULTS,
22081 &Default::default(),
22082 executor.clone(),
22083 )
22084 .await;
22085
22086 if matches.len() >= MAX_RESULTS {
22087 is_incomplete = true;
22088 }
22089
22090 // Remove all candidates where the query's start does not match the start of any word in the candidate
22091 if let Some(query_start) = last_word.chars().next() {
22092 matches.retain(|string_match| {
22093 split_words(&string_match.string).any(|word| {
22094 // Check that the first codepoint of the word as lowercase matches the first
22095 // codepoint of the query as lowercase
22096 word.chars()
22097 .flat_map(|codepoint| codepoint.to_lowercase())
22098 .zip(query_start.to_lowercase())
22099 .all(|(word_cp, query_cp)| word_cp == query_cp)
22100 })
22101 });
22102 }
22103
22104 let matched_strings = matches
22105 .into_iter()
22106 .map(|m| m.string)
22107 .collect::<HashSet<_>>();
22108
22109 completions.extend(snippets.iter().filter_map(|snippet| {
22110 let matching_prefix = snippet
22111 .prefix
22112 .iter()
22113 .find(|prefix| matched_strings.contains(*prefix))?;
22114 let start = as_offset - last_word.len();
22115 let start = snapshot.anchor_before(start);
22116 let range = start..buffer_position;
22117 let lsp_start = to_lsp(&start);
22118 let lsp_range = lsp::Range {
22119 start: lsp_start,
22120 end: lsp_end,
22121 };
22122 Some(Completion {
22123 replace_range: range,
22124 new_text: snippet.body.clone(),
22125 source: CompletionSource::Lsp {
22126 insert_range: None,
22127 server_id: LanguageServerId(usize::MAX),
22128 resolved: true,
22129 lsp_completion: Box::new(lsp::CompletionItem {
22130 label: snippet.prefix.first().unwrap().clone(),
22131 kind: Some(CompletionItemKind::SNIPPET),
22132 label_details: snippet.description.as_ref().map(|description| {
22133 lsp::CompletionItemLabelDetails {
22134 detail: Some(description.clone()),
22135 description: None,
22136 }
22137 }),
22138 insert_text_format: Some(InsertTextFormat::SNIPPET),
22139 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22140 lsp::InsertReplaceEdit {
22141 new_text: snippet.body.clone(),
22142 insert: lsp_range,
22143 replace: lsp_range,
22144 },
22145 )),
22146 filter_text: Some(snippet.body.clone()),
22147 sort_text: Some(char::MAX.to_string()),
22148 ..lsp::CompletionItem::default()
22149 }),
22150 lsp_defaults: None,
22151 },
22152 label: CodeLabel {
22153 text: matching_prefix.clone(),
22154 runs: Vec::new(),
22155 filter_range: 0..matching_prefix.len(),
22156 },
22157 icon_path: None,
22158 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
22159 single_line: snippet.name.clone().into(),
22160 plain_text: snippet
22161 .description
22162 .clone()
22163 .map(|description| description.into()),
22164 }),
22165 insert_text_mode: None,
22166 confirm: None,
22167 })
22168 }))
22169 }
22170
22171 Ok(CompletionResponse {
22172 completions,
22173 is_incomplete,
22174 })
22175 })
22176}
22177
22178impl CompletionProvider for Entity<Project> {
22179 fn completions(
22180 &self,
22181 _excerpt_id: ExcerptId,
22182 buffer: &Entity<Buffer>,
22183 buffer_position: text::Anchor,
22184 options: CompletionContext,
22185 _window: &mut Window,
22186 cx: &mut Context<Editor>,
22187 ) -> Task<Result<Vec<CompletionResponse>>> {
22188 self.update(cx, |project, cx| {
22189 let snippets = snippet_completions(project, buffer, buffer_position, cx);
22190 let project_completions = project.completions(buffer, buffer_position, options, cx);
22191 cx.background_spawn(async move {
22192 let mut responses = project_completions.await?;
22193 let snippets = snippets.await?;
22194 if !snippets.completions.is_empty() {
22195 responses.push(snippets);
22196 }
22197 Ok(responses)
22198 })
22199 })
22200 }
22201
22202 fn resolve_completions(
22203 &self,
22204 buffer: Entity<Buffer>,
22205 completion_indices: Vec<usize>,
22206 completions: Rc<RefCell<Box<[Completion]>>>,
22207 cx: &mut Context<Editor>,
22208 ) -> Task<Result<bool>> {
22209 self.update(cx, |project, cx| {
22210 project.lsp_store().update(cx, |lsp_store, cx| {
22211 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
22212 })
22213 })
22214 }
22215
22216 fn apply_additional_edits_for_completion(
22217 &self,
22218 buffer: Entity<Buffer>,
22219 completions: Rc<RefCell<Box<[Completion]>>>,
22220 completion_index: usize,
22221 push_to_history: bool,
22222 cx: &mut Context<Editor>,
22223 ) -> Task<Result<Option<language::Transaction>>> {
22224 self.update(cx, |project, cx| {
22225 project.lsp_store().update(cx, |lsp_store, cx| {
22226 lsp_store.apply_additional_edits_for_completion(
22227 buffer,
22228 completions,
22229 completion_index,
22230 push_to_history,
22231 cx,
22232 )
22233 })
22234 })
22235 }
22236
22237 fn is_completion_trigger(
22238 &self,
22239 buffer: &Entity<Buffer>,
22240 position: language::Anchor,
22241 text: &str,
22242 trigger_in_words: bool,
22243 menu_is_open: bool,
22244 cx: &mut Context<Editor>,
22245 ) -> bool {
22246 let mut chars = text.chars();
22247 let char = if let Some(char) = chars.next() {
22248 char
22249 } else {
22250 return false;
22251 };
22252 if chars.next().is_some() {
22253 return false;
22254 }
22255
22256 let buffer = buffer.read(cx);
22257 let snapshot = buffer.snapshot();
22258 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
22259 return false;
22260 }
22261 let classifier = snapshot.char_classifier_at(position).for_completion(true);
22262 if trigger_in_words && classifier.is_word(char) {
22263 return true;
22264 }
22265
22266 buffer.completion_triggers().contains(text)
22267 }
22268}
22269
22270impl SemanticsProvider for Entity<Project> {
22271 fn hover(
22272 &self,
22273 buffer: &Entity<Buffer>,
22274 position: text::Anchor,
22275 cx: &mut App,
22276 ) -> Option<Task<Vec<project::Hover>>> {
22277 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
22278 }
22279
22280 fn document_highlights(
22281 &self,
22282 buffer: &Entity<Buffer>,
22283 position: text::Anchor,
22284 cx: &mut App,
22285 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
22286 Some(self.update(cx, |project, cx| {
22287 project.document_highlights(buffer, position, cx)
22288 }))
22289 }
22290
22291 fn definitions(
22292 &self,
22293 buffer: &Entity<Buffer>,
22294 position: text::Anchor,
22295 kind: GotoDefinitionKind,
22296 cx: &mut App,
22297 ) -> Option<Task<Result<Vec<LocationLink>>>> {
22298 Some(self.update(cx, |project, cx| match kind {
22299 GotoDefinitionKind::Symbol => project.definitions(&buffer, position, cx),
22300 GotoDefinitionKind::Declaration => project.declarations(&buffer, position, cx),
22301 GotoDefinitionKind::Type => project.type_definitions(&buffer, position, cx),
22302 GotoDefinitionKind::Implementation => project.implementations(&buffer, position, cx),
22303 }))
22304 }
22305
22306 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
22307 self.update(cx, |project, cx| {
22308 if project
22309 .active_debug_session(cx)
22310 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
22311 {
22312 return true;
22313 }
22314
22315 buffer.update(cx, |buffer, cx| {
22316 project.any_language_server_supports_inlay_hints(buffer, cx)
22317 })
22318 })
22319 }
22320
22321 fn inline_values(
22322 &self,
22323 buffer_handle: Entity<Buffer>,
22324 range: Range<text::Anchor>,
22325 cx: &mut App,
22326 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22327 self.update(cx, |project, cx| {
22328 let (session, active_stack_frame) = project.active_debug_session(cx)?;
22329
22330 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
22331 })
22332 }
22333
22334 fn inlay_hints(
22335 &self,
22336 buffer_handle: Entity<Buffer>,
22337 range: Range<text::Anchor>,
22338 cx: &mut App,
22339 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22340 Some(self.update(cx, |project, cx| {
22341 project.inlay_hints(buffer_handle, range, cx)
22342 }))
22343 }
22344
22345 fn resolve_inlay_hint(
22346 &self,
22347 hint: InlayHint,
22348 buffer_handle: Entity<Buffer>,
22349 server_id: LanguageServerId,
22350 cx: &mut App,
22351 ) -> Option<Task<anyhow::Result<InlayHint>>> {
22352 Some(self.update(cx, |project, cx| {
22353 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
22354 }))
22355 }
22356
22357 fn range_for_rename(
22358 &self,
22359 buffer: &Entity<Buffer>,
22360 position: text::Anchor,
22361 cx: &mut App,
22362 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
22363 Some(self.update(cx, |project, cx| {
22364 let buffer = buffer.clone();
22365 let task = project.prepare_rename(buffer.clone(), position, cx);
22366 cx.spawn(async move |_, cx| {
22367 Ok(match task.await? {
22368 PrepareRenameResponse::Success(range) => Some(range),
22369 PrepareRenameResponse::InvalidPosition => None,
22370 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
22371 // Fallback on using TreeSitter info to determine identifier range
22372 buffer.read_with(cx, |buffer, _| {
22373 let snapshot = buffer.snapshot();
22374 let (range, kind) = snapshot.surrounding_word(position, false);
22375 if kind != Some(CharKind::Word) {
22376 return None;
22377 }
22378 Some(
22379 snapshot.anchor_before(range.start)
22380 ..snapshot.anchor_after(range.end),
22381 )
22382 })?
22383 }
22384 })
22385 })
22386 }))
22387 }
22388
22389 fn perform_rename(
22390 &self,
22391 buffer: &Entity<Buffer>,
22392 position: text::Anchor,
22393 new_name: String,
22394 cx: &mut App,
22395 ) -> Option<Task<Result<ProjectTransaction>>> {
22396 Some(self.update(cx, |project, cx| {
22397 project.perform_rename(buffer.clone(), position, new_name, cx)
22398 }))
22399 }
22400}
22401
22402fn inlay_hint_settings(
22403 location: Anchor,
22404 snapshot: &MultiBufferSnapshot,
22405 cx: &mut Context<Editor>,
22406) -> InlayHintSettings {
22407 let file = snapshot.file_at(location);
22408 let language = snapshot.language_at(location).map(|l| l.name());
22409 language_settings(language, file, cx).inlay_hints
22410}
22411
22412fn consume_contiguous_rows(
22413 contiguous_row_selections: &mut Vec<Selection<Point>>,
22414 selection: &Selection<Point>,
22415 display_map: &DisplaySnapshot,
22416 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
22417) -> (MultiBufferRow, MultiBufferRow) {
22418 contiguous_row_selections.push(selection.clone());
22419 let start_row = starting_row(selection, display_map);
22420 let mut end_row = ending_row(selection, display_map);
22421
22422 while let Some(next_selection) = selections.peek() {
22423 if next_selection.start.row <= end_row.0 {
22424 end_row = ending_row(next_selection, display_map);
22425 contiguous_row_selections.push(selections.next().unwrap().clone());
22426 } else {
22427 break;
22428 }
22429 }
22430 (start_row, end_row)
22431}
22432
22433fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22434 if selection.start.column > 0 {
22435 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
22436 } else {
22437 MultiBufferRow(selection.start.row)
22438 }
22439}
22440
22441fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22442 if next_selection.end.column > 0 || next_selection.is_empty() {
22443 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
22444 } else {
22445 MultiBufferRow(next_selection.end.row)
22446 }
22447}
22448
22449impl EditorSnapshot {
22450 pub fn remote_selections_in_range<'a>(
22451 &'a self,
22452 range: &'a Range<Anchor>,
22453 collaboration_hub: &dyn CollaborationHub,
22454 cx: &'a App,
22455 ) -> impl 'a + Iterator<Item = RemoteSelection> {
22456 let participant_names = collaboration_hub.user_names(cx);
22457 let participant_indices = collaboration_hub.user_participant_indices(cx);
22458 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
22459 let collaborators_by_replica_id = collaborators_by_peer_id
22460 .values()
22461 .map(|collaborator| (collaborator.replica_id, collaborator))
22462 .collect::<HashMap<_, _>>();
22463 self.buffer_snapshot
22464 .selections_in_range(range, false)
22465 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
22466 if replica_id == AGENT_REPLICA_ID {
22467 Some(RemoteSelection {
22468 replica_id,
22469 selection,
22470 cursor_shape,
22471 line_mode,
22472 collaborator_id: CollaboratorId::Agent,
22473 user_name: Some("Agent".into()),
22474 color: cx.theme().players().agent(),
22475 })
22476 } else {
22477 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
22478 let participant_index = participant_indices.get(&collaborator.user_id).copied();
22479 let user_name = participant_names.get(&collaborator.user_id).cloned();
22480 Some(RemoteSelection {
22481 replica_id,
22482 selection,
22483 cursor_shape,
22484 line_mode,
22485 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
22486 user_name,
22487 color: if let Some(index) = participant_index {
22488 cx.theme().players().color_for_participant(index.0)
22489 } else {
22490 cx.theme().players().absent()
22491 },
22492 })
22493 }
22494 })
22495 }
22496
22497 pub fn hunks_for_ranges(
22498 &self,
22499 ranges: impl IntoIterator<Item = Range<Point>>,
22500 ) -> Vec<MultiBufferDiffHunk> {
22501 let mut hunks = Vec::new();
22502 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
22503 HashMap::default();
22504 for query_range in ranges {
22505 let query_rows =
22506 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
22507 for hunk in self.buffer_snapshot.diff_hunks_in_range(
22508 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
22509 ) {
22510 // Include deleted hunks that are adjacent to the query range, because
22511 // otherwise they would be missed.
22512 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
22513 if hunk.status().is_deleted() {
22514 intersects_range |= hunk.row_range.start == query_rows.end;
22515 intersects_range |= hunk.row_range.end == query_rows.start;
22516 }
22517 if intersects_range {
22518 if !processed_buffer_rows
22519 .entry(hunk.buffer_id)
22520 .or_default()
22521 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
22522 {
22523 continue;
22524 }
22525 hunks.push(hunk);
22526 }
22527 }
22528 }
22529
22530 hunks
22531 }
22532
22533 fn display_diff_hunks_for_rows<'a>(
22534 &'a self,
22535 display_rows: Range<DisplayRow>,
22536 folded_buffers: &'a HashSet<BufferId>,
22537 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
22538 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
22539 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
22540
22541 self.buffer_snapshot
22542 .diff_hunks_in_range(buffer_start..buffer_end)
22543 .filter_map(|hunk| {
22544 if folded_buffers.contains(&hunk.buffer_id) {
22545 return None;
22546 }
22547
22548 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
22549 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
22550
22551 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
22552 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
22553
22554 let display_hunk = if hunk_display_start.column() != 0 {
22555 DisplayDiffHunk::Folded {
22556 display_row: hunk_display_start.row(),
22557 }
22558 } else {
22559 let mut end_row = hunk_display_end.row();
22560 if hunk_display_end.column() > 0 {
22561 end_row.0 += 1;
22562 }
22563 let is_created_file = hunk.is_created_file();
22564 DisplayDiffHunk::Unfolded {
22565 status: hunk.status(),
22566 diff_base_byte_range: hunk.diff_base_byte_range,
22567 display_row_range: hunk_display_start.row()..end_row,
22568 multi_buffer_range: Anchor::range_in_buffer(
22569 hunk.excerpt_id,
22570 hunk.buffer_id,
22571 hunk.buffer_range,
22572 ),
22573 is_created_file,
22574 }
22575 };
22576
22577 Some(display_hunk)
22578 })
22579 }
22580
22581 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
22582 self.display_snapshot.buffer_snapshot.language_at(position)
22583 }
22584
22585 pub fn is_focused(&self) -> bool {
22586 self.is_focused
22587 }
22588
22589 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
22590 self.placeholder_text.as_ref()
22591 }
22592
22593 pub fn scroll_position(&self) -> gpui::Point<f32> {
22594 self.scroll_anchor.scroll_position(&self.display_snapshot)
22595 }
22596
22597 fn gutter_dimensions(
22598 &self,
22599 font_id: FontId,
22600 font_size: Pixels,
22601 max_line_number_width: Pixels,
22602 cx: &App,
22603 ) -> Option<GutterDimensions> {
22604 if !self.show_gutter {
22605 return None;
22606 }
22607
22608 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
22609 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
22610
22611 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
22612 matches!(
22613 ProjectSettings::get_global(cx).git.git_gutter,
22614 Some(GitGutterSetting::TrackedFiles)
22615 )
22616 });
22617 let gutter_settings = EditorSettings::get_global(cx).gutter;
22618 let show_line_numbers = self
22619 .show_line_numbers
22620 .unwrap_or(gutter_settings.line_numbers);
22621 let line_gutter_width = if show_line_numbers {
22622 // Avoid flicker-like gutter resizes when the line number gains another digit by
22623 // only resizing the gutter on files with > 10**min_line_number_digits lines.
22624 let min_width_for_number_on_gutter =
22625 ch_advance * gutter_settings.min_line_number_digits as f32;
22626 max_line_number_width.max(min_width_for_number_on_gutter)
22627 } else {
22628 0.0.into()
22629 };
22630
22631 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
22632 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
22633
22634 let git_blame_entries_width =
22635 self.git_blame_gutter_max_author_length
22636 .map(|max_author_length| {
22637 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
22638 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
22639
22640 /// The number of characters to dedicate to gaps and margins.
22641 const SPACING_WIDTH: usize = 4;
22642
22643 let max_char_count = max_author_length.min(renderer.max_author_length())
22644 + ::git::SHORT_SHA_LENGTH
22645 + MAX_RELATIVE_TIMESTAMP.len()
22646 + SPACING_WIDTH;
22647
22648 ch_advance * max_char_count
22649 });
22650
22651 let is_singleton = self.buffer_snapshot.is_singleton();
22652
22653 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
22654 left_padding += if !is_singleton {
22655 ch_width * 4.0
22656 } else if show_runnables || show_breakpoints {
22657 ch_width * 3.0
22658 } else if show_git_gutter && show_line_numbers {
22659 ch_width * 2.0
22660 } else if show_git_gutter || show_line_numbers {
22661 ch_width
22662 } else {
22663 px(0.)
22664 };
22665
22666 let shows_folds = is_singleton && gutter_settings.folds;
22667
22668 let right_padding = if shows_folds && show_line_numbers {
22669 ch_width * 4.0
22670 } else if shows_folds || (!is_singleton && show_line_numbers) {
22671 ch_width * 3.0
22672 } else if show_line_numbers {
22673 ch_width
22674 } else {
22675 px(0.)
22676 };
22677
22678 Some(GutterDimensions {
22679 left_padding,
22680 right_padding,
22681 width: line_gutter_width + left_padding + right_padding,
22682 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
22683 git_blame_entries_width,
22684 })
22685 }
22686
22687 pub fn render_crease_toggle(
22688 &self,
22689 buffer_row: MultiBufferRow,
22690 row_contains_cursor: bool,
22691 editor: Entity<Editor>,
22692 window: &mut Window,
22693 cx: &mut App,
22694 ) -> Option<AnyElement> {
22695 let folded = self.is_line_folded(buffer_row);
22696 let mut is_foldable = false;
22697
22698 if let Some(crease) = self
22699 .crease_snapshot
22700 .query_row(buffer_row, &self.buffer_snapshot)
22701 {
22702 is_foldable = true;
22703 match crease {
22704 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
22705 if let Some(render_toggle) = render_toggle {
22706 let toggle_callback =
22707 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
22708 if folded {
22709 editor.update(cx, |editor, cx| {
22710 editor.fold_at(buffer_row, window, cx)
22711 });
22712 } else {
22713 editor.update(cx, |editor, cx| {
22714 editor.unfold_at(buffer_row, window, cx)
22715 });
22716 }
22717 });
22718 return Some((render_toggle)(
22719 buffer_row,
22720 folded,
22721 toggle_callback,
22722 window,
22723 cx,
22724 ));
22725 }
22726 }
22727 }
22728 }
22729
22730 is_foldable |= self.starts_indent(buffer_row);
22731
22732 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
22733 Some(
22734 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
22735 .toggle_state(folded)
22736 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
22737 if folded {
22738 this.unfold_at(buffer_row, window, cx);
22739 } else {
22740 this.fold_at(buffer_row, window, cx);
22741 }
22742 }))
22743 .into_any_element(),
22744 )
22745 } else {
22746 None
22747 }
22748 }
22749
22750 pub fn render_crease_trailer(
22751 &self,
22752 buffer_row: MultiBufferRow,
22753 window: &mut Window,
22754 cx: &mut App,
22755 ) -> Option<AnyElement> {
22756 let folded = self.is_line_folded(buffer_row);
22757 if let Crease::Inline { render_trailer, .. } = self
22758 .crease_snapshot
22759 .query_row(buffer_row, &self.buffer_snapshot)?
22760 {
22761 let render_trailer = render_trailer.as_ref()?;
22762 Some(render_trailer(buffer_row, folded, window, cx))
22763 } else {
22764 None
22765 }
22766 }
22767}
22768
22769impl Deref for EditorSnapshot {
22770 type Target = DisplaySnapshot;
22771
22772 fn deref(&self) -> &Self::Target {
22773 &self.display_snapshot
22774 }
22775}
22776
22777#[derive(Clone, Debug, PartialEq, Eq)]
22778pub enum EditorEvent {
22779 InputIgnored {
22780 text: Arc<str>,
22781 },
22782 InputHandled {
22783 utf16_range_to_replace: Option<Range<isize>>,
22784 text: Arc<str>,
22785 },
22786 ExcerptsAdded {
22787 buffer: Entity<Buffer>,
22788 predecessor: ExcerptId,
22789 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
22790 },
22791 ExcerptsRemoved {
22792 ids: Vec<ExcerptId>,
22793 removed_buffer_ids: Vec<BufferId>,
22794 },
22795 BufferFoldToggled {
22796 ids: Vec<ExcerptId>,
22797 folded: bool,
22798 },
22799 ExcerptsEdited {
22800 ids: Vec<ExcerptId>,
22801 },
22802 ExcerptsExpanded {
22803 ids: Vec<ExcerptId>,
22804 },
22805 BufferEdited,
22806 Edited {
22807 transaction_id: clock::Lamport,
22808 },
22809 Reparsed(BufferId),
22810 Focused,
22811 FocusedIn,
22812 Blurred,
22813 DirtyChanged,
22814 Saved,
22815 TitleChanged,
22816 DiffBaseChanged,
22817 SelectionsChanged {
22818 local: bool,
22819 },
22820 ScrollPositionChanged {
22821 local: bool,
22822 autoscroll: bool,
22823 },
22824 Closed,
22825 TransactionUndone {
22826 transaction_id: clock::Lamport,
22827 },
22828 TransactionBegun {
22829 transaction_id: clock::Lamport,
22830 },
22831 Reloaded,
22832 CursorShapeChanged,
22833 PushedToNavHistory {
22834 anchor: Anchor,
22835 is_deactivate: bool,
22836 },
22837}
22838
22839impl EventEmitter<EditorEvent> for Editor {}
22840
22841impl Focusable for Editor {
22842 fn focus_handle(&self, _cx: &App) -> FocusHandle {
22843 self.focus_handle.clone()
22844 }
22845}
22846
22847impl Render for Editor {
22848 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22849 let settings = ThemeSettings::get_global(cx);
22850
22851 let mut text_style = match self.mode {
22852 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
22853 color: cx.theme().colors().editor_foreground,
22854 font_family: settings.ui_font.family.clone(),
22855 font_features: settings.ui_font.features.clone(),
22856 font_fallbacks: settings.ui_font.fallbacks.clone(),
22857 font_size: rems(0.875).into(),
22858 font_weight: settings.ui_font.weight,
22859 line_height: relative(settings.buffer_line_height.value()),
22860 ..Default::default()
22861 },
22862 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
22863 color: cx.theme().colors().editor_foreground,
22864 font_family: settings.buffer_font.family.clone(),
22865 font_features: settings.buffer_font.features.clone(),
22866 font_fallbacks: settings.buffer_font.fallbacks.clone(),
22867 font_size: settings.buffer_font_size(cx).into(),
22868 font_weight: settings.buffer_font.weight,
22869 line_height: relative(settings.buffer_line_height.value()),
22870 ..Default::default()
22871 },
22872 };
22873 if let Some(text_style_refinement) = &self.text_style_refinement {
22874 text_style.refine(text_style_refinement)
22875 }
22876
22877 let background = match self.mode {
22878 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
22879 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
22880 EditorMode::Full { .. } => cx.theme().colors().editor_background,
22881 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
22882 };
22883
22884 EditorElement::new(
22885 &cx.entity(),
22886 EditorStyle {
22887 background,
22888 border: cx.theme().colors().border,
22889 local_player: cx.theme().players().local(),
22890 text: text_style,
22891 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
22892 syntax: cx.theme().syntax().clone(),
22893 status: cx.theme().status().clone(),
22894 inlay_hints_style: make_inlay_hints_style(cx),
22895 edit_prediction_styles: make_suggestion_styles(cx),
22896 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
22897 show_underlines: self.diagnostics_enabled(),
22898 },
22899 )
22900 }
22901}
22902
22903impl EntityInputHandler for Editor {
22904 fn text_for_range(
22905 &mut self,
22906 range_utf16: Range<usize>,
22907 adjusted_range: &mut Option<Range<usize>>,
22908 _: &mut Window,
22909 cx: &mut Context<Self>,
22910 ) -> Option<String> {
22911 let snapshot = self.buffer.read(cx).read(cx);
22912 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
22913 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
22914 if (start.0..end.0) != range_utf16 {
22915 adjusted_range.replace(start.0..end.0);
22916 }
22917 Some(snapshot.text_for_range(start..end).collect())
22918 }
22919
22920 fn selected_text_range(
22921 &mut self,
22922 ignore_disabled_input: bool,
22923 _: &mut Window,
22924 cx: &mut Context<Self>,
22925 ) -> Option<UTF16Selection> {
22926 // Prevent the IME menu from appearing when holding down an alphabetic key
22927 // while input is disabled.
22928 if !ignore_disabled_input && !self.input_enabled {
22929 return None;
22930 }
22931
22932 let selection = self.selections.newest::<OffsetUtf16>(cx);
22933 let range = selection.range();
22934
22935 Some(UTF16Selection {
22936 range: range.start.0..range.end.0,
22937 reversed: selection.reversed,
22938 })
22939 }
22940
22941 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
22942 let snapshot = self.buffer.read(cx).read(cx);
22943 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
22944 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
22945 }
22946
22947 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
22948 self.clear_highlights::<InputComposition>(cx);
22949 self.ime_transaction.take();
22950 }
22951
22952 fn replace_text_in_range(
22953 &mut self,
22954 range_utf16: Option<Range<usize>>,
22955 text: &str,
22956 window: &mut Window,
22957 cx: &mut Context<Self>,
22958 ) {
22959 if !self.input_enabled {
22960 cx.emit(EditorEvent::InputIgnored { text: text.into() });
22961 return;
22962 }
22963
22964 self.transact(window, cx, |this, window, cx| {
22965 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
22966 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
22967 Some(this.selection_replacement_ranges(range_utf16, cx))
22968 } else {
22969 this.marked_text_ranges(cx)
22970 };
22971
22972 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
22973 let newest_selection_id = this.selections.newest_anchor().id;
22974 this.selections
22975 .all::<OffsetUtf16>(cx)
22976 .iter()
22977 .zip(ranges_to_replace.iter())
22978 .find_map(|(selection, range)| {
22979 if selection.id == newest_selection_id {
22980 Some(
22981 (range.start.0 as isize - selection.head().0 as isize)
22982 ..(range.end.0 as isize - selection.head().0 as isize),
22983 )
22984 } else {
22985 None
22986 }
22987 })
22988 });
22989
22990 cx.emit(EditorEvent::InputHandled {
22991 utf16_range_to_replace: range_to_replace,
22992 text: text.into(),
22993 });
22994
22995 if let Some(new_selected_ranges) = new_selected_ranges {
22996 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22997 selections.select_ranges(new_selected_ranges)
22998 });
22999 this.backspace(&Default::default(), window, cx);
23000 }
23001
23002 this.handle_input(text, window, cx);
23003 });
23004
23005 if let Some(transaction) = self.ime_transaction {
23006 self.buffer.update(cx, |buffer, cx| {
23007 buffer.group_until_transaction(transaction, cx);
23008 });
23009 }
23010
23011 self.unmark_text(window, cx);
23012 }
23013
23014 fn replace_and_mark_text_in_range(
23015 &mut self,
23016 range_utf16: Option<Range<usize>>,
23017 text: &str,
23018 new_selected_range_utf16: Option<Range<usize>>,
23019 window: &mut Window,
23020 cx: &mut Context<Self>,
23021 ) {
23022 if !self.input_enabled {
23023 return;
23024 }
23025
23026 let transaction = self.transact(window, cx, |this, window, cx| {
23027 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
23028 let snapshot = this.buffer.read(cx).read(cx);
23029 if let Some(relative_range_utf16) = range_utf16.as_ref() {
23030 for marked_range in &mut marked_ranges {
23031 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
23032 marked_range.start.0 += relative_range_utf16.start;
23033 marked_range.start =
23034 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
23035 marked_range.end =
23036 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
23037 }
23038 }
23039 Some(marked_ranges)
23040 } else if let Some(range_utf16) = range_utf16 {
23041 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23042 Some(this.selection_replacement_ranges(range_utf16, cx))
23043 } else {
23044 None
23045 };
23046
23047 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
23048 let newest_selection_id = this.selections.newest_anchor().id;
23049 this.selections
23050 .all::<OffsetUtf16>(cx)
23051 .iter()
23052 .zip(ranges_to_replace.iter())
23053 .find_map(|(selection, range)| {
23054 if selection.id == newest_selection_id {
23055 Some(
23056 (range.start.0 as isize - selection.head().0 as isize)
23057 ..(range.end.0 as isize - selection.head().0 as isize),
23058 )
23059 } else {
23060 None
23061 }
23062 })
23063 });
23064
23065 cx.emit(EditorEvent::InputHandled {
23066 utf16_range_to_replace: range_to_replace,
23067 text: text.into(),
23068 });
23069
23070 if let Some(ranges) = ranges_to_replace {
23071 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23072 s.select_ranges(ranges)
23073 });
23074 }
23075
23076 let marked_ranges = {
23077 let snapshot = this.buffer.read(cx).read(cx);
23078 this.selections
23079 .disjoint_anchors()
23080 .iter()
23081 .map(|selection| {
23082 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
23083 })
23084 .collect::<Vec<_>>()
23085 };
23086
23087 if text.is_empty() {
23088 this.unmark_text(window, cx);
23089 } else {
23090 this.highlight_text::<InputComposition>(
23091 marked_ranges.clone(),
23092 HighlightStyle {
23093 underline: Some(UnderlineStyle {
23094 thickness: px(1.),
23095 color: None,
23096 wavy: false,
23097 }),
23098 ..Default::default()
23099 },
23100 cx,
23101 );
23102 }
23103
23104 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
23105 let use_autoclose = this.use_autoclose;
23106 let use_auto_surround = this.use_auto_surround;
23107 this.set_use_autoclose(false);
23108 this.set_use_auto_surround(false);
23109 this.handle_input(text, window, cx);
23110 this.set_use_autoclose(use_autoclose);
23111 this.set_use_auto_surround(use_auto_surround);
23112
23113 if let Some(new_selected_range) = new_selected_range_utf16 {
23114 let snapshot = this.buffer.read(cx).read(cx);
23115 let new_selected_ranges = marked_ranges
23116 .into_iter()
23117 .map(|marked_range| {
23118 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
23119 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
23120 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
23121 snapshot.clip_offset_utf16(new_start, Bias::Left)
23122 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
23123 })
23124 .collect::<Vec<_>>();
23125
23126 drop(snapshot);
23127 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23128 selections.select_ranges(new_selected_ranges)
23129 });
23130 }
23131 });
23132
23133 self.ime_transaction = self.ime_transaction.or(transaction);
23134 if let Some(transaction) = self.ime_transaction {
23135 self.buffer.update(cx, |buffer, cx| {
23136 buffer.group_until_transaction(transaction, cx);
23137 });
23138 }
23139
23140 if self.text_highlights::<InputComposition>(cx).is_none() {
23141 self.ime_transaction.take();
23142 }
23143 }
23144
23145 fn bounds_for_range(
23146 &mut self,
23147 range_utf16: Range<usize>,
23148 element_bounds: gpui::Bounds<Pixels>,
23149 window: &mut Window,
23150 cx: &mut Context<Self>,
23151 ) -> Option<gpui::Bounds<Pixels>> {
23152 let text_layout_details = self.text_layout_details(window);
23153 let CharacterDimensions {
23154 em_width,
23155 em_advance,
23156 line_height,
23157 } = self.character_dimensions(window);
23158
23159 let snapshot = self.snapshot(window, cx);
23160 let scroll_position = snapshot.scroll_position();
23161 let scroll_left = scroll_position.x * em_advance;
23162
23163 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
23164 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
23165 + self.gutter_dimensions.full_width();
23166 let y = line_height * (start.row().as_f32() - scroll_position.y);
23167
23168 Some(Bounds {
23169 origin: element_bounds.origin + point(x, y),
23170 size: size(em_width, line_height),
23171 })
23172 }
23173
23174 fn character_index_for_point(
23175 &mut self,
23176 point: gpui::Point<Pixels>,
23177 _window: &mut Window,
23178 _cx: &mut Context<Self>,
23179 ) -> Option<usize> {
23180 let position_map = self.last_position_map.as_ref()?;
23181 if !position_map.text_hitbox.contains(&point) {
23182 return None;
23183 }
23184 let display_point = position_map.point_for_position(point).previous_valid;
23185 let anchor = position_map
23186 .snapshot
23187 .display_point_to_anchor(display_point, Bias::Left);
23188 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
23189 Some(utf16_offset.0)
23190 }
23191}
23192
23193trait SelectionExt {
23194 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
23195 fn spanned_rows(
23196 &self,
23197 include_end_if_at_line_start: bool,
23198 map: &DisplaySnapshot,
23199 ) -> Range<MultiBufferRow>;
23200}
23201
23202impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
23203 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
23204 let start = self
23205 .start
23206 .to_point(&map.buffer_snapshot)
23207 .to_display_point(map);
23208 let end = self
23209 .end
23210 .to_point(&map.buffer_snapshot)
23211 .to_display_point(map);
23212 if self.reversed {
23213 end..start
23214 } else {
23215 start..end
23216 }
23217 }
23218
23219 fn spanned_rows(
23220 &self,
23221 include_end_if_at_line_start: bool,
23222 map: &DisplaySnapshot,
23223 ) -> Range<MultiBufferRow> {
23224 let start = self.start.to_point(&map.buffer_snapshot);
23225 let mut end = self.end.to_point(&map.buffer_snapshot);
23226 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
23227 end.row -= 1;
23228 }
23229
23230 let buffer_start = map.prev_line_boundary(start).0;
23231 let buffer_end = map.next_line_boundary(end).0;
23232 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
23233 }
23234}
23235
23236impl<T: InvalidationRegion> InvalidationStack<T> {
23237 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
23238 where
23239 S: Clone + ToOffset,
23240 {
23241 while let Some(region) = self.last() {
23242 let all_selections_inside_invalidation_ranges =
23243 if selections.len() == region.ranges().len() {
23244 selections
23245 .iter()
23246 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
23247 .all(|(selection, invalidation_range)| {
23248 let head = selection.head().to_offset(buffer);
23249 invalidation_range.start <= head && invalidation_range.end >= head
23250 })
23251 } else {
23252 false
23253 };
23254
23255 if all_selections_inside_invalidation_ranges {
23256 break;
23257 } else {
23258 self.pop();
23259 }
23260 }
23261 }
23262}
23263
23264impl<T> Default for InvalidationStack<T> {
23265 fn default() -> Self {
23266 Self(Default::default())
23267 }
23268}
23269
23270impl<T> Deref for InvalidationStack<T> {
23271 type Target = Vec<T>;
23272
23273 fn deref(&self) -> &Self::Target {
23274 &self.0
23275 }
23276}
23277
23278impl<T> DerefMut for InvalidationStack<T> {
23279 fn deref_mut(&mut self) -> &mut Self::Target {
23280 &mut self.0
23281 }
23282}
23283
23284impl InvalidationRegion for SnippetState {
23285 fn ranges(&self) -> &[Range<Anchor>] {
23286 &self.ranges[self.active_index]
23287 }
23288}
23289
23290fn edit_prediction_edit_text(
23291 current_snapshot: &BufferSnapshot,
23292 edits: &[(Range<Anchor>, String)],
23293 edit_preview: &EditPreview,
23294 include_deletions: bool,
23295 cx: &App,
23296) -> HighlightedText {
23297 let edits = edits
23298 .iter()
23299 .map(|(anchor, text)| {
23300 (
23301 anchor.start.text_anchor..anchor.end.text_anchor,
23302 text.clone(),
23303 )
23304 })
23305 .collect::<Vec<_>>();
23306
23307 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
23308}
23309
23310fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, String)], cx: &App) -> HighlightedText {
23311 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
23312 // Just show the raw edit text with basic styling
23313 let mut text = String::new();
23314 let mut highlights = Vec::new();
23315
23316 let insertion_highlight_style = HighlightStyle {
23317 color: Some(cx.theme().colors().text),
23318 ..Default::default()
23319 };
23320
23321 for (_, edit_text) in edits {
23322 let start_offset = text.len();
23323 text.push_str(edit_text);
23324 let end_offset = text.len();
23325
23326 if start_offset < end_offset {
23327 highlights.push((start_offset..end_offset, insertion_highlight_style));
23328 }
23329 }
23330
23331 HighlightedText {
23332 text: text.into(),
23333 highlights,
23334 }
23335}
23336
23337pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
23338 match severity {
23339 lsp::DiagnosticSeverity::ERROR => colors.error,
23340 lsp::DiagnosticSeverity::WARNING => colors.warning,
23341 lsp::DiagnosticSeverity::INFORMATION => colors.info,
23342 lsp::DiagnosticSeverity::HINT => colors.info,
23343 _ => colors.ignored,
23344 }
23345}
23346
23347pub fn styled_runs_for_code_label<'a>(
23348 label: &'a CodeLabel,
23349 syntax_theme: &'a theme::SyntaxTheme,
23350) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
23351 let fade_out = HighlightStyle {
23352 fade_out: Some(0.35),
23353 ..Default::default()
23354 };
23355
23356 let mut prev_end = label.filter_range.end;
23357 label
23358 .runs
23359 .iter()
23360 .enumerate()
23361 .flat_map(move |(ix, (range, highlight_id))| {
23362 let style = if let Some(style) = highlight_id.style(syntax_theme) {
23363 style
23364 } else {
23365 return Default::default();
23366 };
23367 let mut muted_style = style;
23368 muted_style.highlight(fade_out);
23369
23370 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
23371 if range.start >= label.filter_range.end {
23372 if range.start > prev_end {
23373 runs.push((prev_end..range.start, fade_out));
23374 }
23375 runs.push((range.clone(), muted_style));
23376 } else if range.end <= label.filter_range.end {
23377 runs.push((range.clone(), style));
23378 } else {
23379 runs.push((range.start..label.filter_range.end, style));
23380 runs.push((label.filter_range.end..range.end, muted_style));
23381 }
23382 prev_end = cmp::max(prev_end, range.end);
23383
23384 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
23385 runs.push((prev_end..label.text.len(), fade_out));
23386 }
23387
23388 runs
23389 })
23390}
23391
23392pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
23393 let mut prev_index = 0;
23394 let mut prev_codepoint: Option<char> = None;
23395 text.char_indices()
23396 .chain([(text.len(), '\0')])
23397 .filter_map(move |(index, codepoint)| {
23398 let prev_codepoint = prev_codepoint.replace(codepoint)?;
23399 let is_boundary = index == text.len()
23400 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
23401 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
23402 if is_boundary {
23403 let chunk = &text[prev_index..index];
23404 prev_index = index;
23405 Some(chunk)
23406 } else {
23407 None
23408 }
23409 })
23410}
23411
23412pub trait RangeToAnchorExt: Sized {
23413 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
23414
23415 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
23416 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
23417 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
23418 }
23419}
23420
23421impl<T: ToOffset> RangeToAnchorExt for Range<T> {
23422 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
23423 let start_offset = self.start.to_offset(snapshot);
23424 let end_offset = self.end.to_offset(snapshot);
23425 if start_offset == end_offset {
23426 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
23427 } else {
23428 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
23429 }
23430 }
23431}
23432
23433pub trait RowExt {
23434 fn as_f32(&self) -> f32;
23435
23436 fn next_row(&self) -> Self;
23437
23438 fn previous_row(&self) -> Self;
23439
23440 fn minus(&self, other: Self) -> u32;
23441}
23442
23443impl RowExt for DisplayRow {
23444 fn as_f32(&self) -> f32 {
23445 self.0 as f32
23446 }
23447
23448 fn next_row(&self) -> Self {
23449 Self(self.0 + 1)
23450 }
23451
23452 fn previous_row(&self) -> Self {
23453 Self(self.0.saturating_sub(1))
23454 }
23455
23456 fn minus(&self, other: Self) -> u32 {
23457 self.0 - other.0
23458 }
23459}
23460
23461impl RowExt for MultiBufferRow {
23462 fn as_f32(&self) -> f32 {
23463 self.0 as f32
23464 }
23465
23466 fn next_row(&self) -> Self {
23467 Self(self.0 + 1)
23468 }
23469
23470 fn previous_row(&self) -> Self {
23471 Self(self.0.saturating_sub(1))
23472 }
23473
23474 fn minus(&self, other: Self) -> u32 {
23475 self.0 - other.0
23476 }
23477}
23478
23479trait RowRangeExt {
23480 type Row;
23481
23482 fn len(&self) -> usize;
23483
23484 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
23485}
23486
23487impl RowRangeExt for Range<MultiBufferRow> {
23488 type Row = MultiBufferRow;
23489
23490 fn len(&self) -> usize {
23491 (self.end.0 - self.start.0) as usize
23492 }
23493
23494 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
23495 (self.start.0..self.end.0).map(MultiBufferRow)
23496 }
23497}
23498
23499impl RowRangeExt for Range<DisplayRow> {
23500 type Row = DisplayRow;
23501
23502 fn len(&self) -> usize {
23503 (self.end.0 - self.start.0) as usize
23504 }
23505
23506 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
23507 (self.start.0..self.end.0).map(DisplayRow)
23508 }
23509}
23510
23511/// If select range has more than one line, we
23512/// just point the cursor to range.start.
23513fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
23514 if range.start.row == range.end.row {
23515 range
23516 } else {
23517 range.start..range.start
23518 }
23519}
23520pub struct KillRing(ClipboardItem);
23521impl Global for KillRing {}
23522
23523const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
23524
23525enum BreakpointPromptEditAction {
23526 Log,
23527 Condition,
23528 HitCondition,
23529}
23530
23531struct BreakpointPromptEditor {
23532 pub(crate) prompt: Entity<Editor>,
23533 editor: WeakEntity<Editor>,
23534 breakpoint_anchor: Anchor,
23535 breakpoint: Breakpoint,
23536 edit_action: BreakpointPromptEditAction,
23537 block_ids: HashSet<CustomBlockId>,
23538 editor_margins: Arc<Mutex<EditorMargins>>,
23539 _subscriptions: Vec<Subscription>,
23540}
23541
23542impl BreakpointPromptEditor {
23543 const MAX_LINES: u8 = 4;
23544
23545 fn new(
23546 editor: WeakEntity<Editor>,
23547 breakpoint_anchor: Anchor,
23548 breakpoint: Breakpoint,
23549 edit_action: BreakpointPromptEditAction,
23550 window: &mut Window,
23551 cx: &mut Context<Self>,
23552 ) -> Self {
23553 let base_text = match edit_action {
23554 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
23555 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
23556 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
23557 }
23558 .map(|msg| msg.to_string())
23559 .unwrap_or_default();
23560
23561 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
23562 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
23563
23564 let prompt = cx.new(|cx| {
23565 let mut prompt = Editor::new(
23566 EditorMode::AutoHeight {
23567 min_lines: 1,
23568 max_lines: Some(Self::MAX_LINES as usize),
23569 },
23570 buffer,
23571 None,
23572 window,
23573 cx,
23574 );
23575 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
23576 prompt.set_show_cursor_when_unfocused(false, cx);
23577 prompt.set_placeholder_text(
23578 match edit_action {
23579 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
23580 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
23581 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
23582 },
23583 cx,
23584 );
23585
23586 prompt
23587 });
23588
23589 Self {
23590 prompt,
23591 editor,
23592 breakpoint_anchor,
23593 breakpoint,
23594 edit_action,
23595 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
23596 block_ids: Default::default(),
23597 _subscriptions: vec![],
23598 }
23599 }
23600
23601 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
23602 self.block_ids.extend(block_ids)
23603 }
23604
23605 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
23606 if let Some(editor) = self.editor.upgrade() {
23607 let message = self
23608 .prompt
23609 .read(cx)
23610 .buffer
23611 .read(cx)
23612 .as_singleton()
23613 .expect("A multi buffer in breakpoint prompt isn't possible")
23614 .read(cx)
23615 .as_rope()
23616 .to_string();
23617
23618 editor.update(cx, |editor, cx| {
23619 editor.edit_breakpoint_at_anchor(
23620 self.breakpoint_anchor,
23621 self.breakpoint.clone(),
23622 match self.edit_action {
23623 BreakpointPromptEditAction::Log => {
23624 BreakpointEditAction::EditLogMessage(message.into())
23625 }
23626 BreakpointPromptEditAction::Condition => {
23627 BreakpointEditAction::EditCondition(message.into())
23628 }
23629 BreakpointPromptEditAction::HitCondition => {
23630 BreakpointEditAction::EditHitCondition(message.into())
23631 }
23632 },
23633 cx,
23634 );
23635
23636 editor.remove_blocks(self.block_ids.clone(), None, cx);
23637 cx.focus_self(window);
23638 });
23639 }
23640 }
23641
23642 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
23643 self.editor
23644 .update(cx, |editor, cx| {
23645 editor.remove_blocks(self.block_ids.clone(), None, cx);
23646 window.focus(&editor.focus_handle);
23647 })
23648 .log_err();
23649 }
23650
23651 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
23652 let settings = ThemeSettings::get_global(cx);
23653 let text_style = TextStyle {
23654 color: if self.prompt.read(cx).read_only(cx) {
23655 cx.theme().colors().text_disabled
23656 } else {
23657 cx.theme().colors().text
23658 },
23659 font_family: settings.buffer_font.family.clone(),
23660 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23661 font_size: settings.buffer_font_size(cx).into(),
23662 font_weight: settings.buffer_font.weight,
23663 line_height: relative(settings.buffer_line_height.value()),
23664 ..Default::default()
23665 };
23666 EditorElement::new(
23667 &self.prompt,
23668 EditorStyle {
23669 background: cx.theme().colors().editor_background,
23670 local_player: cx.theme().players().local(),
23671 text: text_style,
23672 ..Default::default()
23673 },
23674 )
23675 }
23676}
23677
23678impl Render for BreakpointPromptEditor {
23679 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23680 let editor_margins = *self.editor_margins.lock();
23681 let gutter_dimensions = editor_margins.gutter;
23682 h_flex()
23683 .key_context("Editor")
23684 .bg(cx.theme().colors().editor_background)
23685 .border_y_1()
23686 .border_color(cx.theme().status().info_border)
23687 .size_full()
23688 .py(window.line_height() / 2.5)
23689 .on_action(cx.listener(Self::confirm))
23690 .on_action(cx.listener(Self::cancel))
23691 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
23692 .child(div().flex_1().child(self.render_prompt_editor(cx)))
23693 }
23694}
23695
23696impl Focusable for BreakpointPromptEditor {
23697 fn focus_handle(&self, cx: &App) -> FocusHandle {
23698 self.prompt.focus_handle(cx)
23699 }
23700}
23701
23702fn all_edits_insertions_or_deletions(
23703 edits: &Vec<(Range<Anchor>, String)>,
23704 snapshot: &MultiBufferSnapshot,
23705) -> bool {
23706 let mut all_insertions = true;
23707 let mut all_deletions = true;
23708
23709 for (range, new_text) in edits.iter() {
23710 let range_is_empty = range.to_offset(&snapshot).is_empty();
23711 let text_is_empty = new_text.is_empty();
23712
23713 if range_is_empty != text_is_empty {
23714 if range_is_empty {
23715 all_deletions = false;
23716 } else {
23717 all_insertions = false;
23718 }
23719 } else {
23720 return false;
23721 }
23722
23723 if !all_insertions && !all_deletions {
23724 return false;
23725 }
23726 }
23727 all_insertions || all_deletions
23728}
23729
23730struct MissingEditPredictionKeybindingTooltip;
23731
23732impl Render for MissingEditPredictionKeybindingTooltip {
23733 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23734 ui::tooltip_container(window, cx, |container, _, cx| {
23735 container
23736 .flex_shrink_0()
23737 .max_w_80()
23738 .min_h(rems_from_px(124.))
23739 .justify_between()
23740 .child(
23741 v_flex()
23742 .flex_1()
23743 .text_ui_sm(cx)
23744 .child(Label::new("Conflict with Accept Keybinding"))
23745 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
23746 )
23747 .child(
23748 h_flex()
23749 .pb_1()
23750 .gap_1()
23751 .items_end()
23752 .w_full()
23753 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
23754 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
23755 }))
23756 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
23757 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
23758 })),
23759 )
23760 })
23761 }
23762}
23763
23764#[derive(Debug, Clone, Copy, PartialEq)]
23765pub struct LineHighlight {
23766 pub background: Background,
23767 pub border: Option<gpui::Hsla>,
23768 pub include_gutter: bool,
23769 pub type_id: Option<TypeId>,
23770}
23771
23772struct LineManipulationResult {
23773 pub new_text: String,
23774 pub line_count_before: usize,
23775 pub line_count_after: usize,
23776}
23777
23778fn render_diff_hunk_controls(
23779 row: u32,
23780 status: &DiffHunkStatus,
23781 hunk_range: Range<Anchor>,
23782 is_created_file: bool,
23783 line_height: Pixels,
23784 editor: &Entity<Editor>,
23785 _window: &mut Window,
23786 cx: &mut App,
23787) -> AnyElement {
23788 h_flex()
23789 .h(line_height)
23790 .mr_1()
23791 .gap_1()
23792 .px_0p5()
23793 .pb_1()
23794 .border_x_1()
23795 .border_b_1()
23796 .border_color(cx.theme().colors().border_variant)
23797 .rounded_b_lg()
23798 .bg(cx.theme().colors().editor_background)
23799 .gap_1()
23800 .block_mouse_except_scroll()
23801 .shadow_md()
23802 .child(if status.has_secondary_hunk() {
23803 Button::new(("stage", row as u64), "Stage")
23804 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
23805 .tooltip({
23806 let focus_handle = editor.focus_handle(cx);
23807 move |window, cx| {
23808 Tooltip::for_action_in(
23809 "Stage Hunk",
23810 &::git::ToggleStaged,
23811 &focus_handle,
23812 window,
23813 cx,
23814 )
23815 }
23816 })
23817 .on_click({
23818 let editor = editor.clone();
23819 move |_event, _window, cx| {
23820 editor.update(cx, |editor, cx| {
23821 editor.stage_or_unstage_diff_hunks(
23822 true,
23823 vec![hunk_range.start..hunk_range.start],
23824 cx,
23825 );
23826 });
23827 }
23828 })
23829 } else {
23830 Button::new(("unstage", row as u64), "Unstage")
23831 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
23832 .tooltip({
23833 let focus_handle = editor.focus_handle(cx);
23834 move |window, cx| {
23835 Tooltip::for_action_in(
23836 "Unstage Hunk",
23837 &::git::ToggleStaged,
23838 &focus_handle,
23839 window,
23840 cx,
23841 )
23842 }
23843 })
23844 .on_click({
23845 let editor = editor.clone();
23846 move |_event, _window, cx| {
23847 editor.update(cx, |editor, cx| {
23848 editor.stage_or_unstage_diff_hunks(
23849 false,
23850 vec![hunk_range.start..hunk_range.start],
23851 cx,
23852 );
23853 });
23854 }
23855 })
23856 })
23857 .child(
23858 Button::new(("restore", row as u64), "Restore")
23859 .tooltip({
23860 let focus_handle = editor.focus_handle(cx);
23861 move |window, cx| {
23862 Tooltip::for_action_in(
23863 "Restore Hunk",
23864 &::git::Restore,
23865 &focus_handle,
23866 window,
23867 cx,
23868 )
23869 }
23870 })
23871 .on_click({
23872 let editor = editor.clone();
23873 move |_event, window, cx| {
23874 editor.update(cx, |editor, cx| {
23875 let snapshot = editor.snapshot(window, cx);
23876 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
23877 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
23878 });
23879 }
23880 })
23881 .disabled(is_created_file),
23882 )
23883 .when(
23884 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
23885 |el| {
23886 el.child(
23887 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
23888 .shape(IconButtonShape::Square)
23889 .icon_size(IconSize::Small)
23890 // .disabled(!has_multiple_hunks)
23891 .tooltip({
23892 let focus_handle = editor.focus_handle(cx);
23893 move |window, cx| {
23894 Tooltip::for_action_in(
23895 "Next Hunk",
23896 &GoToHunk,
23897 &focus_handle,
23898 window,
23899 cx,
23900 )
23901 }
23902 })
23903 .on_click({
23904 let editor = editor.clone();
23905 move |_event, window, cx| {
23906 editor.update(cx, |editor, cx| {
23907 let snapshot = editor.snapshot(window, cx);
23908 let position =
23909 hunk_range.end.to_point(&snapshot.buffer_snapshot);
23910 editor.go_to_hunk_before_or_after_position(
23911 &snapshot,
23912 position,
23913 Direction::Next,
23914 window,
23915 cx,
23916 );
23917 editor.expand_selected_diff_hunks(cx);
23918 });
23919 }
23920 }),
23921 )
23922 .child(
23923 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
23924 .shape(IconButtonShape::Square)
23925 .icon_size(IconSize::Small)
23926 // .disabled(!has_multiple_hunks)
23927 .tooltip({
23928 let focus_handle = editor.focus_handle(cx);
23929 move |window, cx| {
23930 Tooltip::for_action_in(
23931 "Previous Hunk",
23932 &GoToPreviousHunk,
23933 &focus_handle,
23934 window,
23935 cx,
23936 )
23937 }
23938 })
23939 .on_click({
23940 let editor = editor.clone();
23941 move |_event, window, cx| {
23942 editor.update(cx, |editor, cx| {
23943 let snapshot = editor.snapshot(window, cx);
23944 let point =
23945 hunk_range.start.to_point(&snapshot.buffer_snapshot);
23946 editor.go_to_hunk_before_or_after_position(
23947 &snapshot,
23948 point,
23949 Direction::Prev,
23950 window,
23951 cx,
23952 );
23953 editor.expand_selected_diff_hunks(cx);
23954 });
23955 }
23956 }),
23957 )
23958 },
23959 )
23960 .into_any_element()
23961}