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::{Either, 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 Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
15670 return Task::ready(Ok(Navigated::No));
15671 };
15672 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
15673 return Task::ready(Ok(Navigated::No));
15674 };
15675
15676 cx.spawn_in(window, async move |editor, cx| {
15677 let definitions = definitions.await?;
15678 let navigated = editor
15679 .update_in(cx, |editor, window, cx| {
15680 editor.navigate_to_hover_links(
15681 Some(kind),
15682 definitions
15683 .into_iter()
15684 .filter(|location| {
15685 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
15686 })
15687 .map(HoverLink::Text)
15688 .collect::<Vec<_>>(),
15689 split,
15690 window,
15691 cx,
15692 )
15693 })?
15694 .await?;
15695 anyhow::Ok(navigated)
15696 })
15697 }
15698
15699 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
15700 let selection = self.selections.newest_anchor();
15701 let head = selection.head();
15702 let tail = selection.tail();
15703
15704 let Some((buffer, start_position)) =
15705 self.buffer.read(cx).text_anchor_for_position(head, cx)
15706 else {
15707 return;
15708 };
15709
15710 let end_position = if head != tail {
15711 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
15712 return;
15713 };
15714 Some(pos)
15715 } else {
15716 None
15717 };
15718
15719 let url_finder = cx.spawn_in(window, async move |editor, cx| {
15720 let url = if let Some(end_pos) = end_position {
15721 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
15722 } else {
15723 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
15724 };
15725
15726 if let Some(url) = url {
15727 editor.update(cx, |_, cx| {
15728 cx.open_url(&url);
15729 })
15730 } else {
15731 Ok(())
15732 }
15733 });
15734
15735 url_finder.detach();
15736 }
15737
15738 pub fn open_selected_filename(
15739 &mut self,
15740 _: &OpenSelectedFilename,
15741 window: &mut Window,
15742 cx: &mut Context<Self>,
15743 ) {
15744 let Some(workspace) = self.workspace() else {
15745 return;
15746 };
15747
15748 let position = self.selections.newest_anchor().head();
15749
15750 let Some((buffer, buffer_position)) =
15751 self.buffer.read(cx).text_anchor_for_position(position, cx)
15752 else {
15753 return;
15754 };
15755
15756 let project = self.project.clone();
15757
15758 cx.spawn_in(window, async move |_, cx| {
15759 let result = find_file(&buffer, project, buffer_position, cx).await;
15760
15761 if let Some((_, path)) = result {
15762 workspace
15763 .update_in(cx, |workspace, window, cx| {
15764 workspace.open_resolved_path(path, window, cx)
15765 })?
15766 .await?;
15767 }
15768 anyhow::Ok(())
15769 })
15770 .detach();
15771 }
15772
15773 pub(crate) fn navigate_to_hover_links(
15774 &mut self,
15775 kind: Option<GotoDefinitionKind>,
15776 definitions: Vec<HoverLink>,
15777 split: bool,
15778 window: &mut Window,
15779 cx: &mut Context<Editor>,
15780 ) -> Task<Result<Navigated>> {
15781 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
15782 let mut first_url_or_file = None;
15783 let definitions: Vec<_> = definitions
15784 .into_iter()
15785 .filter_map(|def| match def {
15786 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
15787 HoverLink::InlayHint(lsp_location, server_id) => {
15788 let computation =
15789 self.compute_target_location(lsp_location, server_id, window, cx);
15790 Some(cx.background_spawn(computation))
15791 }
15792 HoverLink::Url(url) => {
15793 first_url_or_file = Some(Either::Left(url));
15794 None
15795 }
15796 HoverLink::File(path) => {
15797 first_url_or_file = Some(Either::Right(path));
15798 None
15799 }
15800 })
15801 .collect();
15802
15803 let workspace = self.workspace();
15804
15805 cx.spawn_in(window, async move |editor, acx| {
15806 let mut locations: Vec<Location> = future::join_all(definitions)
15807 .await
15808 .into_iter()
15809 .filter_map(|location| location.transpose())
15810 .collect::<Result<_>>()
15811 .context("location tasks")?;
15812
15813 if locations.len() > 1 {
15814 let Some(workspace) = workspace else {
15815 return Ok(Navigated::No);
15816 };
15817
15818 let tab_kind = match kind {
15819 Some(GotoDefinitionKind::Implementation) => "Implementations",
15820 _ => "Definitions",
15821 };
15822 let title = editor
15823 .update_in(acx, |_, _, cx| {
15824 let origin = locations.first().unwrap();
15825 let buffer = origin.buffer.read(cx);
15826 format!(
15827 "{} for {}",
15828 tab_kind,
15829 buffer
15830 .text_for_range(origin.range.clone())
15831 .collect::<String>()
15832 )
15833 })
15834 .context("buffer title")?;
15835
15836 let opened = workspace
15837 .update_in(acx, |workspace, window, cx| {
15838 Self::open_locations_in_multibuffer(
15839 workspace,
15840 locations,
15841 title,
15842 split,
15843 MultibufferSelectionMode::First,
15844 window,
15845 cx,
15846 )
15847 })
15848 .is_ok();
15849
15850 anyhow::Ok(Navigated::from_bool(opened))
15851 } else if locations.is_empty() {
15852 // If there is one definition, just open it directly
15853 match first_url_or_file {
15854 Some(Either::Left(url)) => {
15855 acx.update(|_, cx| cx.open_url(&url))?;
15856 Ok(Navigated::Yes)
15857 }
15858 Some(Either::Right(path)) => {
15859 let Some(workspace) = workspace else {
15860 return Ok(Navigated::No);
15861 };
15862
15863 workspace
15864 .update_in(acx, |workspace, window, cx| {
15865 workspace.open_resolved_path(path, window, cx)
15866 })?
15867 .await?;
15868 Ok(Navigated::Yes)
15869 }
15870 None => Ok(Navigated::No),
15871 }
15872 } else {
15873 let Some(workspace) = workspace else {
15874 return Ok(Navigated::No);
15875 };
15876
15877 let target = locations.pop().unwrap();
15878 editor.update_in(acx, |editor, window, cx| {
15879 let pane = workspace.read(cx).active_pane().clone();
15880
15881 let range = target.range.to_point(target.buffer.read(cx));
15882 let range = editor.range_for_match(&range);
15883 let range = collapse_multiline_range(range);
15884
15885 if !split
15886 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
15887 {
15888 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
15889 } else {
15890 window.defer(cx, move |window, cx| {
15891 let target_editor: Entity<Self> =
15892 workspace.update(cx, |workspace, cx| {
15893 let pane = if split {
15894 workspace.adjacent_pane(window, cx)
15895 } else {
15896 workspace.active_pane().clone()
15897 };
15898
15899 workspace.open_project_item(
15900 pane,
15901 target.buffer.clone(),
15902 true,
15903 true,
15904 window,
15905 cx,
15906 )
15907 });
15908 target_editor.update(cx, |target_editor, cx| {
15909 // When selecting a definition in a different buffer, disable the nav history
15910 // to avoid creating a history entry at the previous cursor location.
15911 pane.update(cx, |pane, _| pane.disable_history());
15912 target_editor.go_to_singleton_buffer_range(range, window, cx);
15913 pane.update(cx, |pane, _| pane.enable_history());
15914 });
15915 });
15916 }
15917 Navigated::Yes
15918 })
15919 }
15920 })
15921 }
15922
15923 fn compute_target_location(
15924 &self,
15925 lsp_location: lsp::Location,
15926 server_id: LanguageServerId,
15927 window: &mut Window,
15928 cx: &mut Context<Self>,
15929 ) -> Task<anyhow::Result<Option<Location>>> {
15930 let Some(project) = self.project.clone() else {
15931 return Task::ready(Ok(None));
15932 };
15933
15934 cx.spawn_in(window, async move |editor, cx| {
15935 let location_task = editor.update(cx, |_, cx| {
15936 project.update(cx, |project, cx| {
15937 let language_server_name = project
15938 .language_server_statuses(cx)
15939 .find(|(id, _)| server_id == *id)
15940 .map(|(_, status)| status.name.clone());
15941 language_server_name.map(|language_server_name| {
15942 project.open_local_buffer_via_lsp(
15943 lsp_location.uri.clone(),
15944 server_id,
15945 language_server_name,
15946 cx,
15947 )
15948 })
15949 })
15950 })?;
15951 let location = match location_task {
15952 Some(task) => Some({
15953 let target_buffer_handle = task.await.context("open local buffer")?;
15954 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
15955 let target_start = target_buffer
15956 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
15957 let target_end = target_buffer
15958 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
15959 target_buffer.anchor_after(target_start)
15960 ..target_buffer.anchor_before(target_end)
15961 })?;
15962 Location {
15963 buffer: target_buffer_handle,
15964 range,
15965 }
15966 }),
15967 None => None,
15968 };
15969 Ok(location)
15970 })
15971 }
15972
15973 pub fn find_all_references(
15974 &mut self,
15975 _: &FindAllReferences,
15976 window: &mut Window,
15977 cx: &mut Context<Self>,
15978 ) -> Option<Task<Result<Navigated>>> {
15979 let selection = self.selections.newest::<usize>(cx);
15980 let multi_buffer = self.buffer.read(cx);
15981 let head = selection.head();
15982
15983 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
15984 let head_anchor = multi_buffer_snapshot.anchor_at(
15985 head,
15986 if head < selection.tail() {
15987 Bias::Right
15988 } else {
15989 Bias::Left
15990 },
15991 );
15992
15993 match self
15994 .find_all_references_task_sources
15995 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15996 {
15997 Ok(_) => {
15998 log::info!(
15999 "Ignoring repeated FindAllReferences invocation with the position of already running task"
16000 );
16001 return None;
16002 }
16003 Err(i) => {
16004 self.find_all_references_task_sources.insert(i, head_anchor);
16005 }
16006 }
16007
16008 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
16009 let workspace = self.workspace()?;
16010 let project = workspace.read(cx).project().clone();
16011 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
16012 Some(cx.spawn_in(window, async move |editor, cx| {
16013 let _cleanup = cx.on_drop(&editor, move |editor, _| {
16014 if let Ok(i) = editor
16015 .find_all_references_task_sources
16016 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16017 {
16018 editor.find_all_references_task_sources.remove(i);
16019 }
16020 });
16021
16022 let locations = references.await?;
16023 if locations.is_empty() {
16024 return anyhow::Ok(Navigated::No);
16025 }
16026
16027 workspace.update_in(cx, |workspace, window, cx| {
16028 let title = locations
16029 .first()
16030 .as_ref()
16031 .map(|location| {
16032 let buffer = location.buffer.read(cx);
16033 format!(
16034 "References to `{}`",
16035 buffer
16036 .text_for_range(location.range.clone())
16037 .collect::<String>()
16038 )
16039 })
16040 .unwrap();
16041 Self::open_locations_in_multibuffer(
16042 workspace,
16043 locations,
16044 title,
16045 false,
16046 MultibufferSelectionMode::First,
16047 window,
16048 cx,
16049 );
16050 Navigated::Yes
16051 })
16052 }))
16053 }
16054
16055 /// Opens a multibuffer with the given project locations in it
16056 pub fn open_locations_in_multibuffer(
16057 workspace: &mut Workspace,
16058 mut locations: Vec<Location>,
16059 title: String,
16060 split: bool,
16061 multibuffer_selection_mode: MultibufferSelectionMode,
16062 window: &mut Window,
16063 cx: &mut Context<Workspace>,
16064 ) {
16065 if locations.is_empty() {
16066 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
16067 return;
16068 }
16069
16070 // If there are multiple definitions, open them in a multibuffer
16071 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
16072 let mut locations = locations.into_iter().peekable();
16073 let mut ranges: Vec<Range<Anchor>> = Vec::new();
16074 let capability = workspace.project().read(cx).capability();
16075
16076 let excerpt_buffer = cx.new(|cx| {
16077 let mut multibuffer = MultiBuffer::new(capability);
16078 while let Some(location) = locations.next() {
16079 let buffer = location.buffer.read(cx);
16080 let mut ranges_for_buffer = Vec::new();
16081 let range = location.range.to_point(buffer);
16082 ranges_for_buffer.push(range.clone());
16083
16084 while let Some(next_location) = locations.peek() {
16085 if next_location.buffer == location.buffer {
16086 ranges_for_buffer.push(next_location.range.to_point(buffer));
16087 locations.next();
16088 } else {
16089 break;
16090 }
16091 }
16092
16093 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
16094 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
16095 PathKey::for_buffer(&location.buffer, cx),
16096 location.buffer.clone(),
16097 ranges_for_buffer,
16098 DEFAULT_MULTIBUFFER_CONTEXT,
16099 cx,
16100 );
16101 ranges.extend(new_ranges)
16102 }
16103
16104 multibuffer.with_title(title)
16105 });
16106
16107 let editor = cx.new(|cx| {
16108 Editor::for_multibuffer(
16109 excerpt_buffer,
16110 Some(workspace.project().clone()),
16111 window,
16112 cx,
16113 )
16114 });
16115 editor.update(cx, |editor, cx| {
16116 match multibuffer_selection_mode {
16117 MultibufferSelectionMode::First => {
16118 if let Some(first_range) = ranges.first() {
16119 editor.change_selections(
16120 SelectionEffects::no_scroll(),
16121 window,
16122 cx,
16123 |selections| {
16124 selections.clear_disjoint();
16125 selections
16126 .select_anchor_ranges(std::iter::once(first_range.clone()));
16127 },
16128 );
16129 }
16130 editor.highlight_background::<Self>(
16131 &ranges,
16132 |theme| theme.colors().editor_highlighted_line_background,
16133 cx,
16134 );
16135 }
16136 MultibufferSelectionMode::All => {
16137 editor.change_selections(
16138 SelectionEffects::no_scroll(),
16139 window,
16140 cx,
16141 |selections| {
16142 selections.clear_disjoint();
16143 selections.select_anchor_ranges(ranges);
16144 },
16145 );
16146 }
16147 }
16148 editor.register_buffers_with_language_servers(cx);
16149 });
16150
16151 let item = Box::new(editor);
16152 let item_id = item.item_id();
16153
16154 if split {
16155 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
16156 } else {
16157 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
16158 let (preview_item_id, preview_item_idx) =
16159 workspace.active_pane().read_with(cx, |pane, _| {
16160 (pane.preview_item_id(), pane.preview_item_idx())
16161 });
16162
16163 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
16164
16165 if let Some(preview_item_id) = preview_item_id {
16166 workspace.active_pane().update(cx, |pane, cx| {
16167 pane.remove_item(preview_item_id, false, false, window, cx);
16168 });
16169 }
16170 } else {
16171 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
16172 }
16173 }
16174 workspace.active_pane().update(cx, |pane, cx| {
16175 pane.set_preview_item_id(Some(item_id), cx);
16176 });
16177 }
16178
16179 pub fn rename(
16180 &mut self,
16181 _: &Rename,
16182 window: &mut Window,
16183 cx: &mut Context<Self>,
16184 ) -> Option<Task<Result<()>>> {
16185 use language::ToOffset as _;
16186
16187 let provider = self.semantics_provider.clone()?;
16188 let selection = self.selections.newest_anchor().clone();
16189 let (cursor_buffer, cursor_buffer_position) = self
16190 .buffer
16191 .read(cx)
16192 .text_anchor_for_position(selection.head(), cx)?;
16193 let (tail_buffer, cursor_buffer_position_end) = self
16194 .buffer
16195 .read(cx)
16196 .text_anchor_for_position(selection.tail(), cx)?;
16197 if tail_buffer != cursor_buffer {
16198 return None;
16199 }
16200
16201 let snapshot = cursor_buffer.read(cx).snapshot();
16202 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
16203 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
16204 let prepare_rename = provider
16205 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
16206 .unwrap_or_else(|| Task::ready(Ok(None)));
16207 drop(snapshot);
16208
16209 Some(cx.spawn_in(window, async move |this, cx| {
16210 let rename_range = if let Some(range) = prepare_rename.await? {
16211 Some(range)
16212 } else {
16213 this.update(cx, |this, cx| {
16214 let buffer = this.buffer.read(cx).snapshot(cx);
16215 let mut buffer_highlights = this
16216 .document_highlights_for_position(selection.head(), &buffer)
16217 .filter(|highlight| {
16218 highlight.start.excerpt_id == selection.head().excerpt_id
16219 && highlight.end.excerpt_id == selection.head().excerpt_id
16220 });
16221 buffer_highlights
16222 .next()
16223 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
16224 })?
16225 };
16226 if let Some(rename_range) = rename_range {
16227 this.update_in(cx, |this, window, cx| {
16228 let snapshot = cursor_buffer.read(cx).snapshot();
16229 let rename_buffer_range = rename_range.to_offset(&snapshot);
16230 let cursor_offset_in_rename_range =
16231 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
16232 let cursor_offset_in_rename_range_end =
16233 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
16234
16235 this.take_rename(false, window, cx);
16236 let buffer = this.buffer.read(cx).read(cx);
16237 let cursor_offset = selection.head().to_offset(&buffer);
16238 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
16239 let rename_end = rename_start + rename_buffer_range.len();
16240 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
16241 let mut old_highlight_id = None;
16242 let old_name: Arc<str> = buffer
16243 .chunks(rename_start..rename_end, true)
16244 .map(|chunk| {
16245 if old_highlight_id.is_none() {
16246 old_highlight_id = chunk.syntax_highlight_id;
16247 }
16248 chunk.text
16249 })
16250 .collect::<String>()
16251 .into();
16252
16253 drop(buffer);
16254
16255 // Position the selection in the rename editor so that it matches the current selection.
16256 this.show_local_selections = false;
16257 let rename_editor = cx.new(|cx| {
16258 let mut editor = Editor::single_line(window, cx);
16259 editor.buffer.update(cx, |buffer, cx| {
16260 buffer.edit([(0..0, old_name.clone())], None, cx)
16261 });
16262 let rename_selection_range = match cursor_offset_in_rename_range
16263 .cmp(&cursor_offset_in_rename_range_end)
16264 {
16265 Ordering::Equal => {
16266 editor.select_all(&SelectAll, window, cx);
16267 return editor;
16268 }
16269 Ordering::Less => {
16270 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
16271 }
16272 Ordering::Greater => {
16273 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
16274 }
16275 };
16276 if rename_selection_range.end > old_name.len() {
16277 editor.select_all(&SelectAll, window, cx);
16278 } else {
16279 editor.change_selections(Default::default(), window, cx, |s| {
16280 s.select_ranges([rename_selection_range]);
16281 });
16282 }
16283 editor
16284 });
16285 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
16286 if e == &EditorEvent::Focused {
16287 cx.emit(EditorEvent::FocusedIn)
16288 }
16289 })
16290 .detach();
16291
16292 let write_highlights =
16293 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
16294 let read_highlights =
16295 this.clear_background_highlights::<DocumentHighlightRead>(cx);
16296 let ranges = write_highlights
16297 .iter()
16298 .flat_map(|(_, ranges)| ranges.iter())
16299 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
16300 .cloned()
16301 .collect();
16302
16303 this.highlight_text::<Rename>(
16304 ranges,
16305 HighlightStyle {
16306 fade_out: Some(0.6),
16307 ..Default::default()
16308 },
16309 cx,
16310 );
16311 let rename_focus_handle = rename_editor.focus_handle(cx);
16312 window.focus(&rename_focus_handle);
16313 let block_id = this.insert_blocks(
16314 [BlockProperties {
16315 style: BlockStyle::Flex,
16316 placement: BlockPlacement::Below(range.start),
16317 height: Some(1),
16318 render: Arc::new({
16319 let rename_editor = rename_editor.clone();
16320 move |cx: &mut BlockContext| {
16321 let mut text_style = cx.editor_style.text.clone();
16322 if let Some(highlight_style) = old_highlight_id
16323 .and_then(|h| h.style(&cx.editor_style.syntax))
16324 {
16325 text_style = text_style.highlight(highlight_style);
16326 }
16327 div()
16328 .block_mouse_except_scroll()
16329 .pl(cx.anchor_x)
16330 .child(EditorElement::new(
16331 &rename_editor,
16332 EditorStyle {
16333 background: cx.theme().system().transparent,
16334 local_player: cx.editor_style.local_player,
16335 text: text_style,
16336 scrollbar_width: cx.editor_style.scrollbar_width,
16337 syntax: cx.editor_style.syntax.clone(),
16338 status: cx.editor_style.status.clone(),
16339 inlay_hints_style: HighlightStyle {
16340 font_weight: Some(FontWeight::BOLD),
16341 ..make_inlay_hints_style(cx.app)
16342 },
16343 edit_prediction_styles: make_suggestion_styles(
16344 cx.app,
16345 ),
16346 ..EditorStyle::default()
16347 },
16348 ))
16349 .into_any_element()
16350 }
16351 }),
16352 priority: 0,
16353 }],
16354 Some(Autoscroll::fit()),
16355 cx,
16356 )[0];
16357 this.pending_rename = Some(RenameState {
16358 range,
16359 old_name,
16360 editor: rename_editor,
16361 block_id,
16362 });
16363 })?;
16364 }
16365
16366 Ok(())
16367 }))
16368 }
16369
16370 pub fn confirm_rename(
16371 &mut self,
16372 _: &ConfirmRename,
16373 window: &mut Window,
16374 cx: &mut Context<Self>,
16375 ) -> Option<Task<Result<()>>> {
16376 let rename = self.take_rename(false, window, cx)?;
16377 let workspace = self.workspace()?.downgrade();
16378 let (buffer, start) = self
16379 .buffer
16380 .read(cx)
16381 .text_anchor_for_position(rename.range.start, cx)?;
16382 let (end_buffer, _) = self
16383 .buffer
16384 .read(cx)
16385 .text_anchor_for_position(rename.range.end, cx)?;
16386 if buffer != end_buffer {
16387 return None;
16388 }
16389
16390 let old_name = rename.old_name;
16391 let new_name = rename.editor.read(cx).text(cx);
16392
16393 let rename = self.semantics_provider.as_ref()?.perform_rename(
16394 &buffer,
16395 start,
16396 new_name.clone(),
16397 cx,
16398 )?;
16399
16400 Some(cx.spawn_in(window, async move |editor, cx| {
16401 let project_transaction = rename.await?;
16402 Self::open_project_transaction(
16403 &editor,
16404 workspace,
16405 project_transaction,
16406 format!("Rename: {} → {}", old_name, new_name),
16407 cx,
16408 )
16409 .await?;
16410
16411 editor.update(cx, |editor, cx| {
16412 editor.refresh_document_highlights(cx);
16413 })?;
16414 Ok(())
16415 }))
16416 }
16417
16418 fn take_rename(
16419 &mut self,
16420 moving_cursor: bool,
16421 window: &mut Window,
16422 cx: &mut Context<Self>,
16423 ) -> Option<RenameState> {
16424 let rename = self.pending_rename.take()?;
16425 if rename.editor.focus_handle(cx).is_focused(window) {
16426 window.focus(&self.focus_handle);
16427 }
16428
16429 self.remove_blocks(
16430 [rename.block_id].into_iter().collect(),
16431 Some(Autoscroll::fit()),
16432 cx,
16433 );
16434 self.clear_highlights::<Rename>(cx);
16435 self.show_local_selections = true;
16436
16437 if moving_cursor {
16438 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
16439 editor.selections.newest::<usize>(cx).head()
16440 });
16441
16442 // Update the selection to match the position of the selection inside
16443 // the rename editor.
16444 let snapshot = self.buffer.read(cx).read(cx);
16445 let rename_range = rename.range.to_offset(&snapshot);
16446 let cursor_in_editor = snapshot
16447 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
16448 .min(rename_range.end);
16449 drop(snapshot);
16450
16451 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16452 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
16453 });
16454 } else {
16455 self.refresh_document_highlights(cx);
16456 }
16457
16458 Some(rename)
16459 }
16460
16461 pub fn pending_rename(&self) -> Option<&RenameState> {
16462 self.pending_rename.as_ref()
16463 }
16464
16465 fn format(
16466 &mut self,
16467 _: &Format,
16468 window: &mut Window,
16469 cx: &mut Context<Self>,
16470 ) -> Option<Task<Result<()>>> {
16471 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16472
16473 let project = match &self.project {
16474 Some(project) => project.clone(),
16475 None => return None,
16476 };
16477
16478 Some(self.perform_format(
16479 project,
16480 FormatTrigger::Manual,
16481 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
16482 window,
16483 cx,
16484 ))
16485 }
16486
16487 fn format_selections(
16488 &mut self,
16489 _: &FormatSelections,
16490 window: &mut Window,
16491 cx: &mut Context<Self>,
16492 ) -> Option<Task<Result<()>>> {
16493 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16494
16495 let project = match &self.project {
16496 Some(project) => project.clone(),
16497 None => return None,
16498 };
16499
16500 let ranges = self
16501 .selections
16502 .all_adjusted(cx)
16503 .into_iter()
16504 .map(|selection| selection.range())
16505 .collect_vec();
16506
16507 Some(self.perform_format(
16508 project,
16509 FormatTrigger::Manual,
16510 FormatTarget::Ranges(ranges),
16511 window,
16512 cx,
16513 ))
16514 }
16515
16516 fn perform_format(
16517 &mut self,
16518 project: Entity<Project>,
16519 trigger: FormatTrigger,
16520 target: FormatTarget,
16521 window: &mut Window,
16522 cx: &mut Context<Self>,
16523 ) -> Task<Result<()>> {
16524 let buffer = self.buffer.clone();
16525 let (buffers, target) = match target {
16526 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
16527 FormatTarget::Ranges(selection_ranges) => {
16528 let multi_buffer = buffer.read(cx);
16529 let snapshot = multi_buffer.read(cx);
16530 let mut buffers = HashSet::default();
16531 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
16532 BTreeMap::new();
16533 for selection_range in selection_ranges {
16534 for (buffer, buffer_range, _) in
16535 snapshot.range_to_buffer_ranges(selection_range)
16536 {
16537 let buffer_id = buffer.remote_id();
16538 let start = buffer.anchor_before(buffer_range.start);
16539 let end = buffer.anchor_after(buffer_range.end);
16540 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
16541 buffer_id_to_ranges
16542 .entry(buffer_id)
16543 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
16544 .or_insert_with(|| vec![start..end]);
16545 }
16546 }
16547 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
16548 }
16549 };
16550
16551 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
16552 let selections_prev = transaction_id_prev
16553 .and_then(|transaction_id_prev| {
16554 // default to selections as they were after the last edit, if we have them,
16555 // instead of how they are now.
16556 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
16557 // will take you back to where you made the last edit, instead of staying where you scrolled
16558 self.selection_history
16559 .transaction(transaction_id_prev)
16560 .map(|t| t.0.clone())
16561 })
16562 .unwrap_or_else(|| {
16563 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
16564 self.selections.disjoint_anchors()
16565 });
16566
16567 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
16568 let format = project.update(cx, |project, cx| {
16569 project.format(buffers, target, true, trigger, cx)
16570 });
16571
16572 cx.spawn_in(window, async move |editor, cx| {
16573 let transaction = futures::select_biased! {
16574 transaction = format.log_err().fuse() => transaction,
16575 () = timeout => {
16576 log::warn!("timed out waiting for formatting");
16577 None
16578 }
16579 };
16580
16581 buffer
16582 .update(cx, |buffer, cx| {
16583 if let Some(transaction) = transaction {
16584 if !buffer.is_singleton() {
16585 buffer.push_transaction(&transaction.0, cx);
16586 }
16587 }
16588 cx.notify();
16589 })
16590 .ok();
16591
16592 if let Some(transaction_id_now) =
16593 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
16594 {
16595 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
16596 if has_new_transaction {
16597 _ = editor.update(cx, |editor, _| {
16598 editor
16599 .selection_history
16600 .insert_transaction(transaction_id_now, selections_prev);
16601 });
16602 }
16603 }
16604
16605 Ok(())
16606 })
16607 }
16608
16609 fn organize_imports(
16610 &mut self,
16611 _: &OrganizeImports,
16612 window: &mut Window,
16613 cx: &mut Context<Self>,
16614 ) -> Option<Task<Result<()>>> {
16615 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16616 let project = match &self.project {
16617 Some(project) => project.clone(),
16618 None => return None,
16619 };
16620 Some(self.perform_code_action_kind(
16621 project,
16622 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
16623 window,
16624 cx,
16625 ))
16626 }
16627
16628 fn perform_code_action_kind(
16629 &mut self,
16630 project: Entity<Project>,
16631 kind: CodeActionKind,
16632 window: &mut Window,
16633 cx: &mut Context<Self>,
16634 ) -> Task<Result<()>> {
16635 let buffer = self.buffer.clone();
16636 let buffers = buffer.read(cx).all_buffers();
16637 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
16638 let apply_action = project.update(cx, |project, cx| {
16639 project.apply_code_action_kind(buffers, kind, true, cx)
16640 });
16641 cx.spawn_in(window, async move |_, cx| {
16642 let transaction = futures::select_biased! {
16643 () = timeout => {
16644 log::warn!("timed out waiting for executing code action");
16645 None
16646 }
16647 transaction = apply_action.log_err().fuse() => transaction,
16648 };
16649 buffer
16650 .update(cx, |buffer, cx| {
16651 // check if we need this
16652 if let Some(transaction) = transaction {
16653 if !buffer.is_singleton() {
16654 buffer.push_transaction(&transaction.0, cx);
16655 }
16656 }
16657 cx.notify();
16658 })
16659 .ok();
16660 Ok(())
16661 })
16662 }
16663
16664 pub fn restart_language_server(
16665 &mut self,
16666 _: &RestartLanguageServer,
16667 _: &mut Window,
16668 cx: &mut Context<Self>,
16669 ) {
16670 if let Some(project) = self.project.clone() {
16671 self.buffer.update(cx, |multi_buffer, cx| {
16672 project.update(cx, |project, cx| {
16673 project.restart_language_servers_for_buffers(
16674 multi_buffer.all_buffers().into_iter().collect(),
16675 HashSet::default(),
16676 cx,
16677 );
16678 });
16679 })
16680 }
16681 }
16682
16683 pub fn stop_language_server(
16684 &mut self,
16685 _: &StopLanguageServer,
16686 _: &mut Window,
16687 cx: &mut Context<Self>,
16688 ) {
16689 if let Some(project) = self.project.clone() {
16690 self.buffer.update(cx, |multi_buffer, cx| {
16691 project.update(cx, |project, cx| {
16692 project.stop_language_servers_for_buffers(
16693 multi_buffer.all_buffers().into_iter().collect(),
16694 HashSet::default(),
16695 cx,
16696 );
16697 cx.emit(project::Event::RefreshInlayHints);
16698 });
16699 });
16700 }
16701 }
16702
16703 fn cancel_language_server_work(
16704 workspace: &mut Workspace,
16705 _: &actions::CancelLanguageServerWork,
16706 _: &mut Window,
16707 cx: &mut Context<Workspace>,
16708 ) {
16709 let project = workspace.project();
16710 let buffers = workspace
16711 .active_item(cx)
16712 .and_then(|item| item.act_as::<Editor>(cx))
16713 .map_or(HashSet::default(), |editor| {
16714 editor.read(cx).buffer.read(cx).all_buffers()
16715 });
16716 project.update(cx, |project, cx| {
16717 project.cancel_language_server_work_for_buffers(buffers, cx);
16718 });
16719 }
16720
16721 fn show_character_palette(
16722 &mut self,
16723 _: &ShowCharacterPalette,
16724 window: &mut Window,
16725 _: &mut Context<Self>,
16726 ) {
16727 window.show_character_palette();
16728 }
16729
16730 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
16731 if !self.diagnostics_enabled() {
16732 return;
16733 }
16734
16735 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
16736 let buffer = self.buffer.read(cx).snapshot(cx);
16737 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
16738 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
16739 let is_valid = buffer
16740 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
16741 .any(|entry| {
16742 entry.diagnostic.is_primary
16743 && !entry.range.is_empty()
16744 && entry.range.start == primary_range_start
16745 && entry.diagnostic.message == active_diagnostics.active_message
16746 });
16747
16748 if !is_valid {
16749 self.dismiss_diagnostics(cx);
16750 }
16751 }
16752 }
16753
16754 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
16755 match &self.active_diagnostics {
16756 ActiveDiagnostic::Group(group) => Some(group),
16757 _ => None,
16758 }
16759 }
16760
16761 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
16762 if !self.diagnostics_enabled() {
16763 return;
16764 }
16765 self.dismiss_diagnostics(cx);
16766 self.active_diagnostics = ActiveDiagnostic::All;
16767 }
16768
16769 fn activate_diagnostics(
16770 &mut self,
16771 buffer_id: BufferId,
16772 diagnostic: DiagnosticEntry<usize>,
16773 window: &mut Window,
16774 cx: &mut Context<Self>,
16775 ) {
16776 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16777 return;
16778 }
16779 self.dismiss_diagnostics(cx);
16780 let snapshot = self.snapshot(window, cx);
16781 let buffer = self.buffer.read(cx).snapshot(cx);
16782 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
16783 return;
16784 };
16785
16786 let diagnostic_group = buffer
16787 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
16788 .collect::<Vec<_>>();
16789
16790 let blocks =
16791 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
16792
16793 let blocks = self.display_map.update(cx, |display_map, cx| {
16794 display_map.insert_blocks(blocks, cx).into_iter().collect()
16795 });
16796 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
16797 active_range: buffer.anchor_before(diagnostic.range.start)
16798 ..buffer.anchor_after(diagnostic.range.end),
16799 active_message: diagnostic.diagnostic.message.clone(),
16800 group_id: diagnostic.diagnostic.group_id,
16801 blocks,
16802 });
16803 cx.notify();
16804 }
16805
16806 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
16807 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16808 return;
16809 };
16810
16811 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
16812 if let ActiveDiagnostic::Group(group) = prev {
16813 self.display_map.update(cx, |display_map, cx| {
16814 display_map.remove_blocks(group.blocks, cx);
16815 });
16816 cx.notify();
16817 }
16818 }
16819
16820 /// Disable inline diagnostics rendering for this editor.
16821 pub fn disable_inline_diagnostics(&mut self) {
16822 self.inline_diagnostics_enabled = false;
16823 self.inline_diagnostics_update = Task::ready(());
16824 self.inline_diagnostics.clear();
16825 }
16826
16827 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
16828 self.diagnostics_enabled = false;
16829 self.dismiss_diagnostics(cx);
16830 self.inline_diagnostics_update = Task::ready(());
16831 self.inline_diagnostics.clear();
16832 }
16833
16834 pub fn diagnostics_enabled(&self) -> bool {
16835 self.diagnostics_enabled && self.mode.is_full()
16836 }
16837
16838 pub fn inline_diagnostics_enabled(&self) -> bool {
16839 self.inline_diagnostics_enabled && self.diagnostics_enabled()
16840 }
16841
16842 pub fn show_inline_diagnostics(&self) -> bool {
16843 self.show_inline_diagnostics
16844 }
16845
16846 pub fn toggle_inline_diagnostics(
16847 &mut self,
16848 _: &ToggleInlineDiagnostics,
16849 window: &mut Window,
16850 cx: &mut Context<Editor>,
16851 ) {
16852 self.show_inline_diagnostics = !self.show_inline_diagnostics;
16853 self.refresh_inline_diagnostics(false, window, cx);
16854 }
16855
16856 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
16857 self.diagnostics_max_severity = severity;
16858 self.display_map.update(cx, |display_map, _| {
16859 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
16860 });
16861 }
16862
16863 pub fn toggle_diagnostics(
16864 &mut self,
16865 _: &ToggleDiagnostics,
16866 window: &mut Window,
16867 cx: &mut Context<Editor>,
16868 ) {
16869 if !self.diagnostics_enabled() {
16870 return;
16871 }
16872
16873 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16874 EditorSettings::get_global(cx)
16875 .diagnostics_max_severity
16876 .filter(|severity| severity != &DiagnosticSeverity::Off)
16877 .unwrap_or(DiagnosticSeverity::Hint)
16878 } else {
16879 DiagnosticSeverity::Off
16880 };
16881 self.set_max_diagnostics_severity(new_severity, cx);
16882 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16883 self.active_diagnostics = ActiveDiagnostic::None;
16884 self.inline_diagnostics_update = Task::ready(());
16885 self.inline_diagnostics.clear();
16886 } else {
16887 self.refresh_inline_diagnostics(false, window, cx);
16888 }
16889
16890 cx.notify();
16891 }
16892
16893 pub fn toggle_minimap(
16894 &mut self,
16895 _: &ToggleMinimap,
16896 window: &mut Window,
16897 cx: &mut Context<Editor>,
16898 ) {
16899 if self.supports_minimap(cx) {
16900 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
16901 }
16902 }
16903
16904 fn refresh_inline_diagnostics(
16905 &mut self,
16906 debounce: bool,
16907 window: &mut Window,
16908 cx: &mut Context<Self>,
16909 ) {
16910 let max_severity = ProjectSettings::get_global(cx)
16911 .diagnostics
16912 .inline
16913 .max_severity
16914 .unwrap_or(self.diagnostics_max_severity);
16915
16916 if !self.inline_diagnostics_enabled()
16917 || !self.show_inline_diagnostics
16918 || max_severity == DiagnosticSeverity::Off
16919 {
16920 self.inline_diagnostics_update = Task::ready(());
16921 self.inline_diagnostics.clear();
16922 return;
16923 }
16924
16925 let debounce_ms = ProjectSettings::get_global(cx)
16926 .diagnostics
16927 .inline
16928 .update_debounce_ms;
16929 let debounce = if debounce && debounce_ms > 0 {
16930 Some(Duration::from_millis(debounce_ms))
16931 } else {
16932 None
16933 };
16934 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
16935 if let Some(debounce) = debounce {
16936 cx.background_executor().timer(debounce).await;
16937 }
16938 let Some(snapshot) = editor.upgrade().and_then(|editor| {
16939 editor
16940 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
16941 .ok()
16942 }) else {
16943 return;
16944 };
16945
16946 let new_inline_diagnostics = cx
16947 .background_spawn(async move {
16948 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
16949 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
16950 let message = diagnostic_entry
16951 .diagnostic
16952 .message
16953 .split_once('\n')
16954 .map(|(line, _)| line)
16955 .map(SharedString::new)
16956 .unwrap_or_else(|| {
16957 SharedString::from(diagnostic_entry.diagnostic.message)
16958 });
16959 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
16960 let (Ok(i) | Err(i)) = inline_diagnostics
16961 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
16962 inline_diagnostics.insert(
16963 i,
16964 (
16965 start_anchor,
16966 InlineDiagnostic {
16967 message,
16968 group_id: diagnostic_entry.diagnostic.group_id,
16969 start: diagnostic_entry.range.start.to_point(&snapshot),
16970 is_primary: diagnostic_entry.diagnostic.is_primary,
16971 severity: diagnostic_entry.diagnostic.severity,
16972 },
16973 ),
16974 );
16975 }
16976 inline_diagnostics
16977 })
16978 .await;
16979
16980 editor
16981 .update(cx, |editor, cx| {
16982 editor.inline_diagnostics = new_inline_diagnostics;
16983 cx.notify();
16984 })
16985 .ok();
16986 });
16987 }
16988
16989 fn pull_diagnostics(
16990 &mut self,
16991 buffer_id: Option<BufferId>,
16992 window: &Window,
16993 cx: &mut Context<Self>,
16994 ) -> Option<()> {
16995 if !self.mode().is_full() {
16996 return None;
16997 }
16998 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
16999 .diagnostics
17000 .lsp_pull_diagnostics;
17001 if !pull_diagnostics_settings.enabled {
17002 return None;
17003 }
17004 let project = self.project.as_ref()?.downgrade();
17005 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
17006 let mut buffers = self.buffer.read(cx).all_buffers();
17007 if let Some(buffer_id) = buffer_id {
17008 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
17009 }
17010
17011 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
17012 cx.background_executor().timer(debounce).await;
17013
17014 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
17015 buffers
17016 .into_iter()
17017 .filter_map(|buffer| {
17018 project
17019 .update(cx, |project, cx| {
17020 project.lsp_store().update(cx, |lsp_store, cx| {
17021 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
17022 })
17023 })
17024 .ok()
17025 })
17026 .collect::<FuturesUnordered<_>>()
17027 }) else {
17028 return;
17029 };
17030
17031 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
17032 match pull_task {
17033 Ok(()) => {
17034 if editor
17035 .update_in(cx, |editor, window, cx| {
17036 editor.update_diagnostics_state(window, cx);
17037 })
17038 .is_err()
17039 {
17040 return;
17041 }
17042 }
17043 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
17044 }
17045 }
17046 });
17047
17048 Some(())
17049 }
17050
17051 pub fn set_selections_from_remote(
17052 &mut self,
17053 selections: Vec<Selection<Anchor>>,
17054 pending_selection: Option<Selection<Anchor>>,
17055 window: &mut Window,
17056 cx: &mut Context<Self>,
17057 ) {
17058 let old_cursor_position = self.selections.newest_anchor().head();
17059 self.selections.change_with(cx, |s| {
17060 s.select_anchors(selections);
17061 if let Some(pending_selection) = pending_selection {
17062 s.set_pending(pending_selection, SelectMode::Character);
17063 } else {
17064 s.clear_pending();
17065 }
17066 });
17067 self.selections_did_change(
17068 false,
17069 &old_cursor_position,
17070 SelectionEffects::default(),
17071 window,
17072 cx,
17073 );
17074 }
17075
17076 pub fn transact(
17077 &mut self,
17078 window: &mut Window,
17079 cx: &mut Context<Self>,
17080 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
17081 ) -> Option<TransactionId> {
17082 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17083 this.start_transaction_at(Instant::now(), window, cx);
17084 update(this, window, cx);
17085 this.end_transaction_at(Instant::now(), cx)
17086 })
17087 }
17088
17089 pub fn start_transaction_at(
17090 &mut self,
17091 now: Instant,
17092 window: &mut Window,
17093 cx: &mut Context<Self>,
17094 ) -> Option<TransactionId> {
17095 self.end_selection(window, cx);
17096 if let Some(tx_id) = self
17097 .buffer
17098 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
17099 {
17100 self.selection_history
17101 .insert_transaction(tx_id, self.selections.disjoint_anchors());
17102 cx.emit(EditorEvent::TransactionBegun {
17103 transaction_id: tx_id,
17104 });
17105 Some(tx_id)
17106 } else {
17107 None
17108 }
17109 }
17110
17111 pub fn end_transaction_at(
17112 &mut self,
17113 now: Instant,
17114 cx: &mut Context<Self>,
17115 ) -> Option<TransactionId> {
17116 if let Some(transaction_id) = self
17117 .buffer
17118 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
17119 {
17120 if let Some((_, end_selections)) =
17121 self.selection_history.transaction_mut(transaction_id)
17122 {
17123 *end_selections = Some(self.selections.disjoint_anchors());
17124 } else {
17125 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
17126 }
17127
17128 cx.emit(EditorEvent::Edited { transaction_id });
17129 Some(transaction_id)
17130 } else {
17131 None
17132 }
17133 }
17134
17135 pub fn modify_transaction_selection_history(
17136 &mut self,
17137 transaction_id: TransactionId,
17138 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
17139 ) -> bool {
17140 self.selection_history
17141 .transaction_mut(transaction_id)
17142 .map(modify)
17143 .is_some()
17144 }
17145
17146 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
17147 if self.selection_mark_mode {
17148 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17149 s.move_with(|_, sel| {
17150 sel.collapse_to(sel.head(), SelectionGoal::None);
17151 });
17152 })
17153 }
17154 self.selection_mark_mode = true;
17155 cx.notify();
17156 }
17157
17158 pub fn swap_selection_ends(
17159 &mut self,
17160 _: &actions::SwapSelectionEnds,
17161 window: &mut Window,
17162 cx: &mut Context<Self>,
17163 ) {
17164 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17165 s.move_with(|_, sel| {
17166 if sel.start != sel.end {
17167 sel.reversed = !sel.reversed
17168 }
17169 });
17170 });
17171 self.request_autoscroll(Autoscroll::newest(), cx);
17172 cx.notify();
17173 }
17174
17175 pub fn toggle_focus(
17176 workspace: &mut Workspace,
17177 _: &actions::ToggleFocus,
17178 window: &mut Window,
17179 cx: &mut Context<Workspace>,
17180 ) {
17181 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
17182 return;
17183 };
17184 workspace.activate_item(&item, true, true, window, cx);
17185 }
17186
17187 pub fn toggle_fold(
17188 &mut self,
17189 _: &actions::ToggleFold,
17190 window: &mut Window,
17191 cx: &mut Context<Self>,
17192 ) {
17193 if self.is_singleton(cx) {
17194 let selection = self.selections.newest::<Point>(cx);
17195
17196 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17197 let range = if selection.is_empty() {
17198 let point = selection.head().to_display_point(&display_map);
17199 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17200 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17201 .to_point(&display_map);
17202 start..end
17203 } else {
17204 selection.range()
17205 };
17206 if display_map.folds_in_range(range).next().is_some() {
17207 self.unfold_lines(&Default::default(), window, cx)
17208 } else {
17209 self.fold(&Default::default(), window, cx)
17210 }
17211 } else {
17212 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17213 let buffer_ids: HashSet<_> = self
17214 .selections
17215 .disjoint_anchor_ranges()
17216 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17217 .collect();
17218
17219 let should_unfold = buffer_ids
17220 .iter()
17221 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17222
17223 for buffer_id in buffer_ids {
17224 if should_unfold {
17225 self.unfold_buffer(buffer_id, cx);
17226 } else {
17227 self.fold_buffer(buffer_id, cx);
17228 }
17229 }
17230 }
17231 }
17232
17233 pub fn toggle_fold_recursive(
17234 &mut self,
17235 _: &actions::ToggleFoldRecursive,
17236 window: &mut Window,
17237 cx: &mut Context<Self>,
17238 ) {
17239 let selection = self.selections.newest::<Point>(cx);
17240
17241 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17242 let range = if selection.is_empty() {
17243 let point = selection.head().to_display_point(&display_map);
17244 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17245 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17246 .to_point(&display_map);
17247 start..end
17248 } else {
17249 selection.range()
17250 };
17251 if display_map.folds_in_range(range).next().is_some() {
17252 self.unfold_recursive(&Default::default(), window, cx)
17253 } else {
17254 self.fold_recursive(&Default::default(), window, cx)
17255 }
17256 }
17257
17258 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
17259 if self.is_singleton(cx) {
17260 let mut to_fold = Vec::new();
17261 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17262 let selections = self.selections.all_adjusted(cx);
17263
17264 for selection in selections {
17265 let range = selection.range().sorted();
17266 let buffer_start_row = range.start.row;
17267
17268 if range.start.row != range.end.row {
17269 let mut found = false;
17270 let mut row = range.start.row;
17271 while row <= range.end.row {
17272 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
17273 {
17274 found = true;
17275 row = crease.range().end.row + 1;
17276 to_fold.push(crease);
17277 } else {
17278 row += 1
17279 }
17280 }
17281 if found {
17282 continue;
17283 }
17284 }
17285
17286 for row in (0..=range.start.row).rev() {
17287 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17288 if crease.range().end.row >= buffer_start_row {
17289 to_fold.push(crease);
17290 if row <= range.start.row {
17291 break;
17292 }
17293 }
17294 }
17295 }
17296 }
17297
17298 self.fold_creases(to_fold, true, window, cx);
17299 } else {
17300 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17301 let buffer_ids = self
17302 .selections
17303 .disjoint_anchor_ranges()
17304 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17305 .collect::<HashSet<_>>();
17306 for buffer_id in buffer_ids {
17307 self.fold_buffer(buffer_id, cx);
17308 }
17309 }
17310 }
17311
17312 pub fn toggle_fold_all(
17313 &mut self,
17314 _: &actions::ToggleFoldAll,
17315 window: &mut Window,
17316 cx: &mut Context<Self>,
17317 ) {
17318 if self.buffer.read(cx).is_singleton() {
17319 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17320 let has_folds = display_map
17321 .folds_in_range(0..display_map.buffer_snapshot.len())
17322 .next()
17323 .is_some();
17324
17325 if has_folds {
17326 self.unfold_all(&actions::UnfoldAll, window, cx);
17327 } else {
17328 self.fold_all(&actions::FoldAll, window, cx);
17329 }
17330 } else {
17331 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
17332 let should_unfold = buffer_ids
17333 .iter()
17334 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17335
17336 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17337 editor
17338 .update_in(cx, |editor, _, cx| {
17339 for buffer_id in buffer_ids {
17340 if should_unfold {
17341 editor.unfold_buffer(buffer_id, cx);
17342 } else {
17343 editor.fold_buffer(buffer_id, cx);
17344 }
17345 }
17346 })
17347 .ok();
17348 });
17349 }
17350 }
17351
17352 fn fold_at_level(
17353 &mut self,
17354 fold_at: &FoldAtLevel,
17355 window: &mut Window,
17356 cx: &mut Context<Self>,
17357 ) {
17358 if !self.buffer.read(cx).is_singleton() {
17359 return;
17360 }
17361
17362 let fold_at_level = fold_at.0;
17363 let snapshot = self.buffer.read(cx).snapshot(cx);
17364 let mut to_fold = Vec::new();
17365 let mut stack = vec![(0, snapshot.max_row().0, 1)];
17366
17367 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
17368 while start_row < end_row {
17369 match self
17370 .snapshot(window, cx)
17371 .crease_for_buffer_row(MultiBufferRow(start_row))
17372 {
17373 Some(crease) => {
17374 let nested_start_row = crease.range().start.row + 1;
17375 let nested_end_row = crease.range().end.row;
17376
17377 if current_level < fold_at_level {
17378 stack.push((nested_start_row, nested_end_row, current_level + 1));
17379 } else if current_level == fold_at_level {
17380 to_fold.push(crease);
17381 }
17382
17383 start_row = nested_end_row + 1;
17384 }
17385 None => start_row += 1,
17386 }
17387 }
17388 }
17389
17390 self.fold_creases(to_fold, true, window, cx);
17391 }
17392
17393 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
17394 if self.buffer.read(cx).is_singleton() {
17395 let mut fold_ranges = Vec::new();
17396 let snapshot = self.buffer.read(cx).snapshot(cx);
17397
17398 for row in 0..snapshot.max_row().0 {
17399 if let Some(foldable_range) = self
17400 .snapshot(window, cx)
17401 .crease_for_buffer_row(MultiBufferRow(row))
17402 {
17403 fold_ranges.push(foldable_range);
17404 }
17405 }
17406
17407 self.fold_creases(fold_ranges, true, window, cx);
17408 } else {
17409 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17410 editor
17411 .update_in(cx, |editor, _, cx| {
17412 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17413 editor.fold_buffer(buffer_id, cx);
17414 }
17415 })
17416 .ok();
17417 });
17418 }
17419 }
17420
17421 pub fn fold_function_bodies(
17422 &mut self,
17423 _: &actions::FoldFunctionBodies,
17424 window: &mut Window,
17425 cx: &mut Context<Self>,
17426 ) {
17427 let snapshot = self.buffer.read(cx).snapshot(cx);
17428
17429 let ranges = snapshot
17430 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
17431 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
17432 .collect::<Vec<_>>();
17433
17434 let creases = ranges
17435 .into_iter()
17436 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
17437 .collect();
17438
17439 self.fold_creases(creases, true, window, cx);
17440 }
17441
17442 pub fn fold_recursive(
17443 &mut self,
17444 _: &actions::FoldRecursive,
17445 window: &mut Window,
17446 cx: &mut Context<Self>,
17447 ) {
17448 let mut to_fold = Vec::new();
17449 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17450 let selections = self.selections.all_adjusted(cx);
17451
17452 for selection in selections {
17453 let range = selection.range().sorted();
17454 let buffer_start_row = range.start.row;
17455
17456 if range.start.row != range.end.row {
17457 let mut found = false;
17458 for row in range.start.row..=range.end.row {
17459 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17460 found = true;
17461 to_fold.push(crease);
17462 }
17463 }
17464 if found {
17465 continue;
17466 }
17467 }
17468
17469 for row in (0..=range.start.row).rev() {
17470 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17471 if crease.range().end.row >= buffer_start_row {
17472 to_fold.push(crease);
17473 } else {
17474 break;
17475 }
17476 }
17477 }
17478 }
17479
17480 self.fold_creases(to_fold, true, window, cx);
17481 }
17482
17483 pub fn fold_at(
17484 &mut self,
17485 buffer_row: MultiBufferRow,
17486 window: &mut Window,
17487 cx: &mut Context<Self>,
17488 ) {
17489 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17490
17491 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
17492 let autoscroll = self
17493 .selections
17494 .all::<Point>(cx)
17495 .iter()
17496 .any(|selection| crease.range().overlaps(&selection.range()));
17497
17498 self.fold_creases(vec![crease], autoscroll, window, cx);
17499 }
17500 }
17501
17502 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
17503 if self.is_singleton(cx) {
17504 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17505 let buffer = &display_map.buffer_snapshot;
17506 let selections = self.selections.all::<Point>(cx);
17507 let ranges = selections
17508 .iter()
17509 .map(|s| {
17510 let range = s.display_range(&display_map).sorted();
17511 let mut start = range.start.to_point(&display_map);
17512 let mut end = range.end.to_point(&display_map);
17513 start.column = 0;
17514 end.column = buffer.line_len(MultiBufferRow(end.row));
17515 start..end
17516 })
17517 .collect::<Vec<_>>();
17518
17519 self.unfold_ranges(&ranges, true, true, cx);
17520 } else {
17521 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17522 let buffer_ids = self
17523 .selections
17524 .disjoint_anchor_ranges()
17525 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17526 .collect::<HashSet<_>>();
17527 for buffer_id in buffer_ids {
17528 self.unfold_buffer(buffer_id, cx);
17529 }
17530 }
17531 }
17532
17533 pub fn unfold_recursive(
17534 &mut self,
17535 _: &UnfoldRecursive,
17536 _window: &mut Window,
17537 cx: &mut Context<Self>,
17538 ) {
17539 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17540 let selections = self.selections.all::<Point>(cx);
17541 let ranges = selections
17542 .iter()
17543 .map(|s| {
17544 let mut range = s.display_range(&display_map).sorted();
17545 *range.start.column_mut() = 0;
17546 *range.end.column_mut() = display_map.line_len(range.end.row());
17547 let start = range.start.to_point(&display_map);
17548 let end = range.end.to_point(&display_map);
17549 start..end
17550 })
17551 .collect::<Vec<_>>();
17552
17553 self.unfold_ranges(&ranges, true, true, cx);
17554 }
17555
17556 pub fn unfold_at(
17557 &mut self,
17558 buffer_row: MultiBufferRow,
17559 _window: &mut Window,
17560 cx: &mut Context<Self>,
17561 ) {
17562 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17563
17564 let intersection_range = Point::new(buffer_row.0, 0)
17565 ..Point::new(
17566 buffer_row.0,
17567 display_map.buffer_snapshot.line_len(buffer_row),
17568 );
17569
17570 let autoscroll = self
17571 .selections
17572 .all::<Point>(cx)
17573 .iter()
17574 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
17575
17576 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
17577 }
17578
17579 pub fn unfold_all(
17580 &mut self,
17581 _: &actions::UnfoldAll,
17582 _window: &mut Window,
17583 cx: &mut Context<Self>,
17584 ) {
17585 if self.buffer.read(cx).is_singleton() {
17586 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17587 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
17588 } else {
17589 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
17590 editor
17591 .update(cx, |editor, cx| {
17592 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17593 editor.unfold_buffer(buffer_id, cx);
17594 }
17595 })
17596 .ok();
17597 });
17598 }
17599 }
17600
17601 pub fn fold_selected_ranges(
17602 &mut self,
17603 _: &FoldSelectedRanges,
17604 window: &mut Window,
17605 cx: &mut Context<Self>,
17606 ) {
17607 let selections = self.selections.all_adjusted(cx);
17608 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17609 let ranges = selections
17610 .into_iter()
17611 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
17612 .collect::<Vec<_>>();
17613 self.fold_creases(ranges, true, window, cx);
17614 }
17615
17616 pub fn fold_ranges<T: ToOffset + Clone>(
17617 &mut self,
17618 ranges: Vec<Range<T>>,
17619 auto_scroll: bool,
17620 window: &mut Window,
17621 cx: &mut Context<Self>,
17622 ) {
17623 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17624 let ranges = ranges
17625 .into_iter()
17626 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
17627 .collect::<Vec<_>>();
17628 self.fold_creases(ranges, auto_scroll, window, cx);
17629 }
17630
17631 pub fn fold_creases<T: ToOffset + Clone>(
17632 &mut self,
17633 creases: Vec<Crease<T>>,
17634 auto_scroll: bool,
17635 _window: &mut Window,
17636 cx: &mut Context<Self>,
17637 ) {
17638 if creases.is_empty() {
17639 return;
17640 }
17641
17642 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
17643
17644 if auto_scroll {
17645 self.request_autoscroll(Autoscroll::fit(), cx);
17646 }
17647
17648 cx.notify();
17649
17650 self.scrollbar_marker_state.dirty = true;
17651 self.folds_did_change(cx);
17652 }
17653
17654 /// Removes any folds whose ranges intersect any of the given ranges.
17655 pub fn unfold_ranges<T: ToOffset + Clone>(
17656 &mut self,
17657 ranges: &[Range<T>],
17658 inclusive: bool,
17659 auto_scroll: bool,
17660 cx: &mut Context<Self>,
17661 ) {
17662 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17663 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
17664 });
17665 self.folds_did_change(cx);
17666 }
17667
17668 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17669 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
17670 return;
17671 }
17672 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17673 self.display_map.update(cx, |display_map, cx| {
17674 display_map.fold_buffers([buffer_id], cx)
17675 });
17676 cx.emit(EditorEvent::BufferFoldToggled {
17677 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
17678 folded: true,
17679 });
17680 cx.notify();
17681 }
17682
17683 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17684 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
17685 return;
17686 }
17687 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17688 self.display_map.update(cx, |display_map, cx| {
17689 display_map.unfold_buffers([buffer_id], cx);
17690 });
17691 cx.emit(EditorEvent::BufferFoldToggled {
17692 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
17693 folded: false,
17694 });
17695 cx.notify();
17696 }
17697
17698 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
17699 self.display_map.read(cx).is_buffer_folded(buffer)
17700 }
17701
17702 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
17703 self.display_map.read(cx).folded_buffers()
17704 }
17705
17706 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17707 self.display_map.update(cx, |display_map, cx| {
17708 display_map.disable_header_for_buffer(buffer_id, cx);
17709 });
17710 cx.notify();
17711 }
17712
17713 /// Removes any folds with the given ranges.
17714 pub fn remove_folds_with_type<T: ToOffset + Clone>(
17715 &mut self,
17716 ranges: &[Range<T>],
17717 type_id: TypeId,
17718 auto_scroll: bool,
17719 cx: &mut Context<Self>,
17720 ) {
17721 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17722 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
17723 });
17724 self.folds_did_change(cx);
17725 }
17726
17727 fn remove_folds_with<T: ToOffset + Clone>(
17728 &mut self,
17729 ranges: &[Range<T>],
17730 auto_scroll: bool,
17731 cx: &mut Context<Self>,
17732 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
17733 ) {
17734 if ranges.is_empty() {
17735 return;
17736 }
17737
17738 let mut buffers_affected = HashSet::default();
17739 let multi_buffer = self.buffer().read(cx);
17740 for range in ranges {
17741 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
17742 buffers_affected.insert(buffer.read(cx).remote_id());
17743 };
17744 }
17745
17746 self.display_map.update(cx, update);
17747
17748 if auto_scroll {
17749 self.request_autoscroll(Autoscroll::fit(), cx);
17750 }
17751
17752 cx.notify();
17753 self.scrollbar_marker_state.dirty = true;
17754 self.active_indent_guides_state.dirty = true;
17755 }
17756
17757 pub fn update_renderer_widths(
17758 &mut self,
17759 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
17760 cx: &mut Context<Self>,
17761 ) -> bool {
17762 self.display_map
17763 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
17764 }
17765
17766 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
17767 self.display_map.read(cx).fold_placeholder.clone()
17768 }
17769
17770 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
17771 self.buffer.update(cx, |buffer, cx| {
17772 buffer.set_all_diff_hunks_expanded(cx);
17773 });
17774 }
17775
17776 pub fn expand_all_diff_hunks(
17777 &mut self,
17778 _: &ExpandAllDiffHunks,
17779 _window: &mut Window,
17780 cx: &mut Context<Self>,
17781 ) {
17782 self.buffer.update(cx, |buffer, cx| {
17783 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
17784 });
17785 }
17786
17787 pub fn toggle_selected_diff_hunks(
17788 &mut self,
17789 _: &ToggleSelectedDiffHunks,
17790 _window: &mut Window,
17791 cx: &mut Context<Self>,
17792 ) {
17793 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17794 self.toggle_diff_hunks_in_ranges(ranges, cx);
17795 }
17796
17797 pub fn diff_hunks_in_ranges<'a>(
17798 &'a self,
17799 ranges: &'a [Range<Anchor>],
17800 buffer: &'a MultiBufferSnapshot,
17801 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
17802 ranges.iter().flat_map(move |range| {
17803 let end_excerpt_id = range.end.excerpt_id;
17804 let range = range.to_point(buffer);
17805 let mut peek_end = range.end;
17806 if range.end.row < buffer.max_row().0 {
17807 peek_end = Point::new(range.end.row + 1, 0);
17808 }
17809 buffer
17810 .diff_hunks_in_range(range.start..peek_end)
17811 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
17812 })
17813 }
17814
17815 pub fn has_stageable_diff_hunks_in_ranges(
17816 &self,
17817 ranges: &[Range<Anchor>],
17818 snapshot: &MultiBufferSnapshot,
17819 ) -> bool {
17820 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
17821 hunks.any(|hunk| hunk.status().has_secondary_hunk())
17822 }
17823
17824 pub fn toggle_staged_selected_diff_hunks(
17825 &mut self,
17826 _: &::git::ToggleStaged,
17827 _: &mut Window,
17828 cx: &mut Context<Self>,
17829 ) {
17830 let snapshot = self.buffer.read(cx).snapshot(cx);
17831 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17832 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
17833 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17834 }
17835
17836 pub fn set_render_diff_hunk_controls(
17837 &mut self,
17838 render_diff_hunk_controls: RenderDiffHunkControlsFn,
17839 cx: &mut Context<Self>,
17840 ) {
17841 self.render_diff_hunk_controls = render_diff_hunk_controls;
17842 cx.notify();
17843 }
17844
17845 pub fn stage_and_next(
17846 &mut self,
17847 _: &::git::StageAndNext,
17848 window: &mut Window,
17849 cx: &mut Context<Self>,
17850 ) {
17851 self.do_stage_or_unstage_and_next(true, window, cx);
17852 }
17853
17854 pub fn unstage_and_next(
17855 &mut self,
17856 _: &::git::UnstageAndNext,
17857 window: &mut Window,
17858 cx: &mut Context<Self>,
17859 ) {
17860 self.do_stage_or_unstage_and_next(false, window, cx);
17861 }
17862
17863 pub fn stage_or_unstage_diff_hunks(
17864 &mut self,
17865 stage: bool,
17866 ranges: Vec<Range<Anchor>>,
17867 cx: &mut Context<Self>,
17868 ) {
17869 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
17870 cx.spawn(async move |this, cx| {
17871 task.await?;
17872 this.update(cx, |this, cx| {
17873 let snapshot = this.buffer.read(cx).snapshot(cx);
17874 let chunk_by = this
17875 .diff_hunks_in_ranges(&ranges, &snapshot)
17876 .chunk_by(|hunk| hunk.buffer_id);
17877 for (buffer_id, hunks) in &chunk_by {
17878 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
17879 }
17880 })
17881 })
17882 .detach_and_log_err(cx);
17883 }
17884
17885 fn save_buffers_for_ranges_if_needed(
17886 &mut self,
17887 ranges: &[Range<Anchor>],
17888 cx: &mut Context<Editor>,
17889 ) -> Task<Result<()>> {
17890 let multibuffer = self.buffer.read(cx);
17891 let snapshot = multibuffer.read(cx);
17892 let buffer_ids: HashSet<_> = ranges
17893 .iter()
17894 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
17895 .collect();
17896 drop(snapshot);
17897
17898 let mut buffers = HashSet::default();
17899 for buffer_id in buffer_ids {
17900 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
17901 let buffer = buffer_entity.read(cx);
17902 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
17903 {
17904 buffers.insert(buffer_entity);
17905 }
17906 }
17907 }
17908
17909 if let Some(project) = &self.project {
17910 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
17911 } else {
17912 Task::ready(Ok(()))
17913 }
17914 }
17915
17916 fn do_stage_or_unstage_and_next(
17917 &mut self,
17918 stage: bool,
17919 window: &mut Window,
17920 cx: &mut Context<Self>,
17921 ) {
17922 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
17923
17924 if ranges.iter().any(|range| range.start != range.end) {
17925 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17926 return;
17927 }
17928
17929 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17930 let snapshot = self.snapshot(window, cx);
17931 let position = self.selections.newest::<Point>(cx).head();
17932 let mut row = snapshot
17933 .buffer_snapshot
17934 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
17935 .find(|hunk| hunk.row_range.start.0 > position.row)
17936 .map(|hunk| hunk.row_range.start);
17937
17938 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
17939 // Outside of the project diff editor, wrap around to the beginning.
17940 if !all_diff_hunks_expanded {
17941 row = row.or_else(|| {
17942 snapshot
17943 .buffer_snapshot
17944 .diff_hunks_in_range(Point::zero()..position)
17945 .find(|hunk| hunk.row_range.end.0 < position.row)
17946 .map(|hunk| hunk.row_range.start)
17947 });
17948 }
17949
17950 if let Some(row) = row {
17951 let destination = Point::new(row.0, 0);
17952 let autoscroll = Autoscroll::center();
17953
17954 self.unfold_ranges(&[destination..destination], false, false, cx);
17955 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17956 s.select_ranges([destination..destination]);
17957 });
17958 }
17959 }
17960
17961 fn do_stage_or_unstage(
17962 &self,
17963 stage: bool,
17964 buffer_id: BufferId,
17965 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
17966 cx: &mut App,
17967 ) -> Option<()> {
17968 let project = self.project.as_ref()?;
17969 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
17970 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
17971 let buffer_snapshot = buffer.read(cx).snapshot();
17972 let file_exists = buffer_snapshot
17973 .file()
17974 .is_some_and(|file| file.disk_state().exists());
17975 diff.update(cx, |diff, cx| {
17976 diff.stage_or_unstage_hunks(
17977 stage,
17978 &hunks
17979 .map(|hunk| buffer_diff::DiffHunk {
17980 buffer_range: hunk.buffer_range,
17981 diff_base_byte_range: hunk.diff_base_byte_range,
17982 secondary_status: hunk.secondary_status,
17983 range: Point::zero()..Point::zero(), // unused
17984 })
17985 .collect::<Vec<_>>(),
17986 &buffer_snapshot,
17987 file_exists,
17988 cx,
17989 )
17990 });
17991 None
17992 }
17993
17994 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
17995 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17996 self.buffer
17997 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
17998 }
17999
18000 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
18001 self.buffer.update(cx, |buffer, cx| {
18002 let ranges = vec![Anchor::min()..Anchor::max()];
18003 if !buffer.all_diff_hunks_expanded()
18004 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
18005 {
18006 buffer.collapse_diff_hunks(ranges, cx);
18007 true
18008 } else {
18009 false
18010 }
18011 })
18012 }
18013
18014 fn toggle_diff_hunks_in_ranges(
18015 &mut self,
18016 ranges: Vec<Range<Anchor>>,
18017 cx: &mut Context<Editor>,
18018 ) {
18019 self.buffer.update(cx, |buffer, cx| {
18020 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
18021 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
18022 })
18023 }
18024
18025 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
18026 self.buffer.update(cx, |buffer, cx| {
18027 let snapshot = buffer.snapshot(cx);
18028 let excerpt_id = range.end.excerpt_id;
18029 let point_range = range.to_point(&snapshot);
18030 let expand = !buffer.single_hunk_is_expanded(range, cx);
18031 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
18032 })
18033 }
18034
18035 pub(crate) fn apply_all_diff_hunks(
18036 &mut self,
18037 _: &ApplyAllDiffHunks,
18038 window: &mut Window,
18039 cx: &mut Context<Self>,
18040 ) {
18041 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18042
18043 let buffers = self.buffer.read(cx).all_buffers();
18044 for branch_buffer in buffers {
18045 branch_buffer.update(cx, |branch_buffer, cx| {
18046 branch_buffer.merge_into_base(Vec::new(), cx);
18047 });
18048 }
18049
18050 if let Some(project) = self.project.clone() {
18051 self.save(
18052 SaveOptions {
18053 format: true,
18054 autosave: false,
18055 },
18056 project,
18057 window,
18058 cx,
18059 )
18060 .detach_and_log_err(cx);
18061 }
18062 }
18063
18064 pub(crate) fn apply_selected_diff_hunks(
18065 &mut self,
18066 _: &ApplyDiffHunk,
18067 window: &mut Window,
18068 cx: &mut Context<Self>,
18069 ) {
18070 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18071 let snapshot = self.snapshot(window, cx);
18072 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
18073 let mut ranges_by_buffer = HashMap::default();
18074 self.transact(window, cx, |editor, _window, cx| {
18075 for hunk in hunks {
18076 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
18077 ranges_by_buffer
18078 .entry(buffer.clone())
18079 .or_insert_with(Vec::new)
18080 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
18081 }
18082 }
18083
18084 for (buffer, ranges) in ranges_by_buffer {
18085 buffer.update(cx, |buffer, cx| {
18086 buffer.merge_into_base(ranges, cx);
18087 });
18088 }
18089 });
18090
18091 if let Some(project) = self.project.clone() {
18092 self.save(
18093 SaveOptions {
18094 format: true,
18095 autosave: false,
18096 },
18097 project,
18098 window,
18099 cx,
18100 )
18101 .detach_and_log_err(cx);
18102 }
18103 }
18104
18105 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
18106 if hovered != self.gutter_hovered {
18107 self.gutter_hovered = hovered;
18108 cx.notify();
18109 }
18110 }
18111
18112 pub fn insert_blocks(
18113 &mut self,
18114 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
18115 autoscroll: Option<Autoscroll>,
18116 cx: &mut Context<Self>,
18117 ) -> Vec<CustomBlockId> {
18118 let blocks = self
18119 .display_map
18120 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
18121 if let Some(autoscroll) = autoscroll {
18122 self.request_autoscroll(autoscroll, cx);
18123 }
18124 cx.notify();
18125 blocks
18126 }
18127
18128 pub fn resize_blocks(
18129 &mut self,
18130 heights: HashMap<CustomBlockId, u32>,
18131 autoscroll: Option<Autoscroll>,
18132 cx: &mut Context<Self>,
18133 ) {
18134 self.display_map
18135 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
18136 if let Some(autoscroll) = autoscroll {
18137 self.request_autoscroll(autoscroll, cx);
18138 }
18139 cx.notify();
18140 }
18141
18142 pub fn replace_blocks(
18143 &mut self,
18144 renderers: HashMap<CustomBlockId, RenderBlock>,
18145 autoscroll: Option<Autoscroll>,
18146 cx: &mut Context<Self>,
18147 ) {
18148 self.display_map
18149 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
18150 if let Some(autoscroll) = autoscroll {
18151 self.request_autoscroll(autoscroll, cx);
18152 }
18153 cx.notify();
18154 }
18155
18156 pub fn remove_blocks(
18157 &mut self,
18158 block_ids: HashSet<CustomBlockId>,
18159 autoscroll: Option<Autoscroll>,
18160 cx: &mut Context<Self>,
18161 ) {
18162 self.display_map.update(cx, |display_map, cx| {
18163 display_map.remove_blocks(block_ids, cx)
18164 });
18165 if let Some(autoscroll) = autoscroll {
18166 self.request_autoscroll(autoscroll, cx);
18167 }
18168 cx.notify();
18169 }
18170
18171 pub fn row_for_block(
18172 &self,
18173 block_id: CustomBlockId,
18174 cx: &mut Context<Self>,
18175 ) -> Option<DisplayRow> {
18176 self.display_map
18177 .update(cx, |map, cx| map.row_for_block(block_id, cx))
18178 }
18179
18180 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
18181 self.focused_block = Some(focused_block);
18182 }
18183
18184 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
18185 self.focused_block.take()
18186 }
18187
18188 pub fn insert_creases(
18189 &mut self,
18190 creases: impl IntoIterator<Item = Crease<Anchor>>,
18191 cx: &mut Context<Self>,
18192 ) -> Vec<CreaseId> {
18193 self.display_map
18194 .update(cx, |map, cx| map.insert_creases(creases, cx))
18195 }
18196
18197 pub fn remove_creases(
18198 &mut self,
18199 ids: impl IntoIterator<Item = CreaseId>,
18200 cx: &mut Context<Self>,
18201 ) -> Vec<(CreaseId, Range<Anchor>)> {
18202 self.display_map
18203 .update(cx, |map, cx| map.remove_creases(ids, cx))
18204 }
18205
18206 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
18207 self.display_map
18208 .update(cx, |map, cx| map.snapshot(cx))
18209 .longest_row()
18210 }
18211
18212 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
18213 self.display_map
18214 .update(cx, |map, cx| map.snapshot(cx))
18215 .max_point()
18216 }
18217
18218 pub fn text(&self, cx: &App) -> String {
18219 self.buffer.read(cx).read(cx).text()
18220 }
18221
18222 pub fn is_empty(&self, cx: &App) -> bool {
18223 self.buffer.read(cx).read(cx).is_empty()
18224 }
18225
18226 pub fn text_option(&self, cx: &App) -> Option<String> {
18227 let text = self.text(cx);
18228 let text = text.trim();
18229
18230 if text.is_empty() {
18231 return None;
18232 }
18233
18234 Some(text.to_string())
18235 }
18236
18237 pub fn set_text(
18238 &mut self,
18239 text: impl Into<Arc<str>>,
18240 window: &mut Window,
18241 cx: &mut Context<Self>,
18242 ) {
18243 self.transact(window, cx, |this, _, cx| {
18244 this.buffer
18245 .read(cx)
18246 .as_singleton()
18247 .expect("you can only call set_text on editors for singleton buffers")
18248 .update(cx, |buffer, cx| buffer.set_text(text, cx));
18249 });
18250 }
18251
18252 pub fn display_text(&self, cx: &mut App) -> String {
18253 self.display_map
18254 .update(cx, |map, cx| map.snapshot(cx))
18255 .text()
18256 }
18257
18258 fn create_minimap(
18259 &self,
18260 minimap_settings: MinimapSettings,
18261 window: &mut Window,
18262 cx: &mut Context<Self>,
18263 ) -> Option<Entity<Self>> {
18264 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
18265 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
18266 }
18267
18268 fn initialize_new_minimap(
18269 &self,
18270 minimap_settings: MinimapSettings,
18271 window: &mut Window,
18272 cx: &mut Context<Self>,
18273 ) -> Entity<Self> {
18274 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
18275
18276 let mut minimap = Editor::new_internal(
18277 EditorMode::Minimap {
18278 parent: cx.weak_entity(),
18279 },
18280 self.buffer.clone(),
18281 None,
18282 Some(self.display_map.clone()),
18283 window,
18284 cx,
18285 );
18286 minimap.scroll_manager.clone_state(&self.scroll_manager);
18287 minimap.set_text_style_refinement(TextStyleRefinement {
18288 font_size: Some(MINIMAP_FONT_SIZE),
18289 font_weight: Some(MINIMAP_FONT_WEIGHT),
18290 ..Default::default()
18291 });
18292 minimap.update_minimap_configuration(minimap_settings, cx);
18293 cx.new(|_| minimap)
18294 }
18295
18296 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
18297 let current_line_highlight = minimap_settings
18298 .current_line_highlight
18299 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
18300 self.set_current_line_highlight(Some(current_line_highlight));
18301 }
18302
18303 pub fn minimap(&self) -> Option<&Entity<Self>> {
18304 self.minimap
18305 .as_ref()
18306 .filter(|_| self.minimap_visibility.visible())
18307 }
18308
18309 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
18310 let mut wrap_guides = smallvec![];
18311
18312 if self.show_wrap_guides == Some(false) {
18313 return wrap_guides;
18314 }
18315
18316 let settings = self.buffer.read(cx).language_settings(cx);
18317 if settings.show_wrap_guides {
18318 match self.soft_wrap_mode(cx) {
18319 SoftWrap::Column(soft_wrap) => {
18320 wrap_guides.push((soft_wrap as usize, true));
18321 }
18322 SoftWrap::Bounded(soft_wrap) => {
18323 wrap_guides.push((soft_wrap as usize, true));
18324 }
18325 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
18326 }
18327 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
18328 }
18329
18330 wrap_guides
18331 }
18332
18333 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
18334 let settings = self.buffer.read(cx).language_settings(cx);
18335 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
18336 match mode {
18337 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
18338 SoftWrap::None
18339 }
18340 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
18341 language_settings::SoftWrap::PreferredLineLength => {
18342 SoftWrap::Column(settings.preferred_line_length)
18343 }
18344 language_settings::SoftWrap::Bounded => {
18345 SoftWrap::Bounded(settings.preferred_line_length)
18346 }
18347 }
18348 }
18349
18350 pub fn set_soft_wrap_mode(
18351 &mut self,
18352 mode: language_settings::SoftWrap,
18353
18354 cx: &mut Context<Self>,
18355 ) {
18356 self.soft_wrap_mode_override = Some(mode);
18357 cx.notify();
18358 }
18359
18360 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
18361 self.hard_wrap = hard_wrap;
18362 cx.notify();
18363 }
18364
18365 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
18366 self.text_style_refinement = Some(style);
18367 }
18368
18369 /// called by the Element so we know what style we were most recently rendered with.
18370 pub(crate) fn set_style(
18371 &mut self,
18372 style: EditorStyle,
18373 window: &mut Window,
18374 cx: &mut Context<Self>,
18375 ) {
18376 // We intentionally do not inform the display map about the minimap style
18377 // so that wrapping is not recalculated and stays consistent for the editor
18378 // and its linked minimap.
18379 if !self.mode.is_minimap() {
18380 let rem_size = window.rem_size();
18381 self.display_map.update(cx, |map, cx| {
18382 map.set_font(
18383 style.text.font(),
18384 style.text.font_size.to_pixels(rem_size),
18385 cx,
18386 )
18387 });
18388 }
18389 self.style = Some(style);
18390 }
18391
18392 pub fn style(&self) -> Option<&EditorStyle> {
18393 self.style.as_ref()
18394 }
18395
18396 // Called by the element. This method is not designed to be called outside of the editor
18397 // element's layout code because it does not notify when rewrapping is computed synchronously.
18398 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
18399 self.display_map
18400 .update(cx, |map, cx| map.set_wrap_width(width, cx))
18401 }
18402
18403 pub fn set_soft_wrap(&mut self) {
18404 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
18405 }
18406
18407 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
18408 if self.soft_wrap_mode_override.is_some() {
18409 self.soft_wrap_mode_override.take();
18410 } else {
18411 let soft_wrap = match self.soft_wrap_mode(cx) {
18412 SoftWrap::GitDiff => return,
18413 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
18414 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
18415 language_settings::SoftWrap::None
18416 }
18417 };
18418 self.soft_wrap_mode_override = Some(soft_wrap);
18419 }
18420 cx.notify();
18421 }
18422
18423 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
18424 let Some(workspace) = self.workspace() else {
18425 return;
18426 };
18427 let fs = workspace.read(cx).app_state().fs.clone();
18428 let current_show = TabBarSettings::get_global(cx).show;
18429 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
18430 setting.show = Some(!current_show);
18431 });
18432 }
18433
18434 pub fn toggle_indent_guides(
18435 &mut self,
18436 _: &ToggleIndentGuides,
18437 _: &mut Window,
18438 cx: &mut Context<Self>,
18439 ) {
18440 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
18441 self.buffer
18442 .read(cx)
18443 .language_settings(cx)
18444 .indent_guides
18445 .enabled
18446 });
18447 self.show_indent_guides = Some(!currently_enabled);
18448 cx.notify();
18449 }
18450
18451 fn should_show_indent_guides(&self) -> Option<bool> {
18452 self.show_indent_guides
18453 }
18454
18455 pub fn toggle_line_numbers(
18456 &mut self,
18457 _: &ToggleLineNumbers,
18458 _: &mut Window,
18459 cx: &mut Context<Self>,
18460 ) {
18461 let mut editor_settings = EditorSettings::get_global(cx).clone();
18462 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
18463 EditorSettings::override_global(editor_settings, cx);
18464 }
18465
18466 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
18467 if let Some(show_line_numbers) = self.show_line_numbers {
18468 return show_line_numbers;
18469 }
18470 EditorSettings::get_global(cx).gutter.line_numbers
18471 }
18472
18473 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
18474 self.use_relative_line_numbers
18475 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
18476 }
18477
18478 pub fn toggle_relative_line_numbers(
18479 &mut self,
18480 _: &ToggleRelativeLineNumbers,
18481 _: &mut Window,
18482 cx: &mut Context<Self>,
18483 ) {
18484 let is_relative = self.should_use_relative_line_numbers(cx);
18485 self.set_relative_line_number(Some(!is_relative), cx)
18486 }
18487
18488 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
18489 self.use_relative_line_numbers = is_relative;
18490 cx.notify();
18491 }
18492
18493 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
18494 self.show_gutter = show_gutter;
18495 cx.notify();
18496 }
18497
18498 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
18499 self.show_scrollbars = ScrollbarAxes {
18500 horizontal: show,
18501 vertical: show,
18502 };
18503 cx.notify();
18504 }
18505
18506 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18507 self.show_scrollbars.vertical = show;
18508 cx.notify();
18509 }
18510
18511 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18512 self.show_scrollbars.horizontal = show;
18513 cx.notify();
18514 }
18515
18516 pub fn set_minimap_visibility(
18517 &mut self,
18518 minimap_visibility: MinimapVisibility,
18519 window: &mut Window,
18520 cx: &mut Context<Self>,
18521 ) {
18522 if self.minimap_visibility != minimap_visibility {
18523 if minimap_visibility.visible() && self.minimap.is_none() {
18524 let minimap_settings = EditorSettings::get_global(cx).minimap;
18525 self.minimap =
18526 self.create_minimap(minimap_settings.with_show_override(), window, cx);
18527 }
18528 self.minimap_visibility = minimap_visibility;
18529 cx.notify();
18530 }
18531 }
18532
18533 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18534 self.set_show_scrollbars(false, cx);
18535 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
18536 }
18537
18538 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18539 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
18540 }
18541
18542 /// Normally the text in full mode and auto height editors is padded on the
18543 /// left side by roughly half a character width for improved hit testing.
18544 ///
18545 /// Use this method to disable this for cases where this is not wanted (e.g.
18546 /// if you want to align the editor text with some other text above or below)
18547 /// or if you want to add this padding to single-line editors.
18548 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
18549 self.offset_content = offset_content;
18550 cx.notify();
18551 }
18552
18553 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
18554 self.show_line_numbers = Some(show_line_numbers);
18555 cx.notify();
18556 }
18557
18558 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
18559 self.disable_expand_excerpt_buttons = true;
18560 cx.notify();
18561 }
18562
18563 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
18564 self.show_git_diff_gutter = Some(show_git_diff_gutter);
18565 cx.notify();
18566 }
18567
18568 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
18569 self.show_code_actions = Some(show_code_actions);
18570 cx.notify();
18571 }
18572
18573 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
18574 self.show_runnables = Some(show_runnables);
18575 cx.notify();
18576 }
18577
18578 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
18579 self.show_breakpoints = Some(show_breakpoints);
18580 cx.notify();
18581 }
18582
18583 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
18584 if self.display_map.read(cx).masked != masked {
18585 self.display_map.update(cx, |map, _| map.masked = masked);
18586 }
18587 cx.notify()
18588 }
18589
18590 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
18591 self.show_wrap_guides = Some(show_wrap_guides);
18592 cx.notify();
18593 }
18594
18595 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
18596 self.show_indent_guides = Some(show_indent_guides);
18597 cx.notify();
18598 }
18599
18600 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
18601 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
18602 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
18603 if let Some(dir) = file.abs_path(cx).parent() {
18604 return Some(dir.to_owned());
18605 }
18606 }
18607
18608 if let Some(project_path) = buffer.read(cx).project_path(cx) {
18609 return Some(project_path.path.to_path_buf());
18610 }
18611 }
18612
18613 None
18614 }
18615
18616 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
18617 self.active_excerpt(cx)?
18618 .1
18619 .read(cx)
18620 .file()
18621 .and_then(|f| f.as_local())
18622 }
18623
18624 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18625 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18626 let buffer = buffer.read(cx);
18627 if let Some(project_path) = buffer.project_path(cx) {
18628 let project = self.project.as_ref()?.read(cx);
18629 project.absolute_path(&project_path, cx)
18630 } else {
18631 buffer
18632 .file()
18633 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
18634 }
18635 })
18636 }
18637
18638 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18639 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18640 let project_path = buffer.read(cx).project_path(cx)?;
18641 let project = self.project.as_ref()?.read(cx);
18642 let entry = project.entry_for_path(&project_path, cx)?;
18643 let path = entry.path.to_path_buf();
18644 Some(path)
18645 })
18646 }
18647
18648 pub fn reveal_in_finder(
18649 &mut self,
18650 _: &RevealInFileManager,
18651 _window: &mut Window,
18652 cx: &mut Context<Self>,
18653 ) {
18654 if let Some(target) = self.target_file(cx) {
18655 cx.reveal_path(&target.abs_path(cx));
18656 }
18657 }
18658
18659 pub fn copy_path(
18660 &mut self,
18661 _: &zed_actions::workspace::CopyPath,
18662 _window: &mut Window,
18663 cx: &mut Context<Self>,
18664 ) {
18665 if let Some(path) = self.target_file_abs_path(cx) {
18666 if let Some(path) = path.to_str() {
18667 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18668 }
18669 }
18670 }
18671
18672 pub fn copy_relative_path(
18673 &mut self,
18674 _: &zed_actions::workspace::CopyRelativePath,
18675 _window: &mut Window,
18676 cx: &mut Context<Self>,
18677 ) {
18678 if let Some(path) = self.target_file_path(cx) {
18679 if let Some(path) = path.to_str() {
18680 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18681 }
18682 }
18683 }
18684
18685 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
18686 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
18687 buffer.read(cx).project_path(cx)
18688 } else {
18689 None
18690 }
18691 }
18692
18693 // Returns true if the editor handled a go-to-line request
18694 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
18695 maybe!({
18696 let breakpoint_store = self.breakpoint_store.as_ref()?;
18697
18698 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
18699 else {
18700 self.clear_row_highlights::<ActiveDebugLine>();
18701 return None;
18702 };
18703
18704 let position = active_stack_frame.position;
18705 let buffer_id = position.buffer_id?;
18706 let snapshot = self
18707 .project
18708 .as_ref()?
18709 .read(cx)
18710 .buffer_for_id(buffer_id, cx)?
18711 .read(cx)
18712 .snapshot();
18713
18714 let mut handled = false;
18715 for (id, ExcerptRange { context, .. }) in
18716 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
18717 {
18718 if context.start.cmp(&position, &snapshot).is_ge()
18719 || context.end.cmp(&position, &snapshot).is_lt()
18720 {
18721 continue;
18722 }
18723 let snapshot = self.buffer.read(cx).snapshot(cx);
18724 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
18725
18726 handled = true;
18727 self.clear_row_highlights::<ActiveDebugLine>();
18728
18729 self.go_to_line::<ActiveDebugLine>(
18730 multibuffer_anchor,
18731 Some(cx.theme().colors().editor_debugger_active_line_background),
18732 window,
18733 cx,
18734 );
18735
18736 cx.notify();
18737 }
18738
18739 handled.then_some(())
18740 })
18741 .is_some()
18742 }
18743
18744 pub fn copy_file_name_without_extension(
18745 &mut self,
18746 _: &CopyFileNameWithoutExtension,
18747 _: &mut Window,
18748 cx: &mut Context<Self>,
18749 ) {
18750 if let Some(file) = self.target_file(cx) {
18751 if let Some(file_stem) = file.path().file_stem() {
18752 if let Some(name) = file_stem.to_str() {
18753 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18754 }
18755 }
18756 }
18757 }
18758
18759 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
18760 if let Some(file) = self.target_file(cx) {
18761 if let Some(file_name) = file.path().file_name() {
18762 if let Some(name) = file_name.to_str() {
18763 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18764 }
18765 }
18766 }
18767 }
18768
18769 pub fn toggle_git_blame(
18770 &mut self,
18771 _: &::git::Blame,
18772 window: &mut Window,
18773 cx: &mut Context<Self>,
18774 ) {
18775 self.show_git_blame_gutter = !self.show_git_blame_gutter;
18776
18777 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
18778 self.start_git_blame(true, window, cx);
18779 }
18780
18781 cx.notify();
18782 }
18783
18784 pub fn toggle_git_blame_inline(
18785 &mut self,
18786 _: &ToggleGitBlameInline,
18787 window: &mut Window,
18788 cx: &mut Context<Self>,
18789 ) {
18790 self.toggle_git_blame_inline_internal(true, window, cx);
18791 cx.notify();
18792 }
18793
18794 pub fn open_git_blame_commit(
18795 &mut self,
18796 _: &OpenGitBlameCommit,
18797 window: &mut Window,
18798 cx: &mut Context<Self>,
18799 ) {
18800 self.open_git_blame_commit_internal(window, cx);
18801 }
18802
18803 fn open_git_blame_commit_internal(
18804 &mut self,
18805 window: &mut Window,
18806 cx: &mut Context<Self>,
18807 ) -> Option<()> {
18808 let blame = self.blame.as_ref()?;
18809 let snapshot = self.snapshot(window, cx);
18810 let cursor = self.selections.newest::<Point>(cx).head();
18811 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
18812 let blame_entry = blame
18813 .update(cx, |blame, cx| {
18814 blame
18815 .blame_for_rows(
18816 &[RowInfo {
18817 buffer_id: Some(buffer.remote_id()),
18818 buffer_row: Some(point.row),
18819 ..Default::default()
18820 }],
18821 cx,
18822 )
18823 .next()
18824 })
18825 .flatten()?;
18826 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
18827 let repo = blame.read(cx).repository(cx)?;
18828 let workspace = self.workspace()?.downgrade();
18829 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
18830 None
18831 }
18832
18833 pub fn git_blame_inline_enabled(&self) -> bool {
18834 self.git_blame_inline_enabled
18835 }
18836
18837 pub fn toggle_selection_menu(
18838 &mut self,
18839 _: &ToggleSelectionMenu,
18840 _: &mut Window,
18841 cx: &mut Context<Self>,
18842 ) {
18843 self.show_selection_menu = self
18844 .show_selection_menu
18845 .map(|show_selections_menu| !show_selections_menu)
18846 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
18847
18848 cx.notify();
18849 }
18850
18851 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
18852 self.show_selection_menu
18853 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
18854 }
18855
18856 fn start_git_blame(
18857 &mut self,
18858 user_triggered: bool,
18859 window: &mut Window,
18860 cx: &mut Context<Self>,
18861 ) {
18862 if let Some(project) = self.project.as_ref() {
18863 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
18864 return;
18865 };
18866
18867 if buffer.read(cx).file().is_none() {
18868 return;
18869 }
18870
18871 let focused = self.focus_handle(cx).contains_focused(window, cx);
18872
18873 let project = project.clone();
18874 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
18875 self.blame_subscription =
18876 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
18877 self.blame = Some(blame);
18878 }
18879 }
18880
18881 fn toggle_git_blame_inline_internal(
18882 &mut self,
18883 user_triggered: bool,
18884 window: &mut Window,
18885 cx: &mut Context<Self>,
18886 ) {
18887 if self.git_blame_inline_enabled {
18888 self.git_blame_inline_enabled = false;
18889 self.show_git_blame_inline = false;
18890 self.show_git_blame_inline_delay_task.take();
18891 } else {
18892 self.git_blame_inline_enabled = true;
18893 self.start_git_blame_inline(user_triggered, window, cx);
18894 }
18895
18896 cx.notify();
18897 }
18898
18899 fn start_git_blame_inline(
18900 &mut self,
18901 user_triggered: bool,
18902 window: &mut Window,
18903 cx: &mut Context<Self>,
18904 ) {
18905 self.start_git_blame(user_triggered, window, cx);
18906
18907 if ProjectSettings::get_global(cx)
18908 .git
18909 .inline_blame_delay()
18910 .is_some()
18911 {
18912 self.start_inline_blame_timer(window, cx);
18913 } else {
18914 self.show_git_blame_inline = true
18915 }
18916 }
18917
18918 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
18919 self.blame.as_ref()
18920 }
18921
18922 pub fn show_git_blame_gutter(&self) -> bool {
18923 self.show_git_blame_gutter
18924 }
18925
18926 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
18927 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
18928 }
18929
18930 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
18931 self.show_git_blame_inline
18932 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
18933 && !self.newest_selection_head_on_empty_line(cx)
18934 && self.has_blame_entries(cx)
18935 }
18936
18937 fn has_blame_entries(&self, cx: &App) -> bool {
18938 self.blame()
18939 .map_or(false, |blame| blame.read(cx).has_generated_entries())
18940 }
18941
18942 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
18943 let cursor_anchor = self.selections.newest_anchor().head();
18944
18945 let snapshot = self.buffer.read(cx).snapshot(cx);
18946 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
18947
18948 snapshot.line_len(buffer_row) == 0
18949 }
18950
18951 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
18952 let buffer_and_selection = maybe!({
18953 let selection = self.selections.newest::<Point>(cx);
18954 let selection_range = selection.range();
18955
18956 let multi_buffer = self.buffer().read(cx);
18957 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18958 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
18959
18960 let (buffer, range, _) = if selection.reversed {
18961 buffer_ranges.first()
18962 } else {
18963 buffer_ranges.last()
18964 }?;
18965
18966 let selection = text::ToPoint::to_point(&range.start, &buffer).row
18967 ..text::ToPoint::to_point(&range.end, &buffer).row;
18968 Some((
18969 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
18970 selection,
18971 ))
18972 });
18973
18974 let Some((buffer, selection)) = buffer_and_selection else {
18975 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
18976 };
18977
18978 let Some(project) = self.project.as_ref() else {
18979 return Task::ready(Err(anyhow!("editor does not have project")));
18980 };
18981
18982 project.update(cx, |project, cx| {
18983 project.get_permalink_to_line(&buffer, selection, cx)
18984 })
18985 }
18986
18987 pub fn copy_permalink_to_line(
18988 &mut self,
18989 _: &CopyPermalinkToLine,
18990 window: &mut Window,
18991 cx: &mut Context<Self>,
18992 ) {
18993 let permalink_task = self.get_permalink_to_line(cx);
18994 let workspace = self.workspace();
18995
18996 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18997 Ok(permalink) => {
18998 cx.update(|_, cx| {
18999 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
19000 })
19001 .ok();
19002 }
19003 Err(err) => {
19004 let message = format!("Failed to copy permalink: {err}");
19005
19006 anyhow::Result::<()>::Err(err).log_err();
19007
19008 if let Some(workspace) = workspace {
19009 workspace
19010 .update_in(cx, |workspace, _, cx| {
19011 struct CopyPermalinkToLine;
19012
19013 workspace.show_toast(
19014 Toast::new(
19015 NotificationId::unique::<CopyPermalinkToLine>(),
19016 message,
19017 ),
19018 cx,
19019 )
19020 })
19021 .ok();
19022 }
19023 }
19024 })
19025 .detach();
19026 }
19027
19028 pub fn copy_file_location(
19029 &mut self,
19030 _: &CopyFileLocation,
19031 _: &mut Window,
19032 cx: &mut Context<Self>,
19033 ) {
19034 let selection = self.selections.newest::<Point>(cx).start.row + 1;
19035 if let Some(file) = self.target_file(cx) {
19036 if let Some(path) = file.path().to_str() {
19037 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
19038 }
19039 }
19040 }
19041
19042 pub fn open_permalink_to_line(
19043 &mut self,
19044 _: &OpenPermalinkToLine,
19045 window: &mut Window,
19046 cx: &mut Context<Self>,
19047 ) {
19048 let permalink_task = self.get_permalink_to_line(cx);
19049 let workspace = self.workspace();
19050
19051 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19052 Ok(permalink) => {
19053 cx.update(|_, cx| {
19054 cx.open_url(permalink.as_ref());
19055 })
19056 .ok();
19057 }
19058 Err(err) => {
19059 let message = format!("Failed to open permalink: {err}");
19060
19061 anyhow::Result::<()>::Err(err).log_err();
19062
19063 if let Some(workspace) = workspace {
19064 workspace
19065 .update(cx, |workspace, cx| {
19066 struct OpenPermalinkToLine;
19067
19068 workspace.show_toast(
19069 Toast::new(
19070 NotificationId::unique::<OpenPermalinkToLine>(),
19071 message,
19072 ),
19073 cx,
19074 )
19075 })
19076 .ok();
19077 }
19078 }
19079 })
19080 .detach();
19081 }
19082
19083 pub fn insert_uuid_v4(
19084 &mut self,
19085 _: &InsertUuidV4,
19086 window: &mut Window,
19087 cx: &mut Context<Self>,
19088 ) {
19089 self.insert_uuid(UuidVersion::V4, window, cx);
19090 }
19091
19092 pub fn insert_uuid_v7(
19093 &mut self,
19094 _: &InsertUuidV7,
19095 window: &mut Window,
19096 cx: &mut Context<Self>,
19097 ) {
19098 self.insert_uuid(UuidVersion::V7, window, cx);
19099 }
19100
19101 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
19102 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19103 self.transact(window, cx, |this, window, cx| {
19104 let edits = this
19105 .selections
19106 .all::<Point>(cx)
19107 .into_iter()
19108 .map(|selection| {
19109 let uuid = match version {
19110 UuidVersion::V4 => uuid::Uuid::new_v4(),
19111 UuidVersion::V7 => uuid::Uuid::now_v7(),
19112 };
19113
19114 (selection.range(), uuid.to_string())
19115 });
19116 this.edit(edits, cx);
19117 this.refresh_edit_prediction(true, false, window, cx);
19118 });
19119 }
19120
19121 pub fn open_selections_in_multibuffer(
19122 &mut self,
19123 _: &OpenSelectionsInMultibuffer,
19124 window: &mut Window,
19125 cx: &mut Context<Self>,
19126 ) {
19127 let multibuffer = self.buffer.read(cx);
19128
19129 let Some(buffer) = multibuffer.as_singleton() else {
19130 return;
19131 };
19132
19133 let Some(workspace) = self.workspace() else {
19134 return;
19135 };
19136
19137 let title = multibuffer.title(cx).to_string();
19138
19139 let locations = self
19140 .selections
19141 .all_anchors(cx)
19142 .into_iter()
19143 .map(|selection| Location {
19144 buffer: buffer.clone(),
19145 range: selection.start.text_anchor..selection.end.text_anchor,
19146 })
19147 .collect::<Vec<_>>();
19148
19149 cx.spawn_in(window, async move |_, cx| {
19150 workspace.update_in(cx, |workspace, window, cx| {
19151 Self::open_locations_in_multibuffer(
19152 workspace,
19153 locations,
19154 format!("Selections for '{title}'"),
19155 false,
19156 MultibufferSelectionMode::All,
19157 window,
19158 cx,
19159 );
19160 })
19161 })
19162 .detach();
19163 }
19164
19165 /// Adds a row highlight for the given range. If a row has multiple highlights, the
19166 /// last highlight added will be used.
19167 ///
19168 /// If the range ends at the beginning of a line, then that line will not be highlighted.
19169 pub fn highlight_rows<T: 'static>(
19170 &mut self,
19171 range: Range<Anchor>,
19172 color: Hsla,
19173 options: RowHighlightOptions,
19174 cx: &mut Context<Self>,
19175 ) {
19176 let snapshot = self.buffer().read(cx).snapshot(cx);
19177 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19178 let ix = row_highlights.binary_search_by(|highlight| {
19179 Ordering::Equal
19180 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
19181 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
19182 });
19183
19184 if let Err(mut ix) = ix {
19185 let index = post_inc(&mut self.highlight_order);
19186
19187 // If this range intersects with the preceding highlight, then merge it with
19188 // the preceding highlight. Otherwise insert a new highlight.
19189 let mut merged = false;
19190 if ix > 0 {
19191 let prev_highlight = &mut row_highlights[ix - 1];
19192 if prev_highlight
19193 .range
19194 .end
19195 .cmp(&range.start, &snapshot)
19196 .is_ge()
19197 {
19198 ix -= 1;
19199 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
19200 prev_highlight.range.end = range.end;
19201 }
19202 merged = true;
19203 prev_highlight.index = index;
19204 prev_highlight.color = color;
19205 prev_highlight.options = options;
19206 }
19207 }
19208
19209 if !merged {
19210 row_highlights.insert(
19211 ix,
19212 RowHighlight {
19213 range: range.clone(),
19214 index,
19215 color,
19216 options,
19217 type_id: TypeId::of::<T>(),
19218 },
19219 );
19220 }
19221
19222 // If any of the following highlights intersect with this one, merge them.
19223 while let Some(next_highlight) = row_highlights.get(ix + 1) {
19224 let highlight = &row_highlights[ix];
19225 if next_highlight
19226 .range
19227 .start
19228 .cmp(&highlight.range.end, &snapshot)
19229 .is_le()
19230 {
19231 if next_highlight
19232 .range
19233 .end
19234 .cmp(&highlight.range.end, &snapshot)
19235 .is_gt()
19236 {
19237 row_highlights[ix].range.end = next_highlight.range.end;
19238 }
19239 row_highlights.remove(ix + 1);
19240 } else {
19241 break;
19242 }
19243 }
19244 }
19245 }
19246
19247 /// Remove any highlighted row ranges of the given type that intersect the
19248 /// given ranges.
19249 pub fn remove_highlighted_rows<T: 'static>(
19250 &mut self,
19251 ranges_to_remove: Vec<Range<Anchor>>,
19252 cx: &mut Context<Self>,
19253 ) {
19254 let snapshot = self.buffer().read(cx).snapshot(cx);
19255 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19256 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19257 row_highlights.retain(|highlight| {
19258 while let Some(range_to_remove) = ranges_to_remove.peek() {
19259 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
19260 Ordering::Less | Ordering::Equal => {
19261 ranges_to_remove.next();
19262 }
19263 Ordering::Greater => {
19264 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
19265 Ordering::Less | Ordering::Equal => {
19266 return false;
19267 }
19268 Ordering::Greater => break,
19269 }
19270 }
19271 }
19272 }
19273
19274 true
19275 })
19276 }
19277
19278 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
19279 pub fn clear_row_highlights<T: 'static>(&mut self) {
19280 self.highlighted_rows.remove(&TypeId::of::<T>());
19281 }
19282
19283 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
19284 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
19285 self.highlighted_rows
19286 .get(&TypeId::of::<T>())
19287 .map_or(&[] as &[_], |vec| vec.as_slice())
19288 .iter()
19289 .map(|highlight| (highlight.range.clone(), highlight.color))
19290 }
19291
19292 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
19293 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
19294 /// Allows to ignore certain kinds of highlights.
19295 pub fn highlighted_display_rows(
19296 &self,
19297 window: &mut Window,
19298 cx: &mut App,
19299 ) -> BTreeMap<DisplayRow, LineHighlight> {
19300 let snapshot = self.snapshot(window, cx);
19301 let mut used_highlight_orders = HashMap::default();
19302 self.highlighted_rows
19303 .iter()
19304 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
19305 .fold(
19306 BTreeMap::<DisplayRow, LineHighlight>::new(),
19307 |mut unique_rows, highlight| {
19308 let start = highlight.range.start.to_display_point(&snapshot);
19309 let end = highlight.range.end.to_display_point(&snapshot);
19310 let start_row = start.row().0;
19311 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
19312 && end.column() == 0
19313 {
19314 end.row().0.saturating_sub(1)
19315 } else {
19316 end.row().0
19317 };
19318 for row in start_row..=end_row {
19319 let used_index =
19320 used_highlight_orders.entry(row).or_insert(highlight.index);
19321 if highlight.index >= *used_index {
19322 *used_index = highlight.index;
19323 unique_rows.insert(
19324 DisplayRow(row),
19325 LineHighlight {
19326 include_gutter: highlight.options.include_gutter,
19327 border: None,
19328 background: highlight.color.into(),
19329 type_id: Some(highlight.type_id),
19330 },
19331 );
19332 }
19333 }
19334 unique_rows
19335 },
19336 )
19337 }
19338
19339 pub fn highlighted_display_row_for_autoscroll(
19340 &self,
19341 snapshot: &DisplaySnapshot,
19342 ) -> Option<DisplayRow> {
19343 self.highlighted_rows
19344 .values()
19345 .flat_map(|highlighted_rows| highlighted_rows.iter())
19346 .filter_map(|highlight| {
19347 if highlight.options.autoscroll {
19348 Some(highlight.range.start.to_display_point(snapshot).row())
19349 } else {
19350 None
19351 }
19352 })
19353 .min()
19354 }
19355
19356 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
19357 self.highlight_background::<SearchWithinRange>(
19358 ranges,
19359 |colors| colors.colors().editor_document_highlight_read_background,
19360 cx,
19361 )
19362 }
19363
19364 pub fn set_breadcrumb_header(&mut self, new_header: String) {
19365 self.breadcrumb_header = Some(new_header);
19366 }
19367
19368 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
19369 self.clear_background_highlights::<SearchWithinRange>(cx);
19370 }
19371
19372 pub fn highlight_background<T: 'static>(
19373 &mut self,
19374 ranges: &[Range<Anchor>],
19375 color_fetcher: fn(&Theme) -> Hsla,
19376 cx: &mut Context<Self>,
19377 ) {
19378 self.background_highlights.insert(
19379 HighlightKey::Type(TypeId::of::<T>()),
19380 (color_fetcher, Arc::from(ranges)),
19381 );
19382 self.scrollbar_marker_state.dirty = true;
19383 cx.notify();
19384 }
19385
19386 pub fn highlight_background_key<T: 'static>(
19387 &mut self,
19388 key: usize,
19389 ranges: &[Range<Anchor>],
19390 color_fetcher: fn(&Theme) -> Hsla,
19391 cx: &mut Context<Self>,
19392 ) {
19393 self.background_highlights.insert(
19394 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19395 (color_fetcher, Arc::from(ranges)),
19396 );
19397 self.scrollbar_marker_state.dirty = true;
19398 cx.notify();
19399 }
19400
19401 pub fn clear_background_highlights<T: 'static>(
19402 &mut self,
19403 cx: &mut Context<Self>,
19404 ) -> Option<BackgroundHighlight> {
19405 let text_highlights = self
19406 .background_highlights
19407 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
19408 if !text_highlights.1.is_empty() {
19409 self.scrollbar_marker_state.dirty = true;
19410 cx.notify();
19411 }
19412 Some(text_highlights)
19413 }
19414
19415 pub fn highlight_gutter<T: 'static>(
19416 &mut self,
19417 ranges: impl Into<Vec<Range<Anchor>>>,
19418 color_fetcher: fn(&App) -> Hsla,
19419 cx: &mut Context<Self>,
19420 ) {
19421 self.gutter_highlights
19422 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
19423 cx.notify();
19424 }
19425
19426 pub fn clear_gutter_highlights<T: 'static>(
19427 &mut self,
19428 cx: &mut Context<Self>,
19429 ) -> Option<GutterHighlight> {
19430 cx.notify();
19431 self.gutter_highlights.remove(&TypeId::of::<T>())
19432 }
19433
19434 pub fn insert_gutter_highlight<T: 'static>(
19435 &mut self,
19436 range: Range<Anchor>,
19437 color_fetcher: fn(&App) -> Hsla,
19438 cx: &mut Context<Self>,
19439 ) {
19440 let snapshot = self.buffer().read(cx).snapshot(cx);
19441 let mut highlights = self
19442 .gutter_highlights
19443 .remove(&TypeId::of::<T>())
19444 .map(|(_, highlights)| highlights)
19445 .unwrap_or_default();
19446 let ix = highlights.binary_search_by(|highlight| {
19447 Ordering::Equal
19448 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
19449 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
19450 });
19451 if let Err(ix) = ix {
19452 highlights.insert(ix, range);
19453 }
19454 self.gutter_highlights
19455 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
19456 }
19457
19458 pub fn remove_gutter_highlights<T: 'static>(
19459 &mut self,
19460 ranges_to_remove: Vec<Range<Anchor>>,
19461 cx: &mut Context<Self>,
19462 ) {
19463 let snapshot = self.buffer().read(cx).snapshot(cx);
19464 let Some((color_fetcher, mut gutter_highlights)) =
19465 self.gutter_highlights.remove(&TypeId::of::<T>())
19466 else {
19467 return;
19468 };
19469 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19470 gutter_highlights.retain(|highlight| {
19471 while let Some(range_to_remove) = ranges_to_remove.peek() {
19472 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
19473 Ordering::Less | Ordering::Equal => {
19474 ranges_to_remove.next();
19475 }
19476 Ordering::Greater => {
19477 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
19478 Ordering::Less | Ordering::Equal => {
19479 return false;
19480 }
19481 Ordering::Greater => break,
19482 }
19483 }
19484 }
19485 }
19486
19487 true
19488 });
19489 self.gutter_highlights
19490 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
19491 }
19492
19493 #[cfg(feature = "test-support")]
19494 pub fn all_text_highlights(
19495 &self,
19496 window: &mut Window,
19497 cx: &mut Context<Self>,
19498 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
19499 let snapshot = self.snapshot(window, cx);
19500 self.display_map.update(cx, |display_map, _| {
19501 display_map
19502 .all_text_highlights()
19503 .map(|highlight| {
19504 let (style, ranges) = highlight.as_ref();
19505 (
19506 *style,
19507 ranges
19508 .iter()
19509 .map(|range| range.clone().to_display_points(&snapshot))
19510 .collect(),
19511 )
19512 })
19513 .collect()
19514 })
19515 }
19516
19517 #[cfg(feature = "test-support")]
19518 pub fn all_text_background_highlights(
19519 &self,
19520 window: &mut Window,
19521 cx: &mut Context<Self>,
19522 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19523 let snapshot = self.snapshot(window, cx);
19524 let buffer = &snapshot.buffer_snapshot;
19525 let start = buffer.anchor_before(0);
19526 let end = buffer.anchor_after(buffer.len());
19527 self.background_highlights_in_range(start..end, &snapshot, cx.theme())
19528 }
19529
19530 #[cfg(feature = "test-support")]
19531 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
19532 let snapshot = self.buffer().read(cx).snapshot(cx);
19533
19534 let highlights = self
19535 .background_highlights
19536 .get(&HighlightKey::Type(TypeId::of::<
19537 items::BufferSearchHighlights,
19538 >()));
19539
19540 if let Some((_color, ranges)) = highlights {
19541 ranges
19542 .iter()
19543 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
19544 .collect_vec()
19545 } else {
19546 vec![]
19547 }
19548 }
19549
19550 fn document_highlights_for_position<'a>(
19551 &'a self,
19552 position: Anchor,
19553 buffer: &'a MultiBufferSnapshot,
19554 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
19555 let read_highlights = self
19556 .background_highlights
19557 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
19558 .map(|h| &h.1);
19559 let write_highlights = self
19560 .background_highlights
19561 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
19562 .map(|h| &h.1);
19563 let left_position = position.bias_left(buffer);
19564 let right_position = position.bias_right(buffer);
19565 read_highlights
19566 .into_iter()
19567 .chain(write_highlights)
19568 .flat_map(move |ranges| {
19569 let start_ix = match ranges.binary_search_by(|probe| {
19570 let cmp = probe.end.cmp(&left_position, buffer);
19571 if cmp.is_ge() {
19572 Ordering::Greater
19573 } else {
19574 Ordering::Less
19575 }
19576 }) {
19577 Ok(i) | Err(i) => i,
19578 };
19579
19580 ranges[start_ix..]
19581 .iter()
19582 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
19583 })
19584 }
19585
19586 pub fn has_background_highlights<T: 'static>(&self) -> bool {
19587 self.background_highlights
19588 .get(&HighlightKey::Type(TypeId::of::<T>()))
19589 .map_or(false, |(_, highlights)| !highlights.is_empty())
19590 }
19591
19592 pub fn background_highlights_in_range(
19593 &self,
19594 search_range: Range<Anchor>,
19595 display_snapshot: &DisplaySnapshot,
19596 theme: &Theme,
19597 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19598 let mut results = Vec::new();
19599 for (color_fetcher, ranges) in self.background_highlights.values() {
19600 let color = color_fetcher(theme);
19601 let start_ix = match ranges.binary_search_by(|probe| {
19602 let cmp = probe
19603 .end
19604 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19605 if cmp.is_gt() {
19606 Ordering::Greater
19607 } else {
19608 Ordering::Less
19609 }
19610 }) {
19611 Ok(i) | Err(i) => i,
19612 };
19613 for range in &ranges[start_ix..] {
19614 if range
19615 .start
19616 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19617 .is_ge()
19618 {
19619 break;
19620 }
19621
19622 let start = range.start.to_display_point(display_snapshot);
19623 let end = range.end.to_display_point(display_snapshot);
19624 results.push((start..end, color))
19625 }
19626 }
19627 results
19628 }
19629
19630 pub fn background_highlight_row_ranges<T: 'static>(
19631 &self,
19632 search_range: Range<Anchor>,
19633 display_snapshot: &DisplaySnapshot,
19634 count: usize,
19635 ) -> Vec<RangeInclusive<DisplayPoint>> {
19636 let mut results = Vec::new();
19637 let Some((_, ranges)) = self
19638 .background_highlights
19639 .get(&HighlightKey::Type(TypeId::of::<T>()))
19640 else {
19641 return vec![];
19642 };
19643
19644 let start_ix = match ranges.binary_search_by(|probe| {
19645 let cmp = probe
19646 .end
19647 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19648 if cmp.is_gt() {
19649 Ordering::Greater
19650 } else {
19651 Ordering::Less
19652 }
19653 }) {
19654 Ok(i) | Err(i) => i,
19655 };
19656 let mut push_region = |start: Option<Point>, end: Option<Point>| {
19657 if let (Some(start_display), Some(end_display)) = (start, end) {
19658 results.push(
19659 start_display.to_display_point(display_snapshot)
19660 ..=end_display.to_display_point(display_snapshot),
19661 );
19662 }
19663 };
19664 let mut start_row: Option<Point> = None;
19665 let mut end_row: Option<Point> = None;
19666 if ranges.len() > count {
19667 return Vec::new();
19668 }
19669 for range in &ranges[start_ix..] {
19670 if range
19671 .start
19672 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19673 .is_ge()
19674 {
19675 break;
19676 }
19677 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
19678 if let Some(current_row) = &end_row {
19679 if end.row == current_row.row {
19680 continue;
19681 }
19682 }
19683 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
19684 if start_row.is_none() {
19685 assert_eq!(end_row, None);
19686 start_row = Some(start);
19687 end_row = Some(end);
19688 continue;
19689 }
19690 if let Some(current_end) = end_row.as_mut() {
19691 if start.row > current_end.row + 1 {
19692 push_region(start_row, end_row);
19693 start_row = Some(start);
19694 end_row = Some(end);
19695 } else {
19696 // Merge two hunks.
19697 *current_end = end;
19698 }
19699 } else {
19700 unreachable!();
19701 }
19702 }
19703 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
19704 push_region(start_row, end_row);
19705 results
19706 }
19707
19708 pub fn gutter_highlights_in_range(
19709 &self,
19710 search_range: Range<Anchor>,
19711 display_snapshot: &DisplaySnapshot,
19712 cx: &App,
19713 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19714 let mut results = Vec::new();
19715 for (color_fetcher, ranges) in self.gutter_highlights.values() {
19716 let color = color_fetcher(cx);
19717 let start_ix = match ranges.binary_search_by(|probe| {
19718 let cmp = probe
19719 .end
19720 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19721 if cmp.is_gt() {
19722 Ordering::Greater
19723 } else {
19724 Ordering::Less
19725 }
19726 }) {
19727 Ok(i) | Err(i) => i,
19728 };
19729 for range in &ranges[start_ix..] {
19730 if range
19731 .start
19732 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19733 .is_ge()
19734 {
19735 break;
19736 }
19737
19738 let start = range.start.to_display_point(display_snapshot);
19739 let end = range.end.to_display_point(display_snapshot);
19740 results.push((start..end, color))
19741 }
19742 }
19743 results
19744 }
19745
19746 /// Get the text ranges corresponding to the redaction query
19747 pub fn redacted_ranges(
19748 &self,
19749 search_range: Range<Anchor>,
19750 display_snapshot: &DisplaySnapshot,
19751 cx: &App,
19752 ) -> Vec<Range<DisplayPoint>> {
19753 display_snapshot
19754 .buffer_snapshot
19755 .redacted_ranges(search_range, |file| {
19756 if let Some(file) = file {
19757 file.is_private()
19758 && EditorSettings::get(
19759 Some(SettingsLocation {
19760 worktree_id: file.worktree_id(cx),
19761 path: file.path().as_ref(),
19762 }),
19763 cx,
19764 )
19765 .redact_private_values
19766 } else {
19767 false
19768 }
19769 })
19770 .map(|range| {
19771 range.start.to_display_point(display_snapshot)
19772 ..range.end.to_display_point(display_snapshot)
19773 })
19774 .collect()
19775 }
19776
19777 pub fn highlight_text_key<T: 'static>(
19778 &mut self,
19779 key: usize,
19780 ranges: Vec<Range<Anchor>>,
19781 style: HighlightStyle,
19782 cx: &mut Context<Self>,
19783 ) {
19784 self.display_map.update(cx, |map, _| {
19785 map.highlight_text(
19786 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19787 ranges,
19788 style,
19789 );
19790 });
19791 cx.notify();
19792 }
19793
19794 pub fn highlight_text<T: 'static>(
19795 &mut self,
19796 ranges: Vec<Range<Anchor>>,
19797 style: HighlightStyle,
19798 cx: &mut Context<Self>,
19799 ) {
19800 self.display_map.update(cx, |map, _| {
19801 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
19802 });
19803 cx.notify();
19804 }
19805
19806 pub(crate) fn highlight_inlays<T: 'static>(
19807 &mut self,
19808 highlights: Vec<InlayHighlight>,
19809 style: HighlightStyle,
19810 cx: &mut Context<Self>,
19811 ) {
19812 self.display_map.update(cx, |map, _| {
19813 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
19814 });
19815 cx.notify();
19816 }
19817
19818 pub fn text_highlights<'a, T: 'static>(
19819 &'a self,
19820 cx: &'a App,
19821 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
19822 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
19823 }
19824
19825 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
19826 let cleared = self
19827 .display_map
19828 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
19829 if cleared {
19830 cx.notify();
19831 }
19832 }
19833
19834 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
19835 (self.read_only(cx) || self.blink_manager.read(cx).visible())
19836 && self.focus_handle.is_focused(window)
19837 }
19838
19839 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
19840 self.show_cursor_when_unfocused = is_enabled;
19841 cx.notify();
19842 }
19843
19844 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
19845 cx.notify();
19846 }
19847
19848 fn on_debug_session_event(
19849 &mut self,
19850 _session: Entity<Session>,
19851 event: &SessionEvent,
19852 cx: &mut Context<Self>,
19853 ) {
19854 match event {
19855 SessionEvent::InvalidateInlineValue => {
19856 self.refresh_inline_values(cx);
19857 }
19858 _ => {}
19859 }
19860 }
19861
19862 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
19863 let Some(project) = self.project.clone() else {
19864 return;
19865 };
19866
19867 if !self.inline_value_cache.enabled {
19868 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
19869 self.splice_inlays(&inlays, Vec::new(), cx);
19870 return;
19871 }
19872
19873 let current_execution_position = self
19874 .highlighted_rows
19875 .get(&TypeId::of::<ActiveDebugLine>())
19876 .and_then(|lines| lines.last().map(|line| line.range.end));
19877
19878 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
19879 let inline_values = editor
19880 .update(cx, |editor, cx| {
19881 let Some(current_execution_position) = current_execution_position else {
19882 return Some(Task::ready(Ok(Vec::new())));
19883 };
19884
19885 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
19886 let snapshot = buffer.snapshot(cx);
19887
19888 let excerpt = snapshot.excerpt_containing(
19889 current_execution_position..current_execution_position,
19890 )?;
19891
19892 editor.buffer.read(cx).buffer(excerpt.buffer_id())
19893 })?;
19894
19895 let range =
19896 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
19897
19898 project.inline_values(buffer, range, cx)
19899 })
19900 .ok()
19901 .flatten()?
19902 .await
19903 .context("refreshing debugger inlays")
19904 .log_err()?;
19905
19906 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
19907
19908 for (buffer_id, inline_value) in inline_values
19909 .into_iter()
19910 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
19911 {
19912 buffer_inline_values
19913 .entry(buffer_id)
19914 .or_default()
19915 .push(inline_value);
19916 }
19917
19918 editor
19919 .update(cx, |editor, cx| {
19920 let snapshot = editor.buffer.read(cx).snapshot(cx);
19921 let mut new_inlays = Vec::default();
19922
19923 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
19924 let buffer_id = buffer_snapshot.remote_id();
19925 buffer_inline_values
19926 .get(&buffer_id)
19927 .into_iter()
19928 .flatten()
19929 .for_each(|hint| {
19930 let inlay = Inlay::debugger(
19931 post_inc(&mut editor.next_inlay_id),
19932 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
19933 hint.text(),
19934 );
19935 if !inlay.text.chars().contains(&'\n') {
19936 new_inlays.push(inlay);
19937 }
19938 });
19939 }
19940
19941 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
19942 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
19943
19944 editor.splice_inlays(&inlay_ids, new_inlays, cx);
19945 })
19946 .ok()?;
19947 Some(())
19948 });
19949 }
19950
19951 fn on_buffer_event(
19952 &mut self,
19953 multibuffer: &Entity<MultiBuffer>,
19954 event: &multi_buffer::Event,
19955 window: &mut Window,
19956 cx: &mut Context<Self>,
19957 ) {
19958 match event {
19959 multi_buffer::Event::Edited {
19960 singleton_buffer_edited,
19961 edited_buffer,
19962 } => {
19963 self.scrollbar_marker_state.dirty = true;
19964 self.active_indent_guides_state.dirty = true;
19965 self.refresh_active_diagnostics(cx);
19966 self.refresh_code_actions(window, cx);
19967 self.refresh_selected_text_highlights(true, window, cx);
19968 self.refresh_single_line_folds(window, cx);
19969 refresh_matching_bracket_highlights(self, window, cx);
19970 if self.has_active_edit_prediction() {
19971 self.update_visible_edit_prediction(window, cx);
19972 }
19973 if let Some(project) = self.project.as_ref() {
19974 if let Some(edited_buffer) = edited_buffer {
19975 project.update(cx, |project, cx| {
19976 self.registered_buffers
19977 .entry(edited_buffer.read(cx).remote_id())
19978 .or_insert_with(|| {
19979 project
19980 .register_buffer_with_language_servers(&edited_buffer, cx)
19981 });
19982 });
19983 }
19984 }
19985 cx.emit(EditorEvent::BufferEdited);
19986 cx.emit(SearchEvent::MatchesInvalidated);
19987
19988 if let Some(buffer) = edited_buffer {
19989 self.update_lsp_data(false, Some(buffer.read(cx).remote_id()), window, cx);
19990 }
19991
19992 if *singleton_buffer_edited {
19993 if let Some(buffer) = edited_buffer {
19994 if buffer.read(cx).file().is_none() {
19995 cx.emit(EditorEvent::TitleChanged);
19996 }
19997 }
19998 if let Some(project) = &self.project {
19999 #[allow(clippy::mutable_key_type)]
20000 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
20001 multibuffer
20002 .all_buffers()
20003 .into_iter()
20004 .filter_map(|buffer| {
20005 buffer.update(cx, |buffer, cx| {
20006 let language = buffer.language()?;
20007 let should_discard = project.update(cx, |project, cx| {
20008 project.is_local()
20009 && !project.has_language_servers_for(buffer, cx)
20010 });
20011 should_discard.not().then_some(language.clone())
20012 })
20013 })
20014 .collect::<HashSet<_>>()
20015 });
20016 if !languages_affected.is_empty() {
20017 self.refresh_inlay_hints(
20018 InlayHintRefreshReason::BufferEdited(languages_affected),
20019 cx,
20020 );
20021 }
20022 }
20023 }
20024
20025 let Some(project) = &self.project else { return };
20026 let (telemetry, is_via_ssh) = {
20027 let project = project.read(cx);
20028 let telemetry = project.client().telemetry().clone();
20029 let is_via_ssh = project.is_via_ssh();
20030 (telemetry, is_via_ssh)
20031 };
20032 refresh_linked_ranges(self, window, cx);
20033 telemetry.log_edit_event("editor", is_via_ssh);
20034 }
20035 multi_buffer::Event::ExcerptsAdded {
20036 buffer,
20037 predecessor,
20038 excerpts,
20039 } => {
20040 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20041 let buffer_id = buffer.read(cx).remote_id();
20042 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
20043 if let Some(project) = &self.project {
20044 update_uncommitted_diff_for_buffer(
20045 cx.entity(),
20046 project,
20047 [buffer.clone()],
20048 self.buffer.clone(),
20049 cx,
20050 )
20051 .detach();
20052 }
20053 }
20054 self.update_lsp_data(false, Some(buffer_id), window, cx);
20055 cx.emit(EditorEvent::ExcerptsAdded {
20056 buffer: buffer.clone(),
20057 predecessor: *predecessor,
20058 excerpts: excerpts.clone(),
20059 });
20060 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20061 }
20062 multi_buffer::Event::ExcerptsRemoved {
20063 ids,
20064 removed_buffer_ids,
20065 } => {
20066 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
20067 let buffer = self.buffer.read(cx);
20068 self.registered_buffers
20069 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
20070 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20071 cx.emit(EditorEvent::ExcerptsRemoved {
20072 ids: ids.clone(),
20073 removed_buffer_ids: removed_buffer_ids.clone(),
20074 });
20075 }
20076 multi_buffer::Event::ExcerptsEdited {
20077 excerpt_ids,
20078 buffer_ids,
20079 } => {
20080 self.display_map.update(cx, |map, cx| {
20081 map.unfold_buffers(buffer_ids.iter().copied(), cx)
20082 });
20083 cx.emit(EditorEvent::ExcerptsEdited {
20084 ids: excerpt_ids.clone(),
20085 });
20086 }
20087 multi_buffer::Event::ExcerptsExpanded { ids } => {
20088 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20089 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
20090 }
20091 multi_buffer::Event::Reparsed(buffer_id) => {
20092 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20093 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20094
20095 cx.emit(EditorEvent::Reparsed(*buffer_id));
20096 }
20097 multi_buffer::Event::DiffHunksToggled => {
20098 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20099 }
20100 multi_buffer::Event::LanguageChanged(buffer_id) => {
20101 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
20102 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20103 cx.emit(EditorEvent::Reparsed(*buffer_id));
20104 cx.notify();
20105 }
20106 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
20107 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
20108 multi_buffer::Event::FileHandleChanged
20109 | multi_buffer::Event::Reloaded
20110 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
20111 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
20112 multi_buffer::Event::DiagnosticsUpdated => {
20113 self.update_diagnostics_state(window, cx);
20114 }
20115 _ => {}
20116 };
20117 }
20118
20119 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
20120 if !self.diagnostics_enabled() {
20121 return;
20122 }
20123 self.refresh_active_diagnostics(cx);
20124 self.refresh_inline_diagnostics(true, window, cx);
20125 self.scrollbar_marker_state.dirty = true;
20126 cx.notify();
20127 }
20128
20129 pub fn start_temporary_diff_override(&mut self) {
20130 self.load_diff_task.take();
20131 self.temporary_diff_override = true;
20132 }
20133
20134 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
20135 self.temporary_diff_override = false;
20136 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
20137 self.buffer.update(cx, |buffer, cx| {
20138 buffer.set_all_diff_hunks_collapsed(cx);
20139 });
20140
20141 if let Some(project) = self.project.clone() {
20142 self.load_diff_task = Some(
20143 update_uncommitted_diff_for_buffer(
20144 cx.entity(),
20145 &project,
20146 self.buffer.read(cx).all_buffers(),
20147 self.buffer.clone(),
20148 cx,
20149 )
20150 .shared(),
20151 );
20152 }
20153 }
20154
20155 fn on_display_map_changed(
20156 &mut self,
20157 _: Entity<DisplayMap>,
20158 _: &mut Window,
20159 cx: &mut Context<Self>,
20160 ) {
20161 cx.notify();
20162 }
20163
20164 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20165 if self.diagnostics_enabled() {
20166 let new_severity = EditorSettings::get_global(cx)
20167 .diagnostics_max_severity
20168 .unwrap_or(DiagnosticSeverity::Hint);
20169 self.set_max_diagnostics_severity(new_severity, cx);
20170 }
20171 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20172 self.update_edit_prediction_settings(cx);
20173 self.refresh_edit_prediction(true, false, window, cx);
20174 self.refresh_inline_values(cx);
20175 self.refresh_inlay_hints(
20176 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
20177 self.selections.newest_anchor().head(),
20178 &self.buffer.read(cx).snapshot(cx),
20179 cx,
20180 )),
20181 cx,
20182 );
20183
20184 let old_cursor_shape = self.cursor_shape;
20185
20186 {
20187 let editor_settings = EditorSettings::get_global(cx);
20188 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
20189 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
20190 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
20191 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
20192 }
20193
20194 if old_cursor_shape != self.cursor_shape {
20195 cx.emit(EditorEvent::CursorShapeChanged);
20196 }
20197
20198 let project_settings = ProjectSettings::get_global(cx);
20199 self.serialize_dirty_buffers =
20200 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
20201
20202 if self.mode.is_full() {
20203 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
20204 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
20205 if self.show_inline_diagnostics != show_inline_diagnostics {
20206 self.show_inline_diagnostics = show_inline_diagnostics;
20207 self.refresh_inline_diagnostics(false, window, cx);
20208 }
20209
20210 if self.git_blame_inline_enabled != inline_blame_enabled {
20211 self.toggle_git_blame_inline_internal(false, window, cx);
20212 }
20213
20214 let minimap_settings = EditorSettings::get_global(cx).minimap;
20215 if self.minimap_visibility != MinimapVisibility::Disabled {
20216 if self.minimap_visibility.settings_visibility()
20217 != minimap_settings.minimap_enabled()
20218 {
20219 self.set_minimap_visibility(
20220 MinimapVisibility::for_mode(self.mode(), cx),
20221 window,
20222 cx,
20223 );
20224 } else if let Some(minimap_entity) = self.minimap.as_ref() {
20225 minimap_entity.update(cx, |minimap_editor, cx| {
20226 minimap_editor.update_minimap_configuration(minimap_settings, cx)
20227 })
20228 }
20229 }
20230 }
20231
20232 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
20233 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
20234 }) {
20235 if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
20236 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
20237 }
20238 self.refresh_colors(false, None, window, cx);
20239 }
20240
20241 cx.notify();
20242 }
20243
20244 pub fn set_searchable(&mut self, searchable: bool) {
20245 self.searchable = searchable;
20246 }
20247
20248 pub fn searchable(&self) -> bool {
20249 self.searchable
20250 }
20251
20252 fn open_proposed_changes_editor(
20253 &mut self,
20254 _: &OpenProposedChangesEditor,
20255 window: &mut Window,
20256 cx: &mut Context<Self>,
20257 ) {
20258 let Some(workspace) = self.workspace() else {
20259 cx.propagate();
20260 return;
20261 };
20262
20263 let selections = self.selections.all::<usize>(cx);
20264 let multi_buffer = self.buffer.read(cx);
20265 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20266 let mut new_selections_by_buffer = HashMap::default();
20267 for selection in selections {
20268 for (buffer, range, _) in
20269 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
20270 {
20271 let mut range = range.to_point(buffer);
20272 range.start.column = 0;
20273 range.end.column = buffer.line_len(range.end.row);
20274 new_selections_by_buffer
20275 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
20276 .or_insert(Vec::new())
20277 .push(range)
20278 }
20279 }
20280
20281 let proposed_changes_buffers = new_selections_by_buffer
20282 .into_iter()
20283 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
20284 .collect::<Vec<_>>();
20285 let proposed_changes_editor = cx.new(|cx| {
20286 ProposedChangesEditor::new(
20287 "Proposed changes",
20288 proposed_changes_buffers,
20289 self.project.clone(),
20290 window,
20291 cx,
20292 )
20293 });
20294
20295 window.defer(cx, move |window, cx| {
20296 workspace.update(cx, |workspace, cx| {
20297 workspace.active_pane().update(cx, |pane, cx| {
20298 pane.add_item(
20299 Box::new(proposed_changes_editor),
20300 true,
20301 true,
20302 None,
20303 window,
20304 cx,
20305 );
20306 });
20307 });
20308 });
20309 }
20310
20311 pub fn open_excerpts_in_split(
20312 &mut self,
20313 _: &OpenExcerptsSplit,
20314 window: &mut Window,
20315 cx: &mut Context<Self>,
20316 ) {
20317 self.open_excerpts_common(None, true, window, cx)
20318 }
20319
20320 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
20321 self.open_excerpts_common(None, false, window, cx)
20322 }
20323
20324 fn open_excerpts_common(
20325 &mut self,
20326 jump_data: Option<JumpData>,
20327 split: bool,
20328 window: &mut Window,
20329 cx: &mut Context<Self>,
20330 ) {
20331 let Some(workspace) = self.workspace() else {
20332 cx.propagate();
20333 return;
20334 };
20335
20336 if self.buffer.read(cx).is_singleton() {
20337 cx.propagate();
20338 return;
20339 }
20340
20341 let mut new_selections_by_buffer = HashMap::default();
20342 match &jump_data {
20343 Some(JumpData::MultiBufferPoint {
20344 excerpt_id,
20345 position,
20346 anchor,
20347 line_offset_from_top,
20348 }) => {
20349 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20350 if let Some(buffer) = multi_buffer_snapshot
20351 .buffer_id_for_excerpt(*excerpt_id)
20352 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
20353 {
20354 let buffer_snapshot = buffer.read(cx).snapshot();
20355 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
20356 language::ToPoint::to_point(anchor, &buffer_snapshot)
20357 } else {
20358 buffer_snapshot.clip_point(*position, Bias::Left)
20359 };
20360 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
20361 new_selections_by_buffer.insert(
20362 buffer,
20363 (
20364 vec![jump_to_offset..jump_to_offset],
20365 Some(*line_offset_from_top),
20366 ),
20367 );
20368 }
20369 }
20370 Some(JumpData::MultiBufferRow {
20371 row,
20372 line_offset_from_top,
20373 }) => {
20374 let point = MultiBufferPoint::new(row.0, 0);
20375 if let Some((buffer, buffer_point, _)) =
20376 self.buffer.read(cx).point_to_buffer_point(point, cx)
20377 {
20378 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
20379 new_selections_by_buffer
20380 .entry(buffer)
20381 .or_insert((Vec::new(), Some(*line_offset_from_top)))
20382 .0
20383 .push(buffer_offset..buffer_offset)
20384 }
20385 }
20386 None => {
20387 let selections = self.selections.all::<usize>(cx);
20388 let multi_buffer = self.buffer.read(cx);
20389 for selection in selections {
20390 for (snapshot, range, _, anchor) in multi_buffer
20391 .snapshot(cx)
20392 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
20393 {
20394 if let Some(anchor) = anchor {
20395 // selection is in a deleted hunk
20396 let Some(buffer_id) = anchor.buffer_id else {
20397 continue;
20398 };
20399 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
20400 continue;
20401 };
20402 let offset = text::ToOffset::to_offset(
20403 &anchor.text_anchor,
20404 &buffer_handle.read(cx).snapshot(),
20405 );
20406 let range = offset..offset;
20407 new_selections_by_buffer
20408 .entry(buffer_handle)
20409 .or_insert((Vec::new(), None))
20410 .0
20411 .push(range)
20412 } else {
20413 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
20414 else {
20415 continue;
20416 };
20417 new_selections_by_buffer
20418 .entry(buffer_handle)
20419 .or_insert((Vec::new(), None))
20420 .0
20421 .push(range)
20422 }
20423 }
20424 }
20425 }
20426 }
20427
20428 new_selections_by_buffer
20429 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
20430
20431 if new_selections_by_buffer.is_empty() {
20432 return;
20433 }
20434
20435 // We defer the pane interaction because we ourselves are a workspace item
20436 // and activating a new item causes the pane to call a method on us reentrantly,
20437 // which panics if we're on the stack.
20438 window.defer(cx, move |window, cx| {
20439 workspace.update(cx, |workspace, cx| {
20440 let pane = if split {
20441 workspace.adjacent_pane(window, cx)
20442 } else {
20443 workspace.active_pane().clone()
20444 };
20445
20446 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
20447 let editor = buffer
20448 .read(cx)
20449 .file()
20450 .is_none()
20451 .then(|| {
20452 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
20453 // so `workspace.open_project_item` will never find them, always opening a new editor.
20454 // Instead, we try to activate the existing editor in the pane first.
20455 let (editor, pane_item_index) =
20456 pane.read(cx).items().enumerate().find_map(|(i, item)| {
20457 let editor = item.downcast::<Editor>()?;
20458 let singleton_buffer =
20459 editor.read(cx).buffer().read(cx).as_singleton()?;
20460 if singleton_buffer == buffer {
20461 Some((editor, i))
20462 } else {
20463 None
20464 }
20465 })?;
20466 pane.update(cx, |pane, cx| {
20467 pane.activate_item(pane_item_index, true, true, window, cx)
20468 });
20469 Some(editor)
20470 })
20471 .flatten()
20472 .unwrap_or_else(|| {
20473 workspace.open_project_item::<Self>(
20474 pane.clone(),
20475 buffer,
20476 true,
20477 true,
20478 window,
20479 cx,
20480 )
20481 });
20482
20483 editor.update(cx, |editor, cx| {
20484 let autoscroll = match scroll_offset {
20485 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
20486 None => Autoscroll::newest(),
20487 };
20488 let nav_history = editor.nav_history.take();
20489 editor.change_selections(
20490 SelectionEffects::scroll(autoscroll),
20491 window,
20492 cx,
20493 |s| {
20494 s.select_ranges(ranges);
20495 },
20496 );
20497 editor.nav_history = nav_history;
20498 });
20499 }
20500 })
20501 });
20502 }
20503
20504 // For now, don't allow opening excerpts in buffers that aren't backed by
20505 // regular project files.
20506 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
20507 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
20508 }
20509
20510 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
20511 let snapshot = self.buffer.read(cx).read(cx);
20512 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
20513 Some(
20514 ranges
20515 .iter()
20516 .map(move |range| {
20517 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
20518 })
20519 .collect(),
20520 )
20521 }
20522
20523 fn selection_replacement_ranges(
20524 &self,
20525 range: Range<OffsetUtf16>,
20526 cx: &mut App,
20527 ) -> Vec<Range<OffsetUtf16>> {
20528 let selections = self.selections.all::<OffsetUtf16>(cx);
20529 let newest_selection = selections
20530 .iter()
20531 .max_by_key(|selection| selection.id)
20532 .unwrap();
20533 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
20534 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
20535 let snapshot = self.buffer.read(cx).read(cx);
20536 selections
20537 .into_iter()
20538 .map(|mut selection| {
20539 selection.start.0 =
20540 (selection.start.0 as isize).saturating_add(start_delta) as usize;
20541 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
20542 snapshot.clip_offset_utf16(selection.start, Bias::Left)
20543 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
20544 })
20545 .collect()
20546 }
20547
20548 fn report_editor_event(
20549 &self,
20550 event_type: &'static str,
20551 file_extension: Option<String>,
20552 cx: &App,
20553 ) {
20554 if cfg!(any(test, feature = "test-support")) {
20555 return;
20556 }
20557
20558 let Some(project) = &self.project else { return };
20559
20560 // If None, we are in a file without an extension
20561 let file = self
20562 .buffer
20563 .read(cx)
20564 .as_singleton()
20565 .and_then(|b| b.read(cx).file());
20566 let file_extension = file_extension.or(file
20567 .as_ref()
20568 .and_then(|file| Path::new(file.file_name(cx)).extension())
20569 .and_then(|e| e.to_str())
20570 .map(|a| a.to_string()));
20571
20572 let vim_mode = vim_enabled(cx);
20573
20574 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
20575 let copilot_enabled = edit_predictions_provider
20576 == language::language_settings::EditPredictionProvider::Copilot;
20577 let copilot_enabled_for_language = self
20578 .buffer
20579 .read(cx)
20580 .language_settings(cx)
20581 .show_edit_predictions;
20582
20583 let project = project.read(cx);
20584 telemetry::event!(
20585 event_type,
20586 file_extension,
20587 vim_mode,
20588 copilot_enabled,
20589 copilot_enabled_for_language,
20590 edit_predictions_provider,
20591 is_via_ssh = project.is_via_ssh(),
20592 );
20593 }
20594
20595 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
20596 /// with each line being an array of {text, highlight} objects.
20597 fn copy_highlight_json(
20598 &mut self,
20599 _: &CopyHighlightJson,
20600 window: &mut Window,
20601 cx: &mut Context<Self>,
20602 ) {
20603 #[derive(Serialize)]
20604 struct Chunk<'a> {
20605 text: String,
20606 highlight: Option<&'a str>,
20607 }
20608
20609 let snapshot = self.buffer.read(cx).snapshot(cx);
20610 let range = self
20611 .selected_text_range(false, window, cx)
20612 .and_then(|selection| {
20613 if selection.range.is_empty() {
20614 None
20615 } else {
20616 Some(selection.range)
20617 }
20618 })
20619 .unwrap_or_else(|| 0..snapshot.len());
20620
20621 let chunks = snapshot.chunks(range, true);
20622 let mut lines = Vec::new();
20623 let mut line: VecDeque<Chunk> = VecDeque::new();
20624
20625 let Some(style) = self.style.as_ref() else {
20626 return;
20627 };
20628
20629 for chunk in chunks {
20630 let highlight = chunk
20631 .syntax_highlight_id
20632 .and_then(|id| id.name(&style.syntax));
20633 let mut chunk_lines = chunk.text.split('\n').peekable();
20634 while let Some(text) = chunk_lines.next() {
20635 let mut merged_with_last_token = false;
20636 if let Some(last_token) = line.back_mut() {
20637 if last_token.highlight == highlight {
20638 last_token.text.push_str(text);
20639 merged_with_last_token = true;
20640 }
20641 }
20642
20643 if !merged_with_last_token {
20644 line.push_back(Chunk {
20645 text: text.into(),
20646 highlight,
20647 });
20648 }
20649
20650 if chunk_lines.peek().is_some() {
20651 if line.len() > 1 && line.front().unwrap().text.is_empty() {
20652 line.pop_front();
20653 }
20654 if line.len() > 1 && line.back().unwrap().text.is_empty() {
20655 line.pop_back();
20656 }
20657
20658 lines.push(mem::take(&mut line));
20659 }
20660 }
20661 }
20662
20663 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
20664 return;
20665 };
20666 cx.write_to_clipboard(ClipboardItem::new_string(lines));
20667 }
20668
20669 pub fn open_context_menu(
20670 &mut self,
20671 _: &OpenContextMenu,
20672 window: &mut Window,
20673 cx: &mut Context<Self>,
20674 ) {
20675 self.request_autoscroll(Autoscroll::newest(), cx);
20676 let position = self.selections.newest_display(cx).start;
20677 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
20678 }
20679
20680 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
20681 &self.inlay_hint_cache
20682 }
20683
20684 pub fn replay_insert_event(
20685 &mut self,
20686 text: &str,
20687 relative_utf16_range: Option<Range<isize>>,
20688 window: &mut Window,
20689 cx: &mut Context<Self>,
20690 ) {
20691 if !self.input_enabled {
20692 cx.emit(EditorEvent::InputIgnored { text: text.into() });
20693 return;
20694 }
20695 if let Some(relative_utf16_range) = relative_utf16_range {
20696 let selections = self.selections.all::<OffsetUtf16>(cx);
20697 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20698 let new_ranges = selections.into_iter().map(|range| {
20699 let start = OffsetUtf16(
20700 range
20701 .head()
20702 .0
20703 .saturating_add_signed(relative_utf16_range.start),
20704 );
20705 let end = OffsetUtf16(
20706 range
20707 .head()
20708 .0
20709 .saturating_add_signed(relative_utf16_range.end),
20710 );
20711 start..end
20712 });
20713 s.select_ranges(new_ranges);
20714 });
20715 }
20716
20717 self.handle_input(text, window, cx);
20718 }
20719
20720 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
20721 let Some(provider) = self.semantics_provider.as_ref() else {
20722 return false;
20723 };
20724
20725 let mut supports = false;
20726 self.buffer().update(cx, |this, cx| {
20727 this.for_each_buffer(|buffer| {
20728 supports |= provider.supports_inlay_hints(buffer, cx);
20729 });
20730 });
20731
20732 supports
20733 }
20734
20735 pub fn is_focused(&self, window: &Window) -> bool {
20736 self.focus_handle.is_focused(window)
20737 }
20738
20739 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20740 cx.emit(EditorEvent::Focused);
20741
20742 if let Some(descendant) = self
20743 .last_focused_descendant
20744 .take()
20745 .and_then(|descendant| descendant.upgrade())
20746 {
20747 window.focus(&descendant);
20748 } else {
20749 if let Some(blame) = self.blame.as_ref() {
20750 blame.update(cx, GitBlame::focus)
20751 }
20752
20753 self.blink_manager.update(cx, BlinkManager::enable);
20754 self.show_cursor_names(window, cx);
20755 self.buffer.update(cx, |buffer, cx| {
20756 buffer.finalize_last_transaction(cx);
20757 if self.leader_id.is_none() {
20758 buffer.set_active_selections(
20759 &self.selections.disjoint_anchors(),
20760 self.selections.line_mode,
20761 self.cursor_shape,
20762 cx,
20763 );
20764 }
20765 });
20766 }
20767 }
20768
20769 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
20770 cx.emit(EditorEvent::FocusedIn)
20771 }
20772
20773 fn handle_focus_out(
20774 &mut self,
20775 event: FocusOutEvent,
20776 _window: &mut Window,
20777 cx: &mut Context<Self>,
20778 ) {
20779 if event.blurred != self.focus_handle {
20780 self.last_focused_descendant = Some(event.blurred);
20781 }
20782 self.selection_drag_state = SelectionDragState::None;
20783 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
20784 }
20785
20786 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20787 self.blink_manager.update(cx, BlinkManager::disable);
20788 self.buffer
20789 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
20790
20791 if let Some(blame) = self.blame.as_ref() {
20792 blame.update(cx, GitBlame::blur)
20793 }
20794 if !self.hover_state.focused(window, cx) {
20795 hide_hover(self, cx);
20796 }
20797 if !self
20798 .context_menu
20799 .borrow()
20800 .as_ref()
20801 .is_some_and(|context_menu| context_menu.focused(window, cx))
20802 {
20803 self.hide_context_menu(window, cx);
20804 }
20805 self.discard_edit_prediction(false, cx);
20806 cx.emit(EditorEvent::Blurred);
20807 cx.notify();
20808 }
20809
20810 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20811 let mut pending: String = window
20812 .pending_input_keystrokes()
20813 .into_iter()
20814 .flatten()
20815 .filter_map(|keystroke| {
20816 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
20817 keystroke.key_char.clone()
20818 } else {
20819 None
20820 }
20821 })
20822 .collect();
20823
20824 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
20825 pending = "".to_string();
20826 }
20827
20828 let existing_pending = self
20829 .text_highlights::<PendingInput>(cx)
20830 .map(|(_, ranges)| ranges.iter().cloned().collect::<Vec<_>>());
20831 if existing_pending.is_none() && pending.is_empty() {
20832 return;
20833 }
20834 let transaction =
20835 self.transact(window, cx, |this, window, cx| {
20836 let selections = this.selections.all::<usize>(cx);
20837 let edits = selections
20838 .iter()
20839 .map(|selection| (selection.end..selection.end, pending.clone()));
20840 this.edit(edits, cx);
20841 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20842 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
20843 sel.start + ix * pending.len()..sel.end + ix * pending.len()
20844 }));
20845 });
20846 if let Some(existing_ranges) = existing_pending {
20847 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
20848 this.edit(edits, cx);
20849 }
20850 });
20851
20852 let snapshot = self.snapshot(window, cx);
20853 let ranges = self
20854 .selections
20855 .all::<usize>(cx)
20856 .into_iter()
20857 .map(|selection| {
20858 snapshot.buffer_snapshot.anchor_after(selection.end)
20859 ..snapshot
20860 .buffer_snapshot
20861 .anchor_before(selection.end + pending.len())
20862 })
20863 .collect();
20864
20865 if pending.is_empty() {
20866 self.clear_highlights::<PendingInput>(cx);
20867 } else {
20868 self.highlight_text::<PendingInput>(
20869 ranges,
20870 HighlightStyle {
20871 underline: Some(UnderlineStyle {
20872 thickness: px(1.),
20873 color: None,
20874 wavy: false,
20875 }),
20876 ..Default::default()
20877 },
20878 cx,
20879 );
20880 }
20881
20882 self.ime_transaction = self.ime_transaction.or(transaction);
20883 if let Some(transaction) = self.ime_transaction {
20884 self.buffer.update(cx, |buffer, cx| {
20885 buffer.group_until_transaction(transaction, cx);
20886 });
20887 }
20888
20889 if self.text_highlights::<PendingInput>(cx).is_none() {
20890 self.ime_transaction.take();
20891 }
20892 }
20893
20894 pub fn register_action_renderer(
20895 &mut self,
20896 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
20897 ) -> Subscription {
20898 let id = self.next_editor_action_id.post_inc();
20899 self.editor_actions
20900 .borrow_mut()
20901 .insert(id, Box::new(listener));
20902
20903 let editor_actions = self.editor_actions.clone();
20904 Subscription::new(move || {
20905 editor_actions.borrow_mut().remove(&id);
20906 })
20907 }
20908
20909 pub fn register_action<A: Action>(
20910 &mut self,
20911 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
20912 ) -> Subscription {
20913 let id = self.next_editor_action_id.post_inc();
20914 let listener = Arc::new(listener);
20915 self.editor_actions.borrow_mut().insert(
20916 id,
20917 Box::new(move |_, window, _| {
20918 let listener = listener.clone();
20919 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
20920 let action = action.downcast_ref().unwrap();
20921 if phase == DispatchPhase::Bubble {
20922 listener(action, window, cx)
20923 }
20924 })
20925 }),
20926 );
20927
20928 let editor_actions = self.editor_actions.clone();
20929 Subscription::new(move || {
20930 editor_actions.borrow_mut().remove(&id);
20931 })
20932 }
20933
20934 pub fn file_header_size(&self) -> u32 {
20935 FILE_HEADER_HEIGHT
20936 }
20937
20938 pub fn restore(
20939 &mut self,
20940 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
20941 window: &mut Window,
20942 cx: &mut Context<Self>,
20943 ) {
20944 let workspace = self.workspace();
20945 let project = self.project.as_ref();
20946 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
20947 let mut tasks = Vec::new();
20948 for (buffer_id, changes) in revert_changes {
20949 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
20950 buffer.update(cx, |buffer, cx| {
20951 buffer.edit(
20952 changes
20953 .into_iter()
20954 .map(|(range, text)| (range, text.to_string())),
20955 None,
20956 cx,
20957 );
20958 });
20959
20960 if let Some(project) =
20961 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
20962 {
20963 project.update(cx, |project, cx| {
20964 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
20965 })
20966 }
20967 }
20968 }
20969 tasks
20970 });
20971 cx.spawn_in(window, async move |_, cx| {
20972 for (buffer, task) in save_tasks {
20973 let result = task.await;
20974 if result.is_err() {
20975 let Some(path) = buffer
20976 .read_with(cx, |buffer, cx| buffer.project_path(cx))
20977 .ok()
20978 else {
20979 continue;
20980 };
20981 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
20982 let Some(task) = cx
20983 .update_window_entity(&workspace, |workspace, window, cx| {
20984 workspace
20985 .open_path_preview(path, None, false, false, false, window, cx)
20986 })
20987 .ok()
20988 else {
20989 continue;
20990 };
20991 task.await.log_err();
20992 }
20993 }
20994 }
20995 })
20996 .detach();
20997 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
20998 selections.refresh()
20999 });
21000 }
21001
21002 pub fn to_pixel_point(
21003 &self,
21004 source: multi_buffer::Anchor,
21005 editor_snapshot: &EditorSnapshot,
21006 window: &mut Window,
21007 ) -> Option<gpui::Point<Pixels>> {
21008 let source_point = source.to_display_point(editor_snapshot);
21009 self.display_to_pixel_point(source_point, editor_snapshot, window)
21010 }
21011
21012 pub fn display_to_pixel_point(
21013 &self,
21014 source: DisplayPoint,
21015 editor_snapshot: &EditorSnapshot,
21016 window: &mut Window,
21017 ) -> Option<gpui::Point<Pixels>> {
21018 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
21019 let text_layout_details = self.text_layout_details(window);
21020 let scroll_top = text_layout_details
21021 .scroll_anchor
21022 .scroll_position(editor_snapshot)
21023 .y;
21024
21025 if source.row().as_f32() < scroll_top.floor() {
21026 return None;
21027 }
21028 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
21029 let source_y = line_height * (source.row().as_f32() - scroll_top);
21030 Some(gpui::Point::new(source_x, source_y))
21031 }
21032
21033 pub fn has_visible_completions_menu(&self) -> bool {
21034 !self.edit_prediction_preview_is_active()
21035 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
21036 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
21037 })
21038 }
21039
21040 pub fn register_addon<T: Addon>(&mut self, instance: T) {
21041 if self.mode.is_minimap() {
21042 return;
21043 }
21044 self.addons
21045 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
21046 }
21047
21048 pub fn unregister_addon<T: Addon>(&mut self) {
21049 self.addons.remove(&std::any::TypeId::of::<T>());
21050 }
21051
21052 pub fn addon<T: Addon>(&self) -> Option<&T> {
21053 let type_id = std::any::TypeId::of::<T>();
21054 self.addons
21055 .get(&type_id)
21056 .and_then(|item| item.to_any().downcast_ref::<T>())
21057 }
21058
21059 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
21060 let type_id = std::any::TypeId::of::<T>();
21061 self.addons
21062 .get_mut(&type_id)
21063 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
21064 }
21065
21066 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
21067 let text_layout_details = self.text_layout_details(window);
21068 let style = &text_layout_details.editor_style;
21069 let font_id = window.text_system().resolve_font(&style.text.font());
21070 let font_size = style.text.font_size.to_pixels(window.rem_size());
21071 let line_height = style.text.line_height_in_pixels(window.rem_size());
21072 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
21073 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
21074
21075 CharacterDimensions {
21076 em_width,
21077 em_advance,
21078 line_height,
21079 }
21080 }
21081
21082 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
21083 self.load_diff_task.clone()
21084 }
21085
21086 fn read_metadata_from_db(
21087 &mut self,
21088 item_id: u64,
21089 workspace_id: WorkspaceId,
21090 window: &mut Window,
21091 cx: &mut Context<Editor>,
21092 ) {
21093 if self.is_singleton(cx)
21094 && !self.mode.is_minimap()
21095 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
21096 {
21097 let buffer_snapshot = OnceCell::new();
21098
21099 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
21100 if !folds.is_empty() {
21101 let snapshot =
21102 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21103 self.fold_ranges(
21104 folds
21105 .into_iter()
21106 .map(|(start, end)| {
21107 snapshot.clip_offset(start, Bias::Left)
21108 ..snapshot.clip_offset(end, Bias::Right)
21109 })
21110 .collect(),
21111 false,
21112 window,
21113 cx,
21114 );
21115 }
21116 }
21117
21118 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
21119 if !selections.is_empty() {
21120 let snapshot =
21121 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21122 // skip adding the initial selection to selection history
21123 self.selection_history.mode = SelectionHistoryMode::Skipping;
21124 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21125 s.select_ranges(selections.into_iter().map(|(start, end)| {
21126 snapshot.clip_offset(start, Bias::Left)
21127 ..snapshot.clip_offset(end, Bias::Right)
21128 }));
21129 });
21130 self.selection_history.mode = SelectionHistoryMode::Normal;
21131 }
21132 };
21133 }
21134
21135 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
21136 }
21137
21138 fn update_lsp_data(
21139 &mut self,
21140 ignore_cache: bool,
21141 for_buffer: Option<BufferId>,
21142 window: &mut Window,
21143 cx: &mut Context<'_, Self>,
21144 ) {
21145 self.pull_diagnostics(for_buffer, window, cx);
21146 self.refresh_colors(ignore_cache, for_buffer, window, cx);
21147 }
21148}
21149
21150fn vim_enabled(cx: &App) -> bool {
21151 cx.global::<SettingsStore>()
21152 .raw_user_settings()
21153 .get("vim_mode")
21154 == Some(&serde_json::Value::Bool(true))
21155}
21156
21157fn process_completion_for_edit(
21158 completion: &Completion,
21159 intent: CompletionIntent,
21160 buffer: &Entity<Buffer>,
21161 cursor_position: &text::Anchor,
21162 cx: &mut Context<Editor>,
21163) -> CompletionEdit {
21164 let buffer = buffer.read(cx);
21165 let buffer_snapshot = buffer.snapshot();
21166 let (snippet, new_text) = if completion.is_snippet() {
21167 // Workaround for typescript language server issues so that methods don't expand within
21168 // strings and functions with type expressions. The previous point is used because the query
21169 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
21170 let mut snippet_source = completion.new_text.clone();
21171 let mut previous_point = text::ToPoint::to_point(cursor_position, buffer);
21172 previous_point.column = previous_point.column.saturating_sub(1);
21173 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point) {
21174 if scope.prefers_label_for_snippet_in_completion() {
21175 if let Some(label) = completion.label() {
21176 if matches!(
21177 completion.kind(),
21178 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
21179 ) {
21180 snippet_source = label;
21181 }
21182 }
21183 }
21184 }
21185 match Snippet::parse(&snippet_source).log_err() {
21186 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
21187 None => (None, completion.new_text.clone()),
21188 }
21189 } else {
21190 (None, completion.new_text.clone())
21191 };
21192
21193 let mut range_to_replace = {
21194 let replace_range = &completion.replace_range;
21195 if let CompletionSource::Lsp {
21196 insert_range: Some(insert_range),
21197 ..
21198 } = &completion.source
21199 {
21200 debug_assert_eq!(
21201 insert_range.start, replace_range.start,
21202 "insert_range and replace_range should start at the same position"
21203 );
21204 debug_assert!(
21205 insert_range
21206 .start
21207 .cmp(&cursor_position, &buffer_snapshot)
21208 .is_le(),
21209 "insert_range should start before or at cursor position"
21210 );
21211 debug_assert!(
21212 replace_range
21213 .start
21214 .cmp(&cursor_position, &buffer_snapshot)
21215 .is_le(),
21216 "replace_range should start before or at cursor position"
21217 );
21218
21219 let should_replace = match intent {
21220 CompletionIntent::CompleteWithInsert => false,
21221 CompletionIntent::CompleteWithReplace => true,
21222 CompletionIntent::Complete | CompletionIntent::Compose => {
21223 let insert_mode =
21224 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
21225 .completions
21226 .lsp_insert_mode;
21227 match insert_mode {
21228 LspInsertMode::Insert => false,
21229 LspInsertMode::Replace => true,
21230 LspInsertMode::ReplaceSubsequence => {
21231 let mut text_to_replace = buffer.chars_for_range(
21232 buffer.anchor_before(replace_range.start)
21233 ..buffer.anchor_after(replace_range.end),
21234 );
21235 let mut current_needle = text_to_replace.next();
21236 for haystack_ch in completion.label.text.chars() {
21237 if let Some(needle_ch) = current_needle {
21238 if haystack_ch.eq_ignore_ascii_case(&needle_ch) {
21239 current_needle = text_to_replace.next();
21240 }
21241 }
21242 }
21243 current_needle.is_none()
21244 }
21245 LspInsertMode::ReplaceSuffix => {
21246 if replace_range
21247 .end
21248 .cmp(&cursor_position, &buffer_snapshot)
21249 .is_gt()
21250 {
21251 let range_after_cursor = *cursor_position..replace_range.end;
21252 let text_after_cursor = buffer
21253 .text_for_range(
21254 buffer.anchor_before(range_after_cursor.start)
21255 ..buffer.anchor_after(range_after_cursor.end),
21256 )
21257 .collect::<String>()
21258 .to_ascii_lowercase();
21259 completion
21260 .label
21261 .text
21262 .to_ascii_lowercase()
21263 .ends_with(&text_after_cursor)
21264 } else {
21265 true
21266 }
21267 }
21268 }
21269 }
21270 };
21271
21272 if should_replace {
21273 replace_range.clone()
21274 } else {
21275 insert_range.clone()
21276 }
21277 } else {
21278 replace_range.clone()
21279 }
21280 };
21281
21282 if range_to_replace
21283 .end
21284 .cmp(&cursor_position, &buffer_snapshot)
21285 .is_lt()
21286 {
21287 range_to_replace.end = *cursor_position;
21288 }
21289
21290 CompletionEdit {
21291 new_text,
21292 replace_range: range_to_replace.to_offset(&buffer),
21293 snippet,
21294 }
21295}
21296
21297struct CompletionEdit {
21298 new_text: String,
21299 replace_range: Range<usize>,
21300 snippet: Option<Snippet>,
21301}
21302
21303fn insert_extra_newline_brackets(
21304 buffer: &MultiBufferSnapshot,
21305 range: Range<usize>,
21306 language: &language::LanguageScope,
21307) -> bool {
21308 let leading_whitespace_len = buffer
21309 .reversed_chars_at(range.start)
21310 .take_while(|c| c.is_whitespace() && *c != '\n')
21311 .map(|c| c.len_utf8())
21312 .sum::<usize>();
21313 let trailing_whitespace_len = buffer
21314 .chars_at(range.end)
21315 .take_while(|c| c.is_whitespace() && *c != '\n')
21316 .map(|c| c.len_utf8())
21317 .sum::<usize>();
21318 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
21319
21320 language.brackets().any(|(pair, enabled)| {
21321 let pair_start = pair.start.trim_end();
21322 let pair_end = pair.end.trim_start();
21323
21324 enabled
21325 && pair.newline
21326 && buffer.contains_str_at(range.end, pair_end)
21327 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
21328 })
21329}
21330
21331fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
21332 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
21333 [(buffer, range, _)] => (*buffer, range.clone()),
21334 _ => return false,
21335 };
21336 let pair = {
21337 let mut result: Option<BracketMatch> = None;
21338
21339 for pair in buffer
21340 .all_bracket_ranges(range.clone())
21341 .filter(move |pair| {
21342 pair.open_range.start <= range.start && pair.close_range.end >= range.end
21343 })
21344 {
21345 let len = pair.close_range.end - pair.open_range.start;
21346
21347 if let Some(existing) = &result {
21348 let existing_len = existing.close_range.end - existing.open_range.start;
21349 if len > existing_len {
21350 continue;
21351 }
21352 }
21353
21354 result = Some(pair);
21355 }
21356
21357 result
21358 };
21359 let Some(pair) = pair else {
21360 return false;
21361 };
21362 pair.newline_only
21363 && buffer
21364 .chars_for_range(pair.open_range.end..range.start)
21365 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
21366 .all(|c| c.is_whitespace() && c != '\n')
21367}
21368
21369fn update_uncommitted_diff_for_buffer(
21370 editor: Entity<Editor>,
21371 project: &Entity<Project>,
21372 buffers: impl IntoIterator<Item = Entity<Buffer>>,
21373 buffer: Entity<MultiBuffer>,
21374 cx: &mut App,
21375) -> Task<()> {
21376 let mut tasks = Vec::new();
21377 project.update(cx, |project, cx| {
21378 for buffer in buffers {
21379 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
21380 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
21381 }
21382 }
21383 });
21384 cx.spawn(async move |cx| {
21385 let diffs = future::join_all(tasks).await;
21386 if editor
21387 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
21388 .unwrap_or(false)
21389 {
21390 return;
21391 }
21392
21393 buffer
21394 .update(cx, |buffer, cx| {
21395 for diff in diffs.into_iter().flatten() {
21396 buffer.add_diff(diff, cx);
21397 }
21398 })
21399 .ok();
21400 })
21401}
21402
21403fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
21404 let tab_size = tab_size.get() as usize;
21405 let mut width = offset;
21406
21407 for ch in text.chars() {
21408 width += if ch == '\t' {
21409 tab_size - (width % tab_size)
21410 } else {
21411 1
21412 };
21413 }
21414
21415 width - offset
21416}
21417
21418#[cfg(test)]
21419mod tests {
21420 use super::*;
21421
21422 #[test]
21423 fn test_string_size_with_expanded_tabs() {
21424 let nz = |val| NonZeroU32::new(val).unwrap();
21425 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
21426 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
21427 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
21428 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
21429 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
21430 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
21431 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
21432 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
21433 }
21434}
21435
21436/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
21437struct WordBreakingTokenizer<'a> {
21438 input: &'a str,
21439}
21440
21441impl<'a> WordBreakingTokenizer<'a> {
21442 fn new(input: &'a str) -> Self {
21443 Self { input }
21444 }
21445}
21446
21447fn is_char_ideographic(ch: char) -> bool {
21448 use unicode_script::Script::*;
21449 use unicode_script::UnicodeScript;
21450 matches!(ch.script(), Han | Tangut | Yi)
21451}
21452
21453fn is_grapheme_ideographic(text: &str) -> bool {
21454 text.chars().any(is_char_ideographic)
21455}
21456
21457fn is_grapheme_whitespace(text: &str) -> bool {
21458 text.chars().any(|x| x.is_whitespace())
21459}
21460
21461fn should_stay_with_preceding_ideograph(text: &str) -> bool {
21462 text.chars().next().map_or(false, |ch| {
21463 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
21464 })
21465}
21466
21467#[derive(PartialEq, Eq, Debug, Clone, Copy)]
21468enum WordBreakToken<'a> {
21469 Word { token: &'a str, grapheme_len: usize },
21470 InlineWhitespace { token: &'a str, grapheme_len: usize },
21471 Newline,
21472}
21473
21474impl<'a> Iterator for WordBreakingTokenizer<'a> {
21475 /// Yields a span, the count of graphemes in the token, and whether it was
21476 /// whitespace. Note that it also breaks at word boundaries.
21477 type Item = WordBreakToken<'a>;
21478
21479 fn next(&mut self) -> Option<Self::Item> {
21480 use unicode_segmentation::UnicodeSegmentation;
21481 if self.input.is_empty() {
21482 return None;
21483 }
21484
21485 let mut iter = self.input.graphemes(true).peekable();
21486 let mut offset = 0;
21487 let mut grapheme_len = 0;
21488 if let Some(first_grapheme) = iter.next() {
21489 let is_newline = first_grapheme == "\n";
21490 let is_whitespace = is_grapheme_whitespace(first_grapheme);
21491 offset += first_grapheme.len();
21492 grapheme_len += 1;
21493 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
21494 if let Some(grapheme) = iter.peek().copied() {
21495 if should_stay_with_preceding_ideograph(grapheme) {
21496 offset += grapheme.len();
21497 grapheme_len += 1;
21498 }
21499 }
21500 } else {
21501 let mut words = self.input[offset..].split_word_bound_indices().peekable();
21502 let mut next_word_bound = words.peek().copied();
21503 if next_word_bound.map_or(false, |(i, _)| i == 0) {
21504 next_word_bound = words.next();
21505 }
21506 while let Some(grapheme) = iter.peek().copied() {
21507 if next_word_bound.map_or(false, |(i, _)| i == offset) {
21508 break;
21509 };
21510 if is_grapheme_whitespace(grapheme) != is_whitespace
21511 || (grapheme == "\n") != is_newline
21512 {
21513 break;
21514 };
21515 offset += grapheme.len();
21516 grapheme_len += 1;
21517 iter.next();
21518 }
21519 }
21520 let token = &self.input[..offset];
21521 self.input = &self.input[offset..];
21522 if token == "\n" {
21523 Some(WordBreakToken::Newline)
21524 } else if is_whitespace {
21525 Some(WordBreakToken::InlineWhitespace {
21526 token,
21527 grapheme_len,
21528 })
21529 } else {
21530 Some(WordBreakToken::Word {
21531 token,
21532 grapheme_len,
21533 })
21534 }
21535 } else {
21536 None
21537 }
21538 }
21539}
21540
21541#[test]
21542fn test_word_breaking_tokenizer() {
21543 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
21544 ("", &[]),
21545 (" ", &[whitespace(" ", 2)]),
21546 ("Ʒ", &[word("Ʒ", 1)]),
21547 ("Ǽ", &[word("Ǽ", 1)]),
21548 ("⋑", &[word("⋑", 1)]),
21549 ("⋑⋑", &[word("⋑⋑", 2)]),
21550 (
21551 "原理,进而",
21552 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
21553 ),
21554 (
21555 "hello world",
21556 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
21557 ),
21558 (
21559 "hello, world",
21560 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
21561 ),
21562 (
21563 " hello world",
21564 &[
21565 whitespace(" ", 2),
21566 word("hello", 5),
21567 whitespace(" ", 1),
21568 word("world", 5),
21569 ],
21570 ),
21571 (
21572 "这是什么 \n 钢笔",
21573 &[
21574 word("这", 1),
21575 word("是", 1),
21576 word("什", 1),
21577 word("么", 1),
21578 whitespace(" ", 1),
21579 newline(),
21580 whitespace(" ", 1),
21581 word("钢", 1),
21582 word("笔", 1),
21583 ],
21584 ),
21585 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
21586 ];
21587
21588 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21589 WordBreakToken::Word {
21590 token,
21591 grapheme_len,
21592 }
21593 }
21594
21595 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21596 WordBreakToken::InlineWhitespace {
21597 token,
21598 grapheme_len,
21599 }
21600 }
21601
21602 fn newline() -> WordBreakToken<'static> {
21603 WordBreakToken::Newline
21604 }
21605
21606 for (input, result) in tests {
21607 assert_eq!(
21608 WordBreakingTokenizer::new(input)
21609 .collect::<Vec<_>>()
21610 .as_slice(),
21611 *result,
21612 );
21613 }
21614}
21615
21616fn wrap_with_prefix(
21617 first_line_prefix: String,
21618 subsequent_lines_prefix: String,
21619 unwrapped_text: String,
21620 wrap_column: usize,
21621 tab_size: NonZeroU32,
21622 preserve_existing_whitespace: bool,
21623) -> String {
21624 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
21625 let subsequent_lines_prefix_len =
21626 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
21627 let mut wrapped_text = String::new();
21628 let mut current_line = first_line_prefix.clone();
21629 let mut is_first_line = true;
21630
21631 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
21632 let mut current_line_len = first_line_prefix_len;
21633 let mut in_whitespace = false;
21634 for token in tokenizer {
21635 let have_preceding_whitespace = in_whitespace;
21636 match token {
21637 WordBreakToken::Word {
21638 token,
21639 grapheme_len,
21640 } => {
21641 in_whitespace = false;
21642 let current_prefix_len = if is_first_line {
21643 first_line_prefix_len
21644 } else {
21645 subsequent_lines_prefix_len
21646 };
21647 if current_line_len + grapheme_len > wrap_column
21648 && current_line_len != current_prefix_len
21649 {
21650 wrapped_text.push_str(current_line.trim_end());
21651 wrapped_text.push('\n');
21652 is_first_line = false;
21653 current_line = subsequent_lines_prefix.clone();
21654 current_line_len = subsequent_lines_prefix_len;
21655 }
21656 current_line.push_str(token);
21657 current_line_len += grapheme_len;
21658 }
21659 WordBreakToken::InlineWhitespace {
21660 mut token,
21661 mut grapheme_len,
21662 } => {
21663 in_whitespace = true;
21664 if have_preceding_whitespace && !preserve_existing_whitespace {
21665 continue;
21666 }
21667 if !preserve_existing_whitespace {
21668 token = " ";
21669 grapheme_len = 1;
21670 }
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 wrapped_text.push_str(current_line.trim_end());
21678 wrapped_text.push('\n');
21679 is_first_line = false;
21680 current_line = subsequent_lines_prefix.clone();
21681 current_line_len = subsequent_lines_prefix_len;
21682 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
21683 current_line.push_str(token);
21684 current_line_len += grapheme_len;
21685 }
21686 }
21687 WordBreakToken::Newline => {
21688 in_whitespace = true;
21689 let current_prefix_len = if is_first_line {
21690 first_line_prefix_len
21691 } else {
21692 subsequent_lines_prefix_len
21693 };
21694 if preserve_existing_whitespace {
21695 wrapped_text.push_str(current_line.trim_end());
21696 wrapped_text.push('\n');
21697 is_first_line = false;
21698 current_line = subsequent_lines_prefix.clone();
21699 current_line_len = subsequent_lines_prefix_len;
21700 } else if have_preceding_whitespace {
21701 continue;
21702 } else if current_line_len + 1 > wrap_column
21703 && current_line_len != current_prefix_len
21704 {
21705 wrapped_text.push_str(current_line.trim_end());
21706 wrapped_text.push('\n');
21707 is_first_line = false;
21708 current_line = subsequent_lines_prefix.clone();
21709 current_line_len = subsequent_lines_prefix_len;
21710 } else if current_line_len != current_prefix_len {
21711 current_line.push(' ');
21712 current_line_len += 1;
21713 }
21714 }
21715 }
21716 }
21717
21718 if !current_line.is_empty() {
21719 wrapped_text.push_str(¤t_line);
21720 }
21721 wrapped_text
21722}
21723
21724#[test]
21725fn test_wrap_with_prefix() {
21726 assert_eq!(
21727 wrap_with_prefix(
21728 "# ".to_string(),
21729 "# ".to_string(),
21730 "abcdefg".to_string(),
21731 4,
21732 NonZeroU32::new(4).unwrap(),
21733 false,
21734 ),
21735 "# abcdefg"
21736 );
21737 assert_eq!(
21738 wrap_with_prefix(
21739 "".to_string(),
21740 "".to_string(),
21741 "\thello world".to_string(),
21742 8,
21743 NonZeroU32::new(4).unwrap(),
21744 false,
21745 ),
21746 "hello\nworld"
21747 );
21748 assert_eq!(
21749 wrap_with_prefix(
21750 "// ".to_string(),
21751 "// ".to_string(),
21752 "xx \nyy zz aa bb cc".to_string(),
21753 12,
21754 NonZeroU32::new(4).unwrap(),
21755 false,
21756 ),
21757 "// xx yy zz\n// aa bb cc"
21758 );
21759 assert_eq!(
21760 wrap_with_prefix(
21761 String::new(),
21762 String::new(),
21763 "这是什么 \n 钢笔".to_string(),
21764 3,
21765 NonZeroU32::new(4).unwrap(),
21766 false,
21767 ),
21768 "这是什\n么 钢\n笔"
21769 );
21770}
21771
21772pub trait CollaborationHub {
21773 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
21774 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
21775 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
21776}
21777
21778impl CollaborationHub for Entity<Project> {
21779 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
21780 self.read(cx).collaborators()
21781 }
21782
21783 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
21784 self.read(cx).user_store().read(cx).participant_indices()
21785 }
21786
21787 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
21788 let this = self.read(cx);
21789 let user_ids = this.collaborators().values().map(|c| c.user_id);
21790 this.user_store().read(cx).participant_names(user_ids, cx)
21791 }
21792}
21793
21794pub trait SemanticsProvider {
21795 fn hover(
21796 &self,
21797 buffer: &Entity<Buffer>,
21798 position: text::Anchor,
21799 cx: &mut App,
21800 ) -> Option<Task<Vec<project::Hover>>>;
21801
21802 fn inline_values(
21803 &self,
21804 buffer_handle: Entity<Buffer>,
21805 range: Range<text::Anchor>,
21806 cx: &mut App,
21807 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21808
21809 fn inlay_hints(
21810 &self,
21811 buffer_handle: Entity<Buffer>,
21812 range: Range<text::Anchor>,
21813 cx: &mut App,
21814 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21815
21816 fn resolve_inlay_hint(
21817 &self,
21818 hint: InlayHint,
21819 buffer_handle: Entity<Buffer>,
21820 server_id: LanguageServerId,
21821 cx: &mut App,
21822 ) -> Option<Task<anyhow::Result<InlayHint>>>;
21823
21824 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
21825
21826 fn document_highlights(
21827 &self,
21828 buffer: &Entity<Buffer>,
21829 position: text::Anchor,
21830 cx: &mut App,
21831 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
21832
21833 fn definitions(
21834 &self,
21835 buffer: &Entity<Buffer>,
21836 position: text::Anchor,
21837 kind: GotoDefinitionKind,
21838 cx: &mut App,
21839 ) -> Option<Task<Result<Vec<LocationLink>>>>;
21840
21841 fn range_for_rename(
21842 &self,
21843 buffer: &Entity<Buffer>,
21844 position: text::Anchor,
21845 cx: &mut App,
21846 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
21847
21848 fn perform_rename(
21849 &self,
21850 buffer: &Entity<Buffer>,
21851 position: text::Anchor,
21852 new_name: String,
21853 cx: &mut App,
21854 ) -> Option<Task<Result<ProjectTransaction>>>;
21855}
21856
21857pub trait CompletionProvider {
21858 fn completions(
21859 &self,
21860 excerpt_id: ExcerptId,
21861 buffer: &Entity<Buffer>,
21862 buffer_position: text::Anchor,
21863 trigger: CompletionContext,
21864 window: &mut Window,
21865 cx: &mut Context<Editor>,
21866 ) -> Task<Result<Vec<CompletionResponse>>>;
21867
21868 fn resolve_completions(
21869 &self,
21870 _buffer: Entity<Buffer>,
21871 _completion_indices: Vec<usize>,
21872 _completions: Rc<RefCell<Box<[Completion]>>>,
21873 _cx: &mut Context<Editor>,
21874 ) -> Task<Result<bool>> {
21875 Task::ready(Ok(false))
21876 }
21877
21878 fn apply_additional_edits_for_completion(
21879 &self,
21880 _buffer: Entity<Buffer>,
21881 _completions: Rc<RefCell<Box<[Completion]>>>,
21882 _completion_index: usize,
21883 _push_to_history: bool,
21884 _cx: &mut Context<Editor>,
21885 ) -> Task<Result<Option<language::Transaction>>> {
21886 Task::ready(Ok(None))
21887 }
21888
21889 fn is_completion_trigger(
21890 &self,
21891 buffer: &Entity<Buffer>,
21892 position: language::Anchor,
21893 text: &str,
21894 trigger_in_words: bool,
21895 menu_is_open: bool,
21896 cx: &mut Context<Editor>,
21897 ) -> bool;
21898
21899 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
21900
21901 fn sort_completions(&self) -> bool {
21902 true
21903 }
21904
21905 fn filter_completions(&self) -> bool {
21906 true
21907 }
21908}
21909
21910pub trait CodeActionProvider {
21911 fn id(&self) -> Arc<str>;
21912
21913 fn code_actions(
21914 &self,
21915 buffer: &Entity<Buffer>,
21916 range: Range<text::Anchor>,
21917 window: &mut Window,
21918 cx: &mut App,
21919 ) -> Task<Result<Vec<CodeAction>>>;
21920
21921 fn apply_code_action(
21922 &self,
21923 buffer_handle: Entity<Buffer>,
21924 action: CodeAction,
21925 excerpt_id: ExcerptId,
21926 push_to_history: bool,
21927 window: &mut Window,
21928 cx: &mut App,
21929 ) -> Task<Result<ProjectTransaction>>;
21930}
21931
21932impl CodeActionProvider for Entity<Project> {
21933 fn id(&self) -> Arc<str> {
21934 "project".into()
21935 }
21936
21937 fn code_actions(
21938 &self,
21939 buffer: &Entity<Buffer>,
21940 range: Range<text::Anchor>,
21941 _window: &mut Window,
21942 cx: &mut App,
21943 ) -> Task<Result<Vec<CodeAction>>> {
21944 self.update(cx, |project, cx| {
21945 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
21946 let code_actions = project.code_actions(buffer, range, None, cx);
21947 cx.background_spawn(async move {
21948 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
21949 Ok(code_lens_actions
21950 .context("code lens fetch")?
21951 .into_iter()
21952 .chain(code_actions.context("code action fetch")?)
21953 .collect())
21954 })
21955 })
21956 }
21957
21958 fn apply_code_action(
21959 &self,
21960 buffer_handle: Entity<Buffer>,
21961 action: CodeAction,
21962 _excerpt_id: ExcerptId,
21963 push_to_history: bool,
21964 _window: &mut Window,
21965 cx: &mut App,
21966 ) -> Task<Result<ProjectTransaction>> {
21967 self.update(cx, |project, cx| {
21968 project.apply_code_action(buffer_handle, action, push_to_history, cx)
21969 })
21970 }
21971}
21972
21973fn snippet_completions(
21974 project: &Project,
21975 buffer: &Entity<Buffer>,
21976 buffer_position: text::Anchor,
21977 cx: &mut App,
21978) -> Task<Result<CompletionResponse>> {
21979 let languages = buffer.read(cx).languages_at(buffer_position);
21980 let snippet_store = project.snippets().read(cx);
21981
21982 let scopes: Vec<_> = languages
21983 .iter()
21984 .filter_map(|language| {
21985 let language_name = language.lsp_id();
21986 let snippets = snippet_store.snippets_for(Some(language_name), cx);
21987
21988 if snippets.is_empty() {
21989 None
21990 } else {
21991 Some((language.default_scope(), snippets))
21992 }
21993 })
21994 .collect();
21995
21996 if scopes.is_empty() {
21997 return Task::ready(Ok(CompletionResponse {
21998 completions: vec![],
21999 is_incomplete: false,
22000 }));
22001 }
22002
22003 let snapshot = buffer.read(cx).text_snapshot();
22004 let chars: String = snapshot
22005 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
22006 .collect();
22007 let executor = cx.background_executor().clone();
22008
22009 cx.background_spawn(async move {
22010 let mut is_incomplete = false;
22011 let mut completions: Vec<Completion> = Vec::new();
22012 for (scope, snippets) in scopes.into_iter() {
22013 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
22014 let mut last_word = chars
22015 .chars()
22016 .take_while(|c| classifier.is_word(*c))
22017 .collect::<String>();
22018 last_word = last_word.chars().rev().collect();
22019
22020 if last_word.is_empty() {
22021 return Ok(CompletionResponse {
22022 completions: vec![],
22023 is_incomplete: true,
22024 });
22025 }
22026
22027 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
22028 let to_lsp = |point: &text::Anchor| {
22029 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
22030 point_to_lsp(end)
22031 };
22032 let lsp_end = to_lsp(&buffer_position);
22033
22034 let candidates = snippets
22035 .iter()
22036 .enumerate()
22037 .flat_map(|(ix, snippet)| {
22038 snippet
22039 .prefix
22040 .iter()
22041 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
22042 })
22043 .collect::<Vec<StringMatchCandidate>>();
22044
22045 const MAX_RESULTS: usize = 100;
22046 let mut matches = fuzzy::match_strings(
22047 &candidates,
22048 &last_word,
22049 last_word.chars().any(|c| c.is_uppercase()),
22050 true,
22051 MAX_RESULTS,
22052 &Default::default(),
22053 executor.clone(),
22054 )
22055 .await;
22056
22057 if matches.len() >= MAX_RESULTS {
22058 is_incomplete = true;
22059 }
22060
22061 // Remove all candidates where the query's start does not match the start of any word in the candidate
22062 if let Some(query_start) = last_word.chars().next() {
22063 matches.retain(|string_match| {
22064 split_words(&string_match.string).any(|word| {
22065 // Check that the first codepoint of the word as lowercase matches the first
22066 // codepoint of the query as lowercase
22067 word.chars()
22068 .flat_map(|codepoint| codepoint.to_lowercase())
22069 .zip(query_start.to_lowercase())
22070 .all(|(word_cp, query_cp)| word_cp == query_cp)
22071 })
22072 });
22073 }
22074
22075 let matched_strings = matches
22076 .into_iter()
22077 .map(|m| m.string)
22078 .collect::<HashSet<_>>();
22079
22080 completions.extend(snippets.iter().filter_map(|snippet| {
22081 let matching_prefix = snippet
22082 .prefix
22083 .iter()
22084 .find(|prefix| matched_strings.contains(*prefix))?;
22085 let start = as_offset - last_word.len();
22086 let start = snapshot.anchor_before(start);
22087 let range = start..buffer_position;
22088 let lsp_start = to_lsp(&start);
22089 let lsp_range = lsp::Range {
22090 start: lsp_start,
22091 end: lsp_end,
22092 };
22093 Some(Completion {
22094 replace_range: range,
22095 new_text: snippet.body.clone(),
22096 source: CompletionSource::Lsp {
22097 insert_range: None,
22098 server_id: LanguageServerId(usize::MAX),
22099 resolved: true,
22100 lsp_completion: Box::new(lsp::CompletionItem {
22101 label: snippet.prefix.first().unwrap().clone(),
22102 kind: Some(CompletionItemKind::SNIPPET),
22103 label_details: snippet.description.as_ref().map(|description| {
22104 lsp::CompletionItemLabelDetails {
22105 detail: Some(description.clone()),
22106 description: None,
22107 }
22108 }),
22109 insert_text_format: Some(InsertTextFormat::SNIPPET),
22110 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22111 lsp::InsertReplaceEdit {
22112 new_text: snippet.body.clone(),
22113 insert: lsp_range,
22114 replace: lsp_range,
22115 },
22116 )),
22117 filter_text: Some(snippet.body.clone()),
22118 sort_text: Some(char::MAX.to_string()),
22119 ..lsp::CompletionItem::default()
22120 }),
22121 lsp_defaults: None,
22122 },
22123 label: CodeLabel {
22124 text: matching_prefix.clone(),
22125 runs: Vec::new(),
22126 filter_range: 0..matching_prefix.len(),
22127 },
22128 icon_path: None,
22129 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
22130 single_line: snippet.name.clone().into(),
22131 plain_text: snippet
22132 .description
22133 .clone()
22134 .map(|description| description.into()),
22135 }),
22136 insert_text_mode: None,
22137 confirm: None,
22138 })
22139 }))
22140 }
22141
22142 Ok(CompletionResponse {
22143 completions,
22144 is_incomplete,
22145 })
22146 })
22147}
22148
22149impl CompletionProvider for Entity<Project> {
22150 fn completions(
22151 &self,
22152 _excerpt_id: ExcerptId,
22153 buffer: &Entity<Buffer>,
22154 buffer_position: text::Anchor,
22155 options: CompletionContext,
22156 _window: &mut Window,
22157 cx: &mut Context<Editor>,
22158 ) -> Task<Result<Vec<CompletionResponse>>> {
22159 self.update(cx, |project, cx| {
22160 let snippets = snippet_completions(project, buffer, buffer_position, cx);
22161 let project_completions = project.completions(buffer, buffer_position, options, cx);
22162 cx.background_spawn(async move {
22163 let mut responses = project_completions.await?;
22164 let snippets = snippets.await?;
22165 if !snippets.completions.is_empty() {
22166 responses.push(snippets);
22167 }
22168 Ok(responses)
22169 })
22170 })
22171 }
22172
22173 fn resolve_completions(
22174 &self,
22175 buffer: Entity<Buffer>,
22176 completion_indices: Vec<usize>,
22177 completions: Rc<RefCell<Box<[Completion]>>>,
22178 cx: &mut Context<Editor>,
22179 ) -> Task<Result<bool>> {
22180 self.update(cx, |project, cx| {
22181 project.lsp_store().update(cx, |lsp_store, cx| {
22182 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
22183 })
22184 })
22185 }
22186
22187 fn apply_additional_edits_for_completion(
22188 &self,
22189 buffer: Entity<Buffer>,
22190 completions: Rc<RefCell<Box<[Completion]>>>,
22191 completion_index: usize,
22192 push_to_history: bool,
22193 cx: &mut Context<Editor>,
22194 ) -> Task<Result<Option<language::Transaction>>> {
22195 self.update(cx, |project, cx| {
22196 project.lsp_store().update(cx, |lsp_store, cx| {
22197 lsp_store.apply_additional_edits_for_completion(
22198 buffer,
22199 completions,
22200 completion_index,
22201 push_to_history,
22202 cx,
22203 )
22204 })
22205 })
22206 }
22207
22208 fn is_completion_trigger(
22209 &self,
22210 buffer: &Entity<Buffer>,
22211 position: language::Anchor,
22212 text: &str,
22213 trigger_in_words: bool,
22214 menu_is_open: bool,
22215 cx: &mut Context<Editor>,
22216 ) -> bool {
22217 let mut chars = text.chars();
22218 let char = if let Some(char) = chars.next() {
22219 char
22220 } else {
22221 return false;
22222 };
22223 if chars.next().is_some() {
22224 return false;
22225 }
22226
22227 let buffer = buffer.read(cx);
22228 let snapshot = buffer.snapshot();
22229 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
22230 return false;
22231 }
22232 let classifier = snapshot.char_classifier_at(position).for_completion(true);
22233 if trigger_in_words && classifier.is_word(char) {
22234 return true;
22235 }
22236
22237 buffer.completion_triggers().contains(text)
22238 }
22239}
22240
22241impl SemanticsProvider for Entity<Project> {
22242 fn hover(
22243 &self,
22244 buffer: &Entity<Buffer>,
22245 position: text::Anchor,
22246 cx: &mut App,
22247 ) -> Option<Task<Vec<project::Hover>>> {
22248 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
22249 }
22250
22251 fn document_highlights(
22252 &self,
22253 buffer: &Entity<Buffer>,
22254 position: text::Anchor,
22255 cx: &mut App,
22256 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
22257 Some(self.update(cx, |project, cx| {
22258 project.document_highlights(buffer, position, cx)
22259 }))
22260 }
22261
22262 fn definitions(
22263 &self,
22264 buffer: &Entity<Buffer>,
22265 position: text::Anchor,
22266 kind: GotoDefinitionKind,
22267 cx: &mut App,
22268 ) -> Option<Task<Result<Vec<LocationLink>>>> {
22269 Some(self.update(cx, |project, cx| match kind {
22270 GotoDefinitionKind::Symbol => project.definitions(&buffer, position, cx),
22271 GotoDefinitionKind::Declaration => project.declarations(&buffer, position, cx),
22272 GotoDefinitionKind::Type => project.type_definitions(&buffer, position, cx),
22273 GotoDefinitionKind::Implementation => project.implementations(&buffer, position, cx),
22274 }))
22275 }
22276
22277 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
22278 self.update(cx, |project, cx| {
22279 if project
22280 .active_debug_session(cx)
22281 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
22282 {
22283 return true;
22284 }
22285
22286 buffer.update(cx, |buffer, cx| {
22287 project.any_language_server_supports_inlay_hints(buffer, cx)
22288 })
22289 })
22290 }
22291
22292 fn inline_values(
22293 &self,
22294 buffer_handle: Entity<Buffer>,
22295 range: Range<text::Anchor>,
22296 cx: &mut App,
22297 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22298 self.update(cx, |project, cx| {
22299 let (session, active_stack_frame) = project.active_debug_session(cx)?;
22300
22301 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
22302 })
22303 }
22304
22305 fn inlay_hints(
22306 &self,
22307 buffer_handle: Entity<Buffer>,
22308 range: Range<text::Anchor>,
22309 cx: &mut App,
22310 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22311 Some(self.update(cx, |project, cx| {
22312 project.inlay_hints(buffer_handle, range, cx)
22313 }))
22314 }
22315
22316 fn resolve_inlay_hint(
22317 &self,
22318 hint: InlayHint,
22319 buffer_handle: Entity<Buffer>,
22320 server_id: LanguageServerId,
22321 cx: &mut App,
22322 ) -> Option<Task<anyhow::Result<InlayHint>>> {
22323 Some(self.update(cx, |project, cx| {
22324 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
22325 }))
22326 }
22327
22328 fn range_for_rename(
22329 &self,
22330 buffer: &Entity<Buffer>,
22331 position: text::Anchor,
22332 cx: &mut App,
22333 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
22334 Some(self.update(cx, |project, cx| {
22335 let buffer = buffer.clone();
22336 let task = project.prepare_rename(buffer.clone(), position, cx);
22337 cx.spawn(async move |_, cx| {
22338 Ok(match task.await? {
22339 PrepareRenameResponse::Success(range) => Some(range),
22340 PrepareRenameResponse::InvalidPosition => None,
22341 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
22342 // Fallback on using TreeSitter info to determine identifier range
22343 buffer.read_with(cx, |buffer, _| {
22344 let snapshot = buffer.snapshot();
22345 let (range, kind) = snapshot.surrounding_word(position, false);
22346 if kind != Some(CharKind::Word) {
22347 return None;
22348 }
22349 Some(
22350 snapshot.anchor_before(range.start)
22351 ..snapshot.anchor_after(range.end),
22352 )
22353 })?
22354 }
22355 })
22356 })
22357 }))
22358 }
22359
22360 fn perform_rename(
22361 &self,
22362 buffer: &Entity<Buffer>,
22363 position: text::Anchor,
22364 new_name: String,
22365 cx: &mut App,
22366 ) -> Option<Task<Result<ProjectTransaction>>> {
22367 Some(self.update(cx, |project, cx| {
22368 project.perform_rename(buffer.clone(), position, new_name, cx)
22369 }))
22370 }
22371}
22372
22373fn inlay_hint_settings(
22374 location: Anchor,
22375 snapshot: &MultiBufferSnapshot,
22376 cx: &mut Context<Editor>,
22377) -> InlayHintSettings {
22378 let file = snapshot.file_at(location);
22379 let language = snapshot.language_at(location).map(|l| l.name());
22380 language_settings(language, file, cx).inlay_hints
22381}
22382
22383fn consume_contiguous_rows(
22384 contiguous_row_selections: &mut Vec<Selection<Point>>,
22385 selection: &Selection<Point>,
22386 display_map: &DisplaySnapshot,
22387 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
22388) -> (MultiBufferRow, MultiBufferRow) {
22389 contiguous_row_selections.push(selection.clone());
22390 let start_row = starting_row(selection, display_map);
22391 let mut end_row = ending_row(selection, display_map);
22392
22393 while let Some(next_selection) = selections.peek() {
22394 if next_selection.start.row <= end_row.0 {
22395 end_row = ending_row(next_selection, display_map);
22396 contiguous_row_selections.push(selections.next().unwrap().clone());
22397 } else {
22398 break;
22399 }
22400 }
22401 (start_row, end_row)
22402}
22403
22404fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22405 if selection.start.column > 0 {
22406 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
22407 } else {
22408 MultiBufferRow(selection.start.row)
22409 }
22410}
22411
22412fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22413 if next_selection.end.column > 0 || next_selection.is_empty() {
22414 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
22415 } else {
22416 MultiBufferRow(next_selection.end.row)
22417 }
22418}
22419
22420impl EditorSnapshot {
22421 pub fn remote_selections_in_range<'a>(
22422 &'a self,
22423 range: &'a Range<Anchor>,
22424 collaboration_hub: &dyn CollaborationHub,
22425 cx: &'a App,
22426 ) -> impl 'a + Iterator<Item = RemoteSelection> {
22427 let participant_names = collaboration_hub.user_names(cx);
22428 let participant_indices = collaboration_hub.user_participant_indices(cx);
22429 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
22430 let collaborators_by_replica_id = collaborators_by_peer_id
22431 .values()
22432 .map(|collaborator| (collaborator.replica_id, collaborator))
22433 .collect::<HashMap<_, _>>();
22434 self.buffer_snapshot
22435 .selections_in_range(range, false)
22436 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
22437 if replica_id == AGENT_REPLICA_ID {
22438 Some(RemoteSelection {
22439 replica_id,
22440 selection,
22441 cursor_shape,
22442 line_mode,
22443 collaborator_id: CollaboratorId::Agent,
22444 user_name: Some("Agent".into()),
22445 color: cx.theme().players().agent(),
22446 })
22447 } else {
22448 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
22449 let participant_index = participant_indices.get(&collaborator.user_id).copied();
22450 let user_name = participant_names.get(&collaborator.user_id).cloned();
22451 Some(RemoteSelection {
22452 replica_id,
22453 selection,
22454 cursor_shape,
22455 line_mode,
22456 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
22457 user_name,
22458 color: if let Some(index) = participant_index {
22459 cx.theme().players().color_for_participant(index.0)
22460 } else {
22461 cx.theme().players().absent()
22462 },
22463 })
22464 }
22465 })
22466 }
22467
22468 pub fn hunks_for_ranges(
22469 &self,
22470 ranges: impl IntoIterator<Item = Range<Point>>,
22471 ) -> Vec<MultiBufferDiffHunk> {
22472 let mut hunks = Vec::new();
22473 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
22474 HashMap::default();
22475 for query_range in ranges {
22476 let query_rows =
22477 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
22478 for hunk in self.buffer_snapshot.diff_hunks_in_range(
22479 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
22480 ) {
22481 // Include deleted hunks that are adjacent to the query range, because
22482 // otherwise they would be missed.
22483 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
22484 if hunk.status().is_deleted() {
22485 intersects_range |= hunk.row_range.start == query_rows.end;
22486 intersects_range |= hunk.row_range.end == query_rows.start;
22487 }
22488 if intersects_range {
22489 if !processed_buffer_rows
22490 .entry(hunk.buffer_id)
22491 .or_default()
22492 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
22493 {
22494 continue;
22495 }
22496 hunks.push(hunk);
22497 }
22498 }
22499 }
22500
22501 hunks
22502 }
22503
22504 fn display_diff_hunks_for_rows<'a>(
22505 &'a self,
22506 display_rows: Range<DisplayRow>,
22507 folded_buffers: &'a HashSet<BufferId>,
22508 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
22509 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
22510 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
22511
22512 self.buffer_snapshot
22513 .diff_hunks_in_range(buffer_start..buffer_end)
22514 .filter_map(|hunk| {
22515 if folded_buffers.contains(&hunk.buffer_id) {
22516 return None;
22517 }
22518
22519 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
22520 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
22521
22522 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
22523 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
22524
22525 let display_hunk = if hunk_display_start.column() != 0 {
22526 DisplayDiffHunk::Folded {
22527 display_row: hunk_display_start.row(),
22528 }
22529 } else {
22530 let mut end_row = hunk_display_end.row();
22531 if hunk_display_end.column() > 0 {
22532 end_row.0 += 1;
22533 }
22534 let is_created_file = hunk.is_created_file();
22535 DisplayDiffHunk::Unfolded {
22536 status: hunk.status(),
22537 diff_base_byte_range: hunk.diff_base_byte_range,
22538 display_row_range: hunk_display_start.row()..end_row,
22539 multi_buffer_range: Anchor::range_in_buffer(
22540 hunk.excerpt_id,
22541 hunk.buffer_id,
22542 hunk.buffer_range,
22543 ),
22544 is_created_file,
22545 }
22546 };
22547
22548 Some(display_hunk)
22549 })
22550 }
22551
22552 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
22553 self.display_snapshot.buffer_snapshot.language_at(position)
22554 }
22555
22556 pub fn is_focused(&self) -> bool {
22557 self.is_focused
22558 }
22559
22560 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
22561 self.placeholder_text.as_ref()
22562 }
22563
22564 pub fn scroll_position(&self) -> gpui::Point<f32> {
22565 self.scroll_anchor.scroll_position(&self.display_snapshot)
22566 }
22567
22568 fn gutter_dimensions(
22569 &self,
22570 font_id: FontId,
22571 font_size: Pixels,
22572 max_line_number_width: Pixels,
22573 cx: &App,
22574 ) -> Option<GutterDimensions> {
22575 if !self.show_gutter {
22576 return None;
22577 }
22578
22579 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
22580 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
22581
22582 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
22583 matches!(
22584 ProjectSettings::get_global(cx).git.git_gutter,
22585 Some(GitGutterSetting::TrackedFiles)
22586 )
22587 });
22588 let gutter_settings = EditorSettings::get_global(cx).gutter;
22589 let show_line_numbers = self
22590 .show_line_numbers
22591 .unwrap_or(gutter_settings.line_numbers);
22592 let line_gutter_width = if show_line_numbers {
22593 // Avoid flicker-like gutter resizes when the line number gains another digit by
22594 // only resizing the gutter on files with > 10**min_line_number_digits lines.
22595 let min_width_for_number_on_gutter =
22596 ch_advance * gutter_settings.min_line_number_digits as f32;
22597 max_line_number_width.max(min_width_for_number_on_gutter)
22598 } else {
22599 0.0.into()
22600 };
22601
22602 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
22603 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
22604
22605 let git_blame_entries_width =
22606 self.git_blame_gutter_max_author_length
22607 .map(|max_author_length| {
22608 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
22609 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
22610
22611 /// The number of characters to dedicate to gaps and margins.
22612 const SPACING_WIDTH: usize = 4;
22613
22614 let max_char_count = max_author_length.min(renderer.max_author_length())
22615 + ::git::SHORT_SHA_LENGTH
22616 + MAX_RELATIVE_TIMESTAMP.len()
22617 + SPACING_WIDTH;
22618
22619 ch_advance * max_char_count
22620 });
22621
22622 let is_singleton = self.buffer_snapshot.is_singleton();
22623
22624 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
22625 left_padding += if !is_singleton {
22626 ch_width * 4.0
22627 } else if show_runnables || show_breakpoints {
22628 ch_width * 3.0
22629 } else if show_git_gutter && show_line_numbers {
22630 ch_width * 2.0
22631 } else if show_git_gutter || show_line_numbers {
22632 ch_width
22633 } else {
22634 px(0.)
22635 };
22636
22637 let shows_folds = is_singleton && gutter_settings.folds;
22638
22639 let right_padding = if shows_folds && show_line_numbers {
22640 ch_width * 4.0
22641 } else if shows_folds || (!is_singleton && show_line_numbers) {
22642 ch_width * 3.0
22643 } else if show_line_numbers {
22644 ch_width
22645 } else {
22646 px(0.)
22647 };
22648
22649 Some(GutterDimensions {
22650 left_padding,
22651 right_padding,
22652 width: line_gutter_width + left_padding + right_padding,
22653 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
22654 git_blame_entries_width,
22655 })
22656 }
22657
22658 pub fn render_crease_toggle(
22659 &self,
22660 buffer_row: MultiBufferRow,
22661 row_contains_cursor: bool,
22662 editor: Entity<Editor>,
22663 window: &mut Window,
22664 cx: &mut App,
22665 ) -> Option<AnyElement> {
22666 let folded = self.is_line_folded(buffer_row);
22667 let mut is_foldable = false;
22668
22669 if let Some(crease) = self
22670 .crease_snapshot
22671 .query_row(buffer_row, &self.buffer_snapshot)
22672 {
22673 is_foldable = true;
22674 match crease {
22675 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
22676 if let Some(render_toggle) = render_toggle {
22677 let toggle_callback =
22678 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
22679 if folded {
22680 editor.update(cx, |editor, cx| {
22681 editor.fold_at(buffer_row, window, cx)
22682 });
22683 } else {
22684 editor.update(cx, |editor, cx| {
22685 editor.unfold_at(buffer_row, window, cx)
22686 });
22687 }
22688 });
22689 return Some((render_toggle)(
22690 buffer_row,
22691 folded,
22692 toggle_callback,
22693 window,
22694 cx,
22695 ));
22696 }
22697 }
22698 }
22699 }
22700
22701 is_foldable |= self.starts_indent(buffer_row);
22702
22703 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
22704 Some(
22705 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
22706 .toggle_state(folded)
22707 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
22708 if folded {
22709 this.unfold_at(buffer_row, window, cx);
22710 } else {
22711 this.fold_at(buffer_row, window, cx);
22712 }
22713 }))
22714 .into_any_element(),
22715 )
22716 } else {
22717 None
22718 }
22719 }
22720
22721 pub fn render_crease_trailer(
22722 &self,
22723 buffer_row: MultiBufferRow,
22724 window: &mut Window,
22725 cx: &mut App,
22726 ) -> Option<AnyElement> {
22727 let folded = self.is_line_folded(buffer_row);
22728 if let Crease::Inline { render_trailer, .. } = self
22729 .crease_snapshot
22730 .query_row(buffer_row, &self.buffer_snapshot)?
22731 {
22732 let render_trailer = render_trailer.as_ref()?;
22733 Some(render_trailer(buffer_row, folded, window, cx))
22734 } else {
22735 None
22736 }
22737 }
22738}
22739
22740impl Deref for EditorSnapshot {
22741 type Target = DisplaySnapshot;
22742
22743 fn deref(&self) -> &Self::Target {
22744 &self.display_snapshot
22745 }
22746}
22747
22748#[derive(Clone, Debug, PartialEq, Eq)]
22749pub enum EditorEvent {
22750 InputIgnored {
22751 text: Arc<str>,
22752 },
22753 InputHandled {
22754 utf16_range_to_replace: Option<Range<isize>>,
22755 text: Arc<str>,
22756 },
22757 ExcerptsAdded {
22758 buffer: Entity<Buffer>,
22759 predecessor: ExcerptId,
22760 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
22761 },
22762 ExcerptsRemoved {
22763 ids: Vec<ExcerptId>,
22764 removed_buffer_ids: Vec<BufferId>,
22765 },
22766 BufferFoldToggled {
22767 ids: Vec<ExcerptId>,
22768 folded: bool,
22769 },
22770 ExcerptsEdited {
22771 ids: Vec<ExcerptId>,
22772 },
22773 ExcerptsExpanded {
22774 ids: Vec<ExcerptId>,
22775 },
22776 BufferEdited,
22777 Edited {
22778 transaction_id: clock::Lamport,
22779 },
22780 Reparsed(BufferId),
22781 Focused,
22782 FocusedIn,
22783 Blurred,
22784 DirtyChanged,
22785 Saved,
22786 TitleChanged,
22787 DiffBaseChanged,
22788 SelectionsChanged {
22789 local: bool,
22790 },
22791 ScrollPositionChanged {
22792 local: bool,
22793 autoscroll: bool,
22794 },
22795 Closed,
22796 TransactionUndone {
22797 transaction_id: clock::Lamport,
22798 },
22799 TransactionBegun {
22800 transaction_id: clock::Lamport,
22801 },
22802 Reloaded,
22803 CursorShapeChanged,
22804 PushedToNavHistory {
22805 anchor: Anchor,
22806 is_deactivate: bool,
22807 },
22808}
22809
22810impl EventEmitter<EditorEvent> for Editor {}
22811
22812impl Focusable for Editor {
22813 fn focus_handle(&self, _cx: &App) -> FocusHandle {
22814 self.focus_handle.clone()
22815 }
22816}
22817
22818impl Render for Editor {
22819 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22820 let settings = ThemeSettings::get_global(cx);
22821
22822 let mut text_style = match self.mode {
22823 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
22824 color: cx.theme().colors().editor_foreground,
22825 font_family: settings.ui_font.family.clone(),
22826 font_features: settings.ui_font.features.clone(),
22827 font_fallbacks: settings.ui_font.fallbacks.clone(),
22828 font_size: rems(0.875).into(),
22829 font_weight: settings.ui_font.weight,
22830 line_height: relative(settings.buffer_line_height.value()),
22831 ..Default::default()
22832 },
22833 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
22834 color: cx.theme().colors().editor_foreground,
22835 font_family: settings.buffer_font.family.clone(),
22836 font_features: settings.buffer_font.features.clone(),
22837 font_fallbacks: settings.buffer_font.fallbacks.clone(),
22838 font_size: settings.buffer_font_size(cx).into(),
22839 font_weight: settings.buffer_font.weight,
22840 line_height: relative(settings.buffer_line_height.value()),
22841 ..Default::default()
22842 },
22843 };
22844 if let Some(text_style_refinement) = &self.text_style_refinement {
22845 text_style.refine(text_style_refinement)
22846 }
22847
22848 let background = match self.mode {
22849 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
22850 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
22851 EditorMode::Full { .. } => cx.theme().colors().editor_background,
22852 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
22853 };
22854
22855 EditorElement::new(
22856 &cx.entity(),
22857 EditorStyle {
22858 background,
22859 border: cx.theme().colors().border,
22860 local_player: cx.theme().players().local(),
22861 text: text_style,
22862 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
22863 syntax: cx.theme().syntax().clone(),
22864 status: cx.theme().status().clone(),
22865 inlay_hints_style: make_inlay_hints_style(cx),
22866 edit_prediction_styles: make_suggestion_styles(cx),
22867 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
22868 show_underlines: self.diagnostics_enabled(),
22869 },
22870 )
22871 }
22872}
22873
22874impl EntityInputHandler for Editor {
22875 fn text_for_range(
22876 &mut self,
22877 range_utf16: Range<usize>,
22878 adjusted_range: &mut Option<Range<usize>>,
22879 _: &mut Window,
22880 cx: &mut Context<Self>,
22881 ) -> Option<String> {
22882 let snapshot = self.buffer.read(cx).read(cx);
22883 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
22884 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
22885 if (start.0..end.0) != range_utf16 {
22886 adjusted_range.replace(start.0..end.0);
22887 }
22888 Some(snapshot.text_for_range(start..end).collect())
22889 }
22890
22891 fn selected_text_range(
22892 &mut self,
22893 ignore_disabled_input: bool,
22894 _: &mut Window,
22895 cx: &mut Context<Self>,
22896 ) -> Option<UTF16Selection> {
22897 // Prevent the IME menu from appearing when holding down an alphabetic key
22898 // while input is disabled.
22899 if !ignore_disabled_input && !self.input_enabled {
22900 return None;
22901 }
22902
22903 let selection = self.selections.newest::<OffsetUtf16>(cx);
22904 let range = selection.range();
22905
22906 Some(UTF16Selection {
22907 range: range.start.0..range.end.0,
22908 reversed: selection.reversed,
22909 })
22910 }
22911
22912 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
22913 let snapshot = self.buffer.read(cx).read(cx);
22914 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
22915 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
22916 }
22917
22918 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
22919 self.clear_highlights::<InputComposition>(cx);
22920 self.ime_transaction.take();
22921 }
22922
22923 fn replace_text_in_range(
22924 &mut self,
22925 range_utf16: Option<Range<usize>>,
22926 text: &str,
22927 window: &mut Window,
22928 cx: &mut Context<Self>,
22929 ) {
22930 if !self.input_enabled {
22931 cx.emit(EditorEvent::InputIgnored { text: text.into() });
22932 return;
22933 }
22934
22935 self.transact(window, cx, |this, window, cx| {
22936 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
22937 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
22938 Some(this.selection_replacement_ranges(range_utf16, cx))
22939 } else {
22940 this.marked_text_ranges(cx)
22941 };
22942
22943 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
22944 let newest_selection_id = this.selections.newest_anchor().id;
22945 this.selections
22946 .all::<OffsetUtf16>(cx)
22947 .iter()
22948 .zip(ranges_to_replace.iter())
22949 .find_map(|(selection, range)| {
22950 if selection.id == newest_selection_id {
22951 Some(
22952 (range.start.0 as isize - selection.head().0 as isize)
22953 ..(range.end.0 as isize - selection.head().0 as isize),
22954 )
22955 } else {
22956 None
22957 }
22958 })
22959 });
22960
22961 cx.emit(EditorEvent::InputHandled {
22962 utf16_range_to_replace: range_to_replace,
22963 text: text.into(),
22964 });
22965
22966 if let Some(new_selected_ranges) = new_selected_ranges {
22967 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22968 selections.select_ranges(new_selected_ranges)
22969 });
22970 this.backspace(&Default::default(), window, cx);
22971 }
22972
22973 this.handle_input(text, window, cx);
22974 });
22975
22976 if let Some(transaction) = self.ime_transaction {
22977 self.buffer.update(cx, |buffer, cx| {
22978 buffer.group_until_transaction(transaction, cx);
22979 });
22980 }
22981
22982 self.unmark_text(window, cx);
22983 }
22984
22985 fn replace_and_mark_text_in_range(
22986 &mut self,
22987 range_utf16: Option<Range<usize>>,
22988 text: &str,
22989 new_selected_range_utf16: Option<Range<usize>>,
22990 window: &mut Window,
22991 cx: &mut Context<Self>,
22992 ) {
22993 if !self.input_enabled {
22994 return;
22995 }
22996
22997 let transaction = self.transact(window, cx, |this, window, cx| {
22998 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
22999 let snapshot = this.buffer.read(cx).read(cx);
23000 if let Some(relative_range_utf16) = range_utf16.as_ref() {
23001 for marked_range in &mut marked_ranges {
23002 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
23003 marked_range.start.0 += relative_range_utf16.start;
23004 marked_range.start =
23005 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
23006 marked_range.end =
23007 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
23008 }
23009 }
23010 Some(marked_ranges)
23011 } else if let Some(range_utf16) = range_utf16 {
23012 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23013 Some(this.selection_replacement_ranges(range_utf16, cx))
23014 } else {
23015 None
23016 };
23017
23018 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
23019 let newest_selection_id = this.selections.newest_anchor().id;
23020 this.selections
23021 .all::<OffsetUtf16>(cx)
23022 .iter()
23023 .zip(ranges_to_replace.iter())
23024 .find_map(|(selection, range)| {
23025 if selection.id == newest_selection_id {
23026 Some(
23027 (range.start.0 as isize - selection.head().0 as isize)
23028 ..(range.end.0 as isize - selection.head().0 as isize),
23029 )
23030 } else {
23031 None
23032 }
23033 })
23034 });
23035
23036 cx.emit(EditorEvent::InputHandled {
23037 utf16_range_to_replace: range_to_replace,
23038 text: text.into(),
23039 });
23040
23041 if let Some(ranges) = ranges_to_replace {
23042 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23043 s.select_ranges(ranges)
23044 });
23045 }
23046
23047 let marked_ranges = {
23048 let snapshot = this.buffer.read(cx).read(cx);
23049 this.selections
23050 .disjoint_anchors()
23051 .iter()
23052 .map(|selection| {
23053 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
23054 })
23055 .collect::<Vec<_>>()
23056 };
23057
23058 if text.is_empty() {
23059 this.unmark_text(window, cx);
23060 } else {
23061 this.highlight_text::<InputComposition>(
23062 marked_ranges.clone(),
23063 HighlightStyle {
23064 underline: Some(UnderlineStyle {
23065 thickness: px(1.),
23066 color: None,
23067 wavy: false,
23068 }),
23069 ..Default::default()
23070 },
23071 cx,
23072 );
23073 }
23074
23075 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
23076 let use_autoclose = this.use_autoclose;
23077 let use_auto_surround = this.use_auto_surround;
23078 this.set_use_autoclose(false);
23079 this.set_use_auto_surround(false);
23080 this.handle_input(text, window, cx);
23081 this.set_use_autoclose(use_autoclose);
23082 this.set_use_auto_surround(use_auto_surround);
23083
23084 if let Some(new_selected_range) = new_selected_range_utf16 {
23085 let snapshot = this.buffer.read(cx).read(cx);
23086 let new_selected_ranges = marked_ranges
23087 .into_iter()
23088 .map(|marked_range| {
23089 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
23090 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
23091 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
23092 snapshot.clip_offset_utf16(new_start, Bias::Left)
23093 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
23094 })
23095 .collect::<Vec<_>>();
23096
23097 drop(snapshot);
23098 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23099 selections.select_ranges(new_selected_ranges)
23100 });
23101 }
23102 });
23103
23104 self.ime_transaction = self.ime_transaction.or(transaction);
23105 if let Some(transaction) = self.ime_transaction {
23106 self.buffer.update(cx, |buffer, cx| {
23107 buffer.group_until_transaction(transaction, cx);
23108 });
23109 }
23110
23111 if self.text_highlights::<InputComposition>(cx).is_none() {
23112 self.ime_transaction.take();
23113 }
23114 }
23115
23116 fn bounds_for_range(
23117 &mut self,
23118 range_utf16: Range<usize>,
23119 element_bounds: gpui::Bounds<Pixels>,
23120 window: &mut Window,
23121 cx: &mut Context<Self>,
23122 ) -> Option<gpui::Bounds<Pixels>> {
23123 let text_layout_details = self.text_layout_details(window);
23124 let CharacterDimensions {
23125 em_width,
23126 em_advance,
23127 line_height,
23128 } = self.character_dimensions(window);
23129
23130 let snapshot = self.snapshot(window, cx);
23131 let scroll_position = snapshot.scroll_position();
23132 let scroll_left = scroll_position.x * em_advance;
23133
23134 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
23135 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
23136 + self.gutter_dimensions.full_width();
23137 let y = line_height * (start.row().as_f32() - scroll_position.y);
23138
23139 Some(Bounds {
23140 origin: element_bounds.origin + point(x, y),
23141 size: size(em_width, line_height),
23142 })
23143 }
23144
23145 fn character_index_for_point(
23146 &mut self,
23147 point: gpui::Point<Pixels>,
23148 _window: &mut Window,
23149 _cx: &mut Context<Self>,
23150 ) -> Option<usize> {
23151 let position_map = self.last_position_map.as_ref()?;
23152 if !position_map.text_hitbox.contains(&point) {
23153 return None;
23154 }
23155 let display_point = position_map.point_for_position(point).previous_valid;
23156 let anchor = position_map
23157 .snapshot
23158 .display_point_to_anchor(display_point, Bias::Left);
23159 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
23160 Some(utf16_offset.0)
23161 }
23162}
23163
23164trait SelectionExt {
23165 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
23166 fn spanned_rows(
23167 &self,
23168 include_end_if_at_line_start: bool,
23169 map: &DisplaySnapshot,
23170 ) -> Range<MultiBufferRow>;
23171}
23172
23173impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
23174 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
23175 let start = self
23176 .start
23177 .to_point(&map.buffer_snapshot)
23178 .to_display_point(map);
23179 let end = self
23180 .end
23181 .to_point(&map.buffer_snapshot)
23182 .to_display_point(map);
23183 if self.reversed {
23184 end..start
23185 } else {
23186 start..end
23187 }
23188 }
23189
23190 fn spanned_rows(
23191 &self,
23192 include_end_if_at_line_start: bool,
23193 map: &DisplaySnapshot,
23194 ) -> Range<MultiBufferRow> {
23195 let start = self.start.to_point(&map.buffer_snapshot);
23196 let mut end = self.end.to_point(&map.buffer_snapshot);
23197 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
23198 end.row -= 1;
23199 }
23200
23201 let buffer_start = map.prev_line_boundary(start).0;
23202 let buffer_end = map.next_line_boundary(end).0;
23203 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
23204 }
23205}
23206
23207impl<T: InvalidationRegion> InvalidationStack<T> {
23208 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
23209 where
23210 S: Clone + ToOffset,
23211 {
23212 while let Some(region) = self.last() {
23213 let all_selections_inside_invalidation_ranges =
23214 if selections.len() == region.ranges().len() {
23215 selections
23216 .iter()
23217 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
23218 .all(|(selection, invalidation_range)| {
23219 let head = selection.head().to_offset(buffer);
23220 invalidation_range.start <= head && invalidation_range.end >= head
23221 })
23222 } else {
23223 false
23224 };
23225
23226 if all_selections_inside_invalidation_ranges {
23227 break;
23228 } else {
23229 self.pop();
23230 }
23231 }
23232 }
23233}
23234
23235impl<T> Default for InvalidationStack<T> {
23236 fn default() -> Self {
23237 Self(Default::default())
23238 }
23239}
23240
23241impl<T> Deref for InvalidationStack<T> {
23242 type Target = Vec<T>;
23243
23244 fn deref(&self) -> &Self::Target {
23245 &self.0
23246 }
23247}
23248
23249impl<T> DerefMut for InvalidationStack<T> {
23250 fn deref_mut(&mut self) -> &mut Self::Target {
23251 &mut self.0
23252 }
23253}
23254
23255impl InvalidationRegion for SnippetState {
23256 fn ranges(&self) -> &[Range<Anchor>] {
23257 &self.ranges[self.active_index]
23258 }
23259}
23260
23261fn edit_prediction_edit_text(
23262 current_snapshot: &BufferSnapshot,
23263 edits: &[(Range<Anchor>, String)],
23264 edit_preview: &EditPreview,
23265 include_deletions: bool,
23266 cx: &App,
23267) -> HighlightedText {
23268 let edits = edits
23269 .iter()
23270 .map(|(anchor, text)| {
23271 (
23272 anchor.start.text_anchor..anchor.end.text_anchor,
23273 text.clone(),
23274 )
23275 })
23276 .collect::<Vec<_>>();
23277
23278 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
23279}
23280
23281fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, String)], cx: &App) -> HighlightedText {
23282 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
23283 // Just show the raw edit text with basic styling
23284 let mut text = String::new();
23285 let mut highlights = Vec::new();
23286
23287 let insertion_highlight_style = HighlightStyle {
23288 color: Some(cx.theme().colors().text),
23289 ..Default::default()
23290 };
23291
23292 for (_, edit_text) in edits {
23293 let start_offset = text.len();
23294 text.push_str(edit_text);
23295 let end_offset = text.len();
23296
23297 if start_offset < end_offset {
23298 highlights.push((start_offset..end_offset, insertion_highlight_style));
23299 }
23300 }
23301
23302 HighlightedText {
23303 text: text.into(),
23304 highlights,
23305 }
23306}
23307
23308pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
23309 match severity {
23310 lsp::DiagnosticSeverity::ERROR => colors.error,
23311 lsp::DiagnosticSeverity::WARNING => colors.warning,
23312 lsp::DiagnosticSeverity::INFORMATION => colors.info,
23313 lsp::DiagnosticSeverity::HINT => colors.info,
23314 _ => colors.ignored,
23315 }
23316}
23317
23318pub fn styled_runs_for_code_label<'a>(
23319 label: &'a CodeLabel,
23320 syntax_theme: &'a theme::SyntaxTheme,
23321) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
23322 let fade_out = HighlightStyle {
23323 fade_out: Some(0.35),
23324 ..Default::default()
23325 };
23326
23327 let mut prev_end = label.filter_range.end;
23328 label
23329 .runs
23330 .iter()
23331 .enumerate()
23332 .flat_map(move |(ix, (range, highlight_id))| {
23333 let style = if let Some(style) = highlight_id.style(syntax_theme) {
23334 style
23335 } else {
23336 return Default::default();
23337 };
23338 let mut muted_style = style;
23339 muted_style.highlight(fade_out);
23340
23341 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
23342 if range.start >= label.filter_range.end {
23343 if range.start > prev_end {
23344 runs.push((prev_end..range.start, fade_out));
23345 }
23346 runs.push((range.clone(), muted_style));
23347 } else if range.end <= label.filter_range.end {
23348 runs.push((range.clone(), style));
23349 } else {
23350 runs.push((range.start..label.filter_range.end, style));
23351 runs.push((label.filter_range.end..range.end, muted_style));
23352 }
23353 prev_end = cmp::max(prev_end, range.end);
23354
23355 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
23356 runs.push((prev_end..label.text.len(), fade_out));
23357 }
23358
23359 runs
23360 })
23361}
23362
23363pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
23364 let mut prev_index = 0;
23365 let mut prev_codepoint: Option<char> = None;
23366 text.char_indices()
23367 .chain([(text.len(), '\0')])
23368 .filter_map(move |(index, codepoint)| {
23369 let prev_codepoint = prev_codepoint.replace(codepoint)?;
23370 let is_boundary = index == text.len()
23371 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
23372 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
23373 if is_boundary {
23374 let chunk = &text[prev_index..index];
23375 prev_index = index;
23376 Some(chunk)
23377 } else {
23378 None
23379 }
23380 })
23381}
23382
23383pub trait RangeToAnchorExt: Sized {
23384 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
23385
23386 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
23387 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
23388 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
23389 }
23390}
23391
23392impl<T: ToOffset> RangeToAnchorExt for Range<T> {
23393 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
23394 let start_offset = self.start.to_offset(snapshot);
23395 let end_offset = self.end.to_offset(snapshot);
23396 if start_offset == end_offset {
23397 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
23398 } else {
23399 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
23400 }
23401 }
23402}
23403
23404pub trait RowExt {
23405 fn as_f32(&self) -> f32;
23406
23407 fn next_row(&self) -> Self;
23408
23409 fn previous_row(&self) -> Self;
23410
23411 fn minus(&self, other: Self) -> u32;
23412}
23413
23414impl RowExt for DisplayRow {
23415 fn as_f32(&self) -> f32 {
23416 self.0 as f32
23417 }
23418
23419 fn next_row(&self) -> Self {
23420 Self(self.0 + 1)
23421 }
23422
23423 fn previous_row(&self) -> Self {
23424 Self(self.0.saturating_sub(1))
23425 }
23426
23427 fn minus(&self, other: Self) -> u32 {
23428 self.0 - other.0
23429 }
23430}
23431
23432impl RowExt for MultiBufferRow {
23433 fn as_f32(&self) -> f32 {
23434 self.0 as f32
23435 }
23436
23437 fn next_row(&self) -> Self {
23438 Self(self.0 + 1)
23439 }
23440
23441 fn previous_row(&self) -> Self {
23442 Self(self.0.saturating_sub(1))
23443 }
23444
23445 fn minus(&self, other: Self) -> u32 {
23446 self.0 - other.0
23447 }
23448}
23449
23450trait RowRangeExt {
23451 type Row;
23452
23453 fn len(&self) -> usize;
23454
23455 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
23456}
23457
23458impl RowRangeExt for Range<MultiBufferRow> {
23459 type Row = MultiBufferRow;
23460
23461 fn len(&self) -> usize {
23462 (self.end.0 - self.start.0) as usize
23463 }
23464
23465 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
23466 (self.start.0..self.end.0).map(MultiBufferRow)
23467 }
23468}
23469
23470impl RowRangeExt for Range<DisplayRow> {
23471 type Row = DisplayRow;
23472
23473 fn len(&self) -> usize {
23474 (self.end.0 - self.start.0) as usize
23475 }
23476
23477 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
23478 (self.start.0..self.end.0).map(DisplayRow)
23479 }
23480}
23481
23482/// If select range has more than one line, we
23483/// just point the cursor to range.start.
23484fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
23485 if range.start.row == range.end.row {
23486 range
23487 } else {
23488 range.start..range.start
23489 }
23490}
23491pub struct KillRing(ClipboardItem);
23492impl Global for KillRing {}
23493
23494const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
23495
23496enum BreakpointPromptEditAction {
23497 Log,
23498 Condition,
23499 HitCondition,
23500}
23501
23502struct BreakpointPromptEditor {
23503 pub(crate) prompt: Entity<Editor>,
23504 editor: WeakEntity<Editor>,
23505 breakpoint_anchor: Anchor,
23506 breakpoint: Breakpoint,
23507 edit_action: BreakpointPromptEditAction,
23508 block_ids: HashSet<CustomBlockId>,
23509 editor_margins: Arc<Mutex<EditorMargins>>,
23510 _subscriptions: Vec<Subscription>,
23511}
23512
23513impl BreakpointPromptEditor {
23514 const MAX_LINES: u8 = 4;
23515
23516 fn new(
23517 editor: WeakEntity<Editor>,
23518 breakpoint_anchor: Anchor,
23519 breakpoint: Breakpoint,
23520 edit_action: BreakpointPromptEditAction,
23521 window: &mut Window,
23522 cx: &mut Context<Self>,
23523 ) -> Self {
23524 let base_text = match edit_action {
23525 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
23526 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
23527 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
23528 }
23529 .map(|msg| msg.to_string())
23530 .unwrap_or_default();
23531
23532 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
23533 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
23534
23535 let prompt = cx.new(|cx| {
23536 let mut prompt = Editor::new(
23537 EditorMode::AutoHeight {
23538 min_lines: 1,
23539 max_lines: Some(Self::MAX_LINES as usize),
23540 },
23541 buffer,
23542 None,
23543 window,
23544 cx,
23545 );
23546 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
23547 prompt.set_show_cursor_when_unfocused(false, cx);
23548 prompt.set_placeholder_text(
23549 match edit_action {
23550 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
23551 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
23552 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
23553 },
23554 cx,
23555 );
23556
23557 prompt
23558 });
23559
23560 Self {
23561 prompt,
23562 editor,
23563 breakpoint_anchor,
23564 breakpoint,
23565 edit_action,
23566 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
23567 block_ids: Default::default(),
23568 _subscriptions: vec![],
23569 }
23570 }
23571
23572 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
23573 self.block_ids.extend(block_ids)
23574 }
23575
23576 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
23577 if let Some(editor) = self.editor.upgrade() {
23578 let message = self
23579 .prompt
23580 .read(cx)
23581 .buffer
23582 .read(cx)
23583 .as_singleton()
23584 .expect("A multi buffer in breakpoint prompt isn't possible")
23585 .read(cx)
23586 .as_rope()
23587 .to_string();
23588
23589 editor.update(cx, |editor, cx| {
23590 editor.edit_breakpoint_at_anchor(
23591 self.breakpoint_anchor,
23592 self.breakpoint.clone(),
23593 match self.edit_action {
23594 BreakpointPromptEditAction::Log => {
23595 BreakpointEditAction::EditLogMessage(message.into())
23596 }
23597 BreakpointPromptEditAction::Condition => {
23598 BreakpointEditAction::EditCondition(message.into())
23599 }
23600 BreakpointPromptEditAction::HitCondition => {
23601 BreakpointEditAction::EditHitCondition(message.into())
23602 }
23603 },
23604 cx,
23605 );
23606
23607 editor.remove_blocks(self.block_ids.clone(), None, cx);
23608 cx.focus_self(window);
23609 });
23610 }
23611 }
23612
23613 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
23614 self.editor
23615 .update(cx, |editor, cx| {
23616 editor.remove_blocks(self.block_ids.clone(), None, cx);
23617 window.focus(&editor.focus_handle);
23618 })
23619 .log_err();
23620 }
23621
23622 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
23623 let settings = ThemeSettings::get_global(cx);
23624 let text_style = TextStyle {
23625 color: if self.prompt.read(cx).read_only(cx) {
23626 cx.theme().colors().text_disabled
23627 } else {
23628 cx.theme().colors().text
23629 },
23630 font_family: settings.buffer_font.family.clone(),
23631 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23632 font_size: settings.buffer_font_size(cx).into(),
23633 font_weight: settings.buffer_font.weight,
23634 line_height: relative(settings.buffer_line_height.value()),
23635 ..Default::default()
23636 };
23637 EditorElement::new(
23638 &self.prompt,
23639 EditorStyle {
23640 background: cx.theme().colors().editor_background,
23641 local_player: cx.theme().players().local(),
23642 text: text_style,
23643 ..Default::default()
23644 },
23645 )
23646 }
23647}
23648
23649impl Render for BreakpointPromptEditor {
23650 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23651 let editor_margins = *self.editor_margins.lock();
23652 let gutter_dimensions = editor_margins.gutter;
23653 h_flex()
23654 .key_context("Editor")
23655 .bg(cx.theme().colors().editor_background)
23656 .border_y_1()
23657 .border_color(cx.theme().status().info_border)
23658 .size_full()
23659 .py(window.line_height() / 2.5)
23660 .on_action(cx.listener(Self::confirm))
23661 .on_action(cx.listener(Self::cancel))
23662 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
23663 .child(div().flex_1().child(self.render_prompt_editor(cx)))
23664 }
23665}
23666
23667impl Focusable for BreakpointPromptEditor {
23668 fn focus_handle(&self, cx: &App) -> FocusHandle {
23669 self.prompt.focus_handle(cx)
23670 }
23671}
23672
23673fn all_edits_insertions_or_deletions(
23674 edits: &Vec<(Range<Anchor>, String)>,
23675 snapshot: &MultiBufferSnapshot,
23676) -> bool {
23677 let mut all_insertions = true;
23678 let mut all_deletions = true;
23679
23680 for (range, new_text) in edits.iter() {
23681 let range_is_empty = range.to_offset(&snapshot).is_empty();
23682 let text_is_empty = new_text.is_empty();
23683
23684 if range_is_empty != text_is_empty {
23685 if range_is_empty {
23686 all_deletions = false;
23687 } else {
23688 all_insertions = false;
23689 }
23690 } else {
23691 return false;
23692 }
23693
23694 if !all_insertions && !all_deletions {
23695 return false;
23696 }
23697 }
23698 all_insertions || all_deletions
23699}
23700
23701struct MissingEditPredictionKeybindingTooltip;
23702
23703impl Render for MissingEditPredictionKeybindingTooltip {
23704 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23705 ui::tooltip_container(window, cx, |container, _, cx| {
23706 container
23707 .flex_shrink_0()
23708 .max_w_80()
23709 .min_h(rems_from_px(124.))
23710 .justify_between()
23711 .child(
23712 v_flex()
23713 .flex_1()
23714 .text_ui_sm(cx)
23715 .child(Label::new("Conflict with Accept Keybinding"))
23716 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
23717 )
23718 .child(
23719 h_flex()
23720 .pb_1()
23721 .gap_1()
23722 .items_end()
23723 .w_full()
23724 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
23725 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
23726 }))
23727 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
23728 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
23729 })),
23730 )
23731 })
23732 }
23733}
23734
23735#[derive(Debug, Clone, Copy, PartialEq)]
23736pub struct LineHighlight {
23737 pub background: Background,
23738 pub border: Option<gpui::Hsla>,
23739 pub include_gutter: bool,
23740 pub type_id: Option<TypeId>,
23741}
23742
23743struct LineManipulationResult {
23744 pub new_text: String,
23745 pub line_count_before: usize,
23746 pub line_count_after: usize,
23747}
23748
23749fn render_diff_hunk_controls(
23750 row: u32,
23751 status: &DiffHunkStatus,
23752 hunk_range: Range<Anchor>,
23753 is_created_file: bool,
23754 line_height: Pixels,
23755 editor: &Entity<Editor>,
23756 _window: &mut Window,
23757 cx: &mut App,
23758) -> AnyElement {
23759 h_flex()
23760 .h(line_height)
23761 .mr_1()
23762 .gap_1()
23763 .px_0p5()
23764 .pb_1()
23765 .border_x_1()
23766 .border_b_1()
23767 .border_color(cx.theme().colors().border_variant)
23768 .rounded_b_lg()
23769 .bg(cx.theme().colors().editor_background)
23770 .gap_1()
23771 .block_mouse_except_scroll()
23772 .shadow_md()
23773 .child(if status.has_secondary_hunk() {
23774 Button::new(("stage", row as u64), "Stage")
23775 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
23776 .tooltip({
23777 let focus_handle = editor.focus_handle(cx);
23778 move |window, cx| {
23779 Tooltip::for_action_in(
23780 "Stage Hunk",
23781 &::git::ToggleStaged,
23782 &focus_handle,
23783 window,
23784 cx,
23785 )
23786 }
23787 })
23788 .on_click({
23789 let editor = editor.clone();
23790 move |_event, _window, cx| {
23791 editor.update(cx, |editor, cx| {
23792 editor.stage_or_unstage_diff_hunks(
23793 true,
23794 vec![hunk_range.start..hunk_range.start],
23795 cx,
23796 );
23797 });
23798 }
23799 })
23800 } else {
23801 Button::new(("unstage", row as u64), "Unstage")
23802 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
23803 .tooltip({
23804 let focus_handle = editor.focus_handle(cx);
23805 move |window, cx| {
23806 Tooltip::for_action_in(
23807 "Unstage Hunk",
23808 &::git::ToggleStaged,
23809 &focus_handle,
23810 window,
23811 cx,
23812 )
23813 }
23814 })
23815 .on_click({
23816 let editor = editor.clone();
23817 move |_event, _window, cx| {
23818 editor.update(cx, |editor, cx| {
23819 editor.stage_or_unstage_diff_hunks(
23820 false,
23821 vec![hunk_range.start..hunk_range.start],
23822 cx,
23823 );
23824 });
23825 }
23826 })
23827 })
23828 .child(
23829 Button::new(("restore", row as u64), "Restore")
23830 .tooltip({
23831 let focus_handle = editor.focus_handle(cx);
23832 move |window, cx| {
23833 Tooltip::for_action_in(
23834 "Restore Hunk",
23835 &::git::Restore,
23836 &focus_handle,
23837 window,
23838 cx,
23839 )
23840 }
23841 })
23842 .on_click({
23843 let editor = editor.clone();
23844 move |_event, window, cx| {
23845 editor.update(cx, |editor, cx| {
23846 let snapshot = editor.snapshot(window, cx);
23847 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
23848 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
23849 });
23850 }
23851 })
23852 .disabled(is_created_file),
23853 )
23854 .when(
23855 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
23856 |el| {
23857 el.child(
23858 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
23859 .shape(IconButtonShape::Square)
23860 .icon_size(IconSize::Small)
23861 // .disabled(!has_multiple_hunks)
23862 .tooltip({
23863 let focus_handle = editor.focus_handle(cx);
23864 move |window, cx| {
23865 Tooltip::for_action_in(
23866 "Next Hunk",
23867 &GoToHunk,
23868 &focus_handle,
23869 window,
23870 cx,
23871 )
23872 }
23873 })
23874 .on_click({
23875 let editor = editor.clone();
23876 move |_event, window, cx| {
23877 editor.update(cx, |editor, cx| {
23878 let snapshot = editor.snapshot(window, cx);
23879 let position =
23880 hunk_range.end.to_point(&snapshot.buffer_snapshot);
23881 editor.go_to_hunk_before_or_after_position(
23882 &snapshot,
23883 position,
23884 Direction::Next,
23885 window,
23886 cx,
23887 );
23888 editor.expand_selected_diff_hunks(cx);
23889 });
23890 }
23891 }),
23892 )
23893 .child(
23894 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
23895 .shape(IconButtonShape::Square)
23896 .icon_size(IconSize::Small)
23897 // .disabled(!has_multiple_hunks)
23898 .tooltip({
23899 let focus_handle = editor.focus_handle(cx);
23900 move |window, cx| {
23901 Tooltip::for_action_in(
23902 "Previous Hunk",
23903 &GoToPreviousHunk,
23904 &focus_handle,
23905 window,
23906 cx,
23907 )
23908 }
23909 })
23910 .on_click({
23911 let editor = editor.clone();
23912 move |_event, window, cx| {
23913 editor.update(cx, |editor, cx| {
23914 let snapshot = editor.snapshot(window, cx);
23915 let point =
23916 hunk_range.start.to_point(&snapshot.buffer_snapshot);
23917 editor.go_to_hunk_before_or_after_position(
23918 &snapshot,
23919 point,
23920 Direction::Prev,
23921 window,
23922 cx,
23923 );
23924 editor.expand_selected_diff_hunks(cx);
23925 });
23926 }
23927 }),
23928 )
23929 },
23930 )
23931 .into_any_element()
23932}