1#![allow(rustdoc::private_intra_doc_links)]
2//! This is the place where everything editor-related is stored (data-wise) and displayed (ui-wise).
3//! The main point of interest in this crate is [`Editor`] type, which is used in every other Zed part as a user input element.
4//! It comes in different flavors: single line, multiline and a fixed height one.
5//!
6//! Editor contains of multiple large submodules:
7//! * [`element`] — the place where all rendering happens
8//! * [`display_map`] - chunks up text in the editor into the logical blocks, establishes coordinates and mapping between each of them.
9//! Contains all metadata related to text transformations (folds, fake inlay text insertions, soft wraps, tab markup, etc.).
10//! * [`inlay_hint_cache`] - is a storage of inlay hints out of LSP requests, responsible for querying LSP and updating `display_map`'s state accordingly.
11//!
12//! All other submodules and structs are mostly concerned with holding editor data about the way it displays current buffer region(s).
13//!
14//! If you're looking to improve Vim mode, you should check out Vim crate that wraps Editor and overrides its behavior.
15pub mod actions;
16mod blink_manager;
17mod clangd_ext;
18pub mod code_context_menus;
19pub mod display_map;
20mod editor_settings;
21mod editor_settings_controls;
22mod element;
23mod git;
24mod highlight_matching_bracket;
25mod hover_links;
26pub mod hover_popover;
27mod indent_guides;
28mod inlay_hint_cache;
29pub mod items;
30mod jsx_tag_auto_close;
31mod linked_editing_ranges;
32mod lsp_colors;
33mod lsp_ext;
34mod mouse_context_menu;
35pub mod movement;
36mod persistence;
37mod proposed_changes_editor;
38mod rust_analyzer_ext;
39pub mod scroll;
40mod selections_collection;
41pub mod tasks;
42
43#[cfg(test)]
44mod code_completion_tests;
45#[cfg(test)]
46mod edit_prediction_tests;
47#[cfg(test)]
48mod editor_tests;
49mod signature_help;
50#[cfg(any(test, feature = "test-support"))]
51pub mod test;
52
53pub(crate) use actions::*;
54pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
55pub use edit_prediction::Direction;
56pub use editor_settings::{
57 CurrentLineHighlight, DocumentColorsRenderMode, EditorSettings, HideMouseMode,
58 ScrollBeyondLastLine, ScrollbarAxes, SearchSettings, ShowMinimap, ShowScrollbar,
59};
60pub use editor_settings_controls::*;
61pub use element::{
62 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
63};
64pub use git::blame::BlameRenderer;
65pub use hover_popover::hover_markdown_style;
66pub use items::MAX_TAB_TITLE_LEN;
67pub use lsp::CompletionContext;
68pub use lsp_ext::lsp_tasks;
69pub use multi_buffer::{
70 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, PathKey,
71 RowInfo, ToOffset, ToPoint,
72};
73pub use proposed_changes_editor::{
74 ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
75};
76pub use text::Bias;
77
78use ::git::{
79 Restore,
80 blame::{BlameEntry, ParsedCommitMessage},
81};
82use aho_corasick::AhoCorasick;
83use anyhow::{Context as _, Result, anyhow};
84use blink_manager::BlinkManager;
85use buffer_diff::DiffHunkStatus;
86use client::{Collaborator, ParticipantIndex};
87use clock::{AGENT_REPLICA_ID, ReplicaId};
88use code_context_menus::{
89 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
90 CompletionsMenu, ContextMenuOrigin,
91};
92use collections::{BTreeMap, HashMap, HashSet, VecDeque};
93use convert_case::{Case, Casing};
94use dap::TelemetrySpawnLocation;
95use display_map::*;
96use edit_prediction::{EditPredictionProvider, EditPredictionProviderHandle};
97use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
98use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
99use futures::{
100 FutureExt, StreamExt as _,
101 future::{self, Shared, join},
102 stream::FuturesUnordered,
103};
104use fuzzy::{StringMatch, StringMatchCandidate};
105use git::blame::{GitBlame, GlobalBlameRenderer};
106use gpui::{
107 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
108 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
109 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
110 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
111 MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render, ScrollHandle,
112 SharedString, Size, Stateful, Styled, Subscription, Task, TextStyle, TextStyleRefinement,
113 UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
114 div, point, prelude::*, pulsating_between, px, relative, size,
115};
116use highlight_matching_bracket::refresh_matching_bracket_highlights;
117use hover_links::{HoverLink, HoveredLinkState, InlayHighlight, find_file};
118use hover_popover::{HoverState, hide_hover};
119use indent_guides::ActiveIndentGuidesState;
120use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
121use itertools::Itertools;
122use language::{
123 AutoindentMode, BlockCommentConfig, BracketMatch, BracketPair, Buffer, BufferRow,
124 BufferSnapshot, Capability, CharClassifier, CharKind, CodeLabel, CursorShape, DiagnosticEntry,
125 DiffOptions, EditPredictionsMode, EditPreview, HighlightedText, IndentKind, IndentSize,
126 Language, OffsetRangeExt, Point, Runnable, RunnableRange, Selection, SelectionGoal, TextObject,
127 TransactionId, TreeSitterOptions, WordsQuery,
128 language_settings::{
129 self, InlayHintSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
130 all_language_settings, language_settings,
131 },
132 point_from_lsp, point_to_lsp, text_diff_with_options,
133};
134use linked_editing_ranges::refresh_linked_ranges;
135use lsp::{
136 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
137 LanguageServerId,
138};
139use lsp_colors::LspColorData;
140use markdown::Markdown;
141use mouse_context_menu::MouseContextMenu;
142use movement::TextLayoutDetails;
143use multi_buffer::{
144 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
145 MultiOrSingleBufferOffsetRange, ToOffsetUtf16,
146};
147use parking_lot::Mutex;
148use persistence::DB;
149use project::{
150 BreakpointWithPosition, CodeAction, Completion, CompletionIntent, CompletionResponse,
151 CompletionSource, DisableAiSettings, DocumentHighlight, InlayHint, Location, LocationLink,
152 PrepareRenameResponse, Project, ProjectItem, ProjectPath, ProjectTransaction, TaskSourceKind,
153 debugger::breakpoint_store::Breakpoint,
154 debugger::{
155 breakpoint_store::{
156 BreakpointEditAction, BreakpointSessionState, BreakpointState, BreakpointStore,
157 BreakpointStoreEvent,
158 },
159 session::{Session, SessionEvent},
160 },
161 git_store::{GitStoreEvent, RepositoryEvent},
162 lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
163 project_settings::{DiagnosticSeverity, GoToDiagnosticSeverityFilter},
164 project_settings::{GitGutterSetting, ProjectSettings},
165};
166use rand::{seq::SliceRandom, thread_rng};
167use rpc::{ErrorCode, ErrorExt, proto::PeerId};
168use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide};
169use selections_collection::{
170 MutableSelectionsCollection, SelectionsCollection, resolve_selections,
171};
172use serde::{Deserialize, Serialize};
173use settings::{Settings, SettingsLocation, SettingsStore, update_settings_file};
174use smallvec::{SmallVec, smallvec};
175use snippet::Snippet;
176use std::{
177 any::TypeId,
178 borrow::Cow,
179 cell::OnceCell,
180 cell::RefCell,
181 cmp::{self, Ordering, Reverse},
182 iter::Peekable,
183 mem,
184 num::NonZeroU32,
185 ops::Not,
186 ops::{ControlFlow, Deref, DerefMut, Range, RangeInclusive},
187 path::{Path, PathBuf},
188 rc::Rc,
189 sync::Arc,
190 time::{Duration, Instant},
191};
192use sum_tree::TreeMap;
193use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
194use text::{BufferId, FromAnchor, OffsetUtf16, Rope};
195use theme::{
196 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, Theme, ThemeSettings,
197 observe_buffer_font_size_adjustment,
198};
199use ui::{
200 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
201 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*,
202};
203use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
204use workspace::{
205 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
206 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
207 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
208 item::{ItemHandle, PreviewTabsSettings, SaveOptions},
209 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
210 searchable::SearchEvent,
211};
212
213use crate::{
214 code_context_menus::CompletionsMenuSource,
215 editor_settings::MultiCursorModifier,
216 hover_links::{find_url, find_url_from_range},
217 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
218};
219
220pub const FILE_HEADER_HEIGHT: u32 = 2;
221pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
222pub const DEFAULT_MULTIBUFFER_CONTEXT: u32 = 2;
223const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
224const MAX_LINE_LEN: usize = 1024;
225const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
226const MAX_SELECTION_HISTORY_LEN: usize = 1024;
227pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
228#[doc(hidden)]
229pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
230const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
231
232pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
233pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
234pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
235
236pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
237pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
238pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
239
240pub type RenderDiffHunkControlsFn = Arc<
241 dyn Fn(
242 u32,
243 &DiffHunkStatus,
244 Range<Anchor>,
245 bool,
246 Pixels,
247 &Entity<Editor>,
248 &mut Window,
249 &mut App,
250 ) -> AnyElement,
251>;
252
253struct InlineValueCache {
254 enabled: bool,
255 inlays: Vec<InlayId>,
256 refresh_task: Task<Option<()>>,
257}
258
259impl InlineValueCache {
260 fn new(enabled: bool) -> Self {
261 Self {
262 enabled,
263 inlays: Vec::new(),
264 refresh_task: Task::ready(None),
265 }
266 }
267}
268
269#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
270pub enum InlayId {
271 EditPrediction(usize),
272 DebuggerValue(usize),
273 // LSP
274 Hint(usize),
275 Color(usize),
276}
277
278impl InlayId {
279 fn id(&self) -> usize {
280 match self {
281 Self::EditPrediction(id) => *id,
282 Self::DebuggerValue(id) => *id,
283 Self::Hint(id) => *id,
284 Self::Color(id) => *id,
285 }
286 }
287}
288
289pub enum ActiveDebugLine {}
290pub enum DebugStackFrameLine {}
291enum DocumentHighlightRead {}
292enum DocumentHighlightWrite {}
293enum InputComposition {}
294pub enum PendingInput {}
295enum SelectedTextHighlight {}
296
297pub enum ConflictsOuter {}
298pub enum ConflictsOurs {}
299pub enum ConflictsTheirs {}
300pub enum ConflictsOursMarker {}
301pub enum ConflictsTheirsMarker {}
302
303#[derive(Debug, Copy, Clone, PartialEq, Eq)]
304pub enum Navigated {
305 Yes,
306 No,
307}
308
309impl Navigated {
310 pub fn from_bool(yes: bool) -> Navigated {
311 if yes { Navigated::Yes } else { Navigated::No }
312 }
313}
314
315#[derive(Debug, Clone, PartialEq, Eq)]
316enum DisplayDiffHunk {
317 Folded {
318 display_row: DisplayRow,
319 },
320 Unfolded {
321 is_created_file: bool,
322 diff_base_byte_range: Range<usize>,
323 display_row_range: Range<DisplayRow>,
324 multi_buffer_range: Range<Anchor>,
325 status: DiffHunkStatus,
326 },
327}
328
329pub enum HideMouseCursorOrigin {
330 TypingAction,
331 MovementAction,
332}
333
334pub fn init_settings(cx: &mut App) {
335 EditorSettings::register(cx);
336}
337
338pub fn init(cx: &mut App) {
339 init_settings(cx);
340
341 cx.set_global(GlobalBlameRenderer(Arc::new(())));
342
343 workspace::register_project_item::<Editor>(cx);
344 workspace::FollowableViewRegistry::register::<Editor>(cx);
345 workspace::register_serializable_item::<Editor>(cx);
346
347 cx.observe_new(
348 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
349 workspace.register_action(Editor::new_file);
350 workspace.register_action(Editor::new_file_vertical);
351 workspace.register_action(Editor::new_file_horizontal);
352 workspace.register_action(Editor::cancel_language_server_work);
353 workspace.register_action(Editor::toggle_focus);
354 },
355 )
356 .detach();
357
358 cx.on_action(move |_: &workspace::NewFile, cx| {
359 let app_state = workspace::AppState::global(cx);
360 if let Some(app_state) = app_state.upgrade() {
361 workspace::open_new(
362 Default::default(),
363 app_state,
364 cx,
365 |workspace, window, cx| {
366 Editor::new_file(workspace, &Default::default(), window, cx)
367 },
368 )
369 .detach();
370 }
371 });
372 cx.on_action(move |_: &workspace::NewWindow, cx| {
373 let app_state = workspace::AppState::global(cx);
374 if let Some(app_state) = app_state.upgrade() {
375 workspace::open_new(
376 Default::default(),
377 app_state,
378 cx,
379 |workspace, window, cx| {
380 cx.activate(true);
381 Editor::new_file(workspace, &Default::default(), window, cx)
382 },
383 )
384 .detach();
385 }
386 });
387}
388
389pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
390 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
391}
392
393pub trait DiagnosticRenderer {
394 fn render_group(
395 &self,
396 diagnostic_group: Vec<DiagnosticEntry<Point>>,
397 buffer_id: BufferId,
398 snapshot: EditorSnapshot,
399 editor: WeakEntity<Editor>,
400 cx: &mut App,
401 ) -> Vec<BlockProperties<Anchor>>;
402
403 fn render_hover(
404 &self,
405 diagnostic_group: Vec<DiagnosticEntry<Point>>,
406 range: Range<Point>,
407 buffer_id: BufferId,
408 cx: &mut App,
409 ) -> Option<Entity<markdown::Markdown>>;
410
411 fn open_link(
412 &self,
413 editor: &mut Editor,
414 link: SharedString,
415 window: &mut Window,
416 cx: &mut Context<Editor>,
417 );
418}
419
420pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
421
422impl GlobalDiagnosticRenderer {
423 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
424 cx.try_global::<Self>().map(|g| g.0.clone())
425 }
426}
427
428impl gpui::Global for GlobalDiagnosticRenderer {}
429pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
430 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
431}
432
433pub struct SearchWithinRange;
434
435trait InvalidationRegion {
436 fn ranges(&self) -> &[Range<Anchor>];
437}
438
439#[derive(Clone, Debug, PartialEq)]
440pub enum SelectPhase {
441 Begin {
442 position: DisplayPoint,
443 add: bool,
444 click_count: usize,
445 },
446 BeginColumnar {
447 position: DisplayPoint,
448 reset: bool,
449 mode: ColumnarMode,
450 goal_column: u32,
451 },
452 Extend {
453 position: DisplayPoint,
454 click_count: usize,
455 },
456 Update {
457 position: DisplayPoint,
458 goal_column: u32,
459 scroll_delta: gpui::Point<f32>,
460 },
461 End,
462}
463
464#[derive(Clone, Debug, PartialEq)]
465pub enum ColumnarMode {
466 FromMouse,
467 FromSelection,
468}
469
470#[derive(Clone, Debug)]
471pub enum SelectMode {
472 Character,
473 Word(Range<Anchor>),
474 Line(Range<Anchor>),
475 All,
476}
477
478#[derive(Clone, PartialEq, Eq, Debug)]
479pub enum EditorMode {
480 SingleLine,
481 AutoHeight {
482 min_lines: usize,
483 max_lines: Option<usize>,
484 },
485 Full {
486 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
487 scale_ui_elements_with_buffer_font_size: bool,
488 /// When set to `true`, the editor will render a background for the active line.
489 show_active_line_background: bool,
490 /// When set to `true`, the editor's height will be determined by its content.
491 sized_by_content: bool,
492 },
493 Minimap {
494 parent: WeakEntity<Editor>,
495 },
496}
497
498impl EditorMode {
499 pub fn full() -> Self {
500 Self::Full {
501 scale_ui_elements_with_buffer_font_size: true,
502 show_active_line_background: true,
503 sized_by_content: false,
504 }
505 }
506
507 #[inline]
508 pub fn is_full(&self) -> bool {
509 matches!(self, Self::Full { .. })
510 }
511
512 #[inline]
513 pub fn is_single_line(&self) -> bool {
514 matches!(self, Self::SingleLine { .. })
515 }
516
517 #[inline]
518 fn is_minimap(&self) -> bool {
519 matches!(self, Self::Minimap { .. })
520 }
521}
522
523#[derive(Copy, Clone, Debug)]
524pub enum SoftWrap {
525 /// Prefer not to wrap at all.
526 ///
527 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
528 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
529 GitDiff,
530 /// Prefer a single line generally, unless an overly long line is encountered.
531 None,
532 /// Soft wrap lines that exceed the editor width.
533 EditorWidth,
534 /// Soft wrap lines at the preferred line length.
535 Column(u32),
536 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
537 Bounded(u32),
538}
539
540#[derive(Clone)]
541pub struct EditorStyle {
542 pub background: Hsla,
543 pub border: Hsla,
544 pub local_player: PlayerColor,
545 pub text: TextStyle,
546 pub scrollbar_width: Pixels,
547 pub syntax: Arc<SyntaxTheme>,
548 pub status: StatusColors,
549 pub inlay_hints_style: HighlightStyle,
550 pub edit_prediction_styles: EditPredictionStyles,
551 pub unnecessary_code_fade: f32,
552 pub show_underlines: bool,
553}
554
555impl Default for EditorStyle {
556 fn default() -> Self {
557 Self {
558 background: Hsla::default(),
559 border: Hsla::default(),
560 local_player: PlayerColor::default(),
561 text: TextStyle::default(),
562 scrollbar_width: Pixels::default(),
563 syntax: Default::default(),
564 // HACK: Status colors don't have a real default.
565 // We should look into removing the status colors from the editor
566 // style and retrieve them directly from the theme.
567 status: StatusColors::dark(),
568 inlay_hints_style: HighlightStyle::default(),
569 edit_prediction_styles: EditPredictionStyles {
570 insertion: HighlightStyle::default(),
571 whitespace: HighlightStyle::default(),
572 },
573 unnecessary_code_fade: Default::default(),
574 show_underlines: true,
575 }
576 }
577}
578
579pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
580 let show_background = language_settings::language_settings(None, None, cx)
581 .inlay_hints
582 .show_background;
583
584 HighlightStyle {
585 color: Some(cx.theme().status().hint),
586 background_color: show_background.then(|| cx.theme().status().hint_background),
587 ..HighlightStyle::default()
588 }
589}
590
591pub fn make_suggestion_styles(cx: &mut App) -> EditPredictionStyles {
592 EditPredictionStyles {
593 insertion: HighlightStyle {
594 color: Some(cx.theme().status().predictive),
595 ..HighlightStyle::default()
596 },
597 whitespace: HighlightStyle {
598 background_color: Some(cx.theme().status().created_background),
599 ..HighlightStyle::default()
600 },
601 }
602}
603
604type CompletionId = usize;
605
606pub(crate) enum EditDisplayMode {
607 TabAccept,
608 DiffPopover,
609 Inline,
610}
611
612enum EditPrediction {
613 Edit {
614 edits: Vec<(Range<Anchor>, String)>,
615 edit_preview: Option<EditPreview>,
616 display_mode: EditDisplayMode,
617 snapshot: BufferSnapshot,
618 },
619 Move {
620 target: Anchor,
621 snapshot: BufferSnapshot,
622 },
623}
624
625struct EditPredictionState {
626 inlay_ids: Vec<InlayId>,
627 completion: EditPrediction,
628 completion_id: Option<SharedString>,
629 invalidation_range: Range<Anchor>,
630}
631
632enum EditPredictionSettings {
633 Disabled,
634 Enabled {
635 show_in_menu: bool,
636 preview_requires_modifier: bool,
637 },
638}
639
640enum EditPredictionHighlight {}
641
642#[derive(Debug, Clone)]
643struct InlineDiagnostic {
644 message: SharedString,
645 group_id: usize,
646 is_primary: bool,
647 start: Point,
648 severity: lsp::DiagnosticSeverity,
649}
650
651pub enum MenuEditPredictionsPolicy {
652 Never,
653 ByProvider,
654}
655
656pub enum EditPredictionPreview {
657 /// Modifier is not pressed
658 Inactive { released_too_fast: bool },
659 /// Modifier pressed
660 Active {
661 since: Instant,
662 previous_scroll_position: Option<ScrollAnchor>,
663 },
664}
665
666impl EditPredictionPreview {
667 pub fn released_too_fast(&self) -> bool {
668 match self {
669 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
670 EditPredictionPreview::Active { .. } => false,
671 }
672 }
673
674 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
675 if let EditPredictionPreview::Active {
676 previous_scroll_position,
677 ..
678 } = self
679 {
680 *previous_scroll_position = scroll_position;
681 }
682 }
683}
684
685pub struct ContextMenuOptions {
686 pub min_entries_visible: usize,
687 pub max_entries_visible: usize,
688 pub placement: Option<ContextMenuPlacement>,
689}
690
691#[derive(Debug, Clone, PartialEq, Eq)]
692pub enum ContextMenuPlacement {
693 Above,
694 Below,
695}
696
697#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
698struct EditorActionId(usize);
699
700impl EditorActionId {
701 pub fn post_inc(&mut self) -> Self {
702 let answer = self.0;
703
704 *self = Self(answer + 1);
705
706 Self(answer)
707 }
708}
709
710// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
711// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
712
713type BackgroundHighlight = (fn(&Theme) -> Hsla, Arc<[Range<Anchor>]>);
714type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
715
716#[derive(Default)]
717struct ScrollbarMarkerState {
718 scrollbar_size: Size<Pixels>,
719 dirty: bool,
720 markers: Arc<[PaintQuad]>,
721 pending_refresh: Option<Task<Result<()>>>,
722}
723
724impl ScrollbarMarkerState {
725 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
726 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
727 }
728}
729
730#[derive(Clone, Copy, PartialEq, Eq)]
731pub enum MinimapVisibility {
732 Disabled,
733 Enabled {
734 /// The configuration currently present in the users settings.
735 setting_configuration: bool,
736 /// Whether to override the currently set visibility from the users setting.
737 toggle_override: bool,
738 },
739}
740
741impl MinimapVisibility {
742 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
743 if mode.is_full() {
744 Self::Enabled {
745 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
746 toggle_override: false,
747 }
748 } else {
749 Self::Disabled
750 }
751 }
752
753 fn hidden(&self) -> Self {
754 match *self {
755 Self::Enabled {
756 setting_configuration,
757 ..
758 } => Self::Enabled {
759 setting_configuration,
760 toggle_override: setting_configuration,
761 },
762 Self::Disabled => Self::Disabled,
763 }
764 }
765
766 fn disabled(&self) -> bool {
767 match *self {
768 Self::Disabled => true,
769 _ => false,
770 }
771 }
772
773 fn settings_visibility(&self) -> bool {
774 match *self {
775 Self::Enabled {
776 setting_configuration,
777 ..
778 } => setting_configuration,
779 _ => false,
780 }
781 }
782
783 fn visible(&self) -> bool {
784 match *self {
785 Self::Enabled {
786 setting_configuration,
787 toggle_override,
788 } => setting_configuration ^ toggle_override,
789 _ => false,
790 }
791 }
792
793 fn toggle_visibility(&self) -> Self {
794 match *self {
795 Self::Enabled {
796 toggle_override,
797 setting_configuration,
798 } => Self::Enabled {
799 setting_configuration,
800 toggle_override: !toggle_override,
801 },
802 Self::Disabled => Self::Disabled,
803 }
804 }
805}
806
807#[derive(Clone, Debug)]
808struct RunnableTasks {
809 templates: Vec<(TaskSourceKind, TaskTemplate)>,
810 offset: multi_buffer::Anchor,
811 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
812 column: u32,
813 // Values of all named captures, including those starting with '_'
814 extra_variables: HashMap<String, String>,
815 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
816 context_range: Range<BufferOffset>,
817}
818
819impl RunnableTasks {
820 fn resolve<'a>(
821 &'a self,
822 cx: &'a task::TaskContext,
823 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
824 self.templates.iter().filter_map(|(kind, template)| {
825 template
826 .resolve_task(&kind.to_id_base(), cx)
827 .map(|task| (kind.clone(), task))
828 })
829 }
830}
831
832#[derive(Clone)]
833pub struct ResolvedTasks {
834 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
835 position: Anchor,
836}
837
838#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
839struct BufferOffset(usize);
840
841// Addons allow storing per-editor state in other crates (e.g. Vim)
842pub trait Addon: 'static {
843 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
844
845 fn render_buffer_header_controls(
846 &self,
847 _: &ExcerptInfo,
848 _: &Window,
849 _: &App,
850 ) -> Option<AnyElement> {
851 None
852 }
853
854 fn to_any(&self) -> &dyn std::any::Any;
855
856 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
857 None
858 }
859}
860
861struct ChangeLocation {
862 current: Option<Vec<Anchor>>,
863 original: Vec<Anchor>,
864}
865impl ChangeLocation {
866 fn locations(&self) -> &[Anchor] {
867 self.current.as_ref().unwrap_or(&self.original)
868 }
869}
870
871/// A set of caret positions, registered when the editor was edited.
872pub struct ChangeList {
873 changes: Vec<ChangeLocation>,
874 /// Currently "selected" change.
875 position: Option<usize>,
876}
877
878impl ChangeList {
879 pub fn new() -> Self {
880 Self {
881 changes: Vec::new(),
882 position: None,
883 }
884 }
885
886 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
887 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
888 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
889 if self.changes.is_empty() {
890 return None;
891 }
892
893 let prev = self.position.unwrap_or(self.changes.len());
894 let next = if direction == Direction::Prev {
895 prev.saturating_sub(count)
896 } else {
897 (prev + count).min(self.changes.len() - 1)
898 };
899 self.position = Some(next);
900 self.changes.get(next).map(|change| change.locations())
901 }
902
903 /// Adds a new change to the list, resetting the change list position.
904 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
905 self.position.take();
906 if let Some(last) = self.changes.last_mut()
907 && group
908 {
909 last.current = Some(new_positions)
910 } else {
911 self.changes.push(ChangeLocation {
912 original: new_positions,
913 current: None,
914 });
915 }
916 }
917
918 pub fn last(&self) -> Option<&[Anchor]> {
919 self.changes.last().map(|change| change.locations())
920 }
921
922 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
923 self.changes.last().map(|change| change.original.as_slice())
924 }
925
926 pub fn invert_last_group(&mut self) {
927 if let Some(last) = self.changes.last_mut() {
928 if let Some(current) = last.current.as_mut() {
929 mem::swap(&mut last.original, current);
930 }
931 }
932 }
933}
934
935#[derive(Clone)]
936struct InlineBlamePopoverState {
937 scroll_handle: ScrollHandle,
938 commit_message: Option<ParsedCommitMessage>,
939 markdown: Entity<Markdown>,
940}
941
942struct InlineBlamePopover {
943 position: gpui::Point<Pixels>,
944 hide_task: Option<Task<()>>,
945 popover_bounds: Option<Bounds<Pixels>>,
946 popover_state: InlineBlamePopoverState,
947 keyboard_grace: bool,
948}
949
950enum SelectionDragState {
951 /// State when no drag related activity is detected.
952 None,
953 /// State when the mouse is down on a selection that is about to be dragged.
954 ReadyToDrag {
955 selection: Selection<Anchor>,
956 click_position: gpui::Point<Pixels>,
957 mouse_down_time: Instant,
958 },
959 /// State when the mouse is dragging the selection in the editor.
960 Dragging {
961 selection: Selection<Anchor>,
962 drop_cursor: Selection<Anchor>,
963 hide_drop_cursor: bool,
964 },
965}
966
967enum ColumnarSelectionState {
968 FromMouse {
969 selection_tail: Anchor,
970 display_point: Option<DisplayPoint>,
971 },
972 FromSelection {
973 selection_tail: Anchor,
974 },
975}
976
977/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
978/// a breakpoint on them.
979#[derive(Clone, Copy, Debug, PartialEq, Eq)]
980struct PhantomBreakpointIndicator {
981 display_row: DisplayRow,
982 /// There's a small debounce between hovering over the line and showing the indicator.
983 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
984 is_active: bool,
985 collides_with_existing_breakpoint: bool,
986}
987
988/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
989///
990/// See the [module level documentation](self) for more information.
991pub struct Editor {
992 focus_handle: FocusHandle,
993 last_focused_descendant: Option<WeakFocusHandle>,
994 /// The text buffer being edited
995 buffer: Entity<MultiBuffer>,
996 /// Map of how text in the buffer should be displayed.
997 /// Handles soft wraps, folds, fake inlay text insertions, etc.
998 pub display_map: Entity<DisplayMap>,
999 pub selections: SelectionsCollection,
1000 pub scroll_manager: ScrollManager,
1001 /// When inline assist editors are linked, they all render cursors because
1002 /// typing enters text into each of them, even the ones that aren't focused.
1003 pub(crate) show_cursor_when_unfocused: bool,
1004 columnar_selection_state: Option<ColumnarSelectionState>,
1005 add_selections_state: Option<AddSelectionsState>,
1006 select_next_state: Option<SelectNextState>,
1007 select_prev_state: Option<SelectNextState>,
1008 selection_history: SelectionHistory,
1009 defer_selection_effects: bool,
1010 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1011 autoclose_regions: Vec<AutocloseRegion>,
1012 snippet_stack: InvalidationStack<SnippetState>,
1013 select_syntax_node_history: SelectSyntaxNodeHistory,
1014 ime_transaction: Option<TransactionId>,
1015 pub diagnostics_max_severity: DiagnosticSeverity,
1016 active_diagnostics: ActiveDiagnostic,
1017 show_inline_diagnostics: bool,
1018 inline_diagnostics_update: Task<()>,
1019 inline_diagnostics_enabled: bool,
1020 diagnostics_enabled: bool,
1021 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1022 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1023 hard_wrap: Option<usize>,
1024
1025 // TODO: make this a access method
1026 pub project: Option<Entity<Project>>,
1027 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1028 completion_provider: Option<Rc<dyn CompletionProvider>>,
1029 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1030 blink_manager: Entity<BlinkManager>,
1031 show_cursor_names: bool,
1032 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1033 pub show_local_selections: bool,
1034 mode: EditorMode,
1035 show_breadcrumbs: bool,
1036 show_gutter: bool,
1037 show_scrollbars: ScrollbarAxes,
1038 minimap_visibility: MinimapVisibility,
1039 offset_content: bool,
1040 disable_expand_excerpt_buttons: bool,
1041 show_line_numbers: Option<bool>,
1042 use_relative_line_numbers: Option<bool>,
1043 show_git_diff_gutter: Option<bool>,
1044 show_code_actions: Option<bool>,
1045 show_runnables: Option<bool>,
1046 show_breakpoints: Option<bool>,
1047 show_wrap_guides: Option<bool>,
1048 show_indent_guides: Option<bool>,
1049 placeholder_text: Option<Arc<str>>,
1050 highlight_order: usize,
1051 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1052 background_highlights: TreeMap<HighlightKey, BackgroundHighlight>,
1053 gutter_highlights: TreeMap<TypeId, GutterHighlight>,
1054 scrollbar_marker_state: ScrollbarMarkerState,
1055 active_indent_guides_state: ActiveIndentGuidesState,
1056 nav_history: Option<ItemNavHistory>,
1057 context_menu: RefCell<Option<CodeContextMenu>>,
1058 context_menu_options: Option<ContextMenuOptions>,
1059 mouse_context_menu: Option<MouseContextMenu>,
1060 completion_tasks: Vec<(CompletionId, Task<()>)>,
1061 inline_blame_popover: Option<InlineBlamePopover>,
1062 inline_blame_popover_show_task: Option<Task<()>>,
1063 signature_help_state: SignatureHelpState,
1064 auto_signature_help: Option<bool>,
1065 find_all_references_task_sources: Vec<Anchor>,
1066 next_completion_id: CompletionId,
1067 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1068 code_actions_task: Option<Task<Result<()>>>,
1069 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1070 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1071 document_highlights_task: Option<Task<()>>,
1072 linked_editing_range_task: Option<Task<Option<()>>>,
1073 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1074 pending_rename: Option<RenameState>,
1075 searchable: bool,
1076 cursor_shape: CursorShape,
1077 current_line_highlight: Option<CurrentLineHighlight>,
1078 collapse_matches: bool,
1079 autoindent_mode: Option<AutoindentMode>,
1080 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1081 input_enabled: bool,
1082 use_modal_editing: bool,
1083 read_only: bool,
1084 leader_id: Option<CollaboratorId>,
1085 remote_id: Option<ViewId>,
1086 pub hover_state: HoverState,
1087 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1088 gutter_hovered: bool,
1089 hovered_link_state: Option<HoveredLinkState>,
1090 edit_prediction_provider: Option<RegisteredEditPredictionProvider>,
1091 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1092 active_edit_prediction: Option<EditPredictionState>,
1093 /// Used to prevent flickering as the user types while the menu is open
1094 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1095 edit_prediction_settings: EditPredictionSettings,
1096 edit_predictions_hidden_for_vim_mode: bool,
1097 show_edit_predictions_override: Option<bool>,
1098 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1099 edit_prediction_preview: EditPredictionPreview,
1100 edit_prediction_indent_conflict: bool,
1101 edit_prediction_requires_modifier_in_indent_conflict: bool,
1102 inlay_hint_cache: InlayHintCache,
1103 next_inlay_id: usize,
1104 _subscriptions: Vec<Subscription>,
1105 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1106 gutter_dimensions: GutterDimensions,
1107 style: Option<EditorStyle>,
1108 text_style_refinement: Option<TextStyleRefinement>,
1109 next_editor_action_id: EditorActionId,
1110 editor_actions: Rc<
1111 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1112 >,
1113 use_autoclose: bool,
1114 use_auto_surround: bool,
1115 auto_replace_emoji_shortcode: bool,
1116 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1117 show_git_blame_gutter: bool,
1118 show_git_blame_inline: bool,
1119 show_git_blame_inline_delay_task: Option<Task<()>>,
1120 git_blame_inline_enabled: bool,
1121 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1122 serialize_dirty_buffers: bool,
1123 show_selection_menu: Option<bool>,
1124 blame: Option<Entity<GitBlame>>,
1125 blame_subscription: Option<Subscription>,
1126 custom_context_menu: Option<
1127 Box<
1128 dyn 'static
1129 + Fn(
1130 &mut Self,
1131 DisplayPoint,
1132 &mut Window,
1133 &mut Context<Self>,
1134 ) -> Option<Entity<ui::ContextMenu>>,
1135 >,
1136 >,
1137 last_bounds: Option<Bounds<Pixels>>,
1138 last_position_map: Option<Rc<PositionMap>>,
1139 expect_bounds_change: Option<Bounds<Pixels>>,
1140 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1141 tasks_update_task: Option<Task<()>>,
1142 breakpoint_store: Option<Entity<BreakpointStore>>,
1143 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1144 hovered_diff_hunk_row: Option<DisplayRow>,
1145 pull_diagnostics_task: Task<()>,
1146 in_project_search: bool,
1147 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1148 breadcrumb_header: Option<String>,
1149 focused_block: Option<FocusedBlock>,
1150 next_scroll_position: NextScrollCursorCenterTopBottom,
1151 addons: HashMap<TypeId, Box<dyn Addon>>,
1152 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1153 load_diff_task: Option<Shared<Task<()>>>,
1154 /// Whether we are temporarily displaying a diff other than git's
1155 temporary_diff_override: bool,
1156 selection_mark_mode: bool,
1157 toggle_fold_multiple_buffers: Task<()>,
1158 _scroll_cursor_center_top_bottom_task: Task<()>,
1159 serialize_selections: Task<()>,
1160 serialize_folds: Task<()>,
1161 mouse_cursor_hidden: bool,
1162 minimap: Option<Entity<Self>>,
1163 hide_mouse_mode: HideMouseMode,
1164 pub change_list: ChangeList,
1165 inline_value_cache: InlineValueCache,
1166 selection_drag_state: SelectionDragState,
1167 next_color_inlay_id: usize,
1168 colors: Option<LspColorData>,
1169 folding_newlines: Task<()>,
1170}
1171
1172#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1173enum NextScrollCursorCenterTopBottom {
1174 #[default]
1175 Center,
1176 Top,
1177 Bottom,
1178}
1179
1180impl NextScrollCursorCenterTopBottom {
1181 fn next(&self) -> Self {
1182 match self {
1183 Self::Center => Self::Top,
1184 Self::Top => Self::Bottom,
1185 Self::Bottom => Self::Center,
1186 }
1187 }
1188}
1189
1190#[derive(Clone)]
1191pub struct EditorSnapshot {
1192 pub mode: EditorMode,
1193 show_gutter: bool,
1194 show_line_numbers: Option<bool>,
1195 show_git_diff_gutter: Option<bool>,
1196 show_code_actions: Option<bool>,
1197 show_runnables: Option<bool>,
1198 show_breakpoints: Option<bool>,
1199 git_blame_gutter_max_author_length: Option<usize>,
1200 pub display_snapshot: DisplaySnapshot,
1201 pub placeholder_text: Option<Arc<str>>,
1202 is_focused: bool,
1203 scroll_anchor: ScrollAnchor,
1204 ongoing_scroll: OngoingScroll,
1205 current_line_highlight: CurrentLineHighlight,
1206 gutter_hovered: bool,
1207}
1208
1209#[derive(Default, Debug, Clone, Copy)]
1210pub struct GutterDimensions {
1211 pub left_padding: Pixels,
1212 pub right_padding: Pixels,
1213 pub width: Pixels,
1214 pub margin: Pixels,
1215 pub git_blame_entries_width: Option<Pixels>,
1216}
1217
1218impl GutterDimensions {
1219 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1220 Self {
1221 margin: Self::default_gutter_margin(font_id, font_size, cx),
1222 ..Default::default()
1223 }
1224 }
1225
1226 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1227 -cx.text_system().descent(font_id, font_size)
1228 }
1229 /// The full width of the space taken up by the gutter.
1230 pub fn full_width(&self) -> Pixels {
1231 self.margin + self.width
1232 }
1233
1234 /// The width of the space reserved for the fold indicators,
1235 /// use alongside 'justify_end' and `gutter_width` to
1236 /// right align content with the line numbers
1237 pub fn fold_area_width(&self) -> Pixels {
1238 self.margin + self.right_padding
1239 }
1240}
1241
1242struct CharacterDimensions {
1243 em_width: Pixels,
1244 em_advance: Pixels,
1245 line_height: Pixels,
1246}
1247
1248#[derive(Debug)]
1249pub struct RemoteSelection {
1250 pub replica_id: ReplicaId,
1251 pub selection: Selection<Anchor>,
1252 pub cursor_shape: CursorShape,
1253 pub collaborator_id: CollaboratorId,
1254 pub line_mode: bool,
1255 pub user_name: Option<SharedString>,
1256 pub color: PlayerColor,
1257}
1258
1259#[derive(Clone, Debug)]
1260struct SelectionHistoryEntry {
1261 selections: Arc<[Selection<Anchor>]>,
1262 select_next_state: Option<SelectNextState>,
1263 select_prev_state: Option<SelectNextState>,
1264 add_selections_state: Option<AddSelectionsState>,
1265}
1266
1267#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1268enum SelectionHistoryMode {
1269 Normal,
1270 Undoing,
1271 Redoing,
1272 Skipping,
1273}
1274
1275#[derive(Clone, PartialEq, Eq, Hash)]
1276struct HoveredCursor {
1277 replica_id: u16,
1278 selection_id: usize,
1279}
1280
1281impl Default for SelectionHistoryMode {
1282 fn default() -> Self {
1283 Self::Normal
1284 }
1285}
1286
1287#[derive(Debug)]
1288/// SelectionEffects controls the side-effects of updating the selection.
1289///
1290/// The default behaviour does "what you mostly want":
1291/// - it pushes to the nav history if the cursor moved by >10 lines
1292/// - it re-triggers completion requests
1293/// - it scrolls to fit
1294///
1295/// You might want to modify these behaviours. For example when doing a "jump"
1296/// like go to definition, we always want to add to nav history; but when scrolling
1297/// in vim mode we never do.
1298///
1299/// Similarly, you might want to disable scrolling if you don't want the viewport to
1300/// move.
1301#[derive(Clone)]
1302pub struct SelectionEffects {
1303 nav_history: Option<bool>,
1304 completions: bool,
1305 scroll: Option<Autoscroll>,
1306}
1307
1308impl Default for SelectionEffects {
1309 fn default() -> Self {
1310 Self {
1311 nav_history: None,
1312 completions: true,
1313 scroll: Some(Autoscroll::fit()),
1314 }
1315 }
1316}
1317impl SelectionEffects {
1318 pub fn scroll(scroll: Autoscroll) -> Self {
1319 Self {
1320 scroll: Some(scroll),
1321 ..Default::default()
1322 }
1323 }
1324
1325 pub fn no_scroll() -> Self {
1326 Self {
1327 scroll: None,
1328 ..Default::default()
1329 }
1330 }
1331
1332 pub fn completions(self, completions: bool) -> Self {
1333 Self {
1334 completions,
1335 ..self
1336 }
1337 }
1338
1339 pub fn nav_history(self, nav_history: bool) -> Self {
1340 Self {
1341 nav_history: Some(nav_history),
1342 ..self
1343 }
1344 }
1345}
1346
1347struct DeferredSelectionEffectsState {
1348 changed: bool,
1349 effects: SelectionEffects,
1350 old_cursor_position: Anchor,
1351 history_entry: SelectionHistoryEntry,
1352}
1353
1354#[derive(Default)]
1355struct SelectionHistory {
1356 #[allow(clippy::type_complexity)]
1357 selections_by_transaction:
1358 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1359 mode: SelectionHistoryMode,
1360 undo_stack: VecDeque<SelectionHistoryEntry>,
1361 redo_stack: VecDeque<SelectionHistoryEntry>,
1362}
1363
1364impl SelectionHistory {
1365 #[track_caller]
1366 fn insert_transaction(
1367 &mut self,
1368 transaction_id: TransactionId,
1369 selections: Arc<[Selection<Anchor>]>,
1370 ) {
1371 if selections.is_empty() {
1372 log::error!(
1373 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1374 std::panic::Location::caller()
1375 );
1376 return;
1377 }
1378 self.selections_by_transaction
1379 .insert(transaction_id, (selections, None));
1380 }
1381
1382 #[allow(clippy::type_complexity)]
1383 fn transaction(
1384 &self,
1385 transaction_id: TransactionId,
1386 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1387 self.selections_by_transaction.get(&transaction_id)
1388 }
1389
1390 #[allow(clippy::type_complexity)]
1391 fn transaction_mut(
1392 &mut self,
1393 transaction_id: TransactionId,
1394 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1395 self.selections_by_transaction.get_mut(&transaction_id)
1396 }
1397
1398 fn push(&mut self, entry: SelectionHistoryEntry) {
1399 if !entry.selections.is_empty() {
1400 match self.mode {
1401 SelectionHistoryMode::Normal => {
1402 self.push_undo(entry);
1403 self.redo_stack.clear();
1404 }
1405 SelectionHistoryMode::Undoing => self.push_redo(entry),
1406 SelectionHistoryMode::Redoing => self.push_undo(entry),
1407 SelectionHistoryMode::Skipping => {}
1408 }
1409 }
1410 }
1411
1412 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1413 if self
1414 .undo_stack
1415 .back()
1416 .map_or(true, |e| e.selections != entry.selections)
1417 {
1418 self.undo_stack.push_back(entry);
1419 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1420 self.undo_stack.pop_front();
1421 }
1422 }
1423 }
1424
1425 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1426 if self
1427 .redo_stack
1428 .back()
1429 .map_or(true, |e| e.selections != entry.selections)
1430 {
1431 self.redo_stack.push_back(entry);
1432 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1433 self.redo_stack.pop_front();
1434 }
1435 }
1436 }
1437}
1438
1439#[derive(Clone, Copy)]
1440pub struct RowHighlightOptions {
1441 pub autoscroll: bool,
1442 pub include_gutter: bool,
1443}
1444
1445impl Default for RowHighlightOptions {
1446 fn default() -> Self {
1447 Self {
1448 autoscroll: Default::default(),
1449 include_gutter: true,
1450 }
1451 }
1452}
1453
1454struct RowHighlight {
1455 index: usize,
1456 range: Range<Anchor>,
1457 color: Hsla,
1458 options: RowHighlightOptions,
1459 type_id: TypeId,
1460}
1461
1462#[derive(Clone, Debug)]
1463struct AddSelectionsState {
1464 groups: Vec<AddSelectionsGroup>,
1465}
1466
1467#[derive(Clone, Debug)]
1468struct AddSelectionsGroup {
1469 above: bool,
1470 stack: Vec<usize>,
1471}
1472
1473#[derive(Clone)]
1474struct SelectNextState {
1475 query: AhoCorasick,
1476 wordwise: bool,
1477 done: bool,
1478}
1479
1480impl std::fmt::Debug for SelectNextState {
1481 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1482 f.debug_struct(std::any::type_name::<Self>())
1483 .field("wordwise", &self.wordwise)
1484 .field("done", &self.done)
1485 .finish()
1486 }
1487}
1488
1489#[derive(Debug)]
1490struct AutocloseRegion {
1491 selection_id: usize,
1492 range: Range<Anchor>,
1493 pair: BracketPair,
1494}
1495
1496#[derive(Debug)]
1497struct SnippetState {
1498 ranges: Vec<Vec<Range<Anchor>>>,
1499 active_index: usize,
1500 choices: Vec<Option<Vec<String>>>,
1501}
1502
1503#[doc(hidden)]
1504pub struct RenameState {
1505 pub range: Range<Anchor>,
1506 pub old_name: Arc<str>,
1507 pub editor: Entity<Editor>,
1508 block_id: CustomBlockId,
1509}
1510
1511struct InvalidationStack<T>(Vec<T>);
1512
1513struct RegisteredEditPredictionProvider {
1514 provider: Arc<dyn EditPredictionProviderHandle>,
1515 _subscription: Subscription,
1516}
1517
1518#[derive(Debug, PartialEq, Eq)]
1519pub struct ActiveDiagnosticGroup {
1520 pub active_range: Range<Anchor>,
1521 pub active_message: String,
1522 pub group_id: usize,
1523 pub blocks: HashSet<CustomBlockId>,
1524}
1525
1526#[derive(Debug, PartialEq, Eq)]
1527
1528pub(crate) enum ActiveDiagnostic {
1529 None,
1530 All,
1531 Group(ActiveDiagnosticGroup),
1532}
1533
1534#[derive(Serialize, Deserialize, Clone, Debug)]
1535pub struct ClipboardSelection {
1536 /// The number of bytes in this selection.
1537 pub len: usize,
1538 /// Whether this was a full-line selection.
1539 pub is_entire_line: bool,
1540 /// The indentation of the first line when this content was originally copied.
1541 pub first_line_indent: u32,
1542}
1543
1544// selections, scroll behavior, was newest selection reversed
1545type SelectSyntaxNodeHistoryState = (
1546 Box<[Selection<usize>]>,
1547 SelectSyntaxNodeScrollBehavior,
1548 bool,
1549);
1550
1551#[derive(Default)]
1552struct SelectSyntaxNodeHistory {
1553 stack: Vec<SelectSyntaxNodeHistoryState>,
1554 // disable temporarily to allow changing selections without losing the stack
1555 pub disable_clearing: bool,
1556}
1557
1558impl SelectSyntaxNodeHistory {
1559 pub fn try_clear(&mut self) {
1560 if !self.disable_clearing {
1561 self.stack.clear();
1562 }
1563 }
1564
1565 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1566 self.stack.push(selection);
1567 }
1568
1569 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1570 self.stack.pop()
1571 }
1572}
1573
1574enum SelectSyntaxNodeScrollBehavior {
1575 CursorTop,
1576 FitSelection,
1577 CursorBottom,
1578}
1579
1580#[derive(Debug)]
1581pub(crate) struct NavigationData {
1582 cursor_anchor: Anchor,
1583 cursor_position: Point,
1584 scroll_anchor: ScrollAnchor,
1585 scroll_top_row: u32,
1586}
1587
1588#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1589pub enum GotoDefinitionKind {
1590 Symbol,
1591 Declaration,
1592 Type,
1593 Implementation,
1594}
1595
1596#[derive(Debug, Clone)]
1597enum InlayHintRefreshReason {
1598 ModifiersChanged(bool),
1599 Toggle(bool),
1600 SettingsChange(InlayHintSettings),
1601 NewLinesShown,
1602 BufferEdited(HashSet<Arc<Language>>),
1603 RefreshRequested,
1604 ExcerptsRemoved(Vec<ExcerptId>),
1605}
1606
1607impl InlayHintRefreshReason {
1608 fn description(&self) -> &'static str {
1609 match self {
1610 Self::ModifiersChanged(_) => "modifiers changed",
1611 Self::Toggle(_) => "toggle",
1612 Self::SettingsChange(_) => "settings change",
1613 Self::NewLinesShown => "new lines shown",
1614 Self::BufferEdited(_) => "buffer edited",
1615 Self::RefreshRequested => "refresh requested",
1616 Self::ExcerptsRemoved(_) => "excerpts removed",
1617 }
1618 }
1619}
1620
1621pub enum FormatTarget {
1622 Buffers(HashSet<Entity<Buffer>>),
1623 Ranges(Vec<Range<MultiBufferPoint>>),
1624}
1625
1626pub(crate) struct FocusedBlock {
1627 id: BlockId,
1628 focus_handle: WeakFocusHandle,
1629}
1630
1631#[derive(Clone)]
1632enum JumpData {
1633 MultiBufferRow {
1634 row: MultiBufferRow,
1635 line_offset_from_top: u32,
1636 },
1637 MultiBufferPoint {
1638 excerpt_id: ExcerptId,
1639 position: Point,
1640 anchor: text::Anchor,
1641 line_offset_from_top: u32,
1642 },
1643}
1644
1645pub enum MultibufferSelectionMode {
1646 First,
1647 All,
1648}
1649
1650#[derive(Clone, Copy, Debug, Default)]
1651pub struct RewrapOptions {
1652 pub override_language_settings: bool,
1653 pub preserve_existing_whitespace: bool,
1654}
1655
1656impl Editor {
1657 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1658 let buffer = cx.new(|cx| Buffer::local("", cx));
1659 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1660 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1661 }
1662
1663 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1664 let buffer = cx.new(|cx| Buffer::local("", cx));
1665 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1666 Self::new(EditorMode::full(), buffer, None, window, cx)
1667 }
1668
1669 pub fn auto_height(
1670 min_lines: usize,
1671 max_lines: usize,
1672 window: &mut Window,
1673 cx: &mut Context<Self>,
1674 ) -> Self {
1675 let buffer = cx.new(|cx| Buffer::local("", cx));
1676 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1677 Self::new(
1678 EditorMode::AutoHeight {
1679 min_lines,
1680 max_lines: Some(max_lines),
1681 },
1682 buffer,
1683 None,
1684 window,
1685 cx,
1686 )
1687 }
1688
1689 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1690 /// The editor grows as tall as needed to fit its content.
1691 pub fn auto_height_unbounded(
1692 min_lines: usize,
1693 window: &mut Window,
1694 cx: &mut Context<Self>,
1695 ) -> Self {
1696 let buffer = cx.new(|cx| Buffer::local("", cx));
1697 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1698 Self::new(
1699 EditorMode::AutoHeight {
1700 min_lines,
1701 max_lines: None,
1702 },
1703 buffer,
1704 None,
1705 window,
1706 cx,
1707 )
1708 }
1709
1710 pub fn for_buffer(
1711 buffer: Entity<Buffer>,
1712 project: Option<Entity<Project>>,
1713 window: &mut Window,
1714 cx: &mut Context<Self>,
1715 ) -> Self {
1716 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1717 Self::new(EditorMode::full(), buffer, project, window, cx)
1718 }
1719
1720 pub fn for_multibuffer(
1721 buffer: Entity<MultiBuffer>,
1722 project: Option<Entity<Project>>,
1723 window: &mut Window,
1724 cx: &mut Context<Self>,
1725 ) -> Self {
1726 Self::new(EditorMode::full(), buffer, project, window, cx)
1727 }
1728
1729 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1730 let mut clone = Self::new(
1731 self.mode.clone(),
1732 self.buffer.clone(),
1733 self.project.clone(),
1734 window,
1735 cx,
1736 );
1737 self.display_map.update(cx, |display_map, cx| {
1738 let snapshot = display_map.snapshot(cx);
1739 clone.display_map.update(cx, |display_map, cx| {
1740 display_map.set_state(&snapshot, cx);
1741 });
1742 });
1743 clone.folds_did_change(cx);
1744 clone.selections.clone_state(&self.selections);
1745 clone.scroll_manager.clone_state(&self.scroll_manager);
1746 clone.searchable = self.searchable;
1747 clone.read_only = self.read_only;
1748 clone
1749 }
1750
1751 pub fn new(
1752 mode: EditorMode,
1753 buffer: Entity<MultiBuffer>,
1754 project: Option<Entity<Project>>,
1755 window: &mut Window,
1756 cx: &mut Context<Self>,
1757 ) -> Self {
1758 Editor::new_internal(mode, buffer, project, None, window, cx)
1759 }
1760
1761 fn new_internal(
1762 mode: EditorMode,
1763 buffer: Entity<MultiBuffer>,
1764 project: Option<Entity<Project>>,
1765 display_map: Option<Entity<DisplayMap>>,
1766 window: &mut Window,
1767 cx: &mut Context<Self>,
1768 ) -> Self {
1769 debug_assert!(
1770 display_map.is_none() || mode.is_minimap(),
1771 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1772 );
1773
1774 let full_mode = mode.is_full();
1775 let is_minimap = mode.is_minimap();
1776 let diagnostics_max_severity = if full_mode {
1777 EditorSettings::get_global(cx)
1778 .diagnostics_max_severity
1779 .unwrap_or(DiagnosticSeverity::Hint)
1780 } else {
1781 DiagnosticSeverity::Off
1782 };
1783 let style = window.text_style();
1784 let font_size = style.font_size.to_pixels(window.rem_size());
1785 let editor = cx.entity().downgrade();
1786 let fold_placeholder = FoldPlaceholder {
1787 constrain_width: true,
1788 render: Arc::new(move |fold_id, fold_range, cx| {
1789 let editor = editor.clone();
1790 div()
1791 .id(fold_id)
1792 .bg(cx.theme().colors().ghost_element_background)
1793 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1794 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1795 .rounded_xs()
1796 .size_full()
1797 .cursor_pointer()
1798 .child("⋯")
1799 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1800 .on_click(move |_, _window, cx| {
1801 editor
1802 .update(cx, |editor, cx| {
1803 editor.unfold_ranges(
1804 &[fold_range.start..fold_range.end],
1805 true,
1806 false,
1807 cx,
1808 );
1809 cx.stop_propagation();
1810 })
1811 .ok();
1812 })
1813 .into_any()
1814 }),
1815 merge_adjacent: true,
1816 ..FoldPlaceholder::default()
1817 };
1818 let display_map = display_map.unwrap_or_else(|| {
1819 cx.new(|cx| {
1820 DisplayMap::new(
1821 buffer.clone(),
1822 style.font(),
1823 font_size,
1824 None,
1825 FILE_HEADER_HEIGHT,
1826 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1827 fold_placeholder,
1828 diagnostics_max_severity,
1829 cx,
1830 )
1831 })
1832 });
1833
1834 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1835
1836 let blink_manager = cx.new(|cx| {
1837 let mut blink_manager = BlinkManager::new(CURSOR_BLINK_INTERVAL, cx);
1838 if is_minimap {
1839 blink_manager.disable(cx);
1840 }
1841 blink_manager
1842 });
1843
1844 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1845 .then(|| language_settings::SoftWrap::None);
1846
1847 let mut project_subscriptions = Vec::new();
1848 if full_mode {
1849 if let Some(project) = project.as_ref() {
1850 project_subscriptions.push(cx.subscribe_in(
1851 project,
1852 window,
1853 |editor, _, event, window, cx| match event {
1854 project::Event::RefreshCodeLens => {
1855 // we always query lens with actions, without storing them, always refreshing them
1856 }
1857 project::Event::RefreshInlayHints => {
1858 editor
1859 .refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1860 }
1861 project::Event::LanguageServerAdded(..)
1862 | project::Event::LanguageServerRemoved(..) => {
1863 if editor.tasks_update_task.is_none() {
1864 editor.tasks_update_task =
1865 Some(editor.refresh_runnables(window, cx));
1866 }
1867 }
1868 project::Event::SnippetEdit(id, snippet_edits) => {
1869 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1870 let focus_handle = editor.focus_handle(cx);
1871 if focus_handle.is_focused(window) {
1872 let snapshot = buffer.read(cx).snapshot();
1873 for (range, snippet) in snippet_edits {
1874 let editor_range =
1875 language::range_from_lsp(*range).to_offset(&snapshot);
1876 editor
1877 .insert_snippet(
1878 &[editor_range],
1879 snippet.clone(),
1880 window,
1881 cx,
1882 )
1883 .ok();
1884 }
1885 }
1886 }
1887 }
1888 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
1889 if editor.buffer().read(cx).buffer(*buffer_id).is_some() {
1890 editor.update_lsp_data(false, Some(*buffer_id), window, cx);
1891 }
1892 }
1893 _ => {}
1894 },
1895 ));
1896 if let Some(task_inventory) = project
1897 .read(cx)
1898 .task_store()
1899 .read(cx)
1900 .task_inventory()
1901 .cloned()
1902 {
1903 project_subscriptions.push(cx.observe_in(
1904 &task_inventory,
1905 window,
1906 |editor, _, window, cx| {
1907 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1908 },
1909 ));
1910 };
1911
1912 project_subscriptions.push(cx.subscribe_in(
1913 &project.read(cx).breakpoint_store(),
1914 window,
1915 |editor, _, event, window, cx| match event {
1916 BreakpointStoreEvent::ClearDebugLines => {
1917 editor.clear_row_highlights::<ActiveDebugLine>();
1918 editor.refresh_inline_values(cx);
1919 }
1920 BreakpointStoreEvent::SetDebugLine => {
1921 if editor.go_to_active_debug_line(window, cx) {
1922 cx.stop_propagation();
1923 }
1924
1925 editor.refresh_inline_values(cx);
1926 }
1927 _ => {}
1928 },
1929 ));
1930 let git_store = project.read(cx).git_store().clone();
1931 let project = project.clone();
1932 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
1933 match event {
1934 GitStoreEvent::RepositoryUpdated(
1935 _,
1936 RepositoryEvent::Updated {
1937 new_instance: true, ..
1938 },
1939 _,
1940 ) => {
1941 this.load_diff_task = Some(
1942 update_uncommitted_diff_for_buffer(
1943 cx.entity(),
1944 &project,
1945 this.buffer.read(cx).all_buffers(),
1946 this.buffer.clone(),
1947 cx,
1948 )
1949 .shared(),
1950 );
1951 }
1952 _ => {}
1953 }
1954 }));
1955 }
1956 }
1957
1958 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1959
1960 let inlay_hint_settings =
1961 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1962 let focus_handle = cx.focus_handle();
1963 if !is_minimap {
1964 cx.on_focus(&focus_handle, window, Self::handle_focus)
1965 .detach();
1966 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1967 .detach();
1968 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1969 .detach();
1970 cx.on_blur(&focus_handle, window, Self::handle_blur)
1971 .detach();
1972 cx.observe_pending_input(window, Self::observe_pending_input)
1973 .detach();
1974 }
1975
1976 let show_indent_guides = if matches!(
1977 mode,
1978 EditorMode::SingleLine { .. } | EditorMode::Minimap { .. }
1979 ) {
1980 Some(false)
1981 } else {
1982 None
1983 };
1984
1985 let breakpoint_store = match (&mode, project.as_ref()) {
1986 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
1987 _ => None,
1988 };
1989
1990 let mut code_action_providers = Vec::new();
1991 let mut load_uncommitted_diff = None;
1992 if let Some(project) = project.clone() {
1993 load_uncommitted_diff = Some(
1994 update_uncommitted_diff_for_buffer(
1995 cx.entity(),
1996 &project,
1997 buffer.read(cx).all_buffers(),
1998 buffer.clone(),
1999 cx,
2000 )
2001 .shared(),
2002 );
2003 code_action_providers.push(Rc::new(project) as Rc<_>);
2004 }
2005
2006 let mut editor = Self {
2007 focus_handle,
2008 show_cursor_when_unfocused: false,
2009 last_focused_descendant: None,
2010 buffer: buffer.clone(),
2011 display_map: display_map.clone(),
2012 selections,
2013 scroll_manager: ScrollManager::new(cx),
2014 columnar_selection_state: None,
2015 add_selections_state: None,
2016 select_next_state: None,
2017 select_prev_state: None,
2018 selection_history: SelectionHistory::default(),
2019 defer_selection_effects: false,
2020 deferred_selection_effects_state: None,
2021 autoclose_regions: Vec::new(),
2022 snippet_stack: InvalidationStack::default(),
2023 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2024 ime_transaction: None,
2025 active_diagnostics: ActiveDiagnostic::None,
2026 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2027 inline_diagnostics_update: Task::ready(()),
2028 inline_diagnostics: Vec::new(),
2029 soft_wrap_mode_override,
2030 diagnostics_max_severity,
2031 hard_wrap: None,
2032 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2033 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2034 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2035 project,
2036 blink_manager: blink_manager.clone(),
2037 show_local_selections: true,
2038 show_scrollbars: ScrollbarAxes {
2039 horizontal: full_mode,
2040 vertical: full_mode,
2041 },
2042 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2043 offset_content: !matches!(mode, EditorMode::SingleLine { .. }),
2044 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2045 show_gutter: full_mode,
2046 show_line_numbers: (!full_mode).then_some(false),
2047 use_relative_line_numbers: None,
2048 disable_expand_excerpt_buttons: !full_mode,
2049 show_git_diff_gutter: None,
2050 show_code_actions: None,
2051 show_runnables: None,
2052 show_breakpoints: None,
2053 show_wrap_guides: None,
2054 show_indent_guides,
2055 placeholder_text: None,
2056 highlight_order: 0,
2057 highlighted_rows: HashMap::default(),
2058 background_highlights: TreeMap::default(),
2059 gutter_highlights: TreeMap::default(),
2060 scrollbar_marker_state: ScrollbarMarkerState::default(),
2061 active_indent_guides_state: ActiveIndentGuidesState::default(),
2062 nav_history: None,
2063 context_menu: RefCell::new(None),
2064 context_menu_options: None,
2065 mouse_context_menu: None,
2066 completion_tasks: Vec::new(),
2067 inline_blame_popover: None,
2068 inline_blame_popover_show_task: None,
2069 signature_help_state: SignatureHelpState::default(),
2070 auto_signature_help: None,
2071 find_all_references_task_sources: Vec::new(),
2072 next_completion_id: 0,
2073 next_inlay_id: 0,
2074 code_action_providers,
2075 available_code_actions: None,
2076 code_actions_task: None,
2077 quick_selection_highlight_task: None,
2078 debounced_selection_highlight_task: None,
2079 document_highlights_task: None,
2080 linked_editing_range_task: None,
2081 pending_rename: None,
2082 searchable: !is_minimap,
2083 cursor_shape: EditorSettings::get_global(cx)
2084 .cursor_shape
2085 .unwrap_or_default(),
2086 current_line_highlight: None,
2087 autoindent_mode: Some(AutoindentMode::EachLine),
2088 collapse_matches: false,
2089 workspace: None,
2090 input_enabled: !is_minimap,
2091 use_modal_editing: full_mode,
2092 read_only: is_minimap,
2093 use_autoclose: true,
2094 use_auto_surround: true,
2095 auto_replace_emoji_shortcode: false,
2096 jsx_tag_auto_close_enabled_in_any_buffer: false,
2097 leader_id: None,
2098 remote_id: None,
2099 hover_state: HoverState::default(),
2100 pending_mouse_down: None,
2101 hovered_link_state: None,
2102 edit_prediction_provider: None,
2103 active_edit_prediction: None,
2104 stale_edit_prediction_in_menu: None,
2105 edit_prediction_preview: EditPredictionPreview::Inactive {
2106 released_too_fast: false,
2107 },
2108 inline_diagnostics_enabled: full_mode,
2109 diagnostics_enabled: full_mode,
2110 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2111 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
2112 gutter_hovered: false,
2113 pixel_position_of_newest_cursor: None,
2114 last_bounds: None,
2115 last_position_map: None,
2116 expect_bounds_change: None,
2117 gutter_dimensions: GutterDimensions::default(),
2118 style: None,
2119 show_cursor_names: false,
2120 hovered_cursors: HashMap::default(),
2121 next_editor_action_id: EditorActionId::default(),
2122 editor_actions: Rc::default(),
2123 edit_predictions_hidden_for_vim_mode: false,
2124 show_edit_predictions_override: None,
2125 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2126 edit_prediction_settings: EditPredictionSettings::Disabled,
2127 edit_prediction_indent_conflict: false,
2128 edit_prediction_requires_modifier_in_indent_conflict: true,
2129 custom_context_menu: None,
2130 show_git_blame_gutter: false,
2131 show_git_blame_inline: false,
2132 show_selection_menu: None,
2133 show_git_blame_inline_delay_task: None,
2134 git_blame_inline_enabled: full_mode
2135 && ProjectSettings::get_global(cx).git.inline_blame_enabled(),
2136 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2137 serialize_dirty_buffers: !is_minimap
2138 && ProjectSettings::get_global(cx)
2139 .session
2140 .restore_unsaved_buffers,
2141 blame: None,
2142 blame_subscription: None,
2143 tasks: BTreeMap::default(),
2144
2145 breakpoint_store,
2146 gutter_breakpoint_indicator: (None, None),
2147 hovered_diff_hunk_row: None,
2148 _subscriptions: (!is_minimap)
2149 .then(|| {
2150 vec![
2151 cx.observe(&buffer, Self::on_buffer_changed),
2152 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
2153 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2154 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2155 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2156 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2157 cx.observe_window_activation(window, |editor, window, cx| {
2158 let active = window.is_window_active();
2159 editor.blink_manager.update(cx, |blink_manager, cx| {
2160 if active {
2161 blink_manager.enable(cx);
2162 } else {
2163 blink_manager.disable(cx);
2164 }
2165 });
2166 if active {
2167 editor.show_mouse_cursor(cx);
2168 }
2169 }),
2170 ]
2171 })
2172 .unwrap_or_default(),
2173 tasks_update_task: None,
2174 pull_diagnostics_task: Task::ready(()),
2175 colors: None,
2176 next_color_inlay_id: 0,
2177 linked_edit_ranges: Default::default(),
2178 in_project_search: false,
2179 previous_search_ranges: None,
2180 breadcrumb_header: None,
2181 focused_block: None,
2182 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2183 addons: HashMap::default(),
2184 registered_buffers: HashMap::default(),
2185 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2186 selection_mark_mode: false,
2187 toggle_fold_multiple_buffers: Task::ready(()),
2188 serialize_selections: Task::ready(()),
2189 serialize_folds: Task::ready(()),
2190 text_style_refinement: None,
2191 load_diff_task: load_uncommitted_diff,
2192 temporary_diff_override: false,
2193 mouse_cursor_hidden: false,
2194 minimap: None,
2195 hide_mouse_mode: EditorSettings::get_global(cx)
2196 .hide_mouse
2197 .unwrap_or_default(),
2198 change_list: ChangeList::new(),
2199 mode,
2200 selection_drag_state: SelectionDragState::None,
2201 folding_newlines: Task::ready(()),
2202 };
2203
2204 if is_minimap {
2205 return editor;
2206 }
2207
2208 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2209 editor
2210 ._subscriptions
2211 .push(cx.observe(breakpoints, |_, _, cx| {
2212 cx.notify();
2213 }));
2214 }
2215 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2216 editor._subscriptions.extend(project_subscriptions);
2217
2218 editor._subscriptions.push(cx.subscribe_in(
2219 &cx.entity(),
2220 window,
2221 |editor, _, e: &EditorEvent, window, cx| match e {
2222 EditorEvent::ScrollPositionChanged { local, .. } => {
2223 if *local {
2224 let new_anchor = editor.scroll_manager.anchor();
2225 let snapshot = editor.snapshot(window, cx);
2226 editor.update_restoration_data(cx, move |data| {
2227 data.scroll_position = (
2228 new_anchor.top_row(&snapshot.buffer_snapshot),
2229 new_anchor.offset,
2230 );
2231 });
2232 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2233 editor.inline_blame_popover.take();
2234 }
2235 }
2236 EditorEvent::Edited { .. } => {
2237 if !vim_enabled(cx) {
2238 let (map, selections) = editor.selections.all_adjusted_display(cx);
2239 let pop_state = editor
2240 .change_list
2241 .last()
2242 .map(|previous| {
2243 previous.len() == selections.len()
2244 && previous.iter().enumerate().all(|(ix, p)| {
2245 p.to_display_point(&map).row()
2246 == selections[ix].head().row()
2247 })
2248 })
2249 .unwrap_or(false);
2250 let new_positions = selections
2251 .into_iter()
2252 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
2253 .collect();
2254 editor
2255 .change_list
2256 .push_to_change_list(pop_state, new_positions);
2257 }
2258 }
2259 _ => (),
2260 },
2261 ));
2262
2263 if let Some(dap_store) = editor
2264 .project
2265 .as_ref()
2266 .map(|project| project.read(cx).dap_store())
2267 {
2268 let weak_editor = cx.weak_entity();
2269
2270 editor
2271 ._subscriptions
2272 .push(
2273 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2274 let session_entity = cx.entity();
2275 weak_editor
2276 .update(cx, |editor, cx| {
2277 editor._subscriptions.push(
2278 cx.subscribe(&session_entity, Self::on_debug_session_event),
2279 );
2280 })
2281 .ok();
2282 }),
2283 );
2284
2285 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2286 editor
2287 ._subscriptions
2288 .push(cx.subscribe(&session, Self::on_debug_session_event));
2289 }
2290 }
2291
2292 // skip adding the initial selection to selection history
2293 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2294 editor.end_selection(window, cx);
2295 editor.selection_history.mode = SelectionHistoryMode::Normal;
2296
2297 editor.scroll_manager.show_scrollbars(window, cx);
2298 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &buffer, cx);
2299
2300 if full_mode {
2301 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2302 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2303
2304 if editor.git_blame_inline_enabled {
2305 editor.start_git_blame_inline(false, window, cx);
2306 }
2307
2308 editor.go_to_active_debug_line(window, cx);
2309
2310 if let Some(buffer) = buffer.read(cx).as_singleton() {
2311 if let Some(project) = editor.project.as_ref() {
2312 let handle = project.update(cx, |project, cx| {
2313 project.register_buffer_with_language_servers(&buffer, cx)
2314 });
2315 editor
2316 .registered_buffers
2317 .insert(buffer.read(cx).remote_id(), handle);
2318 }
2319 }
2320
2321 editor.minimap =
2322 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2323 editor.colors = Some(LspColorData::new(cx));
2324 editor.update_lsp_data(false, None, window, cx);
2325 }
2326
2327 if editor.mode.is_full() {
2328 editor.report_editor_event("Editor Opened", None, cx);
2329 }
2330
2331 editor
2332 }
2333
2334 pub fn deploy_mouse_context_menu(
2335 &mut self,
2336 position: gpui::Point<Pixels>,
2337 context_menu: Entity<ContextMenu>,
2338 window: &mut Window,
2339 cx: &mut Context<Self>,
2340 ) {
2341 self.mouse_context_menu = Some(MouseContextMenu::new(
2342 self,
2343 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2344 context_menu,
2345 window,
2346 cx,
2347 ));
2348 }
2349
2350 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2351 self.mouse_context_menu
2352 .as_ref()
2353 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2354 }
2355
2356 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2357 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2358 }
2359
2360 fn key_context_internal(
2361 &self,
2362 has_active_edit_prediction: bool,
2363 window: &Window,
2364 cx: &App,
2365 ) -> KeyContext {
2366 let mut key_context = KeyContext::new_with_defaults();
2367 key_context.add("Editor");
2368 let mode = match self.mode {
2369 EditorMode::SingleLine { .. } => "single_line",
2370 EditorMode::AutoHeight { .. } => "auto_height",
2371 EditorMode::Minimap { .. } => "minimap",
2372 EditorMode::Full { .. } => "full",
2373 };
2374
2375 if EditorSettings::jupyter_enabled(cx) {
2376 key_context.add("jupyter");
2377 }
2378
2379 key_context.set("mode", mode);
2380 if self.pending_rename.is_some() {
2381 key_context.add("renaming");
2382 }
2383
2384 match self.context_menu.borrow().as_ref() {
2385 Some(CodeContextMenu::Completions(menu)) => {
2386 if menu.visible() {
2387 key_context.add("menu");
2388 key_context.add("showing_completions");
2389 }
2390 }
2391 Some(CodeContextMenu::CodeActions(menu)) => {
2392 if menu.visible() {
2393 key_context.add("menu");
2394 key_context.add("showing_code_actions")
2395 }
2396 }
2397 None => {}
2398 }
2399
2400 if self.signature_help_state.has_multiple_signatures() {
2401 key_context.add("showing_signature_help");
2402 }
2403
2404 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2405 if !self.focus_handle(cx).contains_focused(window, cx)
2406 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2407 {
2408 for addon in self.addons.values() {
2409 addon.extend_key_context(&mut key_context, cx)
2410 }
2411 }
2412
2413 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2414 if let Some(extension) = singleton_buffer
2415 .read(cx)
2416 .file()
2417 .and_then(|file| file.path().extension()?.to_str())
2418 {
2419 key_context.set("extension", extension.to_string());
2420 }
2421 } else {
2422 key_context.add("multibuffer");
2423 }
2424
2425 if has_active_edit_prediction {
2426 if self.edit_prediction_in_conflict() {
2427 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2428 } else {
2429 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2430 key_context.add("copilot_suggestion");
2431 }
2432 }
2433
2434 if self.selection_mark_mode {
2435 key_context.add("selection_mode");
2436 }
2437
2438 key_context
2439 }
2440
2441 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2442 if self.mouse_cursor_hidden {
2443 self.mouse_cursor_hidden = false;
2444 cx.notify();
2445 }
2446 }
2447
2448 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2449 let hide_mouse_cursor = match origin {
2450 HideMouseCursorOrigin::TypingAction => {
2451 matches!(
2452 self.hide_mouse_mode,
2453 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2454 )
2455 }
2456 HideMouseCursorOrigin::MovementAction => {
2457 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2458 }
2459 };
2460 if self.mouse_cursor_hidden != hide_mouse_cursor {
2461 self.mouse_cursor_hidden = hide_mouse_cursor;
2462 cx.notify();
2463 }
2464 }
2465
2466 pub fn edit_prediction_in_conflict(&self) -> bool {
2467 if !self.show_edit_predictions_in_menu() {
2468 return false;
2469 }
2470
2471 let showing_completions = self
2472 .context_menu
2473 .borrow()
2474 .as_ref()
2475 .map_or(false, |context| {
2476 matches!(context, CodeContextMenu::Completions(_))
2477 });
2478
2479 showing_completions
2480 || self.edit_prediction_requires_modifier()
2481 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2482 // bindings to insert tab characters.
2483 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2484 }
2485
2486 pub fn accept_edit_prediction_keybind(
2487 &self,
2488 accept_partial: bool,
2489 window: &Window,
2490 cx: &App,
2491 ) -> AcceptEditPredictionBinding {
2492 let key_context = self.key_context_internal(true, window, cx);
2493 let in_conflict = self.edit_prediction_in_conflict();
2494
2495 let bindings = if accept_partial {
2496 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2497 } else {
2498 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2499 };
2500
2501 // TODO: if the binding contains multiple keystrokes, display all of them, not
2502 // just the first one.
2503 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2504 !in_conflict
2505 || binding
2506 .keystrokes()
2507 .first()
2508 .map_or(false, |keystroke| keystroke.modifiers.modified())
2509 }))
2510 }
2511
2512 pub fn new_file(
2513 workspace: &mut Workspace,
2514 _: &workspace::NewFile,
2515 window: &mut Window,
2516 cx: &mut Context<Workspace>,
2517 ) {
2518 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2519 "Failed to create buffer",
2520 window,
2521 cx,
2522 |e, _, _| match e.error_code() {
2523 ErrorCode::RemoteUpgradeRequired => Some(format!(
2524 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2525 e.error_tag("required").unwrap_or("the latest version")
2526 )),
2527 _ => None,
2528 },
2529 );
2530 }
2531
2532 pub fn new_in_workspace(
2533 workspace: &mut Workspace,
2534 window: &mut Window,
2535 cx: &mut Context<Workspace>,
2536 ) -> Task<Result<Entity<Editor>>> {
2537 let project = workspace.project().clone();
2538 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2539
2540 cx.spawn_in(window, async move |workspace, cx| {
2541 let buffer = create.await?;
2542 workspace.update_in(cx, |workspace, window, cx| {
2543 let editor =
2544 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2545 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2546 editor
2547 })
2548 })
2549 }
2550
2551 fn new_file_vertical(
2552 workspace: &mut Workspace,
2553 _: &workspace::NewFileSplitVertical,
2554 window: &mut Window,
2555 cx: &mut Context<Workspace>,
2556 ) {
2557 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2558 }
2559
2560 fn new_file_horizontal(
2561 workspace: &mut Workspace,
2562 _: &workspace::NewFileSplitHorizontal,
2563 window: &mut Window,
2564 cx: &mut Context<Workspace>,
2565 ) {
2566 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2567 }
2568
2569 fn new_file_in_direction(
2570 workspace: &mut Workspace,
2571 direction: SplitDirection,
2572 window: &mut Window,
2573 cx: &mut Context<Workspace>,
2574 ) {
2575 let project = workspace.project().clone();
2576 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2577
2578 cx.spawn_in(window, async move |workspace, cx| {
2579 let buffer = create.await?;
2580 workspace.update_in(cx, move |workspace, window, cx| {
2581 workspace.split_item(
2582 direction,
2583 Box::new(
2584 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2585 ),
2586 window,
2587 cx,
2588 )
2589 })?;
2590 anyhow::Ok(())
2591 })
2592 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2593 match e.error_code() {
2594 ErrorCode::RemoteUpgradeRequired => Some(format!(
2595 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2596 e.error_tag("required").unwrap_or("the latest version")
2597 )),
2598 _ => None,
2599 }
2600 });
2601 }
2602
2603 pub fn leader_id(&self) -> Option<CollaboratorId> {
2604 self.leader_id
2605 }
2606
2607 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2608 &self.buffer
2609 }
2610
2611 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2612 self.workspace.as_ref()?.0.upgrade()
2613 }
2614
2615 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2616 self.buffer().read(cx).title(cx)
2617 }
2618
2619 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2620 let git_blame_gutter_max_author_length = self
2621 .render_git_blame_gutter(cx)
2622 .then(|| {
2623 if let Some(blame) = self.blame.as_ref() {
2624 let max_author_length =
2625 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2626 Some(max_author_length)
2627 } else {
2628 None
2629 }
2630 })
2631 .flatten();
2632
2633 EditorSnapshot {
2634 mode: self.mode.clone(),
2635 show_gutter: self.show_gutter,
2636 show_line_numbers: self.show_line_numbers,
2637 show_git_diff_gutter: self.show_git_diff_gutter,
2638 show_code_actions: self.show_code_actions,
2639 show_runnables: self.show_runnables,
2640 show_breakpoints: self.show_breakpoints,
2641 git_blame_gutter_max_author_length,
2642 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2643 scroll_anchor: self.scroll_manager.anchor(),
2644 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2645 placeholder_text: self.placeholder_text.clone(),
2646 is_focused: self.focus_handle.is_focused(window),
2647 current_line_highlight: self
2648 .current_line_highlight
2649 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2650 gutter_hovered: self.gutter_hovered,
2651 }
2652 }
2653
2654 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2655 self.buffer.read(cx).language_at(point, cx)
2656 }
2657
2658 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2659 self.buffer.read(cx).read(cx).file_at(point).cloned()
2660 }
2661
2662 pub fn active_excerpt(
2663 &self,
2664 cx: &App,
2665 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2666 self.buffer
2667 .read(cx)
2668 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2669 }
2670
2671 pub fn mode(&self) -> &EditorMode {
2672 &self.mode
2673 }
2674
2675 pub fn set_mode(&mut self, mode: EditorMode) {
2676 self.mode = mode;
2677 }
2678
2679 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2680 self.collaboration_hub.as_deref()
2681 }
2682
2683 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2684 self.collaboration_hub = Some(hub);
2685 }
2686
2687 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2688 self.in_project_search = in_project_search;
2689 }
2690
2691 pub fn set_custom_context_menu(
2692 &mut self,
2693 f: impl 'static
2694 + Fn(
2695 &mut Self,
2696 DisplayPoint,
2697 &mut Window,
2698 &mut Context<Self>,
2699 ) -> Option<Entity<ui::ContextMenu>>,
2700 ) {
2701 self.custom_context_menu = Some(Box::new(f))
2702 }
2703
2704 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2705 self.completion_provider = provider;
2706 }
2707
2708 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2709 self.semantics_provider.clone()
2710 }
2711
2712 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2713 self.semantics_provider = provider;
2714 }
2715
2716 pub fn set_edit_prediction_provider<T>(
2717 &mut self,
2718 provider: Option<Entity<T>>,
2719 window: &mut Window,
2720 cx: &mut Context<Self>,
2721 ) where
2722 T: EditPredictionProvider,
2723 {
2724 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionProvider {
2725 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2726 if this.focus_handle.is_focused(window) {
2727 this.update_visible_edit_prediction(window, cx);
2728 }
2729 }),
2730 provider: Arc::new(provider),
2731 });
2732 self.update_edit_prediction_settings(cx);
2733 self.refresh_edit_prediction(false, false, window, cx);
2734 }
2735
2736 pub fn placeholder_text(&self) -> Option<&str> {
2737 self.placeholder_text.as_deref()
2738 }
2739
2740 pub fn set_placeholder_text(
2741 &mut self,
2742 placeholder_text: impl Into<Arc<str>>,
2743 cx: &mut Context<Self>,
2744 ) {
2745 let placeholder_text = Some(placeholder_text.into());
2746 if self.placeholder_text != placeholder_text {
2747 self.placeholder_text = placeholder_text;
2748 cx.notify();
2749 }
2750 }
2751
2752 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2753 self.cursor_shape = cursor_shape;
2754
2755 // Disrupt blink for immediate user feedback that the cursor shape has changed
2756 self.blink_manager.update(cx, BlinkManager::show_cursor);
2757
2758 cx.notify();
2759 }
2760
2761 pub fn set_current_line_highlight(
2762 &mut self,
2763 current_line_highlight: Option<CurrentLineHighlight>,
2764 ) {
2765 self.current_line_highlight = current_line_highlight;
2766 }
2767
2768 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2769 self.collapse_matches = collapse_matches;
2770 }
2771
2772 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2773 let buffers = self.buffer.read(cx).all_buffers();
2774 let Some(project) = self.project.as_ref() else {
2775 return;
2776 };
2777 project.update(cx, |project, cx| {
2778 for buffer in buffers {
2779 self.registered_buffers
2780 .entry(buffer.read(cx).remote_id())
2781 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2782 }
2783 })
2784 }
2785
2786 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2787 if self.collapse_matches {
2788 return range.start..range.start;
2789 }
2790 range.clone()
2791 }
2792
2793 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2794 if self.display_map.read(cx).clip_at_line_ends != clip {
2795 self.display_map
2796 .update(cx, |map, _| map.clip_at_line_ends = clip);
2797 }
2798 }
2799
2800 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2801 self.input_enabled = input_enabled;
2802 }
2803
2804 pub fn set_edit_predictions_hidden_for_vim_mode(
2805 &mut self,
2806 hidden: bool,
2807 window: &mut Window,
2808 cx: &mut Context<Self>,
2809 ) {
2810 if hidden != self.edit_predictions_hidden_for_vim_mode {
2811 self.edit_predictions_hidden_for_vim_mode = hidden;
2812 if hidden {
2813 self.update_visible_edit_prediction(window, cx);
2814 } else {
2815 self.refresh_edit_prediction(true, false, window, cx);
2816 }
2817 }
2818 }
2819
2820 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
2821 self.menu_edit_predictions_policy = value;
2822 }
2823
2824 pub fn set_autoindent(&mut self, autoindent: bool) {
2825 if autoindent {
2826 self.autoindent_mode = Some(AutoindentMode::EachLine);
2827 } else {
2828 self.autoindent_mode = None;
2829 }
2830 }
2831
2832 pub fn read_only(&self, cx: &App) -> bool {
2833 self.read_only || self.buffer.read(cx).read_only()
2834 }
2835
2836 pub fn set_read_only(&mut self, read_only: bool) {
2837 self.read_only = read_only;
2838 }
2839
2840 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2841 self.use_autoclose = autoclose;
2842 }
2843
2844 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2845 self.use_auto_surround = auto_surround;
2846 }
2847
2848 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2849 self.auto_replace_emoji_shortcode = auto_replace;
2850 }
2851
2852 pub fn toggle_edit_predictions(
2853 &mut self,
2854 _: &ToggleEditPrediction,
2855 window: &mut Window,
2856 cx: &mut Context<Self>,
2857 ) {
2858 if self.show_edit_predictions_override.is_some() {
2859 self.set_show_edit_predictions(None, window, cx);
2860 } else {
2861 let show_edit_predictions = !self.edit_predictions_enabled();
2862 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2863 }
2864 }
2865
2866 pub fn set_show_edit_predictions(
2867 &mut self,
2868 show_edit_predictions: Option<bool>,
2869 window: &mut Window,
2870 cx: &mut Context<Self>,
2871 ) {
2872 self.show_edit_predictions_override = show_edit_predictions;
2873 self.update_edit_prediction_settings(cx);
2874
2875 if let Some(false) = show_edit_predictions {
2876 self.discard_edit_prediction(false, cx);
2877 } else {
2878 self.refresh_edit_prediction(false, true, window, cx);
2879 }
2880 }
2881
2882 fn edit_predictions_disabled_in_scope(
2883 &self,
2884 buffer: &Entity<Buffer>,
2885 buffer_position: language::Anchor,
2886 cx: &App,
2887 ) -> bool {
2888 let snapshot = buffer.read(cx).snapshot();
2889 let settings = snapshot.settings_at(buffer_position, cx);
2890
2891 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2892 return false;
2893 };
2894
2895 scope.override_name().map_or(false, |scope_name| {
2896 settings
2897 .edit_predictions_disabled_in
2898 .iter()
2899 .any(|s| s == scope_name)
2900 })
2901 }
2902
2903 pub fn set_use_modal_editing(&mut self, to: bool) {
2904 self.use_modal_editing = to;
2905 }
2906
2907 pub fn use_modal_editing(&self) -> bool {
2908 self.use_modal_editing
2909 }
2910
2911 fn selections_did_change(
2912 &mut self,
2913 local: bool,
2914 old_cursor_position: &Anchor,
2915 effects: SelectionEffects,
2916 window: &mut Window,
2917 cx: &mut Context<Self>,
2918 ) {
2919 window.invalidate_character_coordinates();
2920
2921 // Copy selections to primary selection buffer
2922 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2923 if local {
2924 let selections = self.selections.all::<usize>(cx);
2925 let buffer_handle = self.buffer.read(cx).read(cx);
2926
2927 let mut text = String::new();
2928 for (index, selection) in selections.iter().enumerate() {
2929 let text_for_selection = buffer_handle
2930 .text_for_range(selection.start..selection.end)
2931 .collect::<String>();
2932
2933 text.push_str(&text_for_selection);
2934 if index != selections.len() - 1 {
2935 text.push('\n');
2936 }
2937 }
2938
2939 if !text.is_empty() {
2940 cx.write_to_primary(ClipboardItem::new_string(text));
2941 }
2942 }
2943
2944 let selection_anchors = self.selections.disjoint_anchors();
2945
2946 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
2947 self.buffer.update(cx, |buffer, cx| {
2948 buffer.set_active_selections(
2949 &selection_anchors,
2950 self.selections.line_mode,
2951 self.cursor_shape,
2952 cx,
2953 )
2954 });
2955 }
2956 let display_map = self
2957 .display_map
2958 .update(cx, |display_map, cx| display_map.snapshot(cx));
2959 let buffer = &display_map.buffer_snapshot;
2960 if self.selections.count() == 1 {
2961 self.add_selections_state = None;
2962 }
2963 self.select_next_state = None;
2964 self.select_prev_state = None;
2965 self.select_syntax_node_history.try_clear();
2966 self.invalidate_autoclose_regions(&selection_anchors, buffer);
2967 self.snippet_stack.invalidate(&selection_anchors, buffer);
2968 self.take_rename(false, window, cx);
2969
2970 let newest_selection = self.selections.newest_anchor();
2971 let new_cursor_position = newest_selection.head();
2972 let selection_start = newest_selection.start;
2973
2974 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
2975 self.push_to_nav_history(
2976 *old_cursor_position,
2977 Some(new_cursor_position.to_point(buffer)),
2978 false,
2979 effects.nav_history == Some(true),
2980 cx,
2981 );
2982 }
2983
2984 if local {
2985 if let Some(buffer_id) = new_cursor_position.buffer_id {
2986 if !self.registered_buffers.contains_key(&buffer_id) {
2987 if let Some(project) = self.project.as_ref() {
2988 project.update(cx, |project, cx| {
2989 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
2990 return;
2991 };
2992 self.registered_buffers.insert(
2993 buffer_id,
2994 project.register_buffer_with_language_servers(&buffer, cx),
2995 );
2996 })
2997 }
2998 }
2999 }
3000
3001 let mut context_menu = self.context_menu.borrow_mut();
3002 let completion_menu = match context_menu.as_ref() {
3003 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3004 Some(CodeContextMenu::CodeActions(_)) => {
3005 *context_menu = None;
3006 None
3007 }
3008 None => None,
3009 };
3010 let completion_position = completion_menu.map(|menu| menu.initial_position);
3011 drop(context_menu);
3012
3013 if effects.completions {
3014 if let Some(completion_position) = completion_position {
3015 let start_offset = selection_start.to_offset(buffer);
3016 let position_matches = start_offset == completion_position.to_offset(buffer);
3017 let continue_showing = if position_matches {
3018 if self.snippet_stack.is_empty() {
3019 buffer.char_kind_before(start_offset, true) == Some(CharKind::Word)
3020 } else {
3021 // Snippet choices can be shown even when the cursor is in whitespace.
3022 // Dismissing the menu with actions like backspace is handled by
3023 // invalidation regions.
3024 true
3025 }
3026 } else {
3027 false
3028 };
3029
3030 if continue_showing {
3031 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
3032 } else {
3033 self.hide_context_menu(window, cx);
3034 }
3035 }
3036 }
3037
3038 hide_hover(self, cx);
3039
3040 if old_cursor_position.to_display_point(&display_map).row()
3041 != new_cursor_position.to_display_point(&display_map).row()
3042 {
3043 self.available_code_actions.take();
3044 }
3045 self.refresh_code_actions(window, cx);
3046 self.refresh_document_highlights(cx);
3047 self.refresh_selected_text_highlights(false, window, cx);
3048 refresh_matching_bracket_highlights(self, window, cx);
3049 self.update_visible_edit_prediction(window, cx);
3050 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3051 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
3052 self.inline_blame_popover.take();
3053 if self.git_blame_inline_enabled {
3054 self.start_inline_blame_timer(window, cx);
3055 }
3056 }
3057
3058 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3059 cx.emit(EditorEvent::SelectionsChanged { local });
3060
3061 let selections = &self.selections.disjoint;
3062 if selections.len() == 1 {
3063 cx.emit(SearchEvent::ActiveMatchChanged)
3064 }
3065 if local {
3066 if let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3067 let inmemory_selections = selections
3068 .iter()
3069 .map(|s| {
3070 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3071 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3072 })
3073 .collect();
3074 self.update_restoration_data(cx, |data| {
3075 data.selections = inmemory_selections;
3076 });
3077
3078 if WorkspaceSettings::get(None, cx).restore_on_startup
3079 != RestoreOnStartupBehavior::None
3080 {
3081 if let Some(workspace_id) =
3082 self.workspace.as_ref().and_then(|workspace| workspace.1)
3083 {
3084 let snapshot = self.buffer().read(cx).snapshot(cx);
3085 let selections = selections.clone();
3086 let background_executor = cx.background_executor().clone();
3087 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3088 self.serialize_selections = cx.background_spawn(async move {
3089 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3090 let db_selections = selections
3091 .iter()
3092 .map(|selection| {
3093 (
3094 selection.start.to_offset(&snapshot),
3095 selection.end.to_offset(&snapshot),
3096 )
3097 })
3098 .collect();
3099
3100 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3101 .await
3102 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
3103 .log_err();
3104 });
3105 }
3106 }
3107 }
3108 }
3109
3110 cx.notify();
3111 }
3112
3113 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3114 use text::ToOffset as _;
3115 use text::ToPoint as _;
3116
3117 if self.mode.is_minimap()
3118 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3119 {
3120 return;
3121 }
3122
3123 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
3124 return;
3125 };
3126
3127 let snapshot = singleton.read(cx).snapshot();
3128 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
3129 let display_snapshot = display_map.snapshot(cx);
3130
3131 display_snapshot
3132 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
3133 .map(|fold| {
3134 fold.range.start.text_anchor.to_point(&snapshot)
3135 ..fold.range.end.text_anchor.to_point(&snapshot)
3136 })
3137 .collect()
3138 });
3139 self.update_restoration_data(cx, |data| {
3140 data.folds = inmemory_folds;
3141 });
3142
3143 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
3144 return;
3145 };
3146 let background_executor = cx.background_executor().clone();
3147 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3148 let db_folds = self.display_map.update(cx, |display_map, cx| {
3149 display_map
3150 .snapshot(cx)
3151 .folds_in_range(0..snapshot.len())
3152 .map(|fold| {
3153 (
3154 fold.range.start.text_anchor.to_offset(&snapshot),
3155 fold.range.end.text_anchor.to_offset(&snapshot),
3156 )
3157 })
3158 .collect()
3159 });
3160 self.serialize_folds = cx.background_spawn(async move {
3161 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3162 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3163 .await
3164 .with_context(|| {
3165 format!(
3166 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3167 )
3168 })
3169 .log_err();
3170 });
3171 }
3172
3173 pub fn sync_selections(
3174 &mut self,
3175 other: Entity<Editor>,
3176 cx: &mut Context<Self>,
3177 ) -> gpui::Subscription {
3178 let other_selections = other.read(cx).selections.disjoint.to_vec();
3179 self.selections.change_with(cx, |selections| {
3180 selections.select_anchors(other_selections);
3181 });
3182
3183 let other_subscription =
3184 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
3185 EditorEvent::SelectionsChanged { local: true } => {
3186 let other_selections = other.read(cx).selections.disjoint.to_vec();
3187 if other_selections.is_empty() {
3188 return;
3189 }
3190 this.selections.change_with(cx, |selections| {
3191 selections.select_anchors(other_selections);
3192 });
3193 }
3194 _ => {}
3195 });
3196
3197 let this_subscription =
3198 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
3199 EditorEvent::SelectionsChanged { local: true } => {
3200 let these_selections = this.selections.disjoint.to_vec();
3201 if these_selections.is_empty() {
3202 return;
3203 }
3204 other.update(cx, |other_editor, cx| {
3205 other_editor.selections.change_with(cx, |selections| {
3206 selections.select_anchors(these_selections);
3207 })
3208 });
3209 }
3210 _ => {}
3211 });
3212
3213 Subscription::join(other_subscription, this_subscription)
3214 }
3215
3216 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3217 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3218 /// effects of selection change occur at the end of the transaction.
3219 pub fn change_selections<R>(
3220 &mut self,
3221 effects: SelectionEffects,
3222 window: &mut Window,
3223 cx: &mut Context<Self>,
3224 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3225 ) -> R {
3226 if let Some(state) = &mut self.deferred_selection_effects_state {
3227 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3228 state.effects.completions = effects.completions;
3229 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3230 let (changed, result) = self.selections.change_with(cx, change);
3231 state.changed |= changed;
3232 return result;
3233 }
3234 let mut state = DeferredSelectionEffectsState {
3235 changed: false,
3236 effects,
3237 old_cursor_position: self.selections.newest_anchor().head(),
3238 history_entry: SelectionHistoryEntry {
3239 selections: self.selections.disjoint_anchors(),
3240 select_next_state: self.select_next_state.clone(),
3241 select_prev_state: self.select_prev_state.clone(),
3242 add_selections_state: self.add_selections_state.clone(),
3243 },
3244 };
3245 let (changed, result) = self.selections.change_with(cx, change);
3246 state.changed = state.changed || changed;
3247 if self.defer_selection_effects {
3248 self.deferred_selection_effects_state = Some(state);
3249 } else {
3250 self.apply_selection_effects(state, window, cx);
3251 }
3252 result
3253 }
3254
3255 /// Defers the effects of selection change, so that the effects of multiple calls to
3256 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3257 /// to selection history and the state of popovers based on selection position aren't
3258 /// erroneously updated.
3259 pub fn with_selection_effects_deferred<R>(
3260 &mut self,
3261 window: &mut Window,
3262 cx: &mut Context<Self>,
3263 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3264 ) -> R {
3265 let already_deferred = self.defer_selection_effects;
3266 self.defer_selection_effects = true;
3267 let result = update(self, window, cx);
3268 if !already_deferred {
3269 self.defer_selection_effects = false;
3270 if let Some(state) = self.deferred_selection_effects_state.take() {
3271 self.apply_selection_effects(state, window, cx);
3272 }
3273 }
3274 result
3275 }
3276
3277 fn apply_selection_effects(
3278 &mut self,
3279 state: DeferredSelectionEffectsState,
3280 window: &mut Window,
3281 cx: &mut Context<Self>,
3282 ) {
3283 if state.changed {
3284 self.selection_history.push(state.history_entry);
3285
3286 if let Some(autoscroll) = state.effects.scroll {
3287 self.request_autoscroll(autoscroll, cx);
3288 }
3289
3290 let old_cursor_position = &state.old_cursor_position;
3291
3292 self.selections_did_change(true, &old_cursor_position, state.effects, window, cx);
3293
3294 if self.should_open_signature_help_automatically(&old_cursor_position, cx) {
3295 self.show_signature_help(&ShowSignatureHelp, window, cx);
3296 }
3297 }
3298 }
3299
3300 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3301 where
3302 I: IntoIterator<Item = (Range<S>, T)>,
3303 S: ToOffset,
3304 T: Into<Arc<str>>,
3305 {
3306 if self.read_only(cx) {
3307 return;
3308 }
3309
3310 self.buffer
3311 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3312 }
3313
3314 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3315 where
3316 I: IntoIterator<Item = (Range<S>, T)>,
3317 S: ToOffset,
3318 T: Into<Arc<str>>,
3319 {
3320 if self.read_only(cx) {
3321 return;
3322 }
3323
3324 self.buffer.update(cx, |buffer, cx| {
3325 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3326 });
3327 }
3328
3329 pub fn edit_with_block_indent<I, S, T>(
3330 &mut self,
3331 edits: I,
3332 original_indent_columns: Vec<Option<u32>>,
3333 cx: &mut Context<Self>,
3334 ) where
3335 I: IntoIterator<Item = (Range<S>, T)>,
3336 S: ToOffset,
3337 T: Into<Arc<str>>,
3338 {
3339 if self.read_only(cx) {
3340 return;
3341 }
3342
3343 self.buffer.update(cx, |buffer, cx| {
3344 buffer.edit(
3345 edits,
3346 Some(AutoindentMode::Block {
3347 original_indent_columns,
3348 }),
3349 cx,
3350 )
3351 });
3352 }
3353
3354 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3355 self.hide_context_menu(window, cx);
3356
3357 match phase {
3358 SelectPhase::Begin {
3359 position,
3360 add,
3361 click_count,
3362 } => self.begin_selection(position, add, click_count, window, cx),
3363 SelectPhase::BeginColumnar {
3364 position,
3365 goal_column,
3366 reset,
3367 mode,
3368 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3369 SelectPhase::Extend {
3370 position,
3371 click_count,
3372 } => self.extend_selection(position, click_count, window, cx),
3373 SelectPhase::Update {
3374 position,
3375 goal_column,
3376 scroll_delta,
3377 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3378 SelectPhase::End => self.end_selection(window, cx),
3379 }
3380 }
3381
3382 fn extend_selection(
3383 &mut self,
3384 position: DisplayPoint,
3385 click_count: usize,
3386 window: &mut Window,
3387 cx: &mut Context<Self>,
3388 ) {
3389 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3390 let tail = self.selections.newest::<usize>(cx).tail();
3391 self.begin_selection(position, false, click_count, window, cx);
3392
3393 let position = position.to_offset(&display_map, Bias::Left);
3394 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3395
3396 let mut pending_selection = self
3397 .selections
3398 .pending_anchor()
3399 .expect("extend_selection not called with pending selection");
3400 if position >= tail {
3401 pending_selection.start = tail_anchor;
3402 } else {
3403 pending_selection.end = tail_anchor;
3404 pending_selection.reversed = true;
3405 }
3406
3407 let mut pending_mode = self.selections.pending_mode().unwrap();
3408 match &mut pending_mode {
3409 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3410 _ => {}
3411 }
3412
3413 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3414 SelectionEffects::scroll(Autoscroll::fit())
3415 } else {
3416 SelectionEffects::no_scroll()
3417 };
3418
3419 self.change_selections(effects, window, cx, |s| {
3420 s.set_pending(pending_selection, pending_mode)
3421 });
3422 }
3423
3424 fn begin_selection(
3425 &mut self,
3426 position: DisplayPoint,
3427 add: bool,
3428 click_count: usize,
3429 window: &mut Window,
3430 cx: &mut Context<Self>,
3431 ) {
3432 if !self.focus_handle.is_focused(window) {
3433 self.last_focused_descendant = None;
3434 window.focus(&self.focus_handle);
3435 }
3436
3437 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3438 let buffer = &display_map.buffer_snapshot;
3439 let position = display_map.clip_point(position, Bias::Left);
3440
3441 let start;
3442 let end;
3443 let mode;
3444 let mut auto_scroll;
3445 match click_count {
3446 1 => {
3447 start = buffer.anchor_before(position.to_point(&display_map));
3448 end = start;
3449 mode = SelectMode::Character;
3450 auto_scroll = true;
3451 }
3452 2 => {
3453 let position = display_map
3454 .clip_point(position, Bias::Left)
3455 .to_offset(&display_map, Bias::Left);
3456 let (range, _) = buffer.surrounding_word(position, false);
3457 start = buffer.anchor_before(range.start);
3458 end = buffer.anchor_before(range.end);
3459 mode = SelectMode::Word(start..end);
3460 auto_scroll = true;
3461 }
3462 3 => {
3463 let position = display_map
3464 .clip_point(position, Bias::Left)
3465 .to_point(&display_map);
3466 let line_start = display_map.prev_line_boundary(position).0;
3467 let next_line_start = buffer.clip_point(
3468 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3469 Bias::Left,
3470 );
3471 start = buffer.anchor_before(line_start);
3472 end = buffer.anchor_before(next_line_start);
3473 mode = SelectMode::Line(start..end);
3474 auto_scroll = true;
3475 }
3476 _ => {
3477 start = buffer.anchor_before(0);
3478 end = buffer.anchor_before(buffer.len());
3479 mode = SelectMode::All;
3480 auto_scroll = false;
3481 }
3482 }
3483 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3484
3485 let point_to_delete: Option<usize> = {
3486 let selected_points: Vec<Selection<Point>> =
3487 self.selections.disjoint_in_range(start..end, cx);
3488
3489 if !add || click_count > 1 {
3490 None
3491 } else if !selected_points.is_empty() {
3492 Some(selected_points[0].id)
3493 } else {
3494 let clicked_point_already_selected =
3495 self.selections.disjoint.iter().find(|selection| {
3496 selection.start.to_point(buffer) == start.to_point(buffer)
3497 || selection.end.to_point(buffer) == end.to_point(buffer)
3498 });
3499
3500 clicked_point_already_selected.map(|selection| selection.id)
3501 }
3502 };
3503
3504 let selections_count = self.selections.count();
3505 let effects = if auto_scroll {
3506 SelectionEffects::default()
3507 } else {
3508 SelectionEffects::no_scroll()
3509 };
3510
3511 self.change_selections(effects, window, cx, |s| {
3512 if let Some(point_to_delete) = point_to_delete {
3513 s.delete(point_to_delete);
3514
3515 if selections_count == 1 {
3516 s.set_pending_anchor_range(start..end, mode);
3517 }
3518 } else {
3519 if !add {
3520 s.clear_disjoint();
3521 }
3522
3523 s.set_pending_anchor_range(start..end, mode);
3524 }
3525 });
3526 }
3527
3528 fn begin_columnar_selection(
3529 &mut self,
3530 position: DisplayPoint,
3531 goal_column: u32,
3532 reset: bool,
3533 mode: ColumnarMode,
3534 window: &mut Window,
3535 cx: &mut Context<Self>,
3536 ) {
3537 if !self.focus_handle.is_focused(window) {
3538 self.last_focused_descendant = None;
3539 window.focus(&self.focus_handle);
3540 }
3541
3542 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3543
3544 if reset {
3545 let pointer_position = display_map
3546 .buffer_snapshot
3547 .anchor_before(position.to_point(&display_map));
3548
3549 self.change_selections(
3550 SelectionEffects::scroll(Autoscroll::newest()),
3551 window,
3552 cx,
3553 |s| {
3554 s.clear_disjoint();
3555 s.set_pending_anchor_range(
3556 pointer_position..pointer_position,
3557 SelectMode::Character,
3558 );
3559 },
3560 );
3561 };
3562
3563 let tail = self.selections.newest::<Point>(cx).tail();
3564 let selection_anchor = display_map.buffer_snapshot.anchor_before(tail);
3565 self.columnar_selection_state = match mode {
3566 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3567 selection_tail: selection_anchor,
3568 display_point: if reset {
3569 if position.column() != goal_column {
3570 Some(DisplayPoint::new(position.row(), goal_column))
3571 } else {
3572 None
3573 }
3574 } else {
3575 None
3576 },
3577 }),
3578 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3579 selection_tail: selection_anchor,
3580 }),
3581 };
3582
3583 if !reset {
3584 self.select_columns(position, goal_column, &display_map, window, cx);
3585 }
3586 }
3587
3588 fn update_selection(
3589 &mut self,
3590 position: DisplayPoint,
3591 goal_column: u32,
3592 scroll_delta: gpui::Point<f32>,
3593 window: &mut Window,
3594 cx: &mut Context<Self>,
3595 ) {
3596 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3597
3598 if self.columnar_selection_state.is_some() {
3599 self.select_columns(position, goal_column, &display_map, window, cx);
3600 } else if let Some(mut pending) = self.selections.pending_anchor() {
3601 let buffer = &display_map.buffer_snapshot;
3602 let head;
3603 let tail;
3604 let mode = self.selections.pending_mode().unwrap();
3605 match &mode {
3606 SelectMode::Character => {
3607 head = position.to_point(&display_map);
3608 tail = pending.tail().to_point(buffer);
3609 }
3610 SelectMode::Word(original_range) => {
3611 let offset = display_map
3612 .clip_point(position, Bias::Left)
3613 .to_offset(&display_map, Bias::Left);
3614 let original_range = original_range.to_offset(buffer);
3615
3616 let head_offset = if buffer.is_inside_word(offset, false)
3617 || original_range.contains(&offset)
3618 {
3619 let (word_range, _) = buffer.surrounding_word(offset, false);
3620 if word_range.start < original_range.start {
3621 word_range.start
3622 } else {
3623 word_range.end
3624 }
3625 } else {
3626 offset
3627 };
3628
3629 head = head_offset.to_point(buffer);
3630 if head_offset <= original_range.start {
3631 tail = original_range.end.to_point(buffer);
3632 } else {
3633 tail = original_range.start.to_point(buffer);
3634 }
3635 }
3636 SelectMode::Line(original_range) => {
3637 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3638
3639 let position = display_map
3640 .clip_point(position, Bias::Left)
3641 .to_point(&display_map);
3642 let line_start = display_map.prev_line_boundary(position).0;
3643 let next_line_start = buffer.clip_point(
3644 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3645 Bias::Left,
3646 );
3647
3648 if line_start < original_range.start {
3649 head = line_start
3650 } else {
3651 head = next_line_start
3652 }
3653
3654 if head <= original_range.start {
3655 tail = original_range.end;
3656 } else {
3657 tail = original_range.start;
3658 }
3659 }
3660 SelectMode::All => {
3661 return;
3662 }
3663 };
3664
3665 if head < tail {
3666 pending.start = buffer.anchor_before(head);
3667 pending.end = buffer.anchor_before(tail);
3668 pending.reversed = true;
3669 } else {
3670 pending.start = buffer.anchor_before(tail);
3671 pending.end = buffer.anchor_before(head);
3672 pending.reversed = false;
3673 }
3674
3675 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3676 s.set_pending(pending, mode);
3677 });
3678 } else {
3679 log::error!("update_selection dispatched with no pending selection");
3680 return;
3681 }
3682
3683 self.apply_scroll_delta(scroll_delta, window, cx);
3684 cx.notify();
3685 }
3686
3687 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3688 self.columnar_selection_state.take();
3689 if self.selections.pending_anchor().is_some() {
3690 let selections = self.selections.all::<usize>(cx);
3691 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3692 s.select(selections);
3693 s.clear_pending();
3694 });
3695 }
3696 }
3697
3698 fn select_columns(
3699 &mut self,
3700 head: DisplayPoint,
3701 goal_column: u32,
3702 display_map: &DisplaySnapshot,
3703 window: &mut Window,
3704 cx: &mut Context<Self>,
3705 ) {
3706 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3707 return;
3708 };
3709
3710 let tail = match columnar_state {
3711 ColumnarSelectionState::FromMouse {
3712 selection_tail,
3713 display_point,
3714 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(&display_map)),
3715 ColumnarSelectionState::FromSelection { selection_tail } => {
3716 selection_tail.to_display_point(&display_map)
3717 }
3718 };
3719
3720 let start_row = cmp::min(tail.row(), head.row());
3721 let end_row = cmp::max(tail.row(), head.row());
3722 let start_column = cmp::min(tail.column(), goal_column);
3723 let end_column = cmp::max(tail.column(), goal_column);
3724 let reversed = start_column < tail.column();
3725
3726 let selection_ranges = (start_row.0..=end_row.0)
3727 .map(DisplayRow)
3728 .filter_map(|row| {
3729 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3730 || start_column <= display_map.line_len(row))
3731 && !display_map.is_block_line(row)
3732 {
3733 let start = display_map
3734 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3735 .to_point(display_map);
3736 let end = display_map
3737 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3738 .to_point(display_map);
3739 if reversed {
3740 Some(end..start)
3741 } else {
3742 Some(start..end)
3743 }
3744 } else {
3745 None
3746 }
3747 })
3748 .collect::<Vec<_>>();
3749
3750 let ranges = match columnar_state {
3751 ColumnarSelectionState::FromMouse { .. } => {
3752 let mut non_empty_ranges = selection_ranges
3753 .iter()
3754 .filter(|selection_range| selection_range.start != selection_range.end)
3755 .peekable();
3756 if non_empty_ranges.peek().is_some() {
3757 non_empty_ranges.cloned().collect()
3758 } else {
3759 selection_ranges
3760 }
3761 }
3762 _ => selection_ranges,
3763 };
3764
3765 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3766 s.select_ranges(ranges);
3767 });
3768 cx.notify();
3769 }
3770
3771 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3772 self.selections
3773 .all_adjusted(cx)
3774 .iter()
3775 .any(|selection| !selection.is_empty())
3776 }
3777
3778 pub fn has_pending_nonempty_selection(&self) -> bool {
3779 let pending_nonempty_selection = match self.selections.pending_anchor() {
3780 Some(Selection { start, end, .. }) => start != end,
3781 None => false,
3782 };
3783
3784 pending_nonempty_selection
3785 || (self.columnar_selection_state.is_some() && self.selections.disjoint.len() > 1)
3786 }
3787
3788 pub fn has_pending_selection(&self) -> bool {
3789 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
3790 }
3791
3792 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3793 self.selection_mark_mode = false;
3794 self.selection_drag_state = SelectionDragState::None;
3795
3796 if self.clear_expanded_diff_hunks(cx) {
3797 cx.notify();
3798 return;
3799 }
3800 if self.dismiss_menus_and_popups(true, window, cx) {
3801 return;
3802 }
3803
3804 if self.mode.is_full()
3805 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
3806 {
3807 return;
3808 }
3809
3810 cx.propagate();
3811 }
3812
3813 pub fn dismiss_menus_and_popups(
3814 &mut self,
3815 is_user_requested: bool,
3816 window: &mut Window,
3817 cx: &mut Context<Self>,
3818 ) -> bool {
3819 if self.take_rename(false, window, cx).is_some() {
3820 return true;
3821 }
3822
3823 if hide_hover(self, cx) {
3824 return true;
3825 }
3826
3827 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3828 return true;
3829 }
3830
3831 if self.hide_context_menu(window, cx).is_some() {
3832 return true;
3833 }
3834
3835 if self.mouse_context_menu.take().is_some() {
3836 return true;
3837 }
3838
3839 if is_user_requested && self.discard_edit_prediction(true, cx) {
3840 return true;
3841 }
3842
3843 if self.snippet_stack.pop().is_some() {
3844 return true;
3845 }
3846
3847 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3848 self.dismiss_diagnostics(cx);
3849 return true;
3850 }
3851
3852 false
3853 }
3854
3855 fn linked_editing_ranges_for(
3856 &self,
3857 selection: Range<text::Anchor>,
3858 cx: &App,
3859 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3860 if self.linked_edit_ranges.is_empty() {
3861 return None;
3862 }
3863 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3864 selection.end.buffer_id.and_then(|end_buffer_id| {
3865 if selection.start.buffer_id != Some(end_buffer_id) {
3866 return None;
3867 }
3868 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3869 let snapshot = buffer.read(cx).snapshot();
3870 self.linked_edit_ranges
3871 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3872 .map(|ranges| (ranges, snapshot, buffer))
3873 })?;
3874 use text::ToOffset as TO;
3875 // find offset from the start of current range to current cursor position
3876 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3877
3878 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3879 let start_difference = start_offset - start_byte_offset;
3880 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3881 let end_difference = end_offset - start_byte_offset;
3882 // Current range has associated linked ranges.
3883 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3884 for range in linked_ranges.iter() {
3885 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3886 let end_offset = start_offset + end_difference;
3887 let start_offset = start_offset + start_difference;
3888 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3889 continue;
3890 }
3891 if self.selections.disjoint_anchor_ranges().any(|s| {
3892 if s.start.buffer_id != selection.start.buffer_id
3893 || s.end.buffer_id != selection.end.buffer_id
3894 {
3895 return false;
3896 }
3897 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3898 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3899 }) {
3900 continue;
3901 }
3902 let start = buffer_snapshot.anchor_after(start_offset);
3903 let end = buffer_snapshot.anchor_after(end_offset);
3904 linked_edits
3905 .entry(buffer.clone())
3906 .or_default()
3907 .push(start..end);
3908 }
3909 Some(linked_edits)
3910 }
3911
3912 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3913 let text: Arc<str> = text.into();
3914
3915 if self.read_only(cx) {
3916 return;
3917 }
3918
3919 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
3920
3921 let selections = self.selections.all_adjusted(cx);
3922 let mut bracket_inserted = false;
3923 let mut edits = Vec::new();
3924 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3925 let mut new_selections = Vec::with_capacity(selections.len());
3926 let mut new_autoclose_regions = Vec::new();
3927 let snapshot = self.buffer.read(cx).read(cx);
3928 let mut clear_linked_edit_ranges = false;
3929
3930 for (selection, autoclose_region) in
3931 self.selections_with_autoclose_regions(selections, &snapshot)
3932 {
3933 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3934 // Determine if the inserted text matches the opening or closing
3935 // bracket of any of this language's bracket pairs.
3936 let mut bracket_pair = None;
3937 let mut is_bracket_pair_start = false;
3938 let mut is_bracket_pair_end = false;
3939 if !text.is_empty() {
3940 let mut bracket_pair_matching_end = None;
3941 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3942 // and they are removing the character that triggered IME popup.
3943 for (pair, enabled) in scope.brackets() {
3944 if !pair.close && !pair.surround {
3945 continue;
3946 }
3947
3948 if enabled && pair.start.ends_with(text.as_ref()) {
3949 let prefix_len = pair.start.len() - text.len();
3950 let preceding_text_matches_prefix = prefix_len == 0
3951 || (selection.start.column >= (prefix_len as u32)
3952 && snapshot.contains_str_at(
3953 Point::new(
3954 selection.start.row,
3955 selection.start.column - (prefix_len as u32),
3956 ),
3957 &pair.start[..prefix_len],
3958 ));
3959 if preceding_text_matches_prefix {
3960 bracket_pair = Some(pair.clone());
3961 is_bracket_pair_start = true;
3962 break;
3963 }
3964 }
3965 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
3966 {
3967 // take first bracket pair matching end, but don't break in case a later bracket
3968 // pair matches start
3969 bracket_pair_matching_end = Some(pair.clone());
3970 }
3971 }
3972 if let Some(end) = bracket_pair_matching_end
3973 && bracket_pair.is_none()
3974 {
3975 bracket_pair = Some(end);
3976 is_bracket_pair_end = true;
3977 }
3978 }
3979
3980 if let Some(bracket_pair) = bracket_pair {
3981 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
3982 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
3983 let auto_surround =
3984 self.use_auto_surround && snapshot_settings.use_auto_surround;
3985 if selection.is_empty() {
3986 if is_bracket_pair_start {
3987 // If the inserted text is a suffix of an opening bracket and the
3988 // selection is preceded by the rest of the opening bracket, then
3989 // insert the closing bracket.
3990 let following_text_allows_autoclose = snapshot
3991 .chars_at(selection.start)
3992 .next()
3993 .map_or(true, |c| scope.should_autoclose_before(c));
3994
3995 let preceding_text_allows_autoclose = selection.start.column == 0
3996 || snapshot.reversed_chars_at(selection.start).next().map_or(
3997 true,
3998 |c| {
3999 bracket_pair.start != bracket_pair.end
4000 || !snapshot
4001 .char_classifier_at(selection.start)
4002 .is_word(c)
4003 },
4004 );
4005
4006 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4007 && bracket_pair.start.len() == 1
4008 {
4009 let target = bracket_pair.start.chars().next().unwrap();
4010 let current_line_count = snapshot
4011 .reversed_chars_at(selection.start)
4012 .take_while(|&c| c != '\n')
4013 .filter(|&c| c == target)
4014 .count();
4015 current_line_count % 2 == 1
4016 } else {
4017 false
4018 };
4019
4020 if autoclose
4021 && bracket_pair.close
4022 && following_text_allows_autoclose
4023 && preceding_text_allows_autoclose
4024 && !is_closing_quote
4025 {
4026 let anchor = snapshot.anchor_before(selection.end);
4027 new_selections.push((selection.map(|_| anchor), text.len()));
4028 new_autoclose_regions.push((
4029 anchor,
4030 text.len(),
4031 selection.id,
4032 bracket_pair.clone(),
4033 ));
4034 edits.push((
4035 selection.range(),
4036 format!("{}{}", text, bracket_pair.end).into(),
4037 ));
4038 bracket_inserted = true;
4039 continue;
4040 }
4041 }
4042
4043 if let Some(region) = autoclose_region {
4044 // If the selection is followed by an auto-inserted closing bracket,
4045 // then don't insert that closing bracket again; just move the selection
4046 // past the closing bracket.
4047 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4048 && text.as_ref() == region.pair.end.as_str()
4049 && snapshot.contains_str_at(region.range.end, text.as_ref());
4050 if should_skip {
4051 let anchor = snapshot.anchor_after(selection.end);
4052 new_selections
4053 .push((selection.map(|_| anchor), region.pair.end.len()));
4054 continue;
4055 }
4056 }
4057
4058 let always_treat_brackets_as_autoclosed = snapshot
4059 .language_settings_at(selection.start, cx)
4060 .always_treat_brackets_as_autoclosed;
4061 if always_treat_brackets_as_autoclosed
4062 && is_bracket_pair_end
4063 && snapshot.contains_str_at(selection.end, text.as_ref())
4064 {
4065 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4066 // and the inserted text is a closing bracket and the selection is followed
4067 // by the closing bracket then move the selection past the closing bracket.
4068 let anchor = snapshot.anchor_after(selection.end);
4069 new_selections.push((selection.map(|_| anchor), text.len()));
4070 continue;
4071 }
4072 }
4073 // If an opening bracket is 1 character long and is typed while
4074 // text is selected, then surround that text with the bracket pair.
4075 else if auto_surround
4076 && bracket_pair.surround
4077 && is_bracket_pair_start
4078 && bracket_pair.start.chars().count() == 1
4079 {
4080 edits.push((selection.start..selection.start, text.clone()));
4081 edits.push((
4082 selection.end..selection.end,
4083 bracket_pair.end.as_str().into(),
4084 ));
4085 bracket_inserted = true;
4086 new_selections.push((
4087 Selection {
4088 id: selection.id,
4089 start: snapshot.anchor_after(selection.start),
4090 end: snapshot.anchor_before(selection.end),
4091 reversed: selection.reversed,
4092 goal: selection.goal,
4093 },
4094 0,
4095 ));
4096 continue;
4097 }
4098 }
4099 }
4100
4101 if self.auto_replace_emoji_shortcode
4102 && selection.is_empty()
4103 && text.as_ref().ends_with(':')
4104 {
4105 if let Some(possible_emoji_short_code) =
4106 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4107 {
4108 if !possible_emoji_short_code.is_empty() {
4109 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
4110 let emoji_shortcode_start = Point::new(
4111 selection.start.row,
4112 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4113 );
4114
4115 // Remove shortcode from buffer
4116 edits.push((
4117 emoji_shortcode_start..selection.start,
4118 "".to_string().into(),
4119 ));
4120 new_selections.push((
4121 Selection {
4122 id: selection.id,
4123 start: snapshot.anchor_after(emoji_shortcode_start),
4124 end: snapshot.anchor_before(selection.start),
4125 reversed: selection.reversed,
4126 goal: selection.goal,
4127 },
4128 0,
4129 ));
4130
4131 // Insert emoji
4132 let selection_start_anchor = snapshot.anchor_after(selection.start);
4133 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4134 edits.push((selection.start..selection.end, emoji.to_string().into()));
4135
4136 continue;
4137 }
4138 }
4139 }
4140 }
4141
4142 // If not handling any auto-close operation, then just replace the selected
4143 // text with the given input and move the selection to the end of the
4144 // newly inserted text.
4145 let anchor = snapshot.anchor_after(selection.end);
4146 if !self.linked_edit_ranges.is_empty() {
4147 let start_anchor = snapshot.anchor_before(selection.start);
4148
4149 let is_word_char = text.chars().next().map_or(true, |char| {
4150 let classifier = snapshot
4151 .char_classifier_at(start_anchor.to_offset(&snapshot))
4152 .ignore_punctuation(true);
4153 classifier.is_word(char)
4154 });
4155
4156 if is_word_char {
4157 if let Some(ranges) = self
4158 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4159 {
4160 for (buffer, edits) in ranges {
4161 linked_edits
4162 .entry(buffer.clone())
4163 .or_default()
4164 .extend(edits.into_iter().map(|range| (range, text.clone())));
4165 }
4166 }
4167 } else {
4168 clear_linked_edit_ranges = true;
4169 }
4170 }
4171
4172 new_selections.push((selection.map(|_| anchor), 0));
4173 edits.push((selection.start..selection.end, text.clone()));
4174 }
4175
4176 drop(snapshot);
4177
4178 self.transact(window, cx, |this, window, cx| {
4179 if clear_linked_edit_ranges {
4180 this.linked_edit_ranges.clear();
4181 }
4182 let initial_buffer_versions =
4183 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4184
4185 this.buffer.update(cx, |buffer, cx| {
4186 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4187 });
4188 for (buffer, edits) in linked_edits {
4189 buffer.update(cx, |buffer, cx| {
4190 let snapshot = buffer.snapshot();
4191 let edits = edits
4192 .into_iter()
4193 .map(|(range, text)| {
4194 use text::ToPoint as TP;
4195 let end_point = TP::to_point(&range.end, &snapshot);
4196 let start_point = TP::to_point(&range.start, &snapshot);
4197 (start_point..end_point, text)
4198 })
4199 .sorted_by_key(|(range, _)| range.start);
4200 buffer.edit(edits, None, cx);
4201 })
4202 }
4203 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4204 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4205 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4206 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
4207 .zip(new_selection_deltas)
4208 .map(|(selection, delta)| Selection {
4209 id: selection.id,
4210 start: selection.start + delta,
4211 end: selection.end + delta,
4212 reversed: selection.reversed,
4213 goal: SelectionGoal::None,
4214 })
4215 .collect::<Vec<_>>();
4216
4217 let mut i = 0;
4218 for (position, delta, selection_id, pair) in new_autoclose_regions {
4219 let position = position.to_offset(&map.buffer_snapshot) + delta;
4220 let start = map.buffer_snapshot.anchor_before(position);
4221 let end = map.buffer_snapshot.anchor_after(position);
4222 while let Some(existing_state) = this.autoclose_regions.get(i) {
4223 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
4224 Ordering::Less => i += 1,
4225 Ordering::Greater => break,
4226 Ordering::Equal => {
4227 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
4228 Ordering::Less => i += 1,
4229 Ordering::Equal => break,
4230 Ordering::Greater => break,
4231 }
4232 }
4233 }
4234 }
4235 this.autoclose_regions.insert(
4236 i,
4237 AutocloseRegion {
4238 selection_id,
4239 range: start..end,
4240 pair,
4241 },
4242 );
4243 }
4244
4245 let had_active_edit_prediction = this.has_active_edit_prediction();
4246 this.change_selections(
4247 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4248 window,
4249 cx,
4250 |s| s.select(new_selections),
4251 );
4252
4253 if !bracket_inserted {
4254 if let Some(on_type_format_task) =
4255 this.trigger_on_type_formatting(text.to_string(), window, cx)
4256 {
4257 on_type_format_task.detach_and_log_err(cx);
4258 }
4259 }
4260
4261 let editor_settings = EditorSettings::get_global(cx);
4262 if bracket_inserted
4263 && (editor_settings.auto_signature_help
4264 || editor_settings.show_signature_help_after_edits)
4265 {
4266 this.show_signature_help(&ShowSignatureHelp, window, cx);
4267 }
4268
4269 let trigger_in_words =
4270 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4271 if this.hard_wrap.is_some() {
4272 let latest: Range<Point> = this.selections.newest(cx).range();
4273 if latest.is_empty()
4274 && this
4275 .buffer()
4276 .read(cx)
4277 .snapshot(cx)
4278 .line_len(MultiBufferRow(latest.start.row))
4279 == latest.start.column
4280 {
4281 this.rewrap_impl(
4282 RewrapOptions {
4283 override_language_settings: true,
4284 preserve_existing_whitespace: true,
4285 },
4286 cx,
4287 )
4288 }
4289 }
4290 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4291 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
4292 this.refresh_edit_prediction(true, false, window, cx);
4293 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4294 });
4295 }
4296
4297 fn find_possible_emoji_shortcode_at_position(
4298 snapshot: &MultiBufferSnapshot,
4299 position: Point,
4300 ) -> Option<String> {
4301 let mut chars = Vec::new();
4302 let mut found_colon = false;
4303 for char in snapshot.reversed_chars_at(position).take(100) {
4304 // Found a possible emoji shortcode in the middle of the buffer
4305 if found_colon {
4306 if char.is_whitespace() {
4307 chars.reverse();
4308 return Some(chars.iter().collect());
4309 }
4310 // If the previous character is not a whitespace, we are in the middle of a word
4311 // and we only want to complete the shortcode if the word is made up of other emojis
4312 let mut containing_word = String::new();
4313 for ch in snapshot
4314 .reversed_chars_at(position)
4315 .skip(chars.len() + 1)
4316 .take(100)
4317 {
4318 if ch.is_whitespace() {
4319 break;
4320 }
4321 containing_word.push(ch);
4322 }
4323 let containing_word = containing_word.chars().rev().collect::<String>();
4324 if util::word_consists_of_emojis(containing_word.as_str()) {
4325 chars.reverse();
4326 return Some(chars.iter().collect());
4327 }
4328 }
4329
4330 if char.is_whitespace() || !char.is_ascii() {
4331 return None;
4332 }
4333 if char == ':' {
4334 found_colon = true;
4335 } else {
4336 chars.push(char);
4337 }
4338 }
4339 // Found a possible emoji shortcode at the beginning of the buffer
4340 chars.reverse();
4341 Some(chars.iter().collect())
4342 }
4343
4344 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4345 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4346 self.transact(window, cx, |this, window, cx| {
4347 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4348 let selections = this.selections.all::<usize>(cx);
4349 let multi_buffer = this.buffer.read(cx);
4350 let buffer = multi_buffer.snapshot(cx);
4351 selections
4352 .iter()
4353 .map(|selection| {
4354 let start_point = selection.start.to_point(&buffer);
4355 let mut existing_indent =
4356 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4357 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4358 let start = selection.start;
4359 let end = selection.end;
4360 let selection_is_empty = start == end;
4361 let language_scope = buffer.language_scope_at(start);
4362 let (
4363 comment_delimiter,
4364 doc_delimiter,
4365 insert_extra_newline,
4366 indent_on_newline,
4367 indent_on_extra_newline,
4368 ) = if let Some(language) = &language_scope {
4369 let mut insert_extra_newline =
4370 insert_extra_newline_brackets(&buffer, start..end, language)
4371 || insert_extra_newline_tree_sitter(&buffer, start..end);
4372
4373 // Comment extension on newline is allowed only for cursor selections
4374 let comment_delimiter = maybe!({
4375 if !selection_is_empty {
4376 return None;
4377 }
4378
4379 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4380 return None;
4381 }
4382
4383 let delimiters = language.line_comment_prefixes();
4384 let max_len_of_delimiter =
4385 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4386 let (snapshot, range) =
4387 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4388
4389 let num_of_whitespaces = snapshot
4390 .chars_for_range(range.clone())
4391 .take_while(|c| c.is_whitespace())
4392 .count();
4393 let comment_candidate = snapshot
4394 .chars_for_range(range.clone())
4395 .skip(num_of_whitespaces)
4396 .take(max_len_of_delimiter)
4397 .collect::<String>();
4398 let (delimiter, trimmed_len) = delimiters
4399 .iter()
4400 .filter_map(|delimiter| {
4401 let prefix = delimiter.trim_end();
4402 if comment_candidate.starts_with(prefix) {
4403 Some((delimiter, prefix.len()))
4404 } else {
4405 None
4406 }
4407 })
4408 .max_by_key(|(_, len)| *len)?;
4409
4410 if let Some(BlockCommentConfig {
4411 start: block_start, ..
4412 }) = language.block_comment()
4413 {
4414 let block_start_trimmed = block_start.trim_end();
4415 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4416 let line_content = snapshot
4417 .chars_for_range(range)
4418 .skip(num_of_whitespaces)
4419 .take(block_start_trimmed.len())
4420 .collect::<String>();
4421
4422 if line_content.starts_with(block_start_trimmed) {
4423 return None;
4424 }
4425 }
4426 }
4427
4428 let cursor_is_placed_after_comment_marker =
4429 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4430 if cursor_is_placed_after_comment_marker {
4431 Some(delimiter.clone())
4432 } else {
4433 None
4434 }
4435 });
4436
4437 let mut indent_on_newline = IndentSize::spaces(0);
4438 let mut indent_on_extra_newline = IndentSize::spaces(0);
4439
4440 let doc_delimiter = maybe!({
4441 if !selection_is_empty {
4442 return None;
4443 }
4444
4445 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4446 return None;
4447 }
4448
4449 let BlockCommentConfig {
4450 start: start_tag,
4451 end: end_tag,
4452 prefix: delimiter,
4453 tab_size: len,
4454 } = language.documentation_comment()?;
4455 let is_within_block_comment = buffer
4456 .language_scope_at(start_point)
4457 .is_some_and(|scope| scope.override_name() == Some("comment"));
4458 if !is_within_block_comment {
4459 return None;
4460 }
4461
4462 let (snapshot, range) =
4463 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4464
4465 let num_of_whitespaces = snapshot
4466 .chars_for_range(range.clone())
4467 .take_while(|c| c.is_whitespace())
4468 .count();
4469
4470 // It is safe to use a column from MultiBufferPoint in context of a single buffer ranges, because we're only ever looking at a single line at a time.
4471 let column = start_point.column;
4472 let cursor_is_after_start_tag = {
4473 let start_tag_len = start_tag.len();
4474 let start_tag_line = snapshot
4475 .chars_for_range(range.clone())
4476 .skip(num_of_whitespaces)
4477 .take(start_tag_len)
4478 .collect::<String>();
4479 if start_tag_line.starts_with(start_tag.as_ref()) {
4480 num_of_whitespaces + start_tag_len <= column as usize
4481 } else {
4482 false
4483 }
4484 };
4485
4486 let cursor_is_after_delimiter = {
4487 let delimiter_trim = delimiter.trim_end();
4488 let delimiter_line = snapshot
4489 .chars_for_range(range.clone())
4490 .skip(num_of_whitespaces)
4491 .take(delimiter_trim.len())
4492 .collect::<String>();
4493 if delimiter_line.starts_with(delimiter_trim) {
4494 num_of_whitespaces + delimiter_trim.len() <= column as usize
4495 } else {
4496 false
4497 }
4498 };
4499
4500 let cursor_is_before_end_tag_if_exists = {
4501 let mut char_position = 0u32;
4502 let mut end_tag_offset = None;
4503
4504 'outer: for chunk in snapshot.text_for_range(range.clone()) {
4505 if let Some(byte_pos) = chunk.find(&**end_tag) {
4506 let chars_before_match =
4507 chunk[..byte_pos].chars().count() as u32;
4508 end_tag_offset =
4509 Some(char_position + chars_before_match);
4510 break 'outer;
4511 }
4512 char_position += chunk.chars().count() as u32;
4513 }
4514
4515 if let Some(end_tag_offset) = end_tag_offset {
4516 let cursor_is_before_end_tag = column <= end_tag_offset;
4517 if cursor_is_after_start_tag {
4518 if cursor_is_before_end_tag {
4519 insert_extra_newline = true;
4520 }
4521 let cursor_is_at_start_of_end_tag =
4522 column == end_tag_offset;
4523 if cursor_is_at_start_of_end_tag {
4524 indent_on_extra_newline.len = *len;
4525 }
4526 }
4527 cursor_is_before_end_tag
4528 } else {
4529 true
4530 }
4531 };
4532
4533 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4534 && cursor_is_before_end_tag_if_exists
4535 {
4536 if cursor_is_after_start_tag {
4537 indent_on_newline.len = *len;
4538 }
4539 Some(delimiter.clone())
4540 } else {
4541 None
4542 }
4543 });
4544
4545 (
4546 comment_delimiter,
4547 doc_delimiter,
4548 insert_extra_newline,
4549 indent_on_newline,
4550 indent_on_extra_newline,
4551 )
4552 } else {
4553 (
4554 None,
4555 None,
4556 false,
4557 IndentSize::default(),
4558 IndentSize::default(),
4559 )
4560 };
4561
4562 let prevent_auto_indent = doc_delimiter.is_some();
4563 let delimiter = comment_delimiter.or(doc_delimiter);
4564
4565 let capacity_for_delimiter =
4566 delimiter.as_deref().map(str::len).unwrap_or_default();
4567 let mut new_text = String::with_capacity(
4568 1 + capacity_for_delimiter
4569 + existing_indent.len as usize
4570 + indent_on_newline.len as usize
4571 + indent_on_extra_newline.len as usize,
4572 );
4573 new_text.push('\n');
4574 new_text.extend(existing_indent.chars());
4575 new_text.extend(indent_on_newline.chars());
4576
4577 if let Some(delimiter) = &delimiter {
4578 new_text.push_str(delimiter);
4579 }
4580
4581 if insert_extra_newline {
4582 new_text.push('\n');
4583 new_text.extend(existing_indent.chars());
4584 new_text.extend(indent_on_extra_newline.chars());
4585 }
4586
4587 let anchor = buffer.anchor_after(end);
4588 let new_selection = selection.map(|_| anchor);
4589 (
4590 ((start..end, new_text), prevent_auto_indent),
4591 (insert_extra_newline, new_selection),
4592 )
4593 })
4594 .unzip()
4595 };
4596
4597 let mut auto_indent_edits = Vec::new();
4598 let mut edits = Vec::new();
4599 for (edit, prevent_auto_indent) in edits_with_flags {
4600 if prevent_auto_indent {
4601 edits.push(edit);
4602 } else {
4603 auto_indent_edits.push(edit);
4604 }
4605 }
4606 if !edits.is_empty() {
4607 this.edit(edits, cx);
4608 }
4609 if !auto_indent_edits.is_empty() {
4610 this.edit_with_autoindent(auto_indent_edits, cx);
4611 }
4612
4613 let buffer = this.buffer.read(cx).snapshot(cx);
4614 let new_selections = selection_info
4615 .into_iter()
4616 .map(|(extra_newline_inserted, new_selection)| {
4617 let mut cursor = new_selection.end.to_point(&buffer);
4618 if extra_newline_inserted {
4619 cursor.row -= 1;
4620 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4621 }
4622 new_selection.map(|_| cursor)
4623 })
4624 .collect();
4625
4626 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4627 this.refresh_edit_prediction(true, false, window, cx);
4628 });
4629 }
4630
4631 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4632 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4633
4634 let buffer = self.buffer.read(cx);
4635 let snapshot = buffer.snapshot(cx);
4636
4637 let mut edits = Vec::new();
4638 let mut rows = Vec::new();
4639
4640 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4641 let cursor = selection.head();
4642 let row = cursor.row;
4643
4644 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4645
4646 let newline = "\n".to_string();
4647 edits.push((start_of_line..start_of_line, newline));
4648
4649 rows.push(row + rows_inserted as u32);
4650 }
4651
4652 self.transact(window, cx, |editor, window, cx| {
4653 editor.edit(edits, cx);
4654
4655 editor.change_selections(Default::default(), window, cx, |s| {
4656 let mut index = 0;
4657 s.move_cursors_with(|map, _, _| {
4658 let row = rows[index];
4659 index += 1;
4660
4661 let point = Point::new(row, 0);
4662 let boundary = map.next_line_boundary(point).1;
4663 let clipped = map.clip_point(boundary, Bias::Left);
4664
4665 (clipped, SelectionGoal::None)
4666 });
4667 });
4668
4669 let mut indent_edits = Vec::new();
4670 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4671 for row in rows {
4672 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4673 for (row, indent) in indents {
4674 if indent.len == 0 {
4675 continue;
4676 }
4677
4678 let text = match indent.kind {
4679 IndentKind::Space => " ".repeat(indent.len as usize),
4680 IndentKind::Tab => "\t".repeat(indent.len as usize),
4681 };
4682 let point = Point::new(row.0, 0);
4683 indent_edits.push((point..point, text));
4684 }
4685 }
4686 editor.edit(indent_edits, cx);
4687 });
4688 }
4689
4690 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4691 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4692
4693 let buffer = self.buffer.read(cx);
4694 let snapshot = buffer.snapshot(cx);
4695
4696 let mut edits = Vec::new();
4697 let mut rows = Vec::new();
4698 let mut rows_inserted = 0;
4699
4700 for selection in self.selections.all_adjusted(cx) {
4701 let cursor = selection.head();
4702 let row = cursor.row;
4703
4704 let point = Point::new(row + 1, 0);
4705 let start_of_line = snapshot.clip_point(point, Bias::Left);
4706
4707 let newline = "\n".to_string();
4708 edits.push((start_of_line..start_of_line, newline));
4709
4710 rows_inserted += 1;
4711 rows.push(row + rows_inserted);
4712 }
4713
4714 self.transact(window, cx, |editor, window, cx| {
4715 editor.edit(edits, cx);
4716
4717 editor.change_selections(Default::default(), window, cx, |s| {
4718 let mut index = 0;
4719 s.move_cursors_with(|map, _, _| {
4720 let row = rows[index];
4721 index += 1;
4722
4723 let point = Point::new(row, 0);
4724 let boundary = map.next_line_boundary(point).1;
4725 let clipped = map.clip_point(boundary, Bias::Left);
4726
4727 (clipped, SelectionGoal::None)
4728 });
4729 });
4730
4731 let mut indent_edits = Vec::new();
4732 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4733 for row in rows {
4734 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4735 for (row, indent) in indents {
4736 if indent.len == 0 {
4737 continue;
4738 }
4739
4740 let text = match indent.kind {
4741 IndentKind::Space => " ".repeat(indent.len as usize),
4742 IndentKind::Tab => "\t".repeat(indent.len as usize),
4743 };
4744 let point = Point::new(row.0, 0);
4745 indent_edits.push((point..point, text));
4746 }
4747 }
4748 editor.edit(indent_edits, cx);
4749 });
4750 }
4751
4752 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4753 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4754 original_indent_columns: Vec::new(),
4755 });
4756 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4757 }
4758
4759 fn insert_with_autoindent_mode(
4760 &mut self,
4761 text: &str,
4762 autoindent_mode: Option<AutoindentMode>,
4763 window: &mut Window,
4764 cx: &mut Context<Self>,
4765 ) {
4766 if self.read_only(cx) {
4767 return;
4768 }
4769
4770 let text: Arc<str> = text.into();
4771 self.transact(window, cx, |this, window, cx| {
4772 let old_selections = this.selections.all_adjusted(cx);
4773 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4774 let anchors = {
4775 let snapshot = buffer.read(cx);
4776 old_selections
4777 .iter()
4778 .map(|s| {
4779 let anchor = snapshot.anchor_after(s.head());
4780 s.map(|_| anchor)
4781 })
4782 .collect::<Vec<_>>()
4783 };
4784 buffer.edit(
4785 old_selections
4786 .iter()
4787 .map(|s| (s.start..s.end, text.clone())),
4788 autoindent_mode,
4789 cx,
4790 );
4791 anchors
4792 });
4793
4794 this.change_selections(Default::default(), window, cx, |s| {
4795 s.select_anchors(selection_anchors);
4796 });
4797
4798 cx.notify();
4799 });
4800 }
4801
4802 fn trigger_completion_on_input(
4803 &mut self,
4804 text: &str,
4805 trigger_in_words: bool,
4806 window: &mut Window,
4807 cx: &mut Context<Self>,
4808 ) {
4809 let completions_source = self
4810 .context_menu
4811 .borrow()
4812 .as_ref()
4813 .and_then(|menu| match menu {
4814 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4815 CodeContextMenu::CodeActions(_) => None,
4816 });
4817
4818 match completions_source {
4819 Some(CompletionsMenuSource::Words) => {
4820 self.show_word_completions(&ShowWordCompletions, window, cx)
4821 }
4822 Some(CompletionsMenuSource::Normal)
4823 | Some(CompletionsMenuSource::SnippetChoices)
4824 | None
4825 if self.is_completion_trigger(
4826 text,
4827 trigger_in_words,
4828 completions_source.is_some(),
4829 cx,
4830 ) =>
4831 {
4832 self.show_completions(
4833 &ShowCompletions {
4834 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4835 },
4836 window,
4837 cx,
4838 )
4839 }
4840 _ => {
4841 self.hide_context_menu(window, cx);
4842 }
4843 }
4844 }
4845
4846 fn is_completion_trigger(
4847 &self,
4848 text: &str,
4849 trigger_in_words: bool,
4850 menu_is_open: bool,
4851 cx: &mut Context<Self>,
4852 ) -> bool {
4853 let position = self.selections.newest_anchor().head();
4854 let multibuffer = self.buffer.read(cx);
4855 let Some(buffer) = position
4856 .buffer_id
4857 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
4858 else {
4859 return false;
4860 };
4861
4862 if let Some(completion_provider) = &self.completion_provider {
4863 completion_provider.is_completion_trigger(
4864 &buffer,
4865 position.text_anchor,
4866 text,
4867 trigger_in_words,
4868 menu_is_open,
4869 cx,
4870 )
4871 } else {
4872 false
4873 }
4874 }
4875
4876 /// If any empty selections is touching the start of its innermost containing autoclose
4877 /// region, expand it to select the brackets.
4878 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4879 let selections = self.selections.all::<usize>(cx);
4880 let buffer = self.buffer.read(cx).read(cx);
4881 let new_selections = self
4882 .selections_with_autoclose_regions(selections, &buffer)
4883 .map(|(mut selection, region)| {
4884 if !selection.is_empty() {
4885 return selection;
4886 }
4887
4888 if let Some(region) = region {
4889 let mut range = region.range.to_offset(&buffer);
4890 if selection.start == range.start && range.start >= region.pair.start.len() {
4891 range.start -= region.pair.start.len();
4892 if buffer.contains_str_at(range.start, ®ion.pair.start)
4893 && buffer.contains_str_at(range.end, ®ion.pair.end)
4894 {
4895 range.end += region.pair.end.len();
4896 selection.start = range.start;
4897 selection.end = range.end;
4898
4899 return selection;
4900 }
4901 }
4902 }
4903
4904 let always_treat_brackets_as_autoclosed = buffer
4905 .language_settings_at(selection.start, cx)
4906 .always_treat_brackets_as_autoclosed;
4907
4908 if !always_treat_brackets_as_autoclosed {
4909 return selection;
4910 }
4911
4912 if let Some(scope) = buffer.language_scope_at(selection.start) {
4913 for (pair, enabled) in scope.brackets() {
4914 if !enabled || !pair.close {
4915 continue;
4916 }
4917
4918 if buffer.contains_str_at(selection.start, &pair.end) {
4919 let pair_start_len = pair.start.len();
4920 if buffer.contains_str_at(
4921 selection.start.saturating_sub(pair_start_len),
4922 &pair.start,
4923 ) {
4924 selection.start -= pair_start_len;
4925 selection.end += pair.end.len();
4926
4927 return selection;
4928 }
4929 }
4930 }
4931 }
4932
4933 selection
4934 })
4935 .collect();
4936
4937 drop(buffer);
4938 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
4939 selections.select(new_selections)
4940 });
4941 }
4942
4943 /// Iterate the given selections, and for each one, find the smallest surrounding
4944 /// autoclose region. This uses the ordering of the selections and the autoclose
4945 /// regions to avoid repeated comparisons.
4946 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
4947 &'a self,
4948 selections: impl IntoIterator<Item = Selection<D>>,
4949 buffer: &'a MultiBufferSnapshot,
4950 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
4951 let mut i = 0;
4952 let mut regions = self.autoclose_regions.as_slice();
4953 selections.into_iter().map(move |selection| {
4954 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
4955
4956 let mut enclosing = None;
4957 while let Some(pair_state) = regions.get(i) {
4958 if pair_state.range.end.to_offset(buffer) < range.start {
4959 regions = ®ions[i + 1..];
4960 i = 0;
4961 } else if pair_state.range.start.to_offset(buffer) > range.end {
4962 break;
4963 } else {
4964 if pair_state.selection_id == selection.id {
4965 enclosing = Some(pair_state);
4966 }
4967 i += 1;
4968 }
4969 }
4970
4971 (selection, enclosing)
4972 })
4973 }
4974
4975 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
4976 fn invalidate_autoclose_regions(
4977 &mut self,
4978 mut selections: &[Selection<Anchor>],
4979 buffer: &MultiBufferSnapshot,
4980 ) {
4981 self.autoclose_regions.retain(|state| {
4982 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
4983 return false;
4984 }
4985
4986 let mut i = 0;
4987 while let Some(selection) = selections.get(i) {
4988 if selection.end.cmp(&state.range.start, buffer).is_lt() {
4989 selections = &selections[1..];
4990 continue;
4991 }
4992 if selection.start.cmp(&state.range.end, buffer).is_gt() {
4993 break;
4994 }
4995 if selection.id == state.selection_id {
4996 return true;
4997 } else {
4998 i += 1;
4999 }
5000 }
5001 false
5002 });
5003 }
5004
5005 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5006 let offset = position.to_offset(buffer);
5007 let (word_range, kind) = buffer.surrounding_word(offset, true);
5008 if offset > word_range.start && kind == Some(CharKind::Word) {
5009 Some(
5010 buffer
5011 .text_for_range(word_range.start..offset)
5012 .collect::<String>(),
5013 )
5014 } else {
5015 None
5016 }
5017 }
5018
5019 pub fn toggle_inline_values(
5020 &mut self,
5021 _: &ToggleInlineValues,
5022 _: &mut Window,
5023 cx: &mut Context<Self>,
5024 ) {
5025 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
5026
5027 self.refresh_inline_values(cx);
5028 }
5029
5030 pub fn toggle_inlay_hints(
5031 &mut self,
5032 _: &ToggleInlayHints,
5033 _: &mut Window,
5034 cx: &mut Context<Self>,
5035 ) {
5036 self.refresh_inlay_hints(
5037 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
5038 cx,
5039 );
5040 }
5041
5042 pub fn inlay_hints_enabled(&self) -> bool {
5043 self.inlay_hint_cache.enabled
5044 }
5045
5046 pub fn inline_values_enabled(&self) -> bool {
5047 self.inline_value_cache.enabled
5048 }
5049
5050 #[cfg(any(test, feature = "test-support"))]
5051 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
5052 self.display_map
5053 .read(cx)
5054 .current_inlays()
5055 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
5056 .cloned()
5057 .collect()
5058 }
5059
5060 #[cfg(any(test, feature = "test-support"))]
5061 pub fn all_inlays(&self, cx: &App) -> Vec<Inlay> {
5062 self.display_map
5063 .read(cx)
5064 .current_inlays()
5065 .cloned()
5066 .collect()
5067 }
5068
5069 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
5070 if self.semantics_provider.is_none() || !self.mode.is_full() {
5071 return;
5072 }
5073
5074 let reason_description = reason.description();
5075 let ignore_debounce = matches!(
5076 reason,
5077 InlayHintRefreshReason::SettingsChange(_)
5078 | InlayHintRefreshReason::Toggle(_)
5079 | InlayHintRefreshReason::ExcerptsRemoved(_)
5080 | InlayHintRefreshReason::ModifiersChanged(_)
5081 );
5082 let (invalidate_cache, required_languages) = match reason {
5083 InlayHintRefreshReason::ModifiersChanged(enabled) => {
5084 match self.inlay_hint_cache.modifiers_override(enabled) {
5085 Some(enabled) => {
5086 if enabled {
5087 (InvalidationStrategy::RefreshRequested, None)
5088 } else {
5089 self.splice_inlays(
5090 &self
5091 .visible_inlay_hints(cx)
5092 .iter()
5093 .map(|inlay| inlay.id)
5094 .collect::<Vec<InlayId>>(),
5095 Vec::new(),
5096 cx,
5097 );
5098 return;
5099 }
5100 }
5101 None => return,
5102 }
5103 }
5104 InlayHintRefreshReason::Toggle(enabled) => {
5105 if self.inlay_hint_cache.toggle(enabled) {
5106 if enabled {
5107 (InvalidationStrategy::RefreshRequested, None)
5108 } else {
5109 self.splice_inlays(
5110 &self
5111 .visible_inlay_hints(cx)
5112 .iter()
5113 .map(|inlay| inlay.id)
5114 .collect::<Vec<InlayId>>(),
5115 Vec::new(),
5116 cx,
5117 );
5118 return;
5119 }
5120 } else {
5121 return;
5122 }
5123 }
5124 InlayHintRefreshReason::SettingsChange(new_settings) => {
5125 match self.inlay_hint_cache.update_settings(
5126 &self.buffer,
5127 new_settings,
5128 self.visible_inlay_hints(cx),
5129 cx,
5130 ) {
5131 ControlFlow::Break(Some(InlaySplice {
5132 to_remove,
5133 to_insert,
5134 })) => {
5135 self.splice_inlays(&to_remove, to_insert, cx);
5136 return;
5137 }
5138 ControlFlow::Break(None) => return,
5139 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
5140 }
5141 }
5142 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
5143 if let Some(InlaySplice {
5144 to_remove,
5145 to_insert,
5146 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
5147 {
5148 self.splice_inlays(&to_remove, to_insert, cx);
5149 }
5150 self.display_map.update(cx, |display_map, _| {
5151 display_map.remove_inlays_for_excerpts(&excerpts_removed)
5152 });
5153 return;
5154 }
5155 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
5156 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
5157 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
5158 }
5159 InlayHintRefreshReason::RefreshRequested => {
5160 (InvalidationStrategy::RefreshRequested, None)
5161 }
5162 };
5163
5164 if let Some(InlaySplice {
5165 to_remove,
5166 to_insert,
5167 }) = self.inlay_hint_cache.spawn_hint_refresh(
5168 reason_description,
5169 self.visible_excerpts(required_languages.as_ref(), cx),
5170 invalidate_cache,
5171 ignore_debounce,
5172 cx,
5173 ) {
5174 self.splice_inlays(&to_remove, to_insert, cx);
5175 }
5176 }
5177
5178 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
5179 self.display_map
5180 .read(cx)
5181 .current_inlays()
5182 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
5183 .cloned()
5184 .collect()
5185 }
5186
5187 pub fn visible_excerpts(
5188 &self,
5189 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
5190 cx: &mut Context<Editor>,
5191 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5192 let Some(project) = self.project.as_ref() else {
5193 return HashMap::default();
5194 };
5195 let project = project.read(cx);
5196 let multi_buffer = self.buffer().read(cx);
5197 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5198 let multi_buffer_visible_start = self
5199 .scroll_manager
5200 .anchor()
5201 .anchor
5202 .to_point(&multi_buffer_snapshot);
5203 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5204 multi_buffer_visible_start
5205 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5206 Bias::Left,
5207 );
5208 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
5209 multi_buffer_snapshot
5210 .range_to_buffer_ranges(multi_buffer_visible_range)
5211 .into_iter()
5212 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5213 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5214 let buffer_file = project::File::from_dyn(buffer.file())?;
5215 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5216 let worktree_entry = buffer_worktree
5217 .read(cx)
5218 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
5219 if worktree_entry.is_ignored {
5220 return None;
5221 }
5222
5223 let language = buffer.language()?;
5224 if let Some(restrict_to_languages) = restrict_to_languages {
5225 if !restrict_to_languages.contains(language) {
5226 return None;
5227 }
5228 }
5229 Some((
5230 excerpt_id,
5231 (
5232 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5233 buffer.version().clone(),
5234 excerpt_visible_range,
5235 ),
5236 ))
5237 })
5238 .collect()
5239 }
5240
5241 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5242 TextLayoutDetails {
5243 text_system: window.text_system().clone(),
5244 editor_style: self.style.clone().unwrap(),
5245 rem_size: window.rem_size(),
5246 scroll_anchor: self.scroll_manager.anchor(),
5247 visible_rows: self.visible_line_count(),
5248 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5249 }
5250 }
5251
5252 pub fn splice_inlays(
5253 &self,
5254 to_remove: &[InlayId],
5255 to_insert: Vec<Inlay>,
5256 cx: &mut Context<Self>,
5257 ) {
5258 self.display_map.update(cx, |display_map, cx| {
5259 display_map.splice_inlays(to_remove, to_insert, cx)
5260 });
5261 cx.notify();
5262 }
5263
5264 fn trigger_on_type_formatting(
5265 &self,
5266 input: String,
5267 window: &mut Window,
5268 cx: &mut Context<Self>,
5269 ) -> Option<Task<Result<()>>> {
5270 if input.len() != 1 {
5271 return None;
5272 }
5273
5274 let project = self.project.as_ref()?;
5275 let position = self.selections.newest_anchor().head();
5276 let (buffer, buffer_position) = self
5277 .buffer
5278 .read(cx)
5279 .text_anchor_for_position(position, cx)?;
5280
5281 let settings = language_settings::language_settings(
5282 buffer
5283 .read(cx)
5284 .language_at(buffer_position)
5285 .map(|l| l.name()),
5286 buffer.read(cx).file(),
5287 cx,
5288 );
5289 if !settings.use_on_type_format {
5290 return None;
5291 }
5292
5293 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5294 // hence we do LSP request & edit on host side only — add formats to host's history.
5295 let push_to_lsp_host_history = true;
5296 // If this is not the host, append its history with new edits.
5297 let push_to_client_history = project.read(cx).is_via_collab();
5298
5299 let on_type_formatting = project.update(cx, |project, cx| {
5300 project.on_type_format(
5301 buffer.clone(),
5302 buffer_position,
5303 input,
5304 push_to_lsp_host_history,
5305 cx,
5306 )
5307 });
5308 Some(cx.spawn_in(window, async move |editor, cx| {
5309 if let Some(transaction) = on_type_formatting.await? {
5310 if push_to_client_history {
5311 buffer
5312 .update(cx, |buffer, _| {
5313 buffer.push_transaction(transaction, Instant::now());
5314 buffer.finalize_last_transaction();
5315 })
5316 .ok();
5317 }
5318 editor.update(cx, |editor, cx| {
5319 editor.refresh_document_highlights(cx);
5320 })?;
5321 }
5322 Ok(())
5323 }))
5324 }
5325
5326 pub fn show_word_completions(
5327 &mut self,
5328 _: &ShowWordCompletions,
5329 window: &mut Window,
5330 cx: &mut Context<Self>,
5331 ) {
5332 self.open_or_update_completions_menu(Some(CompletionsMenuSource::Words), None, window, cx);
5333 }
5334
5335 pub fn show_completions(
5336 &mut self,
5337 options: &ShowCompletions,
5338 window: &mut Window,
5339 cx: &mut Context<Self>,
5340 ) {
5341 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5342 }
5343
5344 fn open_or_update_completions_menu(
5345 &mut self,
5346 requested_source: Option<CompletionsMenuSource>,
5347 trigger: Option<&str>,
5348 window: &mut Window,
5349 cx: &mut Context<Self>,
5350 ) {
5351 if self.pending_rename.is_some() {
5352 return;
5353 }
5354
5355 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5356
5357 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5358 // inserted and selected. To handle that case, the start of the selection is used so that
5359 // the menu starts with all choices.
5360 let position = self
5361 .selections
5362 .newest_anchor()
5363 .start
5364 .bias_right(&multibuffer_snapshot);
5365 if position.diff_base_anchor.is_some() {
5366 return;
5367 }
5368 let (buffer, buffer_position) =
5369 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
5370 output
5371 } else {
5372 return;
5373 };
5374 let buffer_snapshot = buffer.read(cx).snapshot();
5375
5376 let query: Option<Arc<String>> =
5377 Self::completion_query(&multibuffer_snapshot, position).map(|query| query.into());
5378
5379 drop(multibuffer_snapshot);
5380
5381 let provider = match requested_source {
5382 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5383 Some(CompletionsMenuSource::Words) => None,
5384 Some(CompletionsMenuSource::SnippetChoices) => {
5385 log::error!("bug: SnippetChoices requested_source is not handled");
5386 None
5387 }
5388 };
5389
5390 let sort_completions = provider
5391 .as_ref()
5392 .map_or(false, |provider| provider.sort_completions());
5393
5394 let filter_completions = provider
5395 .as_ref()
5396 .map_or(true, |provider| provider.filter_completions());
5397
5398 let trigger_kind = match trigger {
5399 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5400 CompletionTriggerKind::TRIGGER_CHARACTER
5401 }
5402 _ => CompletionTriggerKind::INVOKED,
5403 };
5404 let completion_context = CompletionContext {
5405 trigger_character: trigger.and_then(|trigger| {
5406 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5407 Some(String::from(trigger))
5408 } else {
5409 None
5410 }
5411 }),
5412 trigger_kind,
5413 };
5414
5415 // Hide the current completions menu when a trigger char is typed. Without this, cached
5416 // completions from before the trigger char may be reused (#32774). Snippet choices could
5417 // involve trigger chars, so this is skipped in that case.
5418 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER && self.snippet_stack.is_empty()
5419 {
5420 let menu_is_open = matches!(
5421 self.context_menu.borrow().as_ref(),
5422 Some(CodeContextMenu::Completions(_))
5423 );
5424 if menu_is_open {
5425 self.hide_context_menu(window, cx);
5426 }
5427 }
5428
5429 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5430 if filter_completions {
5431 menu.filter(query.clone(), provider.clone(), window, cx);
5432 }
5433 // When `is_incomplete` is false, no need to re-query completions when the current query
5434 // is a suffix of the initial query.
5435 if !menu.is_incomplete {
5436 // If the new query is a suffix of the old query (typing more characters) and
5437 // the previous result was complete, the existing completions can be filtered.
5438 //
5439 // Note that this is always true for snippet completions.
5440 let query_matches = match (&menu.initial_query, &query) {
5441 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5442 (None, _) => true,
5443 _ => false,
5444 };
5445 if query_matches {
5446 let position_matches = if menu.initial_position == position {
5447 true
5448 } else {
5449 let snapshot = self.buffer.read(cx).read(cx);
5450 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5451 };
5452 if position_matches {
5453 return;
5454 }
5455 }
5456 }
5457 };
5458
5459 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5460 buffer_snapshot.surrounding_word(buffer_position, false)
5461 {
5462 let word_to_exclude = buffer_snapshot
5463 .text_for_range(word_range.clone())
5464 .collect::<String>();
5465 (
5466 buffer_snapshot.anchor_before(word_range.start)
5467 ..buffer_snapshot.anchor_after(buffer_position),
5468 Some(word_to_exclude),
5469 )
5470 } else {
5471 (buffer_position..buffer_position, None)
5472 };
5473
5474 let language = buffer_snapshot
5475 .language_at(buffer_position)
5476 .map(|language| language.name());
5477
5478 let completion_settings =
5479 language_settings(language.clone(), buffer_snapshot.file(), cx).completions;
5480
5481 let show_completion_documentation = buffer_snapshot
5482 .settings_at(buffer_position, cx)
5483 .show_completion_documentation;
5484
5485 // The document can be large, so stay in reasonable bounds when searching for words,
5486 // otherwise completion pop-up might be slow to appear.
5487 const WORD_LOOKUP_ROWS: u32 = 5_000;
5488 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5489 let min_word_search = buffer_snapshot.clip_point(
5490 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5491 Bias::Left,
5492 );
5493 let max_word_search = buffer_snapshot.clip_point(
5494 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5495 Bias::Right,
5496 );
5497 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5498 ..buffer_snapshot.point_to_offset(max_word_search);
5499
5500 let skip_digits = query
5501 .as_ref()
5502 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
5503
5504 let (mut words, provider_responses) = match &provider {
5505 Some(provider) => {
5506 let provider_responses = provider.completions(
5507 position.excerpt_id,
5508 &buffer,
5509 buffer_position,
5510 completion_context,
5511 window,
5512 cx,
5513 );
5514
5515 let words = match completion_settings.words {
5516 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
5517 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
5518 .background_spawn(async move {
5519 buffer_snapshot.words_in_range(WordsQuery {
5520 fuzzy_contents: None,
5521 range: word_search_range,
5522 skip_digits,
5523 })
5524 }),
5525 };
5526
5527 (words, provider_responses)
5528 }
5529 None => (
5530 cx.background_spawn(async move {
5531 buffer_snapshot.words_in_range(WordsQuery {
5532 fuzzy_contents: None,
5533 range: word_search_range,
5534 skip_digits,
5535 })
5536 }),
5537 Task::ready(Ok(Vec::new())),
5538 ),
5539 };
5540
5541 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5542
5543 let id = post_inc(&mut self.next_completion_id);
5544 let task = cx.spawn_in(window, async move |editor, cx| {
5545 let Ok(()) = editor.update(cx, |this, _| {
5546 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5547 }) else {
5548 return;
5549 };
5550
5551 // TODO: Ideally completions from different sources would be selectively re-queried, so
5552 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5553 let mut completions = Vec::new();
5554 let mut is_incomplete = false;
5555 if let Some(provider_responses) = provider_responses.await.log_err() {
5556 if !provider_responses.is_empty() {
5557 for response in provider_responses {
5558 completions.extend(response.completions);
5559 is_incomplete = is_incomplete || response.is_incomplete;
5560 }
5561 if completion_settings.words == WordsCompletionMode::Fallback {
5562 words = Task::ready(BTreeMap::default());
5563 }
5564 }
5565 }
5566
5567 let mut words = words.await;
5568 if let Some(word_to_exclude) = &word_to_exclude {
5569 words.remove(word_to_exclude);
5570 }
5571 for lsp_completion in &completions {
5572 words.remove(&lsp_completion.new_text);
5573 }
5574 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5575 replace_range: word_replace_range.clone(),
5576 new_text: word.clone(),
5577 label: CodeLabel::plain(word, None),
5578 icon_path: None,
5579 documentation: None,
5580 source: CompletionSource::BufferWord {
5581 word_range,
5582 resolved: false,
5583 },
5584 insert_text_mode: Some(InsertTextMode::AS_IS),
5585 confirm: None,
5586 }));
5587
5588 let menu = if completions.is_empty() {
5589 None
5590 } else {
5591 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5592 let languages = editor
5593 .workspace
5594 .as_ref()
5595 .and_then(|(workspace, _)| workspace.upgrade())
5596 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5597 let menu = CompletionsMenu::new(
5598 id,
5599 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5600 sort_completions,
5601 show_completion_documentation,
5602 position,
5603 query.clone(),
5604 is_incomplete,
5605 buffer.clone(),
5606 completions.into(),
5607 snippet_sort_order,
5608 languages,
5609 language,
5610 cx,
5611 );
5612
5613 let query = if filter_completions { query } else { None };
5614 let matches_task = if let Some(query) = query {
5615 menu.do_async_filtering(query, cx)
5616 } else {
5617 Task::ready(menu.unfiltered_matches())
5618 };
5619 (menu, matches_task)
5620 }) else {
5621 return;
5622 };
5623
5624 let matches = matches_task.await;
5625
5626 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5627 // Newer menu already set, so exit.
5628 match editor.context_menu.borrow().as_ref() {
5629 Some(CodeContextMenu::Completions(prev_menu)) => {
5630 if prev_menu.id > id {
5631 return;
5632 }
5633 }
5634 _ => {}
5635 };
5636
5637 // Only valid to take prev_menu because it the new menu is immediately set
5638 // below, or the menu is hidden.
5639 match editor.context_menu.borrow_mut().take() {
5640 Some(CodeContextMenu::Completions(prev_menu)) => {
5641 let position_matches =
5642 if prev_menu.initial_position == menu.initial_position {
5643 true
5644 } else {
5645 let snapshot = editor.buffer.read(cx).read(cx);
5646 prev_menu.initial_position.to_offset(&snapshot)
5647 == menu.initial_position.to_offset(&snapshot)
5648 };
5649 if position_matches {
5650 // Preserve markdown cache before `set_filter_results` because it will
5651 // try to populate the documentation cache.
5652 menu.preserve_markdown_cache(prev_menu);
5653 }
5654 }
5655 _ => {}
5656 };
5657
5658 menu.set_filter_results(matches, provider, window, cx);
5659 }) else {
5660 return;
5661 };
5662
5663 menu.visible().then_some(menu)
5664 };
5665
5666 editor
5667 .update_in(cx, |editor, window, cx| {
5668 if editor.focus_handle.is_focused(window) {
5669 if let Some(menu) = menu {
5670 *editor.context_menu.borrow_mut() =
5671 Some(CodeContextMenu::Completions(menu));
5672
5673 crate::hover_popover::hide_hover(editor, cx);
5674 if editor.show_edit_predictions_in_menu() {
5675 editor.update_visible_edit_prediction(window, cx);
5676 } else {
5677 editor.discard_edit_prediction(false, cx);
5678 }
5679
5680 cx.notify();
5681 return;
5682 }
5683 }
5684
5685 if editor.completion_tasks.len() <= 1 {
5686 // If there are no more completion tasks and the last menu was empty, we should hide it.
5687 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5688 // If it was already hidden and we don't show edit predictions in the menu,
5689 // we should also show the edit prediction when available.
5690 if was_hidden && editor.show_edit_predictions_in_menu() {
5691 editor.update_visible_edit_prediction(window, cx);
5692 }
5693 }
5694 })
5695 .ok();
5696 });
5697
5698 self.completion_tasks.push((id, task));
5699 }
5700
5701 #[cfg(feature = "test-support")]
5702 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5703 let menu = self.context_menu.borrow();
5704 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5705 let completions = menu.completions.borrow();
5706 Some(completions.to_vec())
5707 } else {
5708 None
5709 }
5710 }
5711
5712 pub fn with_completions_menu_matching_id<R>(
5713 &self,
5714 id: CompletionId,
5715 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5716 ) -> R {
5717 let mut context_menu = self.context_menu.borrow_mut();
5718 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5719 return f(None);
5720 };
5721 if completions_menu.id != id {
5722 return f(None);
5723 }
5724 f(Some(completions_menu))
5725 }
5726
5727 pub fn confirm_completion(
5728 &mut self,
5729 action: &ConfirmCompletion,
5730 window: &mut Window,
5731 cx: &mut Context<Self>,
5732 ) -> Option<Task<Result<()>>> {
5733 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5734 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5735 }
5736
5737 pub fn confirm_completion_insert(
5738 &mut self,
5739 _: &ConfirmCompletionInsert,
5740 window: &mut Window,
5741 cx: &mut Context<Self>,
5742 ) -> Option<Task<Result<()>>> {
5743 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5744 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5745 }
5746
5747 pub fn confirm_completion_replace(
5748 &mut self,
5749 _: &ConfirmCompletionReplace,
5750 window: &mut Window,
5751 cx: &mut Context<Self>,
5752 ) -> Option<Task<Result<()>>> {
5753 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5754 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5755 }
5756
5757 pub fn compose_completion(
5758 &mut self,
5759 action: &ComposeCompletion,
5760 window: &mut Window,
5761 cx: &mut Context<Self>,
5762 ) -> Option<Task<Result<()>>> {
5763 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5764 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5765 }
5766
5767 fn do_completion(
5768 &mut self,
5769 item_ix: Option<usize>,
5770 intent: CompletionIntent,
5771 window: &mut Window,
5772 cx: &mut Context<Editor>,
5773 ) -> Option<Task<Result<()>>> {
5774 use language::ToOffset as _;
5775
5776 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5777 else {
5778 return None;
5779 };
5780
5781 let candidate_id = {
5782 let entries = completions_menu.entries.borrow();
5783 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5784 if self.show_edit_predictions_in_menu() {
5785 self.discard_edit_prediction(true, cx);
5786 }
5787 mat.candidate_id
5788 };
5789
5790 let completion = completions_menu
5791 .completions
5792 .borrow()
5793 .get(candidate_id)?
5794 .clone();
5795 cx.stop_propagation();
5796
5797 let buffer_handle = completions_menu.buffer.clone();
5798
5799 let CompletionEdit {
5800 new_text,
5801 snippet,
5802 replace_range,
5803 } = process_completion_for_edit(
5804 &completion,
5805 intent,
5806 &buffer_handle,
5807 &completions_menu.initial_position.text_anchor,
5808 cx,
5809 );
5810
5811 let buffer = buffer_handle.read(cx);
5812 let snapshot = self.buffer.read(cx).snapshot(cx);
5813 let newest_anchor = self.selections.newest_anchor();
5814 let replace_range_multibuffer = {
5815 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5816 let multibuffer_anchor = snapshot
5817 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5818 .unwrap()
5819 ..snapshot
5820 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5821 .unwrap();
5822 multibuffer_anchor.start.to_offset(&snapshot)
5823 ..multibuffer_anchor.end.to_offset(&snapshot)
5824 };
5825 if newest_anchor.head().buffer_id != Some(buffer.remote_id()) {
5826 return None;
5827 }
5828
5829 let old_text = buffer
5830 .text_for_range(replace_range.clone())
5831 .collect::<String>();
5832 let lookbehind = newest_anchor
5833 .start
5834 .text_anchor
5835 .to_offset(buffer)
5836 .saturating_sub(replace_range.start);
5837 let lookahead = replace_range
5838 .end
5839 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5840 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5841 let suffix = &old_text[lookbehind.min(old_text.len())..];
5842
5843 let selections = self.selections.all::<usize>(cx);
5844 let mut ranges = Vec::new();
5845 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5846
5847 for selection in &selections {
5848 let range = if selection.id == newest_anchor.id {
5849 replace_range_multibuffer.clone()
5850 } else {
5851 let mut range = selection.range();
5852
5853 // if prefix is present, don't duplicate it
5854 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5855 range.start = range.start.saturating_sub(lookbehind);
5856
5857 // if suffix is also present, mimic the newest cursor and replace it
5858 if selection.id != newest_anchor.id
5859 && snapshot.contains_str_at(range.end, suffix)
5860 {
5861 range.end += lookahead;
5862 }
5863 }
5864 range
5865 };
5866
5867 ranges.push(range.clone());
5868
5869 if !self.linked_edit_ranges.is_empty() {
5870 let start_anchor = snapshot.anchor_before(range.start);
5871 let end_anchor = snapshot.anchor_after(range.end);
5872 if let Some(ranges) = self
5873 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5874 {
5875 for (buffer, edits) in ranges {
5876 linked_edits
5877 .entry(buffer.clone())
5878 .or_default()
5879 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5880 }
5881 }
5882 }
5883 }
5884
5885 let common_prefix_len = old_text
5886 .chars()
5887 .zip(new_text.chars())
5888 .take_while(|(a, b)| a == b)
5889 .map(|(a, _)| a.len_utf8())
5890 .sum::<usize>();
5891
5892 cx.emit(EditorEvent::InputHandled {
5893 utf16_range_to_replace: None,
5894 text: new_text[common_prefix_len..].into(),
5895 });
5896
5897 self.transact(window, cx, |editor, window, cx| {
5898 if let Some(mut snippet) = snippet {
5899 snippet.text = new_text.to_string();
5900 editor
5901 .insert_snippet(&ranges, snippet, window, cx)
5902 .log_err();
5903 } else {
5904 editor.buffer.update(cx, |multi_buffer, cx| {
5905 let auto_indent = match completion.insert_text_mode {
5906 Some(InsertTextMode::AS_IS) => None,
5907 _ => editor.autoindent_mode.clone(),
5908 };
5909 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5910 multi_buffer.edit(edits, auto_indent, cx);
5911 });
5912 }
5913 for (buffer, edits) in linked_edits {
5914 buffer.update(cx, |buffer, cx| {
5915 let snapshot = buffer.snapshot();
5916 let edits = edits
5917 .into_iter()
5918 .map(|(range, text)| {
5919 use text::ToPoint as TP;
5920 let end_point = TP::to_point(&range.end, &snapshot);
5921 let start_point = TP::to_point(&range.start, &snapshot);
5922 (start_point..end_point, text)
5923 })
5924 .sorted_by_key(|(range, _)| range.start);
5925 buffer.edit(edits, None, cx);
5926 })
5927 }
5928
5929 editor.refresh_edit_prediction(true, false, window, cx);
5930 });
5931 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), &snapshot);
5932
5933 let show_new_completions_on_confirm = completion
5934 .confirm
5935 .as_ref()
5936 .map_or(false, |confirm| confirm(intent, window, cx));
5937 if show_new_completions_on_confirm {
5938 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5939 }
5940
5941 let provider = self.completion_provider.as_ref()?;
5942 drop(completion);
5943 let apply_edits = provider.apply_additional_edits_for_completion(
5944 buffer_handle,
5945 completions_menu.completions.clone(),
5946 candidate_id,
5947 true,
5948 cx,
5949 );
5950
5951 let editor_settings = EditorSettings::get_global(cx);
5952 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5953 // After the code completion is finished, users often want to know what signatures are needed.
5954 // so we should automatically call signature_help
5955 self.show_signature_help(&ShowSignatureHelp, window, cx);
5956 }
5957
5958 Some(cx.foreground_executor().spawn(async move {
5959 apply_edits.await?;
5960 Ok(())
5961 }))
5962 }
5963
5964 pub fn toggle_code_actions(
5965 &mut self,
5966 action: &ToggleCodeActions,
5967 window: &mut Window,
5968 cx: &mut Context<Self>,
5969 ) {
5970 let quick_launch = action.quick_launch;
5971 let mut context_menu = self.context_menu.borrow_mut();
5972 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5973 if code_actions.deployed_from == action.deployed_from {
5974 // Toggle if we're selecting the same one
5975 *context_menu = None;
5976 cx.notify();
5977 return;
5978 } else {
5979 // Otherwise, clear it and start a new one
5980 *context_menu = None;
5981 cx.notify();
5982 }
5983 }
5984 drop(context_menu);
5985 let snapshot = self.snapshot(window, cx);
5986 let deployed_from = action.deployed_from.clone();
5987 let action = action.clone();
5988 self.completion_tasks.clear();
5989 self.discard_edit_prediction(false, cx);
5990
5991 let multibuffer_point = match &action.deployed_from {
5992 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
5993 DisplayPoint::new(*row, 0).to_point(&snapshot)
5994 }
5995 _ => self.selections.newest::<Point>(cx).head(),
5996 };
5997 let Some((buffer, buffer_row)) = snapshot
5998 .buffer_snapshot
5999 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6000 .and_then(|(buffer_snapshot, range)| {
6001 self.buffer()
6002 .read(cx)
6003 .buffer(buffer_snapshot.remote_id())
6004 .map(|buffer| (buffer, range.start.row))
6005 })
6006 else {
6007 return;
6008 };
6009 let buffer_id = buffer.read(cx).remote_id();
6010 let tasks = self
6011 .tasks
6012 .get(&(buffer_id, buffer_row))
6013 .map(|t| Arc::new(t.to_owned()));
6014
6015 if !self.focus_handle.is_focused(window) {
6016 return;
6017 }
6018 let project = self.project.clone();
6019
6020 let code_actions_task = match deployed_from {
6021 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6022 _ => self.code_actions(buffer_row, window, cx),
6023 };
6024
6025 let runnable_task = match deployed_from {
6026 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6027 _ => {
6028 let mut task_context_task = Task::ready(None);
6029 if let Some(tasks) = &tasks {
6030 if let Some(project) = project {
6031 task_context_task =
6032 Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
6033 }
6034 }
6035
6036 cx.spawn_in(window, {
6037 let buffer = buffer.clone();
6038 async move |editor, cx| {
6039 let task_context = task_context_task.await;
6040
6041 let resolved_tasks =
6042 tasks
6043 .zip(task_context.clone())
6044 .map(|(tasks, task_context)| ResolvedTasks {
6045 templates: tasks.resolve(&task_context).collect(),
6046 position: snapshot.buffer_snapshot.anchor_before(Point::new(
6047 multibuffer_point.row,
6048 tasks.column,
6049 )),
6050 });
6051 let debug_scenarios = editor
6052 .update(cx, |editor, cx| {
6053 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6054 })?
6055 .await;
6056 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6057 }
6058 })
6059 }
6060 };
6061
6062 cx.spawn_in(window, async move |editor, cx| {
6063 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6064 let code_actions = code_actions_task.await;
6065 let spawn_straight_away = quick_launch
6066 && resolved_tasks
6067 .as_ref()
6068 .map_or(false, |tasks| tasks.templates.len() == 1)
6069 && code_actions
6070 .as_ref()
6071 .map_or(true, |actions| actions.is_empty())
6072 && debug_scenarios.is_empty();
6073
6074 editor.update_in(cx, |editor, window, cx| {
6075 crate::hover_popover::hide_hover(editor, cx);
6076 let actions = CodeActionContents::new(
6077 resolved_tasks,
6078 code_actions,
6079 debug_scenarios,
6080 task_context.unwrap_or_default(),
6081 );
6082
6083 // Don't show the menu if there are no actions available
6084 if actions.is_empty() {
6085 cx.notify();
6086 return Task::ready(Ok(()));
6087 }
6088
6089 *editor.context_menu.borrow_mut() =
6090 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6091 buffer,
6092 actions,
6093 selected_item: Default::default(),
6094 scroll_handle: UniformListScrollHandle::default(),
6095 deployed_from,
6096 }));
6097 cx.notify();
6098 if spawn_straight_away {
6099 if let Some(task) = editor.confirm_code_action(
6100 &ConfirmCodeAction { item_ix: Some(0) },
6101 window,
6102 cx,
6103 ) {
6104 return task;
6105 }
6106 }
6107
6108 Task::ready(Ok(()))
6109 })
6110 })
6111 .detach_and_log_err(cx);
6112 }
6113
6114 fn debug_scenarios(
6115 &mut self,
6116 resolved_tasks: &Option<ResolvedTasks>,
6117 buffer: &Entity<Buffer>,
6118 cx: &mut App,
6119 ) -> Task<Vec<task::DebugScenario>> {
6120 maybe!({
6121 let project = self.project.as_ref()?;
6122 let dap_store = project.read(cx).dap_store();
6123 let mut scenarios = vec![];
6124 let resolved_tasks = resolved_tasks.as_ref()?;
6125 let buffer = buffer.read(cx);
6126 let language = buffer.language()?;
6127 let file = buffer.file();
6128 let debug_adapter = language_settings(language.name().into(), file, cx)
6129 .debuggers
6130 .first()
6131 .map(SharedString::from)
6132 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6133
6134 dap_store.update(cx, |dap_store, cx| {
6135 for (_, task) in &resolved_tasks.templates {
6136 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6137 task.original_task().clone(),
6138 debug_adapter.clone().into(),
6139 task.display_label().to_owned().into(),
6140 cx,
6141 );
6142 scenarios.push(maybe_scenario);
6143 }
6144 });
6145 Some(cx.background_spawn(async move {
6146 let scenarios = futures::future::join_all(scenarios)
6147 .await
6148 .into_iter()
6149 .flatten()
6150 .collect::<Vec<_>>();
6151 scenarios
6152 }))
6153 })
6154 .unwrap_or_else(|| Task::ready(vec![]))
6155 }
6156
6157 fn code_actions(
6158 &mut self,
6159 buffer_row: u32,
6160 window: &mut Window,
6161 cx: &mut Context<Self>,
6162 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6163 let mut task = self.code_actions_task.take();
6164 cx.spawn_in(window, async move |editor, cx| {
6165 while let Some(prev_task) = task {
6166 prev_task.await.log_err();
6167 task = editor
6168 .update(cx, |this, _| this.code_actions_task.take())
6169 .ok()?;
6170 }
6171
6172 editor
6173 .update(cx, |editor, cx| {
6174 editor
6175 .available_code_actions
6176 .clone()
6177 .and_then(|(location, code_actions)| {
6178 let snapshot = location.buffer.read(cx).snapshot();
6179 let point_range = location.range.to_point(&snapshot);
6180 let point_range = point_range.start.row..=point_range.end.row;
6181 if point_range.contains(&buffer_row) {
6182 Some(code_actions)
6183 } else {
6184 None
6185 }
6186 })
6187 })
6188 .ok()
6189 .flatten()
6190 })
6191 }
6192
6193 pub fn confirm_code_action(
6194 &mut self,
6195 action: &ConfirmCodeAction,
6196 window: &mut Window,
6197 cx: &mut Context<Self>,
6198 ) -> Option<Task<Result<()>>> {
6199 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6200
6201 let actions_menu =
6202 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6203 menu
6204 } else {
6205 return None;
6206 };
6207
6208 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6209 let action = actions_menu.actions.get(action_ix)?;
6210 let title = action.label();
6211 let buffer = actions_menu.buffer;
6212 let workspace = self.workspace()?;
6213
6214 match action {
6215 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6216 workspace.update(cx, |workspace, cx| {
6217 workspace.schedule_resolved_task(
6218 task_source_kind,
6219 resolved_task,
6220 false,
6221 window,
6222 cx,
6223 );
6224
6225 Some(Task::ready(Ok(())))
6226 })
6227 }
6228 CodeActionsItem::CodeAction {
6229 excerpt_id,
6230 action,
6231 provider,
6232 } => {
6233 let apply_code_action =
6234 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6235 let workspace = workspace.downgrade();
6236 Some(cx.spawn_in(window, async move |editor, cx| {
6237 let project_transaction = apply_code_action.await?;
6238 Self::open_project_transaction(
6239 &editor,
6240 workspace,
6241 project_transaction,
6242 title,
6243 cx,
6244 )
6245 .await
6246 }))
6247 }
6248 CodeActionsItem::DebugScenario(scenario) => {
6249 let context = actions_menu.actions.context.clone();
6250
6251 workspace.update(cx, |workspace, cx| {
6252 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6253 workspace.start_debug_session(
6254 scenario,
6255 context,
6256 Some(buffer),
6257 None,
6258 window,
6259 cx,
6260 );
6261 });
6262 Some(Task::ready(Ok(())))
6263 }
6264 }
6265 }
6266
6267 pub async fn open_project_transaction(
6268 this: &WeakEntity<Editor>,
6269 workspace: WeakEntity<Workspace>,
6270 transaction: ProjectTransaction,
6271 title: String,
6272 cx: &mut AsyncWindowContext,
6273 ) -> Result<()> {
6274 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6275 cx.update(|_, cx| {
6276 entries.sort_unstable_by_key(|(buffer, _)| {
6277 buffer.read(cx).file().map(|f| f.path().clone())
6278 });
6279 })?;
6280
6281 // If the project transaction's edits are all contained within this editor, then
6282 // avoid opening a new editor to display them.
6283
6284 if let Some((buffer, transaction)) = entries.first() {
6285 if entries.len() == 1 {
6286 let excerpt = this.update(cx, |editor, cx| {
6287 editor
6288 .buffer()
6289 .read(cx)
6290 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6291 })?;
6292 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
6293 if excerpted_buffer == *buffer {
6294 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6295 let excerpt_range = excerpt_range.to_offset(buffer);
6296 buffer
6297 .edited_ranges_for_transaction::<usize>(transaction)
6298 .all(|range| {
6299 excerpt_range.start <= range.start
6300 && excerpt_range.end >= range.end
6301 })
6302 })?;
6303
6304 if all_edits_within_excerpt {
6305 return Ok(());
6306 }
6307 }
6308 }
6309 }
6310 } else {
6311 return Ok(());
6312 }
6313
6314 let mut ranges_to_highlight = Vec::new();
6315 let excerpt_buffer = cx.new(|cx| {
6316 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6317 for (buffer_handle, transaction) in &entries {
6318 let edited_ranges = buffer_handle
6319 .read(cx)
6320 .edited_ranges_for_transaction::<Point>(transaction)
6321 .collect::<Vec<_>>();
6322 let (ranges, _) = multibuffer.set_excerpts_for_path(
6323 PathKey::for_buffer(buffer_handle, cx),
6324 buffer_handle.clone(),
6325 edited_ranges,
6326 DEFAULT_MULTIBUFFER_CONTEXT,
6327 cx,
6328 );
6329
6330 ranges_to_highlight.extend(ranges);
6331 }
6332 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6333 multibuffer
6334 })?;
6335
6336 workspace.update_in(cx, |workspace, window, cx| {
6337 let project = workspace.project().clone();
6338 let editor =
6339 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6340 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6341 editor.update(cx, |editor, cx| {
6342 editor.highlight_background::<Self>(
6343 &ranges_to_highlight,
6344 |theme| theme.colors().editor_highlighted_line_background,
6345 cx,
6346 );
6347 });
6348 })?;
6349
6350 Ok(())
6351 }
6352
6353 pub fn clear_code_action_providers(&mut self) {
6354 self.code_action_providers.clear();
6355 self.available_code_actions.take();
6356 }
6357
6358 pub fn add_code_action_provider(
6359 &mut self,
6360 provider: Rc<dyn CodeActionProvider>,
6361 window: &mut Window,
6362 cx: &mut Context<Self>,
6363 ) {
6364 if self
6365 .code_action_providers
6366 .iter()
6367 .any(|existing_provider| existing_provider.id() == provider.id())
6368 {
6369 return;
6370 }
6371
6372 self.code_action_providers.push(provider);
6373 self.refresh_code_actions(window, cx);
6374 }
6375
6376 pub fn remove_code_action_provider(
6377 &mut self,
6378 id: Arc<str>,
6379 window: &mut Window,
6380 cx: &mut Context<Self>,
6381 ) {
6382 self.code_action_providers
6383 .retain(|provider| provider.id() != id);
6384 self.refresh_code_actions(window, cx);
6385 }
6386
6387 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6388 !self.code_action_providers.is_empty()
6389 && EditorSettings::get_global(cx).toolbar.code_actions
6390 }
6391
6392 pub fn has_available_code_actions(&self) -> bool {
6393 self.available_code_actions
6394 .as_ref()
6395 .is_some_and(|(_, actions)| !actions.is_empty())
6396 }
6397
6398 fn render_inline_code_actions(
6399 &self,
6400 icon_size: ui::IconSize,
6401 display_row: DisplayRow,
6402 is_active: bool,
6403 cx: &mut Context<Self>,
6404 ) -> AnyElement {
6405 let show_tooltip = !self.context_menu_visible();
6406 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6407 .icon_size(icon_size)
6408 .shape(ui::IconButtonShape::Square)
6409 .icon_color(ui::Color::Hidden)
6410 .toggle_state(is_active)
6411 .when(show_tooltip, |this| {
6412 this.tooltip({
6413 let focus_handle = self.focus_handle.clone();
6414 move |window, cx| {
6415 Tooltip::for_action_in(
6416 "Toggle Code Actions",
6417 &ToggleCodeActions {
6418 deployed_from: None,
6419 quick_launch: false,
6420 },
6421 &focus_handle,
6422 window,
6423 cx,
6424 )
6425 }
6426 })
6427 })
6428 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6429 window.focus(&editor.focus_handle(cx));
6430 editor.toggle_code_actions(
6431 &crate::actions::ToggleCodeActions {
6432 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6433 display_row,
6434 )),
6435 quick_launch: false,
6436 },
6437 window,
6438 cx,
6439 );
6440 }))
6441 .into_any_element()
6442 }
6443
6444 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6445 &self.context_menu
6446 }
6447
6448 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
6449 let newest_selection = self.selections.newest_anchor().clone();
6450 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
6451 let buffer = self.buffer.read(cx);
6452 if newest_selection.head().diff_base_anchor.is_some() {
6453 return None;
6454 }
6455 let (start_buffer, start) =
6456 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6457 let (end_buffer, end) =
6458 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6459 if start_buffer != end_buffer {
6460 return None;
6461 }
6462
6463 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6464 cx.background_executor()
6465 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6466 .await;
6467
6468 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6469 let providers = this.code_action_providers.clone();
6470 let tasks = this
6471 .code_action_providers
6472 .iter()
6473 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6474 .collect::<Vec<_>>();
6475 (providers, tasks)
6476 })?;
6477
6478 let mut actions = Vec::new();
6479 for (provider, provider_actions) in
6480 providers.into_iter().zip(future::join_all(tasks).await)
6481 {
6482 if let Some(provider_actions) = provider_actions.log_err() {
6483 actions.extend(provider_actions.into_iter().map(|action| {
6484 AvailableCodeAction {
6485 excerpt_id: newest_selection.start.excerpt_id,
6486 action,
6487 provider: provider.clone(),
6488 }
6489 }));
6490 }
6491 }
6492
6493 this.update(cx, |this, cx| {
6494 this.available_code_actions = if actions.is_empty() {
6495 None
6496 } else {
6497 Some((
6498 Location {
6499 buffer: start_buffer,
6500 range: start..end,
6501 },
6502 actions.into(),
6503 ))
6504 };
6505 cx.notify();
6506 })
6507 }));
6508 None
6509 }
6510
6511 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6512 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6513 self.show_git_blame_inline = false;
6514
6515 self.show_git_blame_inline_delay_task =
6516 Some(cx.spawn_in(window, async move |this, cx| {
6517 cx.background_executor().timer(delay).await;
6518
6519 this.update(cx, |this, cx| {
6520 this.show_git_blame_inline = true;
6521 cx.notify();
6522 })
6523 .log_err();
6524 }));
6525 }
6526 }
6527
6528 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6529 let snapshot = self.snapshot(window, cx);
6530 let cursor = self.selections.newest::<Point>(cx).head();
6531 let Some((buffer, point, _)) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)
6532 else {
6533 return;
6534 };
6535
6536 let Some(blame) = self.blame.as_ref() else {
6537 return;
6538 };
6539
6540 let row_info = RowInfo {
6541 buffer_id: Some(buffer.remote_id()),
6542 buffer_row: Some(point.row),
6543 ..Default::default()
6544 };
6545 let Some(blame_entry) = blame
6546 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6547 .flatten()
6548 else {
6549 return;
6550 };
6551
6552 let anchor = self.selections.newest_anchor().head();
6553 let position = self.to_pixel_point(anchor, &snapshot, window);
6554 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6555 self.show_blame_popover(&blame_entry, position + last_bounds.origin, true, cx);
6556 };
6557 }
6558
6559 fn show_blame_popover(
6560 &mut self,
6561 blame_entry: &BlameEntry,
6562 position: gpui::Point<Pixels>,
6563 ignore_timeout: bool,
6564 cx: &mut Context<Self>,
6565 ) {
6566 if let Some(state) = &mut self.inline_blame_popover {
6567 state.hide_task.take();
6568 } else {
6569 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay;
6570 let blame_entry = blame_entry.clone();
6571 let show_task = cx.spawn(async move |editor, cx| {
6572 if !ignore_timeout {
6573 cx.background_executor()
6574 .timer(std::time::Duration::from_millis(blame_popover_delay))
6575 .await;
6576 }
6577 editor
6578 .update(cx, |editor, cx| {
6579 editor.inline_blame_popover_show_task.take();
6580 let Some(blame) = editor.blame.as_ref() else {
6581 return;
6582 };
6583 let blame = blame.read(cx);
6584 let details = blame.details_for_entry(&blame_entry);
6585 let markdown = cx.new(|cx| {
6586 Markdown::new(
6587 details
6588 .as_ref()
6589 .map(|message| message.message.clone())
6590 .unwrap_or_default(),
6591 None,
6592 None,
6593 cx,
6594 )
6595 });
6596 editor.inline_blame_popover = Some(InlineBlamePopover {
6597 position,
6598 hide_task: None,
6599 popover_bounds: None,
6600 popover_state: InlineBlamePopoverState {
6601 scroll_handle: ScrollHandle::new(),
6602 commit_message: details,
6603 markdown,
6604 },
6605 keyboard_grace: ignore_timeout,
6606 });
6607 cx.notify();
6608 })
6609 .ok();
6610 });
6611 self.inline_blame_popover_show_task = Some(show_task);
6612 }
6613 }
6614
6615 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6616 self.inline_blame_popover_show_task.take();
6617 if let Some(state) = &mut self.inline_blame_popover {
6618 let hide_task = cx.spawn(async move |editor, cx| {
6619 cx.background_executor()
6620 .timer(std::time::Duration::from_millis(100))
6621 .await;
6622 editor
6623 .update(cx, |editor, cx| {
6624 editor.inline_blame_popover.take();
6625 cx.notify();
6626 })
6627 .ok();
6628 });
6629 state.hide_task = Some(hide_task);
6630 }
6631 }
6632
6633 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6634 if self.pending_rename.is_some() {
6635 return None;
6636 }
6637
6638 let provider = self.semantics_provider.clone()?;
6639 let buffer = self.buffer.read(cx);
6640 let newest_selection = self.selections.newest_anchor().clone();
6641 let cursor_position = newest_selection.head();
6642 let (cursor_buffer, cursor_buffer_position) =
6643 buffer.text_anchor_for_position(cursor_position, cx)?;
6644 let (tail_buffer, tail_buffer_position) =
6645 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6646 if cursor_buffer != tail_buffer {
6647 return None;
6648 }
6649
6650 let snapshot = cursor_buffer.read(cx).snapshot();
6651 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, false);
6652 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, false);
6653 if start_word_range != end_word_range {
6654 self.document_highlights_task.take();
6655 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6656 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6657 return None;
6658 }
6659
6660 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6661 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6662 cx.background_executor()
6663 .timer(Duration::from_millis(debounce))
6664 .await;
6665
6666 let highlights = if let Some(highlights) = cx
6667 .update(|cx| {
6668 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6669 })
6670 .ok()
6671 .flatten()
6672 {
6673 highlights.await.log_err()
6674 } else {
6675 None
6676 };
6677
6678 if let Some(highlights) = highlights {
6679 this.update(cx, |this, cx| {
6680 if this.pending_rename.is_some() {
6681 return;
6682 }
6683
6684 let buffer_id = cursor_position.buffer_id;
6685 let buffer = this.buffer.read(cx);
6686 if !buffer
6687 .text_anchor_for_position(cursor_position, cx)
6688 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
6689 {
6690 return;
6691 }
6692
6693 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6694 let mut write_ranges = Vec::new();
6695 let mut read_ranges = Vec::new();
6696 for highlight in highlights {
6697 for (excerpt_id, excerpt_range) in
6698 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
6699 {
6700 let start = highlight
6701 .range
6702 .start
6703 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6704 let end = highlight
6705 .range
6706 .end
6707 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6708 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6709 continue;
6710 }
6711
6712 let range = Anchor {
6713 buffer_id,
6714 excerpt_id,
6715 text_anchor: start,
6716 diff_base_anchor: None,
6717 }..Anchor {
6718 buffer_id,
6719 excerpt_id,
6720 text_anchor: end,
6721 diff_base_anchor: None,
6722 };
6723 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6724 write_ranges.push(range);
6725 } else {
6726 read_ranges.push(range);
6727 }
6728 }
6729 }
6730
6731 this.highlight_background::<DocumentHighlightRead>(
6732 &read_ranges,
6733 |theme| theme.colors().editor_document_highlight_read_background,
6734 cx,
6735 );
6736 this.highlight_background::<DocumentHighlightWrite>(
6737 &write_ranges,
6738 |theme| theme.colors().editor_document_highlight_write_background,
6739 cx,
6740 );
6741 cx.notify();
6742 })
6743 .log_err();
6744 }
6745 }));
6746 None
6747 }
6748
6749 fn prepare_highlight_query_from_selection(
6750 &mut self,
6751 cx: &mut Context<Editor>,
6752 ) -> Option<(String, Range<Anchor>)> {
6753 if matches!(self.mode, EditorMode::SingleLine { .. }) {
6754 return None;
6755 }
6756 if !EditorSettings::get_global(cx).selection_highlight {
6757 return None;
6758 }
6759 if self.selections.count() != 1 || self.selections.line_mode {
6760 return None;
6761 }
6762 let selection = self.selections.newest::<Point>(cx);
6763 if selection.is_empty() || selection.start.row != selection.end.row {
6764 return None;
6765 }
6766 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6767 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6768 let query = multi_buffer_snapshot
6769 .text_for_range(selection_anchor_range.clone())
6770 .collect::<String>();
6771 if query.trim().is_empty() {
6772 return None;
6773 }
6774 Some((query, selection_anchor_range))
6775 }
6776
6777 fn update_selection_occurrence_highlights(
6778 &mut self,
6779 query_text: String,
6780 query_range: Range<Anchor>,
6781 multi_buffer_range_to_query: Range<Point>,
6782 use_debounce: bool,
6783 window: &mut Window,
6784 cx: &mut Context<Editor>,
6785 ) -> Task<()> {
6786 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6787 cx.spawn_in(window, async move |editor, cx| {
6788 if use_debounce {
6789 cx.background_executor()
6790 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6791 .await;
6792 }
6793 let match_task = cx.background_spawn(async move {
6794 let buffer_ranges = multi_buffer_snapshot
6795 .range_to_buffer_ranges(multi_buffer_range_to_query)
6796 .into_iter()
6797 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6798 let mut match_ranges = Vec::new();
6799 let Ok(regex) = project::search::SearchQuery::text(
6800 query_text.clone(),
6801 false,
6802 false,
6803 false,
6804 Default::default(),
6805 Default::default(),
6806 false,
6807 None,
6808 ) else {
6809 return Vec::default();
6810 };
6811 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6812 match_ranges.extend(
6813 regex
6814 .search(&buffer_snapshot, Some(search_range.clone()))
6815 .await
6816 .into_iter()
6817 .filter_map(|match_range| {
6818 let match_start = buffer_snapshot
6819 .anchor_after(search_range.start + match_range.start);
6820 let match_end = buffer_snapshot
6821 .anchor_before(search_range.start + match_range.end);
6822 let match_anchor_range = Anchor::range_in_buffer(
6823 excerpt_id,
6824 buffer_snapshot.remote_id(),
6825 match_start..match_end,
6826 );
6827 (match_anchor_range != query_range).then_some(match_anchor_range)
6828 }),
6829 );
6830 }
6831 match_ranges
6832 });
6833 let match_ranges = match_task.await;
6834 editor
6835 .update_in(cx, |editor, _, cx| {
6836 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6837 if !match_ranges.is_empty() {
6838 editor.highlight_background::<SelectedTextHighlight>(
6839 &match_ranges,
6840 |theme| theme.colors().editor_document_highlight_bracket_background,
6841 cx,
6842 )
6843 }
6844 })
6845 .log_err();
6846 })
6847 }
6848
6849 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
6850 struct NewlineFold;
6851 let type_id = std::any::TypeId::of::<NewlineFold>();
6852 if !self.mode.is_single_line() {
6853 return;
6854 }
6855 let snapshot = self.snapshot(window, cx);
6856 if snapshot.buffer_snapshot.max_point().row == 0 {
6857 return;
6858 }
6859 let task = cx.background_spawn(async move {
6860 let new_newlines = snapshot
6861 .buffer_chars_at(0)
6862 .filter_map(|(c, i)| {
6863 if c == '\n' {
6864 Some(
6865 snapshot.buffer_snapshot.anchor_after(i)
6866 ..snapshot.buffer_snapshot.anchor_before(i + 1),
6867 )
6868 } else {
6869 None
6870 }
6871 })
6872 .collect::<Vec<_>>();
6873 let existing_newlines = snapshot
6874 .folds_in_range(0..snapshot.buffer_snapshot.len())
6875 .filter_map(|fold| {
6876 if fold.placeholder.type_tag == Some(type_id) {
6877 Some(fold.range.start..fold.range.end)
6878 } else {
6879 None
6880 }
6881 })
6882 .collect::<Vec<_>>();
6883
6884 (new_newlines, existing_newlines)
6885 });
6886 self.folding_newlines = cx.spawn(async move |this, cx| {
6887 let (new_newlines, existing_newlines) = task.await;
6888 if new_newlines == existing_newlines {
6889 return;
6890 }
6891 let placeholder = FoldPlaceholder {
6892 render: Arc::new(move |_, _, cx| {
6893 div()
6894 .bg(cx.theme().status().hint_background)
6895 .border_b_1()
6896 .size_full()
6897 .font(ThemeSettings::get_global(cx).buffer_font.clone())
6898 .border_color(cx.theme().status().hint)
6899 .child("\\n")
6900 .into_any()
6901 }),
6902 constrain_width: false,
6903 merge_adjacent: false,
6904 type_tag: Some(type_id),
6905 };
6906 let creases = new_newlines
6907 .into_iter()
6908 .map(|range| Crease::simple(range, placeholder.clone()))
6909 .collect();
6910 this.update(cx, |this, cx| {
6911 this.display_map.update(cx, |display_map, cx| {
6912 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
6913 display_map.fold(creases, cx);
6914 });
6915 })
6916 .ok();
6917 });
6918 }
6919
6920 fn refresh_selected_text_highlights(
6921 &mut self,
6922 on_buffer_edit: bool,
6923 window: &mut Window,
6924 cx: &mut Context<Editor>,
6925 ) {
6926 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
6927 else {
6928 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6929 self.quick_selection_highlight_task.take();
6930 self.debounced_selection_highlight_task.take();
6931 return;
6932 };
6933 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6934 if on_buffer_edit
6935 || self
6936 .quick_selection_highlight_task
6937 .as_ref()
6938 .map_or(true, |(prev_anchor_range, _)| {
6939 prev_anchor_range != &query_range
6940 })
6941 {
6942 let multi_buffer_visible_start = self
6943 .scroll_manager
6944 .anchor()
6945 .anchor
6946 .to_point(&multi_buffer_snapshot);
6947 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
6948 multi_buffer_visible_start
6949 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
6950 Bias::Left,
6951 );
6952 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
6953 self.quick_selection_highlight_task = Some((
6954 query_range.clone(),
6955 self.update_selection_occurrence_highlights(
6956 query_text.clone(),
6957 query_range.clone(),
6958 multi_buffer_visible_range,
6959 false,
6960 window,
6961 cx,
6962 ),
6963 ));
6964 }
6965 if on_buffer_edit
6966 || self
6967 .debounced_selection_highlight_task
6968 .as_ref()
6969 .map_or(true, |(prev_anchor_range, _)| {
6970 prev_anchor_range != &query_range
6971 })
6972 {
6973 let multi_buffer_start = multi_buffer_snapshot
6974 .anchor_before(0)
6975 .to_point(&multi_buffer_snapshot);
6976 let multi_buffer_end = multi_buffer_snapshot
6977 .anchor_after(multi_buffer_snapshot.len())
6978 .to_point(&multi_buffer_snapshot);
6979 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
6980 self.debounced_selection_highlight_task = Some((
6981 query_range.clone(),
6982 self.update_selection_occurrence_highlights(
6983 query_text,
6984 query_range,
6985 multi_buffer_full_range,
6986 true,
6987 window,
6988 cx,
6989 ),
6990 ));
6991 }
6992 }
6993
6994 pub fn refresh_edit_prediction(
6995 &mut self,
6996 debounce: bool,
6997 user_requested: bool,
6998 window: &mut Window,
6999 cx: &mut Context<Self>,
7000 ) -> Option<()> {
7001 if DisableAiSettings::get_global(cx).disable_ai {
7002 return None;
7003 }
7004
7005 let provider = self.edit_prediction_provider()?;
7006 let cursor = self.selections.newest_anchor().head();
7007 let (buffer, cursor_buffer_position) =
7008 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7009
7010 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7011 self.discard_edit_prediction(false, cx);
7012 return None;
7013 }
7014
7015 if !user_requested
7016 && (!self.should_show_edit_predictions()
7017 || !self.is_focused(window)
7018 || buffer.read(cx).is_empty())
7019 {
7020 self.discard_edit_prediction(false, cx);
7021 return None;
7022 }
7023
7024 self.update_visible_edit_prediction(window, cx);
7025 provider.refresh(
7026 self.project.clone(),
7027 buffer,
7028 cursor_buffer_position,
7029 debounce,
7030 cx,
7031 );
7032 Some(())
7033 }
7034
7035 fn show_edit_predictions_in_menu(&self) -> bool {
7036 match self.edit_prediction_settings {
7037 EditPredictionSettings::Disabled => false,
7038 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7039 }
7040 }
7041
7042 pub fn edit_predictions_enabled(&self) -> bool {
7043 match self.edit_prediction_settings {
7044 EditPredictionSettings::Disabled => false,
7045 EditPredictionSettings::Enabled { .. } => true,
7046 }
7047 }
7048
7049 fn edit_prediction_requires_modifier(&self) -> bool {
7050 match self.edit_prediction_settings {
7051 EditPredictionSettings::Disabled => false,
7052 EditPredictionSettings::Enabled {
7053 preview_requires_modifier,
7054 ..
7055 } => preview_requires_modifier,
7056 }
7057 }
7058
7059 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7060 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7061 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7062 self.discard_edit_prediction(false, cx);
7063 } else {
7064 let selection = self.selections.newest_anchor();
7065 let cursor = selection.head();
7066
7067 if let Some((buffer, cursor_buffer_position)) =
7068 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7069 {
7070 self.edit_prediction_settings =
7071 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7072 }
7073 }
7074 }
7075
7076 fn edit_prediction_settings_at_position(
7077 &self,
7078 buffer: &Entity<Buffer>,
7079 buffer_position: language::Anchor,
7080 cx: &App,
7081 ) -> EditPredictionSettings {
7082 if !self.mode.is_full()
7083 || !self.show_edit_predictions_override.unwrap_or(true)
7084 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7085 {
7086 return EditPredictionSettings::Disabled;
7087 }
7088
7089 let buffer = buffer.read(cx);
7090
7091 let file = buffer.file();
7092
7093 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7094 return EditPredictionSettings::Disabled;
7095 };
7096
7097 let by_provider = matches!(
7098 self.menu_edit_predictions_policy,
7099 MenuEditPredictionsPolicy::ByProvider
7100 );
7101
7102 let show_in_menu = by_provider
7103 && self
7104 .edit_prediction_provider
7105 .as_ref()
7106 .map_or(false, |provider| {
7107 provider.provider.show_completions_in_menu()
7108 });
7109
7110 let preview_requires_modifier =
7111 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7112
7113 EditPredictionSettings::Enabled {
7114 show_in_menu,
7115 preview_requires_modifier,
7116 }
7117 }
7118
7119 fn should_show_edit_predictions(&self) -> bool {
7120 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7121 }
7122
7123 pub fn edit_prediction_preview_is_active(&self) -> bool {
7124 matches!(
7125 self.edit_prediction_preview,
7126 EditPredictionPreview::Active { .. }
7127 )
7128 }
7129
7130 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7131 let cursor = self.selections.newest_anchor().head();
7132 if let Some((buffer, cursor_position)) =
7133 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7134 {
7135 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7136 } else {
7137 false
7138 }
7139 }
7140
7141 pub fn supports_minimap(&self, cx: &App) -> bool {
7142 !self.minimap_visibility.disabled() && self.is_singleton(cx)
7143 }
7144
7145 fn edit_predictions_enabled_in_buffer(
7146 &self,
7147 buffer: &Entity<Buffer>,
7148 buffer_position: language::Anchor,
7149 cx: &App,
7150 ) -> bool {
7151 maybe!({
7152 if self.read_only(cx) {
7153 return Some(false);
7154 }
7155 let provider = self.edit_prediction_provider()?;
7156 if !provider.is_enabled(&buffer, buffer_position, cx) {
7157 return Some(false);
7158 }
7159 let buffer = buffer.read(cx);
7160 let Some(file) = buffer.file() else {
7161 return Some(true);
7162 };
7163 let settings = all_language_settings(Some(file), cx);
7164 Some(settings.edit_predictions_enabled_for_file(file, cx))
7165 })
7166 .unwrap_or(false)
7167 }
7168
7169 fn cycle_edit_prediction(
7170 &mut self,
7171 direction: Direction,
7172 window: &mut Window,
7173 cx: &mut Context<Self>,
7174 ) -> Option<()> {
7175 let provider = self.edit_prediction_provider()?;
7176 let cursor = self.selections.newest_anchor().head();
7177 let (buffer, cursor_buffer_position) =
7178 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7179 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7180 return None;
7181 }
7182
7183 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7184 self.update_visible_edit_prediction(window, cx);
7185
7186 Some(())
7187 }
7188
7189 pub fn show_edit_prediction(
7190 &mut self,
7191 _: &ShowEditPrediction,
7192 window: &mut Window,
7193 cx: &mut Context<Self>,
7194 ) {
7195 if !self.has_active_edit_prediction() {
7196 self.refresh_edit_prediction(false, true, window, cx);
7197 return;
7198 }
7199
7200 self.update_visible_edit_prediction(window, cx);
7201 }
7202
7203 pub fn display_cursor_names(
7204 &mut self,
7205 _: &DisplayCursorNames,
7206 window: &mut Window,
7207 cx: &mut Context<Self>,
7208 ) {
7209 self.show_cursor_names(window, cx);
7210 }
7211
7212 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7213 self.show_cursor_names = true;
7214 cx.notify();
7215 cx.spawn_in(window, async move |this, cx| {
7216 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7217 this.update(cx, |this, cx| {
7218 this.show_cursor_names = false;
7219 cx.notify()
7220 })
7221 .ok()
7222 })
7223 .detach();
7224 }
7225
7226 pub fn next_edit_prediction(
7227 &mut self,
7228 _: &NextEditPrediction,
7229 window: &mut Window,
7230 cx: &mut Context<Self>,
7231 ) {
7232 if self.has_active_edit_prediction() {
7233 self.cycle_edit_prediction(Direction::Next, window, cx);
7234 } else {
7235 let is_copilot_disabled = self
7236 .refresh_edit_prediction(false, true, window, cx)
7237 .is_none();
7238 if is_copilot_disabled {
7239 cx.propagate();
7240 }
7241 }
7242 }
7243
7244 pub fn previous_edit_prediction(
7245 &mut self,
7246 _: &PreviousEditPrediction,
7247 window: &mut Window,
7248 cx: &mut Context<Self>,
7249 ) {
7250 if self.has_active_edit_prediction() {
7251 self.cycle_edit_prediction(Direction::Prev, window, cx);
7252 } else {
7253 let is_copilot_disabled = self
7254 .refresh_edit_prediction(false, true, window, cx)
7255 .is_none();
7256 if is_copilot_disabled {
7257 cx.propagate();
7258 }
7259 }
7260 }
7261
7262 pub fn accept_edit_prediction(
7263 &mut self,
7264 _: &AcceptEditPrediction,
7265 window: &mut Window,
7266 cx: &mut Context<Self>,
7267 ) {
7268 if self.show_edit_predictions_in_menu() {
7269 self.hide_context_menu(window, cx);
7270 }
7271
7272 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7273 return;
7274 };
7275
7276 self.report_edit_prediction_event(active_edit_prediction.completion_id.clone(), true, cx);
7277
7278 match &active_edit_prediction.completion {
7279 EditPrediction::Move { target, .. } => {
7280 let target = *target;
7281
7282 if let Some(position_map) = &self.last_position_map {
7283 if position_map
7284 .visible_row_range
7285 .contains(&target.to_display_point(&position_map.snapshot).row())
7286 || !self.edit_prediction_requires_modifier()
7287 {
7288 self.unfold_ranges(&[target..target], true, false, cx);
7289 // Note that this is also done in vim's handler of the Tab action.
7290 self.change_selections(
7291 SelectionEffects::scroll(Autoscroll::newest()),
7292 window,
7293 cx,
7294 |selections| {
7295 selections.select_anchor_ranges([target..target]);
7296 },
7297 );
7298 self.clear_row_highlights::<EditPredictionPreview>();
7299
7300 self.edit_prediction_preview
7301 .set_previous_scroll_position(None);
7302 } else {
7303 self.edit_prediction_preview
7304 .set_previous_scroll_position(Some(
7305 position_map.snapshot.scroll_anchor,
7306 ));
7307
7308 self.highlight_rows::<EditPredictionPreview>(
7309 target..target,
7310 cx.theme().colors().editor_highlighted_line_background,
7311 RowHighlightOptions {
7312 autoscroll: true,
7313 ..Default::default()
7314 },
7315 cx,
7316 );
7317 self.request_autoscroll(Autoscroll::fit(), cx);
7318 }
7319 }
7320 }
7321 EditPrediction::Edit { edits, .. } => {
7322 if let Some(provider) = self.edit_prediction_provider() {
7323 provider.accept(cx);
7324 }
7325
7326 // Store the transaction ID and selections before applying the edit
7327 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7328
7329 let snapshot = self.buffer.read(cx).snapshot(cx);
7330 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7331
7332 self.buffer.update(cx, |buffer, cx| {
7333 buffer.edit(edits.iter().cloned(), None, cx)
7334 });
7335
7336 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7337 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7338 });
7339
7340 let selections = self.selections.disjoint_anchors();
7341 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7342 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7343 if has_new_transaction {
7344 self.selection_history
7345 .insert_transaction(transaction_id_now, selections);
7346 }
7347 }
7348
7349 self.update_visible_edit_prediction(window, cx);
7350 if self.active_edit_prediction.is_none() {
7351 self.refresh_edit_prediction(true, true, window, cx);
7352 }
7353
7354 cx.notify();
7355 }
7356 }
7357
7358 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7359 }
7360
7361 pub fn accept_partial_edit_prediction(
7362 &mut self,
7363 _: &AcceptPartialEditPrediction,
7364 window: &mut Window,
7365 cx: &mut Context<Self>,
7366 ) {
7367 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7368 return;
7369 };
7370 if self.selections.count() != 1 {
7371 return;
7372 }
7373
7374 self.report_edit_prediction_event(active_edit_prediction.completion_id.clone(), true, cx);
7375
7376 match &active_edit_prediction.completion {
7377 EditPrediction::Move { target, .. } => {
7378 let target = *target;
7379 self.change_selections(
7380 SelectionEffects::scroll(Autoscroll::newest()),
7381 window,
7382 cx,
7383 |selections| {
7384 selections.select_anchor_ranges([target..target]);
7385 },
7386 );
7387 }
7388 EditPrediction::Edit { edits, .. } => {
7389 // Find an insertion that starts at the cursor position.
7390 let snapshot = self.buffer.read(cx).snapshot(cx);
7391 let cursor_offset = self.selections.newest::<usize>(cx).head();
7392 let insertion = edits.iter().find_map(|(range, text)| {
7393 let range = range.to_offset(&snapshot);
7394 if range.is_empty() && range.start == cursor_offset {
7395 Some(text)
7396 } else {
7397 None
7398 }
7399 });
7400
7401 if let Some(text) = insertion {
7402 let mut partial_completion = text
7403 .chars()
7404 .by_ref()
7405 .take_while(|c| c.is_alphabetic())
7406 .collect::<String>();
7407 if partial_completion.is_empty() {
7408 partial_completion = text
7409 .chars()
7410 .by_ref()
7411 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7412 .collect::<String>();
7413 }
7414
7415 cx.emit(EditorEvent::InputHandled {
7416 utf16_range_to_replace: None,
7417 text: partial_completion.clone().into(),
7418 });
7419
7420 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7421
7422 self.refresh_edit_prediction(true, true, window, cx);
7423 cx.notify();
7424 } else {
7425 self.accept_edit_prediction(&Default::default(), window, cx);
7426 }
7427 }
7428 }
7429 }
7430
7431 fn discard_edit_prediction(
7432 &mut self,
7433 should_report_edit_prediction_event: bool,
7434 cx: &mut Context<Self>,
7435 ) -> bool {
7436 if should_report_edit_prediction_event {
7437 let completion_id = self
7438 .active_edit_prediction
7439 .as_ref()
7440 .and_then(|active_completion| active_completion.completion_id.clone());
7441
7442 self.report_edit_prediction_event(completion_id, false, cx);
7443 }
7444
7445 if let Some(provider) = self.edit_prediction_provider() {
7446 provider.discard(cx);
7447 }
7448
7449 self.take_active_edit_prediction(cx)
7450 }
7451
7452 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7453 let Some(provider) = self.edit_prediction_provider() else {
7454 return;
7455 };
7456
7457 let Some((_, buffer, _)) = self
7458 .buffer
7459 .read(cx)
7460 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7461 else {
7462 return;
7463 };
7464
7465 let extension = buffer
7466 .read(cx)
7467 .file()
7468 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
7469
7470 let event_type = match accepted {
7471 true => "Edit Prediction Accepted",
7472 false => "Edit Prediction Discarded",
7473 };
7474 telemetry::event!(
7475 event_type,
7476 provider = provider.name(),
7477 prediction_id = id,
7478 suggestion_accepted = accepted,
7479 file_extension = extension,
7480 );
7481 }
7482
7483 pub fn has_active_edit_prediction(&self) -> bool {
7484 self.active_edit_prediction.is_some()
7485 }
7486
7487 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7488 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7489 return false;
7490 };
7491
7492 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7493 self.clear_highlights::<EditPredictionHighlight>(cx);
7494 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7495 true
7496 }
7497
7498 /// Returns true when we're displaying the edit prediction popover below the cursor
7499 /// like we are not previewing and the LSP autocomplete menu is visible
7500 /// or we are in `when_holding_modifier` mode.
7501 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7502 if self.edit_prediction_preview_is_active()
7503 || !self.show_edit_predictions_in_menu()
7504 || !self.edit_predictions_enabled()
7505 {
7506 return false;
7507 }
7508
7509 if self.has_visible_completions_menu() {
7510 return true;
7511 }
7512
7513 has_completion && self.edit_prediction_requires_modifier()
7514 }
7515
7516 fn handle_modifiers_changed(
7517 &mut self,
7518 modifiers: Modifiers,
7519 position_map: &PositionMap,
7520 window: &mut Window,
7521 cx: &mut Context<Self>,
7522 ) {
7523 if self.show_edit_predictions_in_menu() {
7524 self.update_edit_prediction_preview(&modifiers, window, cx);
7525 }
7526
7527 self.update_selection_mode(&modifiers, position_map, window, cx);
7528
7529 let mouse_position = window.mouse_position();
7530 if !position_map.text_hitbox.is_hovered(window) {
7531 return;
7532 }
7533
7534 self.update_hovered_link(
7535 position_map.point_for_position(mouse_position),
7536 &position_map.snapshot,
7537 modifiers,
7538 window,
7539 cx,
7540 )
7541 }
7542
7543 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7544 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7545 if invert {
7546 match multi_cursor_setting {
7547 MultiCursorModifier::Alt => modifiers.alt,
7548 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7549 }
7550 } else {
7551 match multi_cursor_setting {
7552 MultiCursorModifier::Alt => modifiers.secondary(),
7553 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7554 }
7555 }
7556 }
7557
7558 fn columnar_selection_mode(
7559 modifiers: &Modifiers,
7560 cx: &mut Context<Self>,
7561 ) -> Option<ColumnarMode> {
7562 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7563 if Self::multi_cursor_modifier(false, modifiers, cx) {
7564 Some(ColumnarMode::FromMouse)
7565 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7566 Some(ColumnarMode::FromSelection)
7567 } else {
7568 None
7569 }
7570 } else {
7571 None
7572 }
7573 }
7574
7575 fn update_selection_mode(
7576 &mut self,
7577 modifiers: &Modifiers,
7578 position_map: &PositionMap,
7579 window: &mut Window,
7580 cx: &mut Context<Self>,
7581 ) {
7582 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7583 return;
7584 };
7585 if self.selections.pending.is_none() {
7586 return;
7587 }
7588
7589 let mouse_position = window.mouse_position();
7590 let point_for_position = position_map.point_for_position(mouse_position);
7591 let position = point_for_position.previous_valid;
7592
7593 self.select(
7594 SelectPhase::BeginColumnar {
7595 position,
7596 reset: false,
7597 mode,
7598 goal_column: point_for_position.exact_unclipped.column(),
7599 },
7600 window,
7601 cx,
7602 );
7603 }
7604
7605 fn update_edit_prediction_preview(
7606 &mut self,
7607 modifiers: &Modifiers,
7608 window: &mut Window,
7609 cx: &mut Context<Self>,
7610 ) {
7611 let mut modifiers_held = false;
7612 if let Some(accept_keystroke) = self
7613 .accept_edit_prediction_keybind(false, window, cx)
7614 .keystroke()
7615 {
7616 modifiers_held = modifiers_held
7617 || (&accept_keystroke.modifiers == modifiers
7618 && accept_keystroke.modifiers.modified());
7619 };
7620 if let Some(accept_partial_keystroke) = self
7621 .accept_edit_prediction_keybind(true, window, cx)
7622 .keystroke()
7623 {
7624 modifiers_held = modifiers_held
7625 || (&accept_partial_keystroke.modifiers == modifiers
7626 && accept_partial_keystroke.modifiers.modified());
7627 }
7628
7629 if modifiers_held {
7630 if matches!(
7631 self.edit_prediction_preview,
7632 EditPredictionPreview::Inactive { .. }
7633 ) {
7634 self.edit_prediction_preview = EditPredictionPreview::Active {
7635 previous_scroll_position: None,
7636 since: Instant::now(),
7637 };
7638
7639 self.update_visible_edit_prediction(window, cx);
7640 cx.notify();
7641 }
7642 } else if let EditPredictionPreview::Active {
7643 previous_scroll_position,
7644 since,
7645 } = self.edit_prediction_preview
7646 {
7647 if let (Some(previous_scroll_position), Some(position_map)) =
7648 (previous_scroll_position, self.last_position_map.as_ref())
7649 {
7650 self.set_scroll_position(
7651 previous_scroll_position
7652 .scroll_position(&position_map.snapshot.display_snapshot),
7653 window,
7654 cx,
7655 );
7656 }
7657
7658 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7659 released_too_fast: since.elapsed() < Duration::from_millis(200),
7660 };
7661 self.clear_row_highlights::<EditPredictionPreview>();
7662 self.update_visible_edit_prediction(window, cx);
7663 cx.notify();
7664 }
7665 }
7666
7667 fn update_visible_edit_prediction(
7668 &mut self,
7669 _window: &mut Window,
7670 cx: &mut Context<Self>,
7671 ) -> Option<()> {
7672 if DisableAiSettings::get_global(cx).disable_ai {
7673 return None;
7674 }
7675
7676 let selection = self.selections.newest_anchor();
7677 let cursor = selection.head();
7678 let multibuffer = self.buffer.read(cx).snapshot(cx);
7679 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7680 let excerpt_id = cursor.excerpt_id;
7681
7682 let show_in_menu = self.show_edit_predictions_in_menu();
7683 let completions_menu_has_precedence = !show_in_menu
7684 && (self.context_menu.borrow().is_some()
7685 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
7686
7687 if completions_menu_has_precedence
7688 || !offset_selection.is_empty()
7689 || self
7690 .active_edit_prediction
7691 .as_ref()
7692 .map_or(false, |completion| {
7693 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
7694 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7695 !invalidation_range.contains(&offset_selection.head())
7696 })
7697 {
7698 self.discard_edit_prediction(false, cx);
7699 return None;
7700 }
7701
7702 self.take_active_edit_prediction(cx);
7703 let Some(provider) = self.edit_prediction_provider() else {
7704 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7705 return None;
7706 };
7707
7708 let (buffer, cursor_buffer_position) =
7709 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7710
7711 self.edit_prediction_settings =
7712 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7713
7714 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7715
7716 if self.edit_prediction_indent_conflict {
7717 let cursor_point = cursor.to_point(&multibuffer);
7718
7719 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7720
7721 if let Some((_, indent)) = indents.iter().next() {
7722 if indent.len == cursor_point.column {
7723 self.edit_prediction_indent_conflict = false;
7724 }
7725 }
7726 }
7727
7728 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7729 let edits = edit_prediction
7730 .edits
7731 .into_iter()
7732 .flat_map(|(range, new_text)| {
7733 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7734 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7735 Some((start..end, new_text))
7736 })
7737 .collect::<Vec<_>>();
7738 if edits.is_empty() {
7739 return None;
7740 }
7741
7742 let first_edit_start = edits.first().unwrap().0.start;
7743 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7744 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7745
7746 let last_edit_end = edits.last().unwrap().0.end;
7747 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7748 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7749
7750 let cursor_row = cursor.to_point(&multibuffer).row;
7751
7752 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7753
7754 let mut inlay_ids = Vec::new();
7755 let invalidation_row_range;
7756 let move_invalidation_row_range = if cursor_row < edit_start_row {
7757 Some(cursor_row..edit_end_row)
7758 } else if cursor_row > edit_end_row {
7759 Some(edit_start_row..cursor_row)
7760 } else {
7761 None
7762 };
7763 let supports_jump = self
7764 .edit_prediction_provider
7765 .as_ref()
7766 .map(|provider| provider.provider.supports_jump_to_edit())
7767 .unwrap_or(true);
7768
7769 let is_move = supports_jump
7770 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
7771 let completion = if is_move {
7772 invalidation_row_range =
7773 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7774 let target = first_edit_start;
7775 EditPrediction::Move { target, snapshot }
7776 } else {
7777 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7778 && !self.edit_predictions_hidden_for_vim_mode;
7779
7780 if show_completions_in_buffer {
7781 if edits
7782 .iter()
7783 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7784 {
7785 let mut inlays = Vec::new();
7786 for (range, new_text) in &edits {
7787 let inlay = Inlay::edit_prediction(
7788 post_inc(&mut self.next_inlay_id),
7789 range.start,
7790 new_text.as_str(),
7791 );
7792 inlay_ids.push(inlay.id);
7793 inlays.push(inlay);
7794 }
7795
7796 self.splice_inlays(&[], inlays, cx);
7797 } else {
7798 let background_color = cx.theme().status().deleted_background;
7799 self.highlight_text::<EditPredictionHighlight>(
7800 edits.iter().map(|(range, _)| range.clone()).collect(),
7801 HighlightStyle {
7802 background_color: Some(background_color),
7803 ..Default::default()
7804 },
7805 cx,
7806 );
7807 }
7808 }
7809
7810 invalidation_row_range = edit_start_row..edit_end_row;
7811
7812 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7813 if provider.show_tab_accept_marker() {
7814 EditDisplayMode::TabAccept
7815 } else {
7816 EditDisplayMode::Inline
7817 }
7818 } else {
7819 EditDisplayMode::DiffPopover
7820 };
7821
7822 EditPrediction::Edit {
7823 edits,
7824 edit_preview: edit_prediction.edit_preview,
7825 display_mode,
7826 snapshot,
7827 }
7828 };
7829
7830 let invalidation_range = multibuffer
7831 .anchor_before(Point::new(invalidation_row_range.start, 0))
7832 ..multibuffer.anchor_after(Point::new(
7833 invalidation_row_range.end,
7834 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7835 ));
7836
7837 self.stale_edit_prediction_in_menu = None;
7838 self.active_edit_prediction = Some(EditPredictionState {
7839 inlay_ids,
7840 completion,
7841 completion_id: edit_prediction.id,
7842 invalidation_range,
7843 });
7844
7845 cx.notify();
7846
7847 Some(())
7848 }
7849
7850 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionProviderHandle>> {
7851 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7852 }
7853
7854 fn clear_tasks(&mut self) {
7855 self.tasks.clear()
7856 }
7857
7858 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7859 if self.tasks.insert(key, value).is_some() {
7860 // This case should hopefully be rare, but just in case...
7861 log::error!(
7862 "multiple different run targets found on a single line, only the last target will be rendered"
7863 )
7864 }
7865 }
7866
7867 /// Get all display points of breakpoints that will be rendered within editor
7868 ///
7869 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7870 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7871 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7872 fn active_breakpoints(
7873 &self,
7874 range: Range<DisplayRow>,
7875 window: &mut Window,
7876 cx: &mut Context<Self>,
7877 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7878 let mut breakpoint_display_points = HashMap::default();
7879
7880 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7881 return breakpoint_display_points;
7882 };
7883
7884 let snapshot = self.snapshot(window, cx);
7885
7886 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
7887 let Some(project) = self.project.as_ref() else {
7888 return breakpoint_display_points;
7889 };
7890
7891 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7892 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7893
7894 for (buffer_snapshot, range, excerpt_id) in
7895 multi_buffer_snapshot.range_to_buffer_ranges(range)
7896 {
7897 let Some(buffer) = project
7898 .read(cx)
7899 .buffer_for_id(buffer_snapshot.remote_id(), cx)
7900 else {
7901 continue;
7902 };
7903 let breakpoints = breakpoint_store.read(cx).breakpoints(
7904 &buffer,
7905 Some(
7906 buffer_snapshot.anchor_before(range.start)
7907 ..buffer_snapshot.anchor_after(range.end),
7908 ),
7909 buffer_snapshot,
7910 cx,
7911 );
7912 for (breakpoint, state) in breakpoints {
7913 let multi_buffer_anchor =
7914 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
7915 let position = multi_buffer_anchor
7916 .to_point(&multi_buffer_snapshot)
7917 .to_display_point(&snapshot);
7918
7919 breakpoint_display_points.insert(
7920 position.row(),
7921 (multi_buffer_anchor, breakpoint.bp.clone(), state),
7922 );
7923 }
7924 }
7925
7926 breakpoint_display_points
7927 }
7928
7929 fn breakpoint_context_menu(
7930 &self,
7931 anchor: Anchor,
7932 window: &mut Window,
7933 cx: &mut Context<Self>,
7934 ) -> Entity<ui::ContextMenu> {
7935 let weak_editor = cx.weak_entity();
7936 let focus_handle = self.focus_handle(cx);
7937
7938 let row = self
7939 .buffer
7940 .read(cx)
7941 .snapshot(cx)
7942 .summary_for_anchor::<Point>(&anchor)
7943 .row;
7944
7945 let breakpoint = self
7946 .breakpoint_at_row(row, window, cx)
7947 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
7948
7949 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
7950 "Edit Log Breakpoint"
7951 } else {
7952 "Set Log Breakpoint"
7953 };
7954
7955 let condition_breakpoint_msg = if breakpoint
7956 .as_ref()
7957 .is_some_and(|bp| bp.1.condition.is_some())
7958 {
7959 "Edit Condition Breakpoint"
7960 } else {
7961 "Set Condition Breakpoint"
7962 };
7963
7964 let hit_condition_breakpoint_msg = if breakpoint
7965 .as_ref()
7966 .is_some_and(|bp| bp.1.hit_condition.is_some())
7967 {
7968 "Edit Hit Condition Breakpoint"
7969 } else {
7970 "Set Hit Condition Breakpoint"
7971 };
7972
7973 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
7974 "Unset Breakpoint"
7975 } else {
7976 "Set Breakpoint"
7977 };
7978
7979 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
7980
7981 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
7982 BreakpointState::Enabled => Some("Disable"),
7983 BreakpointState::Disabled => Some("Enable"),
7984 });
7985
7986 let (anchor, breakpoint) =
7987 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
7988
7989 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
7990 menu.on_blur_subscription(Subscription::new(|| {}))
7991 .context(focus_handle)
7992 .when(run_to_cursor, |this| {
7993 let weak_editor = weak_editor.clone();
7994 this.entry("Run to cursor", None, move |window, cx| {
7995 weak_editor
7996 .update(cx, |editor, cx| {
7997 editor.change_selections(
7998 SelectionEffects::no_scroll(),
7999 window,
8000 cx,
8001 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8002 );
8003 })
8004 .ok();
8005
8006 window.dispatch_action(Box::new(RunToCursor), cx);
8007 })
8008 .separator()
8009 })
8010 .when_some(toggle_state_msg, |this, msg| {
8011 this.entry(msg, None, {
8012 let weak_editor = weak_editor.clone();
8013 let breakpoint = breakpoint.clone();
8014 move |_window, cx| {
8015 weak_editor
8016 .update(cx, |this, cx| {
8017 this.edit_breakpoint_at_anchor(
8018 anchor,
8019 breakpoint.as_ref().clone(),
8020 BreakpointEditAction::InvertState,
8021 cx,
8022 );
8023 })
8024 .log_err();
8025 }
8026 })
8027 })
8028 .entry(set_breakpoint_msg, None, {
8029 let weak_editor = weak_editor.clone();
8030 let breakpoint = breakpoint.clone();
8031 move |_window, cx| {
8032 weak_editor
8033 .update(cx, |this, cx| {
8034 this.edit_breakpoint_at_anchor(
8035 anchor,
8036 breakpoint.as_ref().clone(),
8037 BreakpointEditAction::Toggle,
8038 cx,
8039 );
8040 })
8041 .log_err();
8042 }
8043 })
8044 .entry(log_breakpoint_msg, None, {
8045 let breakpoint = breakpoint.clone();
8046 let weak_editor = weak_editor.clone();
8047 move |window, cx| {
8048 weak_editor
8049 .update(cx, |this, cx| {
8050 this.add_edit_breakpoint_block(
8051 anchor,
8052 breakpoint.as_ref(),
8053 BreakpointPromptEditAction::Log,
8054 window,
8055 cx,
8056 );
8057 })
8058 .log_err();
8059 }
8060 })
8061 .entry(condition_breakpoint_msg, None, {
8062 let breakpoint = breakpoint.clone();
8063 let weak_editor = weak_editor.clone();
8064 move |window, cx| {
8065 weak_editor
8066 .update(cx, |this, cx| {
8067 this.add_edit_breakpoint_block(
8068 anchor,
8069 breakpoint.as_ref(),
8070 BreakpointPromptEditAction::Condition,
8071 window,
8072 cx,
8073 );
8074 })
8075 .log_err();
8076 }
8077 })
8078 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8079 weak_editor
8080 .update(cx, |this, cx| {
8081 this.add_edit_breakpoint_block(
8082 anchor,
8083 breakpoint.as_ref(),
8084 BreakpointPromptEditAction::HitCondition,
8085 window,
8086 cx,
8087 );
8088 })
8089 .log_err();
8090 })
8091 })
8092 }
8093
8094 fn render_breakpoint(
8095 &self,
8096 position: Anchor,
8097 row: DisplayRow,
8098 breakpoint: &Breakpoint,
8099 state: Option<BreakpointSessionState>,
8100 cx: &mut Context<Self>,
8101 ) -> IconButton {
8102 let is_rejected = state.is_some_and(|s| !s.verified);
8103 // Is it a breakpoint that shows up when hovering over gutter?
8104 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8105 (false, false),
8106 |PhantomBreakpointIndicator {
8107 is_active,
8108 display_row,
8109 collides_with_existing_breakpoint,
8110 }| {
8111 (
8112 is_active && display_row == row,
8113 collides_with_existing_breakpoint,
8114 )
8115 },
8116 );
8117
8118 let (color, icon) = {
8119 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8120 (false, false) => ui::IconName::DebugBreakpoint,
8121 (true, false) => ui::IconName::DebugLogBreakpoint,
8122 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8123 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8124 };
8125
8126 let color = if is_phantom {
8127 Color::Hint
8128 } else if is_rejected {
8129 Color::Disabled
8130 } else {
8131 Color::Debugger
8132 };
8133
8134 (color, icon)
8135 };
8136
8137 let breakpoint = Arc::from(breakpoint.clone());
8138
8139 let alt_as_text = gpui::Keystroke {
8140 modifiers: Modifiers::secondary_key(),
8141 ..Default::default()
8142 };
8143 let primary_action_text = if breakpoint.is_disabled() {
8144 "Enable breakpoint"
8145 } else if is_phantom && !collides_with_existing {
8146 "Set breakpoint"
8147 } else {
8148 "Unset breakpoint"
8149 };
8150 let focus_handle = self.focus_handle.clone();
8151
8152 let meta = if is_rejected {
8153 SharedString::from("No executable code is associated with this line.")
8154 } else if collides_with_existing && !breakpoint.is_disabled() {
8155 SharedString::from(format!(
8156 "{alt_as_text}-click to disable,\nright-click for more options."
8157 ))
8158 } else {
8159 SharedString::from("Right-click for more options.")
8160 };
8161 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8162 .icon_size(IconSize::XSmall)
8163 .size(ui::ButtonSize::None)
8164 .when(is_rejected, |this| {
8165 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8166 })
8167 .icon_color(color)
8168 .style(ButtonStyle::Transparent)
8169 .on_click(cx.listener({
8170 let breakpoint = breakpoint.clone();
8171
8172 move |editor, event: &ClickEvent, window, cx| {
8173 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8174 BreakpointEditAction::InvertState
8175 } else {
8176 BreakpointEditAction::Toggle
8177 };
8178
8179 window.focus(&editor.focus_handle(cx));
8180 editor.edit_breakpoint_at_anchor(
8181 position,
8182 breakpoint.as_ref().clone(),
8183 edit_action,
8184 cx,
8185 );
8186 }
8187 }))
8188 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8189 editor.set_breakpoint_context_menu(
8190 row,
8191 Some(position),
8192 event.position(),
8193 window,
8194 cx,
8195 );
8196 }))
8197 .tooltip(move |window, cx| {
8198 Tooltip::with_meta_in(
8199 primary_action_text,
8200 Some(&ToggleBreakpoint),
8201 meta.clone(),
8202 &focus_handle,
8203 window,
8204 cx,
8205 )
8206 })
8207 }
8208
8209 fn build_tasks_context(
8210 project: &Entity<Project>,
8211 buffer: &Entity<Buffer>,
8212 buffer_row: u32,
8213 tasks: &Arc<RunnableTasks>,
8214 cx: &mut Context<Self>,
8215 ) -> Task<Option<task::TaskContext>> {
8216 let position = Point::new(buffer_row, tasks.column);
8217 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8218 let location = Location {
8219 buffer: buffer.clone(),
8220 range: range_start..range_start,
8221 };
8222 // Fill in the environmental variables from the tree-sitter captures
8223 let mut captured_task_variables = TaskVariables::default();
8224 for (capture_name, value) in tasks.extra_variables.clone() {
8225 captured_task_variables.insert(
8226 task::VariableName::Custom(capture_name.into()),
8227 value.clone(),
8228 );
8229 }
8230 project.update(cx, |project, cx| {
8231 project.task_store().update(cx, |task_store, cx| {
8232 task_store.task_context_for_location(captured_task_variables, location, cx)
8233 })
8234 })
8235 }
8236
8237 pub fn spawn_nearest_task(
8238 &mut self,
8239 action: &SpawnNearestTask,
8240 window: &mut Window,
8241 cx: &mut Context<Self>,
8242 ) {
8243 let Some((workspace, _)) = self.workspace.clone() else {
8244 return;
8245 };
8246 let Some(project) = self.project.clone() else {
8247 return;
8248 };
8249
8250 // Try to find a closest, enclosing node using tree-sitter that has a task
8251 let Some((buffer, buffer_row, tasks)) = self
8252 .find_enclosing_node_task(cx)
8253 // Or find the task that's closest in row-distance.
8254 .or_else(|| self.find_closest_task(cx))
8255 else {
8256 return;
8257 };
8258
8259 let reveal_strategy = action.reveal;
8260 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8261 cx.spawn_in(window, async move |_, cx| {
8262 let context = task_context.await?;
8263 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8264
8265 let resolved = &mut resolved_task.resolved;
8266 resolved.reveal = reveal_strategy;
8267
8268 workspace
8269 .update_in(cx, |workspace, window, cx| {
8270 workspace.schedule_resolved_task(
8271 task_source_kind,
8272 resolved_task,
8273 false,
8274 window,
8275 cx,
8276 );
8277 })
8278 .ok()
8279 })
8280 .detach();
8281 }
8282
8283 fn find_closest_task(
8284 &mut self,
8285 cx: &mut Context<Self>,
8286 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8287 let cursor_row = self.selections.newest_adjusted(cx).head().row;
8288
8289 let ((buffer_id, row), tasks) = self
8290 .tasks
8291 .iter()
8292 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8293
8294 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8295 let tasks = Arc::new(tasks.to_owned());
8296 Some((buffer, *row, tasks))
8297 }
8298
8299 fn find_enclosing_node_task(
8300 &mut self,
8301 cx: &mut Context<Self>,
8302 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8303 let snapshot = self.buffer.read(cx).snapshot(cx);
8304 let offset = self.selections.newest::<usize>(cx).head();
8305 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8306 let buffer_id = excerpt.buffer().remote_id();
8307
8308 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8309 let mut cursor = layer.node().walk();
8310
8311 while cursor.goto_first_child_for_byte(offset).is_some() {
8312 if cursor.node().end_byte() == offset {
8313 cursor.goto_next_sibling();
8314 }
8315 }
8316
8317 // Ascend to the smallest ancestor that contains the range and has a task.
8318 loop {
8319 let node = cursor.node();
8320 let node_range = node.byte_range();
8321 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8322
8323 // Check if this node contains our offset
8324 if node_range.start <= offset && node_range.end >= offset {
8325 // If it contains offset, check for task
8326 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8327 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8328 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8329 }
8330 }
8331
8332 if !cursor.goto_parent() {
8333 break;
8334 }
8335 }
8336 None
8337 }
8338
8339 fn render_run_indicator(
8340 &self,
8341 _style: &EditorStyle,
8342 is_active: bool,
8343 row: DisplayRow,
8344 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8345 cx: &mut Context<Self>,
8346 ) -> IconButton {
8347 let color = Color::Muted;
8348 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8349
8350 IconButton::new(
8351 ("run_indicator", row.0 as usize),
8352 ui::IconName::PlayOutlined,
8353 )
8354 .shape(ui::IconButtonShape::Square)
8355 .icon_size(IconSize::XSmall)
8356 .icon_color(color)
8357 .toggle_state(is_active)
8358 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8359 let quick_launch = match e {
8360 ClickEvent::Keyboard(_) => true,
8361 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8362 };
8363
8364 window.focus(&editor.focus_handle(cx));
8365 editor.toggle_code_actions(
8366 &ToggleCodeActions {
8367 deployed_from: Some(CodeActionSource::RunMenu(row)),
8368 quick_launch,
8369 },
8370 window,
8371 cx,
8372 );
8373 }))
8374 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8375 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8376 }))
8377 }
8378
8379 pub fn context_menu_visible(&self) -> bool {
8380 !self.edit_prediction_preview_is_active()
8381 && self
8382 .context_menu
8383 .borrow()
8384 .as_ref()
8385 .map_or(false, |menu| menu.visible())
8386 }
8387
8388 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8389 self.context_menu
8390 .borrow()
8391 .as_ref()
8392 .map(|menu| menu.origin())
8393 }
8394
8395 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8396 self.context_menu_options = Some(options);
8397 }
8398
8399 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
8400 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
8401
8402 fn render_edit_prediction_popover(
8403 &mut self,
8404 text_bounds: &Bounds<Pixels>,
8405 content_origin: gpui::Point<Pixels>,
8406 right_margin: Pixels,
8407 editor_snapshot: &EditorSnapshot,
8408 visible_row_range: Range<DisplayRow>,
8409 scroll_top: f32,
8410 scroll_bottom: f32,
8411 line_layouts: &[LineWithInvisibles],
8412 line_height: Pixels,
8413 scroll_pixel_position: gpui::Point<Pixels>,
8414 newest_selection_head: Option<DisplayPoint>,
8415 editor_width: Pixels,
8416 style: &EditorStyle,
8417 window: &mut Window,
8418 cx: &mut App,
8419 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8420 if self.mode().is_minimap() {
8421 return None;
8422 }
8423 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8424
8425 if self.edit_prediction_visible_in_cursor_popover(true) {
8426 return None;
8427 }
8428
8429 match &active_edit_prediction.completion {
8430 EditPrediction::Move { target, .. } => {
8431 let target_display_point = target.to_display_point(editor_snapshot);
8432
8433 if self.edit_prediction_requires_modifier() {
8434 if !self.edit_prediction_preview_is_active() {
8435 return None;
8436 }
8437
8438 self.render_edit_prediction_modifier_jump_popover(
8439 text_bounds,
8440 content_origin,
8441 visible_row_range,
8442 line_layouts,
8443 line_height,
8444 scroll_pixel_position,
8445 newest_selection_head,
8446 target_display_point,
8447 window,
8448 cx,
8449 )
8450 } else {
8451 self.render_edit_prediction_eager_jump_popover(
8452 text_bounds,
8453 content_origin,
8454 editor_snapshot,
8455 visible_row_range,
8456 scroll_top,
8457 scroll_bottom,
8458 line_height,
8459 scroll_pixel_position,
8460 target_display_point,
8461 editor_width,
8462 window,
8463 cx,
8464 )
8465 }
8466 }
8467 EditPrediction::Edit {
8468 display_mode: EditDisplayMode::Inline,
8469 ..
8470 } => None,
8471 EditPrediction::Edit {
8472 display_mode: EditDisplayMode::TabAccept,
8473 edits,
8474 ..
8475 } => {
8476 let range = &edits.first()?.0;
8477 let target_display_point = range.end.to_display_point(editor_snapshot);
8478
8479 self.render_edit_prediction_end_of_line_popover(
8480 "Accept",
8481 editor_snapshot,
8482 visible_row_range,
8483 target_display_point,
8484 line_height,
8485 scroll_pixel_position,
8486 content_origin,
8487 editor_width,
8488 window,
8489 cx,
8490 )
8491 }
8492 EditPrediction::Edit {
8493 edits,
8494 edit_preview,
8495 display_mode: EditDisplayMode::DiffPopover,
8496 snapshot,
8497 } => self.render_edit_prediction_diff_popover(
8498 text_bounds,
8499 content_origin,
8500 right_margin,
8501 editor_snapshot,
8502 visible_row_range,
8503 line_layouts,
8504 line_height,
8505 scroll_pixel_position,
8506 newest_selection_head,
8507 editor_width,
8508 style,
8509 edits,
8510 edit_preview,
8511 snapshot,
8512 window,
8513 cx,
8514 ),
8515 }
8516 }
8517
8518 fn render_edit_prediction_modifier_jump_popover(
8519 &mut self,
8520 text_bounds: &Bounds<Pixels>,
8521 content_origin: gpui::Point<Pixels>,
8522 visible_row_range: Range<DisplayRow>,
8523 line_layouts: &[LineWithInvisibles],
8524 line_height: Pixels,
8525 scroll_pixel_position: gpui::Point<Pixels>,
8526 newest_selection_head: Option<DisplayPoint>,
8527 target_display_point: DisplayPoint,
8528 window: &mut Window,
8529 cx: &mut App,
8530 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8531 let scrolled_content_origin =
8532 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
8533
8534 const SCROLL_PADDING_Y: Pixels = px(12.);
8535
8536 if target_display_point.row() < visible_row_range.start {
8537 return self.render_edit_prediction_scroll_popover(
8538 |_| SCROLL_PADDING_Y,
8539 IconName::ArrowUp,
8540 visible_row_range,
8541 line_layouts,
8542 newest_selection_head,
8543 scrolled_content_origin,
8544 window,
8545 cx,
8546 );
8547 } else if target_display_point.row() >= visible_row_range.end {
8548 return self.render_edit_prediction_scroll_popover(
8549 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8550 IconName::ArrowDown,
8551 visible_row_range,
8552 line_layouts,
8553 newest_selection_head,
8554 scrolled_content_origin,
8555 window,
8556 cx,
8557 );
8558 }
8559
8560 const POLE_WIDTH: Pixels = px(2.);
8561
8562 let line_layout =
8563 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8564 let target_column = target_display_point.column() as usize;
8565
8566 let target_x = line_layout.x_for_index(target_column);
8567 let target_y =
8568 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
8569
8570 let flag_on_right = target_x < text_bounds.size.width / 2.;
8571
8572 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8573 border_color.l += 0.001;
8574
8575 let mut element = v_flex()
8576 .items_end()
8577 .when(flag_on_right, |el| el.items_start())
8578 .child(if flag_on_right {
8579 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8580 .rounded_bl(px(0.))
8581 .rounded_tl(px(0.))
8582 .border_l_2()
8583 .border_color(border_color)
8584 } else {
8585 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8586 .rounded_br(px(0.))
8587 .rounded_tr(px(0.))
8588 .border_r_2()
8589 .border_color(border_color)
8590 })
8591 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8592 .into_any();
8593
8594 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8595
8596 let mut origin = scrolled_content_origin + point(target_x, target_y)
8597 - point(
8598 if flag_on_right {
8599 POLE_WIDTH
8600 } else {
8601 size.width - POLE_WIDTH
8602 },
8603 size.height - line_height,
8604 );
8605
8606 origin.x = origin.x.max(content_origin.x);
8607
8608 element.prepaint_at(origin, window, cx);
8609
8610 Some((element, origin))
8611 }
8612
8613 fn render_edit_prediction_scroll_popover(
8614 &mut self,
8615 to_y: impl Fn(Size<Pixels>) -> Pixels,
8616 scroll_icon: IconName,
8617 visible_row_range: Range<DisplayRow>,
8618 line_layouts: &[LineWithInvisibles],
8619 newest_selection_head: Option<DisplayPoint>,
8620 scrolled_content_origin: gpui::Point<Pixels>,
8621 window: &mut Window,
8622 cx: &mut App,
8623 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8624 let mut element = self
8625 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
8626 .into_any();
8627
8628 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8629
8630 let cursor = newest_selection_head?;
8631 let cursor_row_layout =
8632 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8633 let cursor_column = cursor.column() as usize;
8634
8635 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8636
8637 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8638
8639 element.prepaint_at(origin, window, cx);
8640 Some((element, origin))
8641 }
8642
8643 fn render_edit_prediction_eager_jump_popover(
8644 &mut self,
8645 text_bounds: &Bounds<Pixels>,
8646 content_origin: gpui::Point<Pixels>,
8647 editor_snapshot: &EditorSnapshot,
8648 visible_row_range: Range<DisplayRow>,
8649 scroll_top: f32,
8650 scroll_bottom: f32,
8651 line_height: Pixels,
8652 scroll_pixel_position: gpui::Point<Pixels>,
8653 target_display_point: DisplayPoint,
8654 editor_width: Pixels,
8655 window: &mut Window,
8656 cx: &mut App,
8657 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8658 if target_display_point.row().as_f32() < scroll_top {
8659 let mut element = self
8660 .render_edit_prediction_line_popover(
8661 "Jump to Edit",
8662 Some(IconName::ArrowUp),
8663 window,
8664 cx,
8665 )?
8666 .into_any();
8667
8668 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8669 let offset = point(
8670 (text_bounds.size.width - size.width) / 2.,
8671 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8672 );
8673
8674 let origin = text_bounds.origin + offset;
8675 element.prepaint_at(origin, window, cx);
8676 Some((element, origin))
8677 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
8678 let mut element = self
8679 .render_edit_prediction_line_popover(
8680 "Jump to Edit",
8681 Some(IconName::ArrowDown),
8682 window,
8683 cx,
8684 )?
8685 .into_any();
8686
8687 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8688 let offset = point(
8689 (text_bounds.size.width - size.width) / 2.,
8690 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8691 );
8692
8693 let origin = text_bounds.origin + offset;
8694 element.prepaint_at(origin, window, cx);
8695 Some((element, origin))
8696 } else {
8697 self.render_edit_prediction_end_of_line_popover(
8698 "Jump to Edit",
8699 editor_snapshot,
8700 visible_row_range,
8701 target_display_point,
8702 line_height,
8703 scroll_pixel_position,
8704 content_origin,
8705 editor_width,
8706 window,
8707 cx,
8708 )
8709 }
8710 }
8711
8712 fn render_edit_prediction_end_of_line_popover(
8713 self: &mut Editor,
8714 label: &'static str,
8715 editor_snapshot: &EditorSnapshot,
8716 visible_row_range: Range<DisplayRow>,
8717 target_display_point: DisplayPoint,
8718 line_height: Pixels,
8719 scroll_pixel_position: gpui::Point<Pixels>,
8720 content_origin: gpui::Point<Pixels>,
8721 editor_width: Pixels,
8722 window: &mut Window,
8723 cx: &mut App,
8724 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8725 let target_line_end = DisplayPoint::new(
8726 target_display_point.row(),
8727 editor_snapshot.line_len(target_display_point.row()),
8728 );
8729
8730 let mut element = self
8731 .render_edit_prediction_line_popover(label, None, window, cx)?
8732 .into_any();
8733
8734 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8735
8736 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8737
8738 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
8739 let mut origin = start_point
8740 + line_origin
8741 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8742 origin.x = origin.x.max(content_origin.x);
8743
8744 let max_x = content_origin.x + editor_width - size.width;
8745
8746 if origin.x > max_x {
8747 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8748
8749 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8750 origin.y += offset;
8751 IconName::ArrowUp
8752 } else {
8753 origin.y -= offset;
8754 IconName::ArrowDown
8755 };
8756
8757 element = self
8758 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
8759 .into_any();
8760
8761 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8762
8763 origin.x = content_origin.x + editor_width - size.width - px(2.);
8764 }
8765
8766 element.prepaint_at(origin, window, cx);
8767 Some((element, origin))
8768 }
8769
8770 fn render_edit_prediction_diff_popover(
8771 self: &Editor,
8772 text_bounds: &Bounds<Pixels>,
8773 content_origin: gpui::Point<Pixels>,
8774 right_margin: Pixels,
8775 editor_snapshot: &EditorSnapshot,
8776 visible_row_range: Range<DisplayRow>,
8777 line_layouts: &[LineWithInvisibles],
8778 line_height: Pixels,
8779 scroll_pixel_position: gpui::Point<Pixels>,
8780 newest_selection_head: Option<DisplayPoint>,
8781 editor_width: Pixels,
8782 style: &EditorStyle,
8783 edits: &Vec<(Range<Anchor>, String)>,
8784 edit_preview: &Option<language::EditPreview>,
8785 snapshot: &language::BufferSnapshot,
8786 window: &mut Window,
8787 cx: &mut App,
8788 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8789 let edit_start = edits
8790 .first()
8791 .unwrap()
8792 .0
8793 .start
8794 .to_display_point(editor_snapshot);
8795 let edit_end = edits
8796 .last()
8797 .unwrap()
8798 .0
8799 .end
8800 .to_display_point(editor_snapshot);
8801
8802 let is_visible = visible_row_range.contains(&edit_start.row())
8803 || visible_row_range.contains(&edit_end.row());
8804 if !is_visible {
8805 return None;
8806 }
8807
8808 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
8809 crate::edit_prediction_edit_text(&snapshot, edits, edit_preview, false, cx)
8810 } else {
8811 // Fallback for providers without edit_preview
8812 crate::edit_prediction_fallback_text(edits, cx)
8813 };
8814
8815 let styled_text = highlighted_edits.to_styled_text(&style.text);
8816 let line_count = highlighted_edits.text.lines().count();
8817
8818 const BORDER_WIDTH: Pixels = px(1.);
8819
8820 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8821 let has_keybind = keybind.is_some();
8822
8823 let mut element = h_flex()
8824 .items_start()
8825 .child(
8826 h_flex()
8827 .bg(cx.theme().colors().editor_background)
8828 .border(BORDER_WIDTH)
8829 .shadow_xs()
8830 .border_color(cx.theme().colors().border)
8831 .rounded_l_lg()
8832 .when(line_count > 1, |el| el.rounded_br_lg())
8833 .pr_1()
8834 .child(styled_text),
8835 )
8836 .child(
8837 h_flex()
8838 .h(line_height + BORDER_WIDTH * 2.)
8839 .px_1p5()
8840 .gap_1()
8841 // Workaround: For some reason, there's a gap if we don't do this
8842 .ml(-BORDER_WIDTH)
8843 .shadow(vec![gpui::BoxShadow {
8844 color: gpui::black().opacity(0.05),
8845 offset: point(px(1.), px(1.)),
8846 blur_radius: px(2.),
8847 spread_radius: px(0.),
8848 }])
8849 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8850 .border(BORDER_WIDTH)
8851 .border_color(cx.theme().colors().border)
8852 .rounded_r_lg()
8853 .id("edit_prediction_diff_popover_keybind")
8854 .when(!has_keybind, |el| {
8855 let status_colors = cx.theme().status();
8856
8857 el.bg(status_colors.error_background)
8858 .border_color(status_colors.error.opacity(0.6))
8859 .child(Icon::new(IconName::Info).color(Color::Error))
8860 .cursor_default()
8861 .hoverable_tooltip(move |_window, cx| {
8862 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8863 })
8864 })
8865 .children(keybind),
8866 )
8867 .into_any();
8868
8869 let longest_row =
8870 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8871 let longest_line_width = if visible_row_range.contains(&longest_row) {
8872 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8873 } else {
8874 layout_line(
8875 longest_row,
8876 editor_snapshot,
8877 style,
8878 editor_width,
8879 |_| false,
8880 window,
8881 cx,
8882 )
8883 .width
8884 };
8885
8886 let viewport_bounds =
8887 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8888 right: -right_margin,
8889 ..Default::default()
8890 });
8891
8892 let x_after_longest =
8893 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
8894 - scroll_pixel_position.x;
8895
8896 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8897
8898 // Fully visible if it can be displayed within the window (allow overlapping other
8899 // panes). However, this is only allowed if the popover starts within text_bounds.
8900 let can_position_to_the_right = x_after_longest < text_bounds.right()
8901 && x_after_longest + element_bounds.width < viewport_bounds.right();
8902
8903 let mut origin = if can_position_to_the_right {
8904 point(
8905 x_after_longest,
8906 text_bounds.origin.y + edit_start.row().as_f32() * line_height
8907 - scroll_pixel_position.y,
8908 )
8909 } else {
8910 let cursor_row = newest_selection_head.map(|head| head.row());
8911 let above_edit = edit_start
8912 .row()
8913 .0
8914 .checked_sub(line_count as u32)
8915 .map(DisplayRow);
8916 let below_edit = Some(edit_end.row() + 1);
8917 let above_cursor =
8918 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
8919 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
8920
8921 // Place the edit popover adjacent to the edit if there is a location
8922 // available that is onscreen and does not obscure the cursor. Otherwise,
8923 // place it adjacent to the cursor.
8924 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
8925 .into_iter()
8926 .flatten()
8927 .find(|&start_row| {
8928 let end_row = start_row + line_count as u32;
8929 visible_row_range.contains(&start_row)
8930 && visible_row_range.contains(&end_row)
8931 && cursor_row.map_or(true, |cursor_row| {
8932 !((start_row..end_row).contains(&cursor_row))
8933 })
8934 })?;
8935
8936 content_origin
8937 + point(
8938 -scroll_pixel_position.x,
8939 row_target.as_f32() * line_height - scroll_pixel_position.y,
8940 )
8941 };
8942
8943 origin.x -= BORDER_WIDTH;
8944
8945 window.defer_draw(element, origin, 1);
8946
8947 // Do not return an element, since it will already be drawn due to defer_draw.
8948 None
8949 }
8950
8951 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
8952 px(30.)
8953 }
8954
8955 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
8956 if self.read_only(cx) {
8957 cx.theme().players().read_only()
8958 } else {
8959 self.style.as_ref().unwrap().local_player
8960 }
8961 }
8962
8963 fn render_edit_prediction_accept_keybind(
8964 &self,
8965 window: &mut Window,
8966 cx: &App,
8967 ) -> Option<AnyElement> {
8968 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
8969 let accept_keystroke = accept_binding.keystroke()?;
8970
8971 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8972
8973 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
8974 Color::Accent
8975 } else {
8976 Color::Muted
8977 };
8978
8979 h_flex()
8980 .px_0p5()
8981 .when(is_platform_style_mac, |parent| parent.gap_0p5())
8982 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8983 .text_size(TextSize::XSmall.rems(cx))
8984 .child(h_flex().children(ui::render_modifiers(
8985 &accept_keystroke.modifiers,
8986 PlatformStyle::platform(),
8987 Some(modifiers_color),
8988 Some(IconSize::XSmall.rems().into()),
8989 true,
8990 )))
8991 .when(is_platform_style_mac, |parent| {
8992 parent.child(accept_keystroke.key.clone())
8993 })
8994 .when(!is_platform_style_mac, |parent| {
8995 parent.child(
8996 Key::new(
8997 util::capitalize(&accept_keystroke.key),
8998 Some(Color::Default),
8999 )
9000 .size(Some(IconSize::XSmall.rems().into())),
9001 )
9002 })
9003 .into_any()
9004 .into()
9005 }
9006
9007 fn render_edit_prediction_line_popover(
9008 &self,
9009 label: impl Into<SharedString>,
9010 icon: Option<IconName>,
9011 window: &mut Window,
9012 cx: &App,
9013 ) -> Option<Stateful<Div>> {
9014 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9015
9016 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9017 let has_keybind = keybind.is_some();
9018
9019 let result = h_flex()
9020 .id("ep-line-popover")
9021 .py_0p5()
9022 .pl_1()
9023 .pr(padding_right)
9024 .gap_1()
9025 .rounded_md()
9026 .border_1()
9027 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9028 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9029 .shadow_xs()
9030 .when(!has_keybind, |el| {
9031 let status_colors = cx.theme().status();
9032
9033 el.bg(status_colors.error_background)
9034 .border_color(status_colors.error.opacity(0.6))
9035 .pl_2()
9036 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9037 .cursor_default()
9038 .hoverable_tooltip(move |_window, cx| {
9039 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9040 })
9041 })
9042 .children(keybind)
9043 .child(
9044 Label::new(label)
9045 .size(LabelSize::Small)
9046 .when(!has_keybind, |el| {
9047 el.color(cx.theme().status().error.into()).strikethrough()
9048 }),
9049 )
9050 .when(!has_keybind, |el| {
9051 el.child(
9052 h_flex().ml_1().child(
9053 Icon::new(IconName::Info)
9054 .size(IconSize::Small)
9055 .color(cx.theme().status().error.into()),
9056 ),
9057 )
9058 })
9059 .when_some(icon, |element, icon| {
9060 element.child(
9061 div()
9062 .mt(px(1.5))
9063 .child(Icon::new(icon).size(IconSize::Small)),
9064 )
9065 });
9066
9067 Some(result)
9068 }
9069
9070 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9071 let accent_color = cx.theme().colors().text_accent;
9072 let editor_bg_color = cx.theme().colors().editor_background;
9073 editor_bg_color.blend(accent_color.opacity(0.1))
9074 }
9075
9076 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9077 let accent_color = cx.theme().colors().text_accent;
9078 let editor_bg_color = cx.theme().colors().editor_background;
9079 editor_bg_color.blend(accent_color.opacity(0.6))
9080 }
9081 fn get_prediction_provider_icon_name(
9082 provider: &Option<RegisteredEditPredictionProvider>,
9083 ) -> IconName {
9084 match provider {
9085 Some(provider) => match provider.provider.name() {
9086 "copilot" => IconName::Copilot,
9087 "supermaven" => IconName::Supermaven,
9088 _ => IconName::ZedPredict,
9089 },
9090 None => IconName::ZedPredict,
9091 }
9092 }
9093
9094 fn render_edit_prediction_cursor_popover(
9095 &self,
9096 min_width: Pixels,
9097 max_width: Pixels,
9098 cursor_point: Point,
9099 style: &EditorStyle,
9100 accept_keystroke: Option<&gpui::Keystroke>,
9101 _window: &Window,
9102 cx: &mut Context<Editor>,
9103 ) -> Option<AnyElement> {
9104 let provider = self.edit_prediction_provider.as_ref()?;
9105 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9106
9107 if provider.provider.needs_terms_acceptance(cx) {
9108 return Some(
9109 h_flex()
9110 .min_w(min_width)
9111 .flex_1()
9112 .px_2()
9113 .py_1()
9114 .gap_3()
9115 .elevation_2(cx)
9116 .hover(|style| style.bg(cx.theme().colors().element_hover))
9117 .id("accept-terms")
9118 .cursor_pointer()
9119 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
9120 .on_click(cx.listener(|this, _event, window, cx| {
9121 cx.stop_propagation();
9122 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
9123 window.dispatch_action(
9124 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
9125 cx,
9126 );
9127 }))
9128 .child(
9129 h_flex()
9130 .flex_1()
9131 .gap_2()
9132 .child(Icon::new(provider_icon))
9133 .child(Label::new("Accept Terms of Service"))
9134 .child(div().w_full())
9135 .child(
9136 Icon::new(IconName::ArrowUpRight)
9137 .color(Color::Muted)
9138 .size(IconSize::Small),
9139 )
9140 .into_any_element(),
9141 )
9142 .into_any(),
9143 );
9144 }
9145
9146 let is_refreshing = provider.provider.is_refreshing(cx);
9147
9148 fn pending_completion_container(icon: IconName) -> Div {
9149 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9150 }
9151
9152 let completion = match &self.active_edit_prediction {
9153 Some(prediction) => {
9154 if !self.has_visible_completions_menu() {
9155 const RADIUS: Pixels = px(6.);
9156 const BORDER_WIDTH: Pixels = px(1.);
9157
9158 return Some(
9159 h_flex()
9160 .elevation_2(cx)
9161 .border(BORDER_WIDTH)
9162 .border_color(cx.theme().colors().border)
9163 .when(accept_keystroke.is_none(), |el| {
9164 el.border_color(cx.theme().status().error)
9165 })
9166 .rounded(RADIUS)
9167 .rounded_tl(px(0.))
9168 .overflow_hidden()
9169 .child(div().px_1p5().child(match &prediction.completion {
9170 EditPrediction::Move { target, snapshot } => {
9171 use text::ToPoint as _;
9172 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
9173 {
9174 Icon::new(IconName::ZedPredictDown)
9175 } else {
9176 Icon::new(IconName::ZedPredictUp)
9177 }
9178 }
9179 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9180 }))
9181 .child(
9182 h_flex()
9183 .gap_1()
9184 .py_1()
9185 .px_2()
9186 .rounded_r(RADIUS - BORDER_WIDTH)
9187 .border_l_1()
9188 .border_color(cx.theme().colors().border)
9189 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9190 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9191 el.child(
9192 Label::new("Hold")
9193 .size(LabelSize::Small)
9194 .when(accept_keystroke.is_none(), |el| {
9195 el.strikethrough()
9196 })
9197 .line_height_style(LineHeightStyle::UiLabel),
9198 )
9199 })
9200 .id("edit_prediction_cursor_popover_keybind")
9201 .when(accept_keystroke.is_none(), |el| {
9202 let status_colors = cx.theme().status();
9203
9204 el.bg(status_colors.error_background)
9205 .border_color(status_colors.error.opacity(0.6))
9206 .child(Icon::new(IconName::Info).color(Color::Error))
9207 .cursor_default()
9208 .hoverable_tooltip(move |_window, cx| {
9209 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9210 .into()
9211 })
9212 })
9213 .when_some(
9214 accept_keystroke.as_ref(),
9215 |el, accept_keystroke| {
9216 el.child(h_flex().children(ui::render_modifiers(
9217 &accept_keystroke.modifiers,
9218 PlatformStyle::platform(),
9219 Some(Color::Default),
9220 Some(IconSize::XSmall.rems().into()),
9221 false,
9222 )))
9223 },
9224 ),
9225 )
9226 .into_any(),
9227 );
9228 }
9229
9230 self.render_edit_prediction_cursor_popover_preview(
9231 prediction,
9232 cursor_point,
9233 style,
9234 cx,
9235 )?
9236 }
9237
9238 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9239 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9240 stale_completion,
9241 cursor_point,
9242 style,
9243 cx,
9244 )?,
9245
9246 None => pending_completion_container(provider_icon)
9247 .child(Label::new("...").size(LabelSize::Small)),
9248 },
9249
9250 None => pending_completion_container(provider_icon)
9251 .child(Label::new("...").size(LabelSize::Small)),
9252 };
9253
9254 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9255 completion
9256 .with_animation(
9257 "loading-completion",
9258 Animation::new(Duration::from_secs(2))
9259 .repeat()
9260 .with_easing(pulsating_between(0.4, 0.8)),
9261 |label, delta| label.opacity(delta),
9262 )
9263 .into_any_element()
9264 } else {
9265 completion.into_any_element()
9266 };
9267
9268 let has_completion = self.active_edit_prediction.is_some();
9269
9270 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9271 Some(
9272 h_flex()
9273 .min_w(min_width)
9274 .max_w(max_width)
9275 .flex_1()
9276 .elevation_2(cx)
9277 .border_color(cx.theme().colors().border)
9278 .child(
9279 div()
9280 .flex_1()
9281 .py_1()
9282 .px_2()
9283 .overflow_hidden()
9284 .child(completion),
9285 )
9286 .when_some(accept_keystroke, |el, accept_keystroke| {
9287 if !accept_keystroke.modifiers.modified() {
9288 return el;
9289 }
9290
9291 el.child(
9292 h_flex()
9293 .h_full()
9294 .border_l_1()
9295 .rounded_r_lg()
9296 .border_color(cx.theme().colors().border)
9297 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9298 .gap_1()
9299 .py_1()
9300 .px_2()
9301 .child(
9302 h_flex()
9303 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9304 .when(is_platform_style_mac, |parent| parent.gap_1())
9305 .child(h_flex().children(ui::render_modifiers(
9306 &accept_keystroke.modifiers,
9307 PlatformStyle::platform(),
9308 Some(if !has_completion {
9309 Color::Muted
9310 } else {
9311 Color::Default
9312 }),
9313 None,
9314 false,
9315 ))),
9316 )
9317 .child(Label::new("Preview").into_any_element())
9318 .opacity(if has_completion { 1.0 } else { 0.4 }),
9319 )
9320 })
9321 .into_any(),
9322 )
9323 }
9324
9325 fn render_edit_prediction_cursor_popover_preview(
9326 &self,
9327 completion: &EditPredictionState,
9328 cursor_point: Point,
9329 style: &EditorStyle,
9330 cx: &mut Context<Editor>,
9331 ) -> Option<Div> {
9332 use text::ToPoint as _;
9333
9334 fn render_relative_row_jump(
9335 prefix: impl Into<String>,
9336 current_row: u32,
9337 target_row: u32,
9338 ) -> Div {
9339 let (row_diff, arrow) = if target_row < current_row {
9340 (current_row - target_row, IconName::ArrowUp)
9341 } else {
9342 (target_row - current_row, IconName::ArrowDown)
9343 };
9344
9345 h_flex()
9346 .child(
9347 Label::new(format!("{}{}", prefix.into(), row_diff))
9348 .color(Color::Muted)
9349 .size(LabelSize::Small),
9350 )
9351 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9352 }
9353
9354 let supports_jump = self
9355 .edit_prediction_provider
9356 .as_ref()
9357 .map(|provider| provider.provider.supports_jump_to_edit())
9358 .unwrap_or(true);
9359
9360 match &completion.completion {
9361 EditPrediction::Move {
9362 target, snapshot, ..
9363 } => {
9364 if !supports_jump {
9365 return None;
9366 }
9367
9368 Some(
9369 h_flex()
9370 .px_2()
9371 .gap_2()
9372 .flex_1()
9373 .child(
9374 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
9375 Icon::new(IconName::ZedPredictDown)
9376 } else {
9377 Icon::new(IconName::ZedPredictUp)
9378 },
9379 )
9380 .child(Label::new("Jump to Edit")),
9381 )
9382 }
9383
9384 EditPrediction::Edit {
9385 edits,
9386 edit_preview,
9387 snapshot,
9388 display_mode: _,
9389 } => {
9390 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
9391
9392 let (highlighted_edits, has_more_lines) =
9393 if let Some(edit_preview) = edit_preview.as_ref() {
9394 crate::edit_prediction_edit_text(&snapshot, &edits, edit_preview, true, cx)
9395 .first_line_preview()
9396 } else {
9397 crate::edit_prediction_fallback_text(&edits, cx).first_line_preview()
9398 };
9399
9400 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9401 .with_default_highlights(&style.text, highlighted_edits.highlights);
9402
9403 let preview = h_flex()
9404 .gap_1()
9405 .min_w_16()
9406 .child(styled_text)
9407 .when(has_more_lines, |parent| parent.child("…"));
9408
9409 let left = if supports_jump && first_edit_row != cursor_point.row {
9410 render_relative_row_jump("", cursor_point.row, first_edit_row)
9411 .into_any_element()
9412 } else {
9413 let icon_name =
9414 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9415 Icon::new(icon_name).into_any_element()
9416 };
9417
9418 Some(
9419 h_flex()
9420 .h_full()
9421 .flex_1()
9422 .gap_2()
9423 .pr_1()
9424 .overflow_x_hidden()
9425 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9426 .child(left)
9427 .child(preview),
9428 )
9429 }
9430 }
9431 }
9432
9433 pub fn render_context_menu(
9434 &self,
9435 style: &EditorStyle,
9436 max_height_in_lines: u32,
9437 window: &mut Window,
9438 cx: &mut Context<Editor>,
9439 ) -> Option<AnyElement> {
9440 let menu = self.context_menu.borrow();
9441 let menu = menu.as_ref()?;
9442 if !menu.visible() {
9443 return None;
9444 };
9445 Some(menu.render(style, max_height_in_lines, window, cx))
9446 }
9447
9448 fn render_context_menu_aside(
9449 &mut self,
9450 max_size: Size<Pixels>,
9451 window: &mut Window,
9452 cx: &mut Context<Editor>,
9453 ) -> Option<AnyElement> {
9454 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9455 if menu.visible() {
9456 menu.render_aside(max_size, window, cx)
9457 } else {
9458 None
9459 }
9460 })
9461 }
9462
9463 fn hide_context_menu(
9464 &mut self,
9465 window: &mut Window,
9466 cx: &mut Context<Self>,
9467 ) -> Option<CodeContextMenu> {
9468 cx.notify();
9469 self.completion_tasks.clear();
9470 let context_menu = self.context_menu.borrow_mut().take();
9471 self.stale_edit_prediction_in_menu.take();
9472 self.update_visible_edit_prediction(window, cx);
9473 if let Some(CodeContextMenu::Completions(_)) = &context_menu {
9474 if let Some(completion_provider) = &self.completion_provider {
9475 completion_provider.selection_changed(None, window, cx);
9476 }
9477 }
9478 context_menu
9479 }
9480
9481 fn show_snippet_choices(
9482 &mut self,
9483 choices: &Vec<String>,
9484 selection: Range<Anchor>,
9485 cx: &mut Context<Self>,
9486 ) {
9487 let buffer_id = match (&selection.start.buffer_id, &selection.end.buffer_id) {
9488 (Some(a), Some(b)) if a == b => a,
9489 _ => {
9490 log::error!("expected anchor range to have matching buffer IDs");
9491 return;
9492 }
9493 };
9494 let multi_buffer = self.buffer().read(cx);
9495 let Some(buffer) = multi_buffer.buffer(*buffer_id) else {
9496 return;
9497 };
9498
9499 let id = post_inc(&mut self.next_completion_id);
9500 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9501 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9502 CompletionsMenu::new_snippet_choices(
9503 id,
9504 true,
9505 choices,
9506 selection,
9507 buffer,
9508 snippet_sort_order,
9509 ),
9510 ));
9511 }
9512
9513 pub fn insert_snippet(
9514 &mut self,
9515 insertion_ranges: &[Range<usize>],
9516 snippet: Snippet,
9517 window: &mut Window,
9518 cx: &mut Context<Self>,
9519 ) -> Result<()> {
9520 struct Tabstop<T> {
9521 is_end_tabstop: bool,
9522 ranges: Vec<Range<T>>,
9523 choices: Option<Vec<String>>,
9524 }
9525
9526 let tabstops = self.buffer.update(cx, |buffer, cx| {
9527 let snippet_text: Arc<str> = snippet.text.clone().into();
9528 let edits = insertion_ranges
9529 .iter()
9530 .cloned()
9531 .map(|range| (range, snippet_text.clone()));
9532 let autoindent_mode = AutoindentMode::Block {
9533 original_indent_columns: Vec::new(),
9534 };
9535 buffer.edit(edits, Some(autoindent_mode), cx);
9536
9537 let snapshot = &*buffer.read(cx);
9538 let snippet = &snippet;
9539 snippet
9540 .tabstops
9541 .iter()
9542 .map(|tabstop| {
9543 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
9544 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9545 });
9546 let mut tabstop_ranges = tabstop
9547 .ranges
9548 .iter()
9549 .flat_map(|tabstop_range| {
9550 let mut delta = 0_isize;
9551 insertion_ranges.iter().map(move |insertion_range| {
9552 let insertion_start = insertion_range.start as isize + delta;
9553 delta +=
9554 snippet.text.len() as isize - insertion_range.len() as isize;
9555
9556 let start = ((insertion_start + tabstop_range.start) as usize)
9557 .min(snapshot.len());
9558 let end = ((insertion_start + tabstop_range.end) as usize)
9559 .min(snapshot.len());
9560 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9561 })
9562 })
9563 .collect::<Vec<_>>();
9564 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9565
9566 Tabstop {
9567 is_end_tabstop,
9568 ranges: tabstop_ranges,
9569 choices: tabstop.choices.clone(),
9570 }
9571 })
9572 .collect::<Vec<_>>()
9573 });
9574 if let Some(tabstop) = tabstops.first() {
9575 self.change_selections(Default::default(), window, cx, |s| {
9576 // Reverse order so that the first range is the newest created selection.
9577 // Completions will use it and autoscroll will prioritize it.
9578 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9579 });
9580
9581 if let Some(choices) = &tabstop.choices {
9582 if let Some(selection) = tabstop.ranges.first() {
9583 self.show_snippet_choices(choices, selection.clone(), cx)
9584 }
9585 }
9586
9587 // If we're already at the last tabstop and it's at the end of the snippet,
9588 // we're done, we don't need to keep the state around.
9589 if !tabstop.is_end_tabstop {
9590 let choices = tabstops
9591 .iter()
9592 .map(|tabstop| tabstop.choices.clone())
9593 .collect();
9594
9595 let ranges = tabstops
9596 .into_iter()
9597 .map(|tabstop| tabstop.ranges)
9598 .collect::<Vec<_>>();
9599
9600 self.snippet_stack.push(SnippetState {
9601 active_index: 0,
9602 ranges,
9603 choices,
9604 });
9605 }
9606
9607 // Check whether the just-entered snippet ends with an auto-closable bracket.
9608 if self.autoclose_regions.is_empty() {
9609 let snapshot = self.buffer.read(cx).snapshot(cx);
9610 let mut all_selections = self.selections.all::<Point>(cx);
9611 for selection in &mut all_selections {
9612 let selection_head = selection.head();
9613 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9614 continue;
9615 };
9616
9617 let mut bracket_pair = None;
9618 let max_lookup_length = scope
9619 .brackets()
9620 .map(|(pair, _)| {
9621 pair.start
9622 .as_str()
9623 .chars()
9624 .count()
9625 .max(pair.end.as_str().chars().count())
9626 })
9627 .max();
9628 if let Some(max_lookup_length) = max_lookup_length {
9629 let next_text = snapshot
9630 .chars_at(selection_head)
9631 .take(max_lookup_length)
9632 .collect::<String>();
9633 let prev_text = snapshot
9634 .reversed_chars_at(selection_head)
9635 .take(max_lookup_length)
9636 .collect::<String>();
9637
9638 for (pair, enabled) in scope.brackets() {
9639 if enabled
9640 && pair.close
9641 && prev_text.starts_with(pair.start.as_str())
9642 && next_text.starts_with(pair.end.as_str())
9643 {
9644 bracket_pair = Some(pair.clone());
9645 break;
9646 }
9647 }
9648 }
9649
9650 if let Some(pair) = bracket_pair {
9651 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9652 let autoclose_enabled =
9653 self.use_autoclose && snapshot_settings.use_autoclose;
9654 if autoclose_enabled {
9655 let start = snapshot.anchor_after(selection_head);
9656 let end = snapshot.anchor_after(selection_head);
9657 self.autoclose_regions.push(AutocloseRegion {
9658 selection_id: selection.id,
9659 range: start..end,
9660 pair,
9661 });
9662 }
9663 }
9664 }
9665 }
9666 }
9667 Ok(())
9668 }
9669
9670 pub fn move_to_next_snippet_tabstop(
9671 &mut self,
9672 window: &mut Window,
9673 cx: &mut Context<Self>,
9674 ) -> bool {
9675 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9676 }
9677
9678 pub fn move_to_prev_snippet_tabstop(
9679 &mut self,
9680 window: &mut Window,
9681 cx: &mut Context<Self>,
9682 ) -> bool {
9683 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9684 }
9685
9686 pub fn move_to_snippet_tabstop(
9687 &mut self,
9688 bias: Bias,
9689 window: &mut Window,
9690 cx: &mut Context<Self>,
9691 ) -> bool {
9692 if let Some(mut snippet) = self.snippet_stack.pop() {
9693 match bias {
9694 Bias::Left => {
9695 if snippet.active_index > 0 {
9696 snippet.active_index -= 1;
9697 } else {
9698 self.snippet_stack.push(snippet);
9699 return false;
9700 }
9701 }
9702 Bias::Right => {
9703 if snippet.active_index + 1 < snippet.ranges.len() {
9704 snippet.active_index += 1;
9705 } else {
9706 self.snippet_stack.push(snippet);
9707 return false;
9708 }
9709 }
9710 }
9711 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9712 self.change_selections(Default::default(), window, cx, |s| {
9713 // Reverse order so that the first range is the newest created selection.
9714 // Completions will use it and autoscroll will prioritize it.
9715 s.select_ranges(current_ranges.iter().rev().cloned())
9716 });
9717
9718 if let Some(choices) = &snippet.choices[snippet.active_index] {
9719 if let Some(selection) = current_ranges.first() {
9720 self.show_snippet_choices(&choices, selection.clone(), cx);
9721 }
9722 }
9723
9724 // If snippet state is not at the last tabstop, push it back on the stack
9725 if snippet.active_index + 1 < snippet.ranges.len() {
9726 self.snippet_stack.push(snippet);
9727 }
9728 return true;
9729 }
9730 }
9731
9732 false
9733 }
9734
9735 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9736 self.transact(window, cx, |this, window, cx| {
9737 this.select_all(&SelectAll, window, cx);
9738 this.insert("", window, cx);
9739 });
9740 }
9741
9742 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9743 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9744 self.transact(window, cx, |this, window, cx| {
9745 this.select_autoclose_pair(window, cx);
9746 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9747 if !this.linked_edit_ranges.is_empty() {
9748 let selections = this.selections.all::<MultiBufferPoint>(cx);
9749 let snapshot = this.buffer.read(cx).snapshot(cx);
9750
9751 for selection in selections.iter() {
9752 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9753 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9754 if selection_start.buffer_id != selection_end.buffer_id {
9755 continue;
9756 }
9757 if let Some(ranges) =
9758 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9759 {
9760 for (buffer, entries) in ranges {
9761 linked_ranges.entry(buffer).or_default().extend(entries);
9762 }
9763 }
9764 }
9765 }
9766
9767 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
9768 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9769 for selection in &mut selections {
9770 if selection.is_empty() {
9771 let old_head = selection.head();
9772 let mut new_head =
9773 movement::left(&display_map, old_head.to_display_point(&display_map))
9774 .to_point(&display_map);
9775 if let Some((buffer, line_buffer_range)) = display_map
9776 .buffer_snapshot
9777 .buffer_line_for_row(MultiBufferRow(old_head.row))
9778 {
9779 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9780 let indent_len = match indent_size.kind {
9781 IndentKind::Space => {
9782 buffer.settings_at(line_buffer_range.start, cx).tab_size
9783 }
9784 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9785 };
9786 if old_head.column <= indent_size.len && old_head.column > 0 {
9787 let indent_len = indent_len.get();
9788 new_head = cmp::min(
9789 new_head,
9790 MultiBufferPoint::new(
9791 old_head.row,
9792 ((old_head.column - 1) / indent_len) * indent_len,
9793 ),
9794 );
9795 }
9796 }
9797
9798 selection.set_head(new_head, SelectionGoal::None);
9799 }
9800 }
9801
9802 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9803 this.insert("", window, cx);
9804 let empty_str: Arc<str> = Arc::from("");
9805 for (buffer, edits) in linked_ranges {
9806 let snapshot = buffer.read(cx).snapshot();
9807 use text::ToPoint as TP;
9808
9809 let edits = edits
9810 .into_iter()
9811 .map(|range| {
9812 let end_point = TP::to_point(&range.end, &snapshot);
9813 let mut start_point = TP::to_point(&range.start, &snapshot);
9814
9815 if end_point == start_point {
9816 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9817 .saturating_sub(1);
9818 start_point =
9819 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9820 };
9821
9822 (start_point..end_point, empty_str.clone())
9823 })
9824 .sorted_by_key(|(range, _)| range.start)
9825 .collect::<Vec<_>>();
9826 buffer.update(cx, |this, cx| {
9827 this.edit(edits, None, cx);
9828 })
9829 }
9830 this.refresh_edit_prediction(true, false, window, cx);
9831 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
9832 });
9833 }
9834
9835 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9836 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9837 self.transact(window, cx, |this, window, cx| {
9838 this.change_selections(Default::default(), window, cx, |s| {
9839 s.move_with(|map, selection| {
9840 if selection.is_empty() {
9841 let cursor = movement::right(map, selection.head());
9842 selection.end = cursor;
9843 selection.reversed = true;
9844 selection.goal = SelectionGoal::None;
9845 }
9846 })
9847 });
9848 this.insert("", window, cx);
9849 this.refresh_edit_prediction(true, false, window, cx);
9850 });
9851 }
9852
9853 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9854 if self.mode.is_single_line() {
9855 cx.propagate();
9856 return;
9857 }
9858
9859 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9860 if self.move_to_prev_snippet_tabstop(window, cx) {
9861 return;
9862 }
9863 self.outdent(&Outdent, window, cx);
9864 }
9865
9866 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9867 if self.mode.is_single_line() {
9868 cx.propagate();
9869 return;
9870 }
9871
9872 if self.move_to_next_snippet_tabstop(window, cx) {
9873 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9874 return;
9875 }
9876 if self.read_only(cx) {
9877 return;
9878 }
9879 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9880 let mut selections = self.selections.all_adjusted(cx);
9881 let buffer = self.buffer.read(cx);
9882 let snapshot = buffer.snapshot(cx);
9883 let rows_iter = selections.iter().map(|s| s.head().row);
9884 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9885
9886 let has_some_cursor_in_whitespace = selections
9887 .iter()
9888 .filter(|selection| selection.is_empty())
9889 .any(|selection| {
9890 let cursor = selection.head();
9891 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9892 cursor.column < current_indent.len
9893 });
9894
9895 let mut edits = Vec::new();
9896 let mut prev_edited_row = 0;
9897 let mut row_delta = 0;
9898 for selection in &mut selections {
9899 if selection.start.row != prev_edited_row {
9900 row_delta = 0;
9901 }
9902 prev_edited_row = selection.end.row;
9903
9904 // If the selection is non-empty, then increase the indentation of the selected lines.
9905 if !selection.is_empty() {
9906 row_delta =
9907 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9908 continue;
9909 }
9910
9911 let cursor = selection.head();
9912 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9913 if let Some(suggested_indent) =
9914 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
9915 {
9916 // Don't do anything if already at suggested indent
9917 // and there is any other cursor which is not
9918 if has_some_cursor_in_whitespace
9919 && cursor.column == current_indent.len
9920 && current_indent.len == suggested_indent.len
9921 {
9922 continue;
9923 }
9924
9925 // Adjust line and move cursor to suggested indent
9926 // if cursor is not at suggested indent
9927 if cursor.column < suggested_indent.len
9928 && cursor.column <= current_indent.len
9929 && current_indent.len <= suggested_indent.len
9930 {
9931 selection.start = Point::new(cursor.row, suggested_indent.len);
9932 selection.end = selection.start;
9933 if row_delta == 0 {
9934 edits.extend(Buffer::edit_for_indent_size_adjustment(
9935 cursor.row,
9936 current_indent,
9937 suggested_indent,
9938 ));
9939 row_delta = suggested_indent.len - current_indent.len;
9940 }
9941 continue;
9942 }
9943
9944 // If current indent is more than suggested indent
9945 // only move cursor to current indent and skip indent
9946 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
9947 selection.start = Point::new(cursor.row, current_indent.len);
9948 selection.end = selection.start;
9949 continue;
9950 }
9951 }
9952
9953 // Otherwise, insert a hard or soft tab.
9954 let settings = buffer.language_settings_at(cursor, cx);
9955 let tab_size = if settings.hard_tabs {
9956 IndentSize::tab()
9957 } else {
9958 let tab_size = settings.tab_size.get();
9959 let indent_remainder = snapshot
9960 .text_for_range(Point::new(cursor.row, 0)..cursor)
9961 .flat_map(str::chars)
9962 .fold(row_delta % tab_size, |counter: u32, c| {
9963 if c == '\t' {
9964 0
9965 } else {
9966 (counter + 1) % tab_size
9967 }
9968 });
9969
9970 let chars_to_next_tab_stop = tab_size - indent_remainder;
9971 IndentSize::spaces(chars_to_next_tab_stop)
9972 };
9973 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
9974 selection.end = selection.start;
9975 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
9976 row_delta += tab_size.len;
9977 }
9978
9979 self.transact(window, cx, |this, window, cx| {
9980 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9981 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9982 this.refresh_edit_prediction(true, false, window, cx);
9983 });
9984 }
9985
9986 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
9987 if self.read_only(cx) {
9988 return;
9989 }
9990 if self.mode.is_single_line() {
9991 cx.propagate();
9992 return;
9993 }
9994
9995 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9996 let mut selections = self.selections.all::<Point>(cx);
9997 let mut prev_edited_row = 0;
9998 let mut row_delta = 0;
9999 let mut edits = Vec::new();
10000 let buffer = self.buffer.read(cx);
10001 let snapshot = buffer.snapshot(cx);
10002 for selection in &mut selections {
10003 if selection.start.row != prev_edited_row {
10004 row_delta = 0;
10005 }
10006 prev_edited_row = selection.end.row;
10007
10008 row_delta =
10009 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10010 }
10011
10012 self.transact(window, cx, |this, window, cx| {
10013 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10014 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10015 });
10016 }
10017
10018 fn indent_selection(
10019 buffer: &MultiBuffer,
10020 snapshot: &MultiBufferSnapshot,
10021 selection: &mut Selection<Point>,
10022 edits: &mut Vec<(Range<Point>, String)>,
10023 delta_for_start_row: u32,
10024 cx: &App,
10025 ) -> u32 {
10026 let settings = buffer.language_settings_at(selection.start, cx);
10027 let tab_size = settings.tab_size.get();
10028 let indent_kind = if settings.hard_tabs {
10029 IndentKind::Tab
10030 } else {
10031 IndentKind::Space
10032 };
10033 let mut start_row = selection.start.row;
10034 let mut end_row = selection.end.row + 1;
10035
10036 // If a selection ends at the beginning of a line, don't indent
10037 // that last line.
10038 if selection.end.column == 0 && selection.end.row > selection.start.row {
10039 end_row -= 1;
10040 }
10041
10042 // Avoid re-indenting a row that has already been indented by a
10043 // previous selection, but still update this selection's column
10044 // to reflect that indentation.
10045 if delta_for_start_row > 0 {
10046 start_row += 1;
10047 selection.start.column += delta_for_start_row;
10048 if selection.end.row == selection.start.row {
10049 selection.end.column += delta_for_start_row;
10050 }
10051 }
10052
10053 let mut delta_for_end_row = 0;
10054 let has_multiple_rows = start_row + 1 != end_row;
10055 for row in start_row..end_row {
10056 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10057 let indent_delta = match (current_indent.kind, indent_kind) {
10058 (IndentKind::Space, IndentKind::Space) => {
10059 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10060 IndentSize::spaces(columns_to_next_tab_stop)
10061 }
10062 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10063 (_, IndentKind::Tab) => IndentSize::tab(),
10064 };
10065
10066 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10067 0
10068 } else {
10069 selection.start.column
10070 };
10071 let row_start = Point::new(row, start);
10072 edits.push((
10073 row_start..row_start,
10074 indent_delta.chars().collect::<String>(),
10075 ));
10076
10077 // Update this selection's endpoints to reflect the indentation.
10078 if row == selection.start.row {
10079 selection.start.column += indent_delta.len;
10080 }
10081 if row == selection.end.row {
10082 selection.end.column += indent_delta.len;
10083 delta_for_end_row = indent_delta.len;
10084 }
10085 }
10086
10087 if selection.start.row == selection.end.row {
10088 delta_for_start_row + delta_for_end_row
10089 } else {
10090 delta_for_end_row
10091 }
10092 }
10093
10094 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10095 if self.read_only(cx) {
10096 return;
10097 }
10098 if self.mode.is_single_line() {
10099 cx.propagate();
10100 return;
10101 }
10102
10103 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10104 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10105 let selections = self.selections.all::<Point>(cx);
10106 let mut deletion_ranges = Vec::new();
10107 let mut last_outdent = None;
10108 {
10109 let buffer = self.buffer.read(cx);
10110 let snapshot = buffer.snapshot(cx);
10111 for selection in &selections {
10112 let settings = buffer.language_settings_at(selection.start, cx);
10113 let tab_size = settings.tab_size.get();
10114 let mut rows = selection.spanned_rows(false, &display_map);
10115
10116 // Avoid re-outdenting a row that has already been outdented by a
10117 // previous selection.
10118 if let Some(last_row) = last_outdent {
10119 if last_row == rows.start {
10120 rows.start = rows.start.next_row();
10121 }
10122 }
10123 let has_multiple_rows = rows.len() > 1;
10124 for row in rows.iter_rows() {
10125 let indent_size = snapshot.indent_size_for_line(row);
10126 if indent_size.len > 0 {
10127 let deletion_len = match indent_size.kind {
10128 IndentKind::Space => {
10129 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10130 if columns_to_prev_tab_stop == 0 {
10131 tab_size
10132 } else {
10133 columns_to_prev_tab_stop
10134 }
10135 }
10136 IndentKind::Tab => 1,
10137 };
10138 let start = if has_multiple_rows
10139 || deletion_len > selection.start.column
10140 || indent_size.len < selection.start.column
10141 {
10142 0
10143 } else {
10144 selection.start.column - deletion_len
10145 };
10146 deletion_ranges.push(
10147 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10148 );
10149 last_outdent = Some(row);
10150 }
10151 }
10152 }
10153 }
10154
10155 self.transact(window, cx, |this, window, cx| {
10156 this.buffer.update(cx, |buffer, cx| {
10157 let empty_str: Arc<str> = Arc::default();
10158 buffer.edit(
10159 deletion_ranges
10160 .into_iter()
10161 .map(|range| (range, empty_str.clone())),
10162 None,
10163 cx,
10164 );
10165 });
10166 let selections = this.selections.all::<usize>(cx);
10167 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10168 });
10169 }
10170
10171 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10172 if self.read_only(cx) {
10173 return;
10174 }
10175 if self.mode.is_single_line() {
10176 cx.propagate();
10177 return;
10178 }
10179
10180 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10181 let selections = self
10182 .selections
10183 .all::<usize>(cx)
10184 .into_iter()
10185 .map(|s| s.range());
10186
10187 self.transact(window, cx, |this, window, cx| {
10188 this.buffer.update(cx, |buffer, cx| {
10189 buffer.autoindent_ranges(selections, cx);
10190 });
10191 let selections = this.selections.all::<usize>(cx);
10192 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10193 });
10194 }
10195
10196 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10197 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10198 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10199 let selections = self.selections.all::<Point>(cx);
10200
10201 let mut new_cursors = Vec::new();
10202 let mut edit_ranges = Vec::new();
10203 let mut selections = selections.iter().peekable();
10204 while let Some(selection) = selections.next() {
10205 let mut rows = selection.spanned_rows(false, &display_map);
10206 let goal_display_column = selection.head().to_display_point(&display_map).column();
10207
10208 // Accumulate contiguous regions of rows that we want to delete.
10209 while let Some(next_selection) = selections.peek() {
10210 let next_rows = next_selection.spanned_rows(false, &display_map);
10211 if next_rows.start <= rows.end {
10212 rows.end = next_rows.end;
10213 selections.next().unwrap();
10214 } else {
10215 break;
10216 }
10217 }
10218
10219 let buffer = &display_map.buffer_snapshot;
10220 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
10221 let edit_end;
10222 let cursor_buffer_row;
10223 if buffer.max_point().row >= rows.end.0 {
10224 // If there's a line after the range, delete the \n from the end of the row range
10225 // and position the cursor on the next line.
10226 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
10227 cursor_buffer_row = rows.end;
10228 } else {
10229 // If there isn't a line after the range, delete the \n from the line before the
10230 // start of the row range and position the cursor there.
10231 edit_start = edit_start.saturating_sub(1);
10232 edit_end = buffer.len();
10233 cursor_buffer_row = rows.start.previous_row();
10234 }
10235
10236 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
10237 *cursor.column_mut() =
10238 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
10239
10240 new_cursors.push((
10241 selection.id,
10242 buffer.anchor_after(cursor.to_point(&display_map)),
10243 ));
10244 edit_ranges.push(edit_start..edit_end);
10245 }
10246
10247 self.transact(window, cx, |this, window, cx| {
10248 let buffer = this.buffer.update(cx, |buffer, cx| {
10249 let empty_str: Arc<str> = Arc::default();
10250 buffer.edit(
10251 edit_ranges
10252 .into_iter()
10253 .map(|range| (range, empty_str.clone())),
10254 None,
10255 cx,
10256 );
10257 buffer.snapshot(cx)
10258 });
10259 let new_selections = new_cursors
10260 .into_iter()
10261 .map(|(id, cursor)| {
10262 let cursor = cursor.to_point(&buffer);
10263 Selection {
10264 id,
10265 start: cursor,
10266 end: cursor,
10267 reversed: false,
10268 goal: SelectionGoal::None,
10269 }
10270 })
10271 .collect();
10272
10273 this.change_selections(Default::default(), window, cx, |s| {
10274 s.select(new_selections);
10275 });
10276 });
10277 }
10278
10279 pub fn join_lines_impl(
10280 &mut self,
10281 insert_whitespace: bool,
10282 window: &mut Window,
10283 cx: &mut Context<Self>,
10284 ) {
10285 if self.read_only(cx) {
10286 return;
10287 }
10288 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10289 for selection in self.selections.all::<Point>(cx) {
10290 let start = MultiBufferRow(selection.start.row);
10291 // Treat single line selections as if they include the next line. Otherwise this action
10292 // would do nothing for single line selections individual cursors.
10293 let end = if selection.start.row == selection.end.row {
10294 MultiBufferRow(selection.start.row + 1)
10295 } else {
10296 MultiBufferRow(selection.end.row)
10297 };
10298
10299 if let Some(last_row_range) = row_ranges.last_mut() {
10300 if start <= last_row_range.end {
10301 last_row_range.end = end;
10302 continue;
10303 }
10304 }
10305 row_ranges.push(start..end);
10306 }
10307
10308 let snapshot = self.buffer.read(cx).snapshot(cx);
10309 let mut cursor_positions = Vec::new();
10310 for row_range in &row_ranges {
10311 let anchor = snapshot.anchor_before(Point::new(
10312 row_range.end.previous_row().0,
10313 snapshot.line_len(row_range.end.previous_row()),
10314 ));
10315 cursor_positions.push(anchor..anchor);
10316 }
10317
10318 self.transact(window, cx, |this, window, cx| {
10319 for row_range in row_ranges.into_iter().rev() {
10320 for row in row_range.iter_rows().rev() {
10321 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10322 let next_line_row = row.next_row();
10323 let indent = snapshot.indent_size_for_line(next_line_row);
10324 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10325
10326 let replace =
10327 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10328 " "
10329 } else {
10330 ""
10331 };
10332
10333 this.buffer.update(cx, |buffer, cx| {
10334 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10335 });
10336 }
10337 }
10338
10339 this.change_selections(Default::default(), window, cx, |s| {
10340 s.select_anchor_ranges(cursor_positions)
10341 });
10342 });
10343 }
10344
10345 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10346 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10347 self.join_lines_impl(true, window, cx);
10348 }
10349
10350 pub fn sort_lines_case_sensitive(
10351 &mut self,
10352 _: &SortLinesCaseSensitive,
10353 window: &mut Window,
10354 cx: &mut Context<Self>,
10355 ) {
10356 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10357 }
10358
10359 pub fn sort_lines_by_length(
10360 &mut self,
10361 _: &SortLinesByLength,
10362 window: &mut Window,
10363 cx: &mut Context<Self>,
10364 ) {
10365 self.manipulate_immutable_lines(window, cx, |lines| {
10366 lines.sort_by_key(|&line| line.chars().count())
10367 })
10368 }
10369
10370 pub fn sort_lines_case_insensitive(
10371 &mut self,
10372 _: &SortLinesCaseInsensitive,
10373 window: &mut Window,
10374 cx: &mut Context<Self>,
10375 ) {
10376 self.manipulate_immutable_lines(window, cx, |lines| {
10377 lines.sort_by_key(|line| line.to_lowercase())
10378 })
10379 }
10380
10381 pub fn unique_lines_case_insensitive(
10382 &mut self,
10383 _: &UniqueLinesCaseInsensitive,
10384 window: &mut Window,
10385 cx: &mut Context<Self>,
10386 ) {
10387 self.manipulate_immutable_lines(window, cx, |lines| {
10388 let mut seen = HashSet::default();
10389 lines.retain(|line| seen.insert(line.to_lowercase()));
10390 })
10391 }
10392
10393 pub fn unique_lines_case_sensitive(
10394 &mut self,
10395 _: &UniqueLinesCaseSensitive,
10396 window: &mut Window,
10397 cx: &mut Context<Self>,
10398 ) {
10399 self.manipulate_immutable_lines(window, cx, |lines| {
10400 let mut seen = HashSet::default();
10401 lines.retain(|line| seen.insert(*line));
10402 })
10403 }
10404
10405 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10406 let Some(project) = self.project.clone() else {
10407 return;
10408 };
10409 self.reload(project, window, cx)
10410 .detach_and_notify_err(window, cx);
10411 }
10412
10413 pub fn restore_file(
10414 &mut self,
10415 _: &::git::RestoreFile,
10416 window: &mut Window,
10417 cx: &mut Context<Self>,
10418 ) {
10419 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10420 let mut buffer_ids = HashSet::default();
10421 let snapshot = self.buffer().read(cx).snapshot(cx);
10422 for selection in self.selections.all::<usize>(cx) {
10423 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10424 }
10425
10426 let buffer = self.buffer().read(cx);
10427 let ranges = buffer_ids
10428 .into_iter()
10429 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10430 .collect::<Vec<_>>();
10431
10432 self.restore_hunks_in_ranges(ranges, window, cx);
10433 }
10434
10435 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10436 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10437 let selections = self
10438 .selections
10439 .all(cx)
10440 .into_iter()
10441 .map(|s| s.range())
10442 .collect();
10443 self.restore_hunks_in_ranges(selections, window, cx);
10444 }
10445
10446 pub fn restore_hunks_in_ranges(
10447 &mut self,
10448 ranges: Vec<Range<Point>>,
10449 window: &mut Window,
10450 cx: &mut Context<Editor>,
10451 ) {
10452 let mut revert_changes = HashMap::default();
10453 let chunk_by = self
10454 .snapshot(window, cx)
10455 .hunks_for_ranges(ranges)
10456 .into_iter()
10457 .chunk_by(|hunk| hunk.buffer_id);
10458 for (buffer_id, hunks) in &chunk_by {
10459 let hunks = hunks.collect::<Vec<_>>();
10460 for hunk in &hunks {
10461 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10462 }
10463 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10464 }
10465 drop(chunk_by);
10466 if !revert_changes.is_empty() {
10467 self.transact(window, cx, |editor, window, cx| {
10468 editor.restore(revert_changes, window, cx);
10469 });
10470 }
10471 }
10472
10473 pub fn open_active_item_in_terminal(
10474 &mut self,
10475 _: &OpenInTerminal,
10476 window: &mut Window,
10477 cx: &mut Context<Self>,
10478 ) {
10479 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10480 let project_path = buffer.read(cx).project_path(cx)?;
10481 let project = self.project.as_ref()?.read(cx);
10482 let entry = project.entry_for_path(&project_path, cx)?;
10483 let parent = match &entry.canonical_path {
10484 Some(canonical_path) => canonical_path.to_path_buf(),
10485 None => project.absolute_path(&project_path, cx)?,
10486 }
10487 .parent()?
10488 .to_path_buf();
10489 Some(parent)
10490 }) {
10491 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10492 }
10493 }
10494
10495 fn set_breakpoint_context_menu(
10496 &mut self,
10497 display_row: DisplayRow,
10498 position: Option<Anchor>,
10499 clicked_point: gpui::Point<Pixels>,
10500 window: &mut Window,
10501 cx: &mut Context<Self>,
10502 ) {
10503 let source = self
10504 .buffer
10505 .read(cx)
10506 .snapshot(cx)
10507 .anchor_before(Point::new(display_row.0, 0u32));
10508
10509 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10510
10511 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10512 self,
10513 source,
10514 clicked_point,
10515 context_menu,
10516 window,
10517 cx,
10518 );
10519 }
10520
10521 fn add_edit_breakpoint_block(
10522 &mut self,
10523 anchor: Anchor,
10524 breakpoint: &Breakpoint,
10525 edit_action: BreakpointPromptEditAction,
10526 window: &mut Window,
10527 cx: &mut Context<Self>,
10528 ) {
10529 let weak_editor = cx.weak_entity();
10530 let bp_prompt = cx.new(|cx| {
10531 BreakpointPromptEditor::new(
10532 weak_editor,
10533 anchor,
10534 breakpoint.clone(),
10535 edit_action,
10536 window,
10537 cx,
10538 )
10539 });
10540
10541 let height = bp_prompt.update(cx, |this, cx| {
10542 this.prompt
10543 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10544 });
10545 let cloned_prompt = bp_prompt.clone();
10546 let blocks = vec![BlockProperties {
10547 style: BlockStyle::Sticky,
10548 placement: BlockPlacement::Above(anchor),
10549 height: Some(height),
10550 render: Arc::new(move |cx| {
10551 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10552 cloned_prompt.clone().into_any_element()
10553 }),
10554 priority: 0,
10555 }];
10556
10557 let focus_handle = bp_prompt.focus_handle(cx);
10558 window.focus(&focus_handle);
10559
10560 let block_ids = self.insert_blocks(blocks, None, cx);
10561 bp_prompt.update(cx, |prompt, _| {
10562 prompt.add_block_ids(block_ids);
10563 });
10564 }
10565
10566 pub(crate) fn breakpoint_at_row(
10567 &self,
10568 row: u32,
10569 window: &mut Window,
10570 cx: &mut Context<Self>,
10571 ) -> Option<(Anchor, Breakpoint)> {
10572 let snapshot = self.snapshot(window, cx);
10573 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
10574
10575 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10576 }
10577
10578 pub(crate) fn breakpoint_at_anchor(
10579 &self,
10580 breakpoint_position: Anchor,
10581 snapshot: &EditorSnapshot,
10582 cx: &mut Context<Self>,
10583 ) -> Option<(Anchor, Breakpoint)> {
10584 let project = self.project.clone()?;
10585
10586 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
10587 snapshot
10588 .buffer_snapshot
10589 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
10590 })?;
10591
10592 let enclosing_excerpt = breakpoint_position.excerpt_id;
10593 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
10594 let buffer_snapshot = buffer.read(cx).snapshot();
10595
10596 let row = buffer_snapshot
10597 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10598 .row;
10599
10600 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
10601 let anchor_end = snapshot
10602 .buffer_snapshot
10603 .anchor_after(Point::new(row, line_len));
10604
10605 let bp = self
10606 .breakpoint_store
10607 .as_ref()?
10608 .read_with(cx, |breakpoint_store, cx| {
10609 breakpoint_store
10610 .breakpoints(
10611 &buffer,
10612 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10613 &buffer_snapshot,
10614 cx,
10615 )
10616 .next()
10617 .and_then(|(bp, _)| {
10618 let breakpoint_row = buffer_snapshot
10619 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10620 .row;
10621
10622 if breakpoint_row == row {
10623 snapshot
10624 .buffer_snapshot
10625 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10626 .map(|position| (position, bp.bp.clone()))
10627 } else {
10628 None
10629 }
10630 })
10631 });
10632 bp
10633 }
10634
10635 pub fn edit_log_breakpoint(
10636 &mut self,
10637 _: &EditLogBreakpoint,
10638 window: &mut Window,
10639 cx: &mut Context<Self>,
10640 ) {
10641 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10642 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10643 message: None,
10644 state: BreakpointState::Enabled,
10645 condition: None,
10646 hit_condition: None,
10647 });
10648
10649 self.add_edit_breakpoint_block(
10650 anchor,
10651 &breakpoint,
10652 BreakpointPromptEditAction::Log,
10653 window,
10654 cx,
10655 );
10656 }
10657 }
10658
10659 fn breakpoints_at_cursors(
10660 &self,
10661 window: &mut Window,
10662 cx: &mut Context<Self>,
10663 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10664 let snapshot = self.snapshot(window, cx);
10665 let cursors = self
10666 .selections
10667 .disjoint_anchors()
10668 .into_iter()
10669 .map(|selection| {
10670 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
10671
10672 let breakpoint_position = self
10673 .breakpoint_at_row(cursor_position.row, window, cx)
10674 .map(|bp| bp.0)
10675 .unwrap_or_else(|| {
10676 snapshot
10677 .display_snapshot
10678 .buffer_snapshot
10679 .anchor_after(Point::new(cursor_position.row, 0))
10680 });
10681
10682 let breakpoint = self
10683 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10684 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10685
10686 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10687 })
10688 // 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.
10689 .collect::<HashMap<Anchor, _>>();
10690
10691 cursors.into_iter().collect()
10692 }
10693
10694 pub fn enable_breakpoint(
10695 &mut self,
10696 _: &crate::actions::EnableBreakpoint,
10697 window: &mut Window,
10698 cx: &mut Context<Self>,
10699 ) {
10700 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10701 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10702 continue;
10703 };
10704 self.edit_breakpoint_at_anchor(
10705 anchor,
10706 breakpoint,
10707 BreakpointEditAction::InvertState,
10708 cx,
10709 );
10710 }
10711 }
10712
10713 pub fn disable_breakpoint(
10714 &mut self,
10715 _: &crate::actions::DisableBreakpoint,
10716 window: &mut Window,
10717 cx: &mut Context<Self>,
10718 ) {
10719 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10720 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10721 continue;
10722 };
10723 self.edit_breakpoint_at_anchor(
10724 anchor,
10725 breakpoint,
10726 BreakpointEditAction::InvertState,
10727 cx,
10728 );
10729 }
10730 }
10731
10732 pub fn toggle_breakpoint(
10733 &mut self,
10734 _: &crate::actions::ToggleBreakpoint,
10735 window: &mut Window,
10736 cx: &mut Context<Self>,
10737 ) {
10738 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10739 if let Some(breakpoint) = breakpoint {
10740 self.edit_breakpoint_at_anchor(
10741 anchor,
10742 breakpoint,
10743 BreakpointEditAction::Toggle,
10744 cx,
10745 );
10746 } else {
10747 self.edit_breakpoint_at_anchor(
10748 anchor,
10749 Breakpoint::new_standard(),
10750 BreakpointEditAction::Toggle,
10751 cx,
10752 );
10753 }
10754 }
10755 }
10756
10757 pub fn edit_breakpoint_at_anchor(
10758 &mut self,
10759 breakpoint_position: Anchor,
10760 breakpoint: Breakpoint,
10761 edit_action: BreakpointEditAction,
10762 cx: &mut Context<Self>,
10763 ) {
10764 let Some(breakpoint_store) = &self.breakpoint_store else {
10765 return;
10766 };
10767
10768 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
10769 if breakpoint_position == Anchor::min() {
10770 self.buffer()
10771 .read(cx)
10772 .excerpt_buffer_ids()
10773 .into_iter()
10774 .next()
10775 } else {
10776 None
10777 }
10778 }) else {
10779 return;
10780 };
10781
10782 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
10783 return;
10784 };
10785
10786 breakpoint_store.update(cx, |breakpoint_store, cx| {
10787 breakpoint_store.toggle_breakpoint(
10788 buffer,
10789 BreakpointWithPosition {
10790 position: breakpoint_position.text_anchor,
10791 bp: breakpoint,
10792 },
10793 edit_action,
10794 cx,
10795 );
10796 });
10797
10798 cx.notify();
10799 }
10800
10801 #[cfg(any(test, feature = "test-support"))]
10802 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10803 self.breakpoint_store.clone()
10804 }
10805
10806 pub fn prepare_restore_change(
10807 &self,
10808 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10809 hunk: &MultiBufferDiffHunk,
10810 cx: &mut App,
10811 ) -> Option<()> {
10812 if hunk.is_created_file() {
10813 return None;
10814 }
10815 let buffer = self.buffer.read(cx);
10816 let diff = buffer.diff_for(hunk.buffer_id)?;
10817 let buffer = buffer.buffer(hunk.buffer_id)?;
10818 let buffer = buffer.read(cx);
10819 let original_text = diff
10820 .read(cx)
10821 .base_text()
10822 .as_rope()
10823 .slice(hunk.diff_base_byte_range.clone());
10824 let buffer_snapshot = buffer.snapshot();
10825 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
10826 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
10827 probe
10828 .0
10829 .start
10830 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
10831 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
10832 }) {
10833 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
10834 Some(())
10835 } else {
10836 None
10837 }
10838 }
10839
10840 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
10841 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
10842 }
10843
10844 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
10845 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
10846 }
10847
10848 fn manipulate_lines<M>(
10849 &mut self,
10850 window: &mut Window,
10851 cx: &mut Context<Self>,
10852 mut manipulate: M,
10853 ) where
10854 M: FnMut(&str) -> LineManipulationResult,
10855 {
10856 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10857
10858 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10859 let buffer = self.buffer.read(cx).snapshot(cx);
10860
10861 let mut edits = Vec::new();
10862
10863 let selections = self.selections.all::<Point>(cx);
10864 let mut selections = selections.iter().peekable();
10865 let mut contiguous_row_selections = Vec::new();
10866 let mut new_selections = Vec::new();
10867 let mut added_lines = 0;
10868 let mut removed_lines = 0;
10869
10870 while let Some(selection) = selections.next() {
10871 let (start_row, end_row) = consume_contiguous_rows(
10872 &mut contiguous_row_selections,
10873 selection,
10874 &display_map,
10875 &mut selections,
10876 );
10877
10878 let start_point = Point::new(start_row.0, 0);
10879 let end_point = Point::new(
10880 end_row.previous_row().0,
10881 buffer.line_len(end_row.previous_row()),
10882 );
10883 let text = buffer
10884 .text_for_range(start_point..end_point)
10885 .collect::<String>();
10886
10887 let LineManipulationResult {
10888 new_text,
10889 line_count_before,
10890 line_count_after,
10891 } = manipulate(&text);
10892
10893 edits.push((start_point..end_point, new_text));
10894
10895 // Selections must change based on added and removed line count
10896 let start_row =
10897 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
10898 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
10899 new_selections.push(Selection {
10900 id: selection.id,
10901 start: start_row,
10902 end: end_row,
10903 goal: SelectionGoal::None,
10904 reversed: selection.reversed,
10905 });
10906
10907 if line_count_after > line_count_before {
10908 added_lines += line_count_after - line_count_before;
10909 } else if line_count_before > line_count_after {
10910 removed_lines += line_count_before - line_count_after;
10911 }
10912 }
10913
10914 self.transact(window, cx, |this, window, cx| {
10915 let buffer = this.buffer.update(cx, |buffer, cx| {
10916 buffer.edit(edits, None, cx);
10917 buffer.snapshot(cx)
10918 });
10919
10920 // Recalculate offsets on newly edited buffer
10921 let new_selections = new_selections
10922 .iter()
10923 .map(|s| {
10924 let start_point = Point::new(s.start.0, 0);
10925 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
10926 Selection {
10927 id: s.id,
10928 start: buffer.point_to_offset(start_point),
10929 end: buffer.point_to_offset(end_point),
10930 goal: s.goal,
10931 reversed: s.reversed,
10932 }
10933 })
10934 .collect();
10935
10936 this.change_selections(Default::default(), window, cx, |s| {
10937 s.select(new_selections);
10938 });
10939
10940 this.request_autoscroll(Autoscroll::fit(), cx);
10941 });
10942 }
10943
10944 fn manipulate_immutable_lines<Fn>(
10945 &mut self,
10946 window: &mut Window,
10947 cx: &mut Context<Self>,
10948 mut callback: Fn,
10949 ) where
10950 Fn: FnMut(&mut Vec<&str>),
10951 {
10952 self.manipulate_lines(window, cx, |text| {
10953 let mut lines: Vec<&str> = text.split('\n').collect();
10954 let line_count_before = lines.len();
10955
10956 callback(&mut lines);
10957
10958 LineManipulationResult {
10959 new_text: lines.join("\n"),
10960 line_count_before,
10961 line_count_after: lines.len(),
10962 }
10963 });
10964 }
10965
10966 fn manipulate_mutable_lines<Fn>(
10967 &mut self,
10968 window: &mut Window,
10969 cx: &mut Context<Self>,
10970 mut callback: Fn,
10971 ) where
10972 Fn: FnMut(&mut Vec<Cow<'_, str>>),
10973 {
10974 self.manipulate_lines(window, cx, |text| {
10975 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
10976 let line_count_before = lines.len();
10977
10978 callback(&mut lines);
10979
10980 LineManipulationResult {
10981 new_text: lines.join("\n"),
10982 line_count_before,
10983 line_count_after: lines.len(),
10984 }
10985 });
10986 }
10987
10988 pub fn convert_indentation_to_spaces(
10989 &mut self,
10990 _: &ConvertIndentationToSpaces,
10991 window: &mut Window,
10992 cx: &mut Context<Self>,
10993 ) {
10994 let settings = self.buffer.read(cx).language_settings(cx);
10995 let tab_size = settings.tab_size.get() as usize;
10996
10997 self.manipulate_mutable_lines(window, cx, |lines| {
10998 // Allocates a reasonably sized scratch buffer once for the whole loop
10999 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11000 // Avoids recomputing spaces that could be inserted many times
11001 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11002 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11003 .collect();
11004
11005 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11006 let mut chars = line.as_ref().chars();
11007 let mut col = 0;
11008 let mut changed = false;
11009
11010 while let Some(ch) = chars.next() {
11011 match ch {
11012 ' ' => {
11013 reindented_line.push(' ');
11014 col += 1;
11015 }
11016 '\t' => {
11017 // \t are converted to spaces depending on the current column
11018 let spaces_len = tab_size - (col % tab_size);
11019 reindented_line.extend(&space_cache[spaces_len - 1]);
11020 col += spaces_len;
11021 changed = true;
11022 }
11023 _ => {
11024 // If we dont append before break, the character is consumed
11025 reindented_line.push(ch);
11026 break;
11027 }
11028 }
11029 }
11030
11031 if !changed {
11032 reindented_line.clear();
11033 continue;
11034 }
11035 // Append the rest of the line and replace old reference with new one
11036 reindented_line.extend(chars);
11037 *line = Cow::Owned(reindented_line.clone());
11038 reindented_line.clear();
11039 }
11040 });
11041 }
11042
11043 pub fn convert_indentation_to_tabs(
11044 &mut self,
11045 _: &ConvertIndentationToTabs,
11046 window: &mut Window,
11047 cx: &mut Context<Self>,
11048 ) {
11049 let settings = self.buffer.read(cx).language_settings(cx);
11050 let tab_size = settings.tab_size.get() as usize;
11051
11052 self.manipulate_mutable_lines(window, cx, |lines| {
11053 // Allocates a reasonably sized buffer once for the whole loop
11054 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11055 // Avoids recomputing spaces that could be inserted many times
11056 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11057 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11058 .collect();
11059
11060 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11061 let mut chars = line.chars();
11062 let mut spaces_count = 0;
11063 let mut first_non_indent_char = None;
11064 let mut changed = false;
11065
11066 while let Some(ch) = chars.next() {
11067 match ch {
11068 ' ' => {
11069 // Keep track of spaces. Append \t when we reach tab_size
11070 spaces_count += 1;
11071 changed = true;
11072 if spaces_count == tab_size {
11073 reindented_line.push('\t');
11074 spaces_count = 0;
11075 }
11076 }
11077 '\t' => {
11078 reindented_line.push('\t');
11079 spaces_count = 0;
11080 }
11081 _ => {
11082 // Dont append it yet, we might have remaining spaces
11083 first_non_indent_char = Some(ch);
11084 break;
11085 }
11086 }
11087 }
11088
11089 if !changed {
11090 reindented_line.clear();
11091 continue;
11092 }
11093 // Remaining spaces that didn't make a full tab stop
11094 if spaces_count > 0 {
11095 reindented_line.extend(&space_cache[spaces_count - 1]);
11096 }
11097 // If we consume an extra character that was not indentation, add it back
11098 if let Some(extra_char) = first_non_indent_char {
11099 reindented_line.push(extra_char);
11100 }
11101 // Append the rest of the line and replace old reference with new one
11102 reindented_line.extend(chars);
11103 *line = Cow::Owned(reindented_line.clone());
11104 reindented_line.clear();
11105 }
11106 });
11107 }
11108
11109 pub fn convert_to_upper_case(
11110 &mut self,
11111 _: &ConvertToUpperCase,
11112 window: &mut Window,
11113 cx: &mut Context<Self>,
11114 ) {
11115 self.manipulate_text(window, cx, |text| text.to_uppercase())
11116 }
11117
11118 pub fn convert_to_lower_case(
11119 &mut self,
11120 _: &ConvertToLowerCase,
11121 window: &mut Window,
11122 cx: &mut Context<Self>,
11123 ) {
11124 self.manipulate_text(window, cx, |text| text.to_lowercase())
11125 }
11126
11127 pub fn convert_to_title_case(
11128 &mut self,
11129 _: &ConvertToTitleCase,
11130 window: &mut Window,
11131 cx: &mut Context<Self>,
11132 ) {
11133 self.manipulate_text(window, cx, |text| {
11134 text.split('\n')
11135 .map(|line| line.to_case(Case::Title))
11136 .join("\n")
11137 })
11138 }
11139
11140 pub fn convert_to_snake_case(
11141 &mut self,
11142 _: &ConvertToSnakeCase,
11143 window: &mut Window,
11144 cx: &mut Context<Self>,
11145 ) {
11146 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11147 }
11148
11149 pub fn convert_to_kebab_case(
11150 &mut self,
11151 _: &ConvertToKebabCase,
11152 window: &mut Window,
11153 cx: &mut Context<Self>,
11154 ) {
11155 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11156 }
11157
11158 pub fn convert_to_upper_camel_case(
11159 &mut self,
11160 _: &ConvertToUpperCamelCase,
11161 window: &mut Window,
11162 cx: &mut Context<Self>,
11163 ) {
11164 self.manipulate_text(window, cx, |text| {
11165 text.split('\n')
11166 .map(|line| line.to_case(Case::UpperCamel))
11167 .join("\n")
11168 })
11169 }
11170
11171 pub fn convert_to_lower_camel_case(
11172 &mut self,
11173 _: &ConvertToLowerCamelCase,
11174 window: &mut Window,
11175 cx: &mut Context<Self>,
11176 ) {
11177 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11178 }
11179
11180 pub fn convert_to_opposite_case(
11181 &mut self,
11182 _: &ConvertToOppositeCase,
11183 window: &mut Window,
11184 cx: &mut Context<Self>,
11185 ) {
11186 self.manipulate_text(window, cx, |text| {
11187 text.chars()
11188 .fold(String::with_capacity(text.len()), |mut t, c| {
11189 if c.is_uppercase() {
11190 t.extend(c.to_lowercase());
11191 } else {
11192 t.extend(c.to_uppercase());
11193 }
11194 t
11195 })
11196 })
11197 }
11198
11199 pub fn convert_to_sentence_case(
11200 &mut self,
11201 _: &ConvertToSentenceCase,
11202 window: &mut Window,
11203 cx: &mut Context<Self>,
11204 ) {
11205 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11206 }
11207
11208 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11209 self.manipulate_text(window, cx, |text| {
11210 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11211 if has_upper_case_characters {
11212 text.to_lowercase()
11213 } else {
11214 text.to_uppercase()
11215 }
11216 })
11217 }
11218
11219 pub fn convert_to_rot13(
11220 &mut self,
11221 _: &ConvertToRot13,
11222 window: &mut Window,
11223 cx: &mut Context<Self>,
11224 ) {
11225 self.manipulate_text(window, cx, |text| {
11226 text.chars()
11227 .map(|c| match c {
11228 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11229 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11230 _ => c,
11231 })
11232 .collect()
11233 })
11234 }
11235
11236 pub fn convert_to_rot47(
11237 &mut self,
11238 _: &ConvertToRot47,
11239 window: &mut Window,
11240 cx: &mut Context<Self>,
11241 ) {
11242 self.manipulate_text(window, cx, |text| {
11243 text.chars()
11244 .map(|c| {
11245 let code_point = c as u32;
11246 if code_point >= 33 && code_point <= 126 {
11247 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11248 }
11249 c
11250 })
11251 .collect()
11252 })
11253 }
11254
11255 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11256 where
11257 Fn: FnMut(&str) -> String,
11258 {
11259 let buffer = self.buffer.read(cx).snapshot(cx);
11260
11261 let mut new_selections = Vec::new();
11262 let mut edits = Vec::new();
11263 let mut selection_adjustment = 0i32;
11264
11265 for selection in self.selections.all::<usize>(cx) {
11266 let selection_is_empty = selection.is_empty();
11267
11268 let (start, end) = if selection_is_empty {
11269 let (word_range, _) = buffer.surrounding_word(selection.start, false);
11270 (word_range.start, word_range.end)
11271 } else {
11272 (selection.start, selection.end)
11273 };
11274
11275 let text = buffer.text_for_range(start..end).collect::<String>();
11276 let old_length = text.len() as i32;
11277 let text = callback(&text);
11278
11279 new_selections.push(Selection {
11280 start: (start as i32 - selection_adjustment) as usize,
11281 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11282 goal: SelectionGoal::None,
11283 ..selection
11284 });
11285
11286 selection_adjustment += old_length - text.len() as i32;
11287
11288 edits.push((start..end, text));
11289 }
11290
11291 self.transact(window, cx, |this, window, cx| {
11292 this.buffer.update(cx, |buffer, cx| {
11293 buffer.edit(edits, None, cx);
11294 });
11295
11296 this.change_selections(Default::default(), window, cx, |s| {
11297 s.select(new_selections);
11298 });
11299
11300 this.request_autoscroll(Autoscroll::fit(), cx);
11301 });
11302 }
11303
11304 pub fn move_selection_on_drop(
11305 &mut self,
11306 selection: &Selection<Anchor>,
11307 target: DisplayPoint,
11308 is_cut: bool,
11309 window: &mut Window,
11310 cx: &mut Context<Self>,
11311 ) {
11312 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11313 let buffer = &display_map.buffer_snapshot;
11314 let mut edits = Vec::new();
11315 let insert_point = display_map
11316 .clip_point(target, Bias::Left)
11317 .to_point(&display_map);
11318 let text = buffer
11319 .text_for_range(selection.start..selection.end)
11320 .collect::<String>();
11321 if is_cut {
11322 edits.push(((selection.start..selection.end), String::new()));
11323 }
11324 let insert_anchor = buffer.anchor_before(insert_point);
11325 edits.push(((insert_anchor..insert_anchor), text));
11326 let last_edit_start = insert_anchor.bias_left(buffer);
11327 let last_edit_end = insert_anchor.bias_right(buffer);
11328 self.transact(window, cx, |this, window, cx| {
11329 this.buffer.update(cx, |buffer, cx| {
11330 buffer.edit(edits, None, cx);
11331 });
11332 this.change_selections(Default::default(), window, cx, |s| {
11333 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11334 });
11335 });
11336 }
11337
11338 pub fn clear_selection_drag_state(&mut self) {
11339 self.selection_drag_state = SelectionDragState::None;
11340 }
11341
11342 pub fn duplicate(
11343 &mut self,
11344 upwards: bool,
11345 whole_lines: bool,
11346 window: &mut Window,
11347 cx: &mut Context<Self>,
11348 ) {
11349 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11350
11351 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11352 let buffer = &display_map.buffer_snapshot;
11353 let selections = self.selections.all::<Point>(cx);
11354
11355 let mut edits = Vec::new();
11356 let mut selections_iter = selections.iter().peekable();
11357 while let Some(selection) = selections_iter.next() {
11358 let mut rows = selection.spanned_rows(false, &display_map);
11359 // duplicate line-wise
11360 if whole_lines || selection.start == selection.end {
11361 // Avoid duplicating the same lines twice.
11362 while let Some(next_selection) = selections_iter.peek() {
11363 let next_rows = next_selection.spanned_rows(false, &display_map);
11364 if next_rows.start < rows.end {
11365 rows.end = next_rows.end;
11366 selections_iter.next().unwrap();
11367 } else {
11368 break;
11369 }
11370 }
11371
11372 // Copy the text from the selected row region and splice it either at the start
11373 // or end of the region.
11374 let start = Point::new(rows.start.0, 0);
11375 let end = Point::new(
11376 rows.end.previous_row().0,
11377 buffer.line_len(rows.end.previous_row()),
11378 );
11379 let text = buffer
11380 .text_for_range(start..end)
11381 .chain(Some("\n"))
11382 .collect::<String>();
11383 let insert_location = if upwards {
11384 Point::new(rows.end.0, 0)
11385 } else {
11386 start
11387 };
11388 edits.push((insert_location..insert_location, text));
11389 } else {
11390 // duplicate character-wise
11391 let start = selection.start;
11392 let end = selection.end;
11393 let text = buffer.text_for_range(start..end).collect::<String>();
11394 edits.push((selection.end..selection.end, text));
11395 }
11396 }
11397
11398 self.transact(window, cx, |this, _, cx| {
11399 this.buffer.update(cx, |buffer, cx| {
11400 buffer.edit(edits, None, cx);
11401 });
11402
11403 this.request_autoscroll(Autoscroll::fit(), cx);
11404 });
11405 }
11406
11407 pub fn duplicate_line_up(
11408 &mut self,
11409 _: &DuplicateLineUp,
11410 window: &mut Window,
11411 cx: &mut Context<Self>,
11412 ) {
11413 self.duplicate(true, true, window, cx);
11414 }
11415
11416 pub fn duplicate_line_down(
11417 &mut self,
11418 _: &DuplicateLineDown,
11419 window: &mut Window,
11420 cx: &mut Context<Self>,
11421 ) {
11422 self.duplicate(false, true, window, cx);
11423 }
11424
11425 pub fn duplicate_selection(
11426 &mut self,
11427 _: &DuplicateSelection,
11428 window: &mut Window,
11429 cx: &mut Context<Self>,
11430 ) {
11431 self.duplicate(false, false, window, cx);
11432 }
11433
11434 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11435 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11436 if self.mode.is_single_line() {
11437 cx.propagate();
11438 return;
11439 }
11440
11441 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11442 let buffer = self.buffer.read(cx).snapshot(cx);
11443
11444 let mut edits = Vec::new();
11445 let mut unfold_ranges = Vec::new();
11446 let mut refold_creases = Vec::new();
11447
11448 let selections = self.selections.all::<Point>(cx);
11449 let mut selections = selections.iter().peekable();
11450 let mut contiguous_row_selections = Vec::new();
11451 let mut new_selections = Vec::new();
11452
11453 while let Some(selection) = selections.next() {
11454 // Find all the selections that span a contiguous row range
11455 let (start_row, end_row) = consume_contiguous_rows(
11456 &mut contiguous_row_selections,
11457 selection,
11458 &display_map,
11459 &mut selections,
11460 );
11461
11462 // Move the text spanned by the row range to be before the line preceding the row range
11463 if start_row.0 > 0 {
11464 let range_to_move = Point::new(
11465 start_row.previous_row().0,
11466 buffer.line_len(start_row.previous_row()),
11467 )
11468 ..Point::new(
11469 end_row.previous_row().0,
11470 buffer.line_len(end_row.previous_row()),
11471 );
11472 let insertion_point = display_map
11473 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11474 .0;
11475
11476 // Don't move lines across excerpts
11477 if buffer
11478 .excerpt_containing(insertion_point..range_to_move.end)
11479 .is_some()
11480 {
11481 let text = buffer
11482 .text_for_range(range_to_move.clone())
11483 .flat_map(|s| s.chars())
11484 .skip(1)
11485 .chain(['\n'])
11486 .collect::<String>();
11487
11488 edits.push((
11489 buffer.anchor_after(range_to_move.start)
11490 ..buffer.anchor_before(range_to_move.end),
11491 String::new(),
11492 ));
11493 let insertion_anchor = buffer.anchor_after(insertion_point);
11494 edits.push((insertion_anchor..insertion_anchor, text));
11495
11496 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11497
11498 // Move selections up
11499 new_selections.extend(contiguous_row_selections.drain(..).map(
11500 |mut selection| {
11501 selection.start.row -= row_delta;
11502 selection.end.row -= row_delta;
11503 selection
11504 },
11505 ));
11506
11507 // Move folds up
11508 unfold_ranges.push(range_to_move.clone());
11509 for fold in display_map.folds_in_range(
11510 buffer.anchor_before(range_to_move.start)
11511 ..buffer.anchor_after(range_to_move.end),
11512 ) {
11513 let mut start = fold.range.start.to_point(&buffer);
11514 let mut end = fold.range.end.to_point(&buffer);
11515 start.row -= row_delta;
11516 end.row -= row_delta;
11517 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11518 }
11519 }
11520 }
11521
11522 // If we didn't move line(s), preserve the existing selections
11523 new_selections.append(&mut contiguous_row_selections);
11524 }
11525
11526 self.transact(window, cx, |this, window, cx| {
11527 this.unfold_ranges(&unfold_ranges, true, true, cx);
11528 this.buffer.update(cx, |buffer, cx| {
11529 for (range, text) in edits {
11530 buffer.edit([(range, text)], None, cx);
11531 }
11532 });
11533 this.fold_creases(refold_creases, true, window, cx);
11534 this.change_selections(Default::default(), window, cx, |s| {
11535 s.select(new_selections);
11536 })
11537 });
11538 }
11539
11540 pub fn move_line_down(
11541 &mut self,
11542 _: &MoveLineDown,
11543 window: &mut Window,
11544 cx: &mut Context<Self>,
11545 ) {
11546 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11547 if self.mode.is_single_line() {
11548 cx.propagate();
11549 return;
11550 }
11551
11552 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11553 let buffer = self.buffer.read(cx).snapshot(cx);
11554
11555 let mut edits = Vec::new();
11556 let mut unfold_ranges = Vec::new();
11557 let mut refold_creases = Vec::new();
11558
11559 let selections = self.selections.all::<Point>(cx);
11560 let mut selections = selections.iter().peekable();
11561 let mut contiguous_row_selections = Vec::new();
11562 let mut new_selections = Vec::new();
11563
11564 while let Some(selection) = selections.next() {
11565 // Find all the selections that span a contiguous row range
11566 let (start_row, end_row) = consume_contiguous_rows(
11567 &mut contiguous_row_selections,
11568 selection,
11569 &display_map,
11570 &mut selections,
11571 );
11572
11573 // Move the text spanned by the row range to be after the last line of the row range
11574 if end_row.0 <= buffer.max_point().row {
11575 let range_to_move =
11576 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11577 let insertion_point = display_map
11578 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11579 .0;
11580
11581 // Don't move lines across excerpt boundaries
11582 if buffer
11583 .excerpt_containing(range_to_move.start..insertion_point)
11584 .is_some()
11585 {
11586 let mut text = String::from("\n");
11587 text.extend(buffer.text_for_range(range_to_move.clone()));
11588 text.pop(); // Drop trailing newline
11589 edits.push((
11590 buffer.anchor_after(range_to_move.start)
11591 ..buffer.anchor_before(range_to_move.end),
11592 String::new(),
11593 ));
11594 let insertion_anchor = buffer.anchor_after(insertion_point);
11595 edits.push((insertion_anchor..insertion_anchor, text));
11596
11597 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11598
11599 // Move selections down
11600 new_selections.extend(contiguous_row_selections.drain(..).map(
11601 |mut selection| {
11602 selection.start.row += row_delta;
11603 selection.end.row += row_delta;
11604 selection
11605 },
11606 ));
11607
11608 // Move folds down
11609 unfold_ranges.push(range_to_move.clone());
11610 for fold in display_map.folds_in_range(
11611 buffer.anchor_before(range_to_move.start)
11612 ..buffer.anchor_after(range_to_move.end),
11613 ) {
11614 let mut start = fold.range.start.to_point(&buffer);
11615 let mut end = fold.range.end.to_point(&buffer);
11616 start.row += row_delta;
11617 end.row += row_delta;
11618 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11619 }
11620 }
11621 }
11622
11623 // If we didn't move line(s), preserve the existing selections
11624 new_selections.append(&mut contiguous_row_selections);
11625 }
11626
11627 self.transact(window, cx, |this, window, cx| {
11628 this.unfold_ranges(&unfold_ranges, true, true, cx);
11629 this.buffer.update(cx, |buffer, cx| {
11630 for (range, text) in edits {
11631 buffer.edit([(range, text)], None, cx);
11632 }
11633 });
11634 this.fold_creases(refold_creases, true, window, cx);
11635 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
11636 });
11637 }
11638
11639 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11640 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11641 let text_layout_details = &self.text_layout_details(window);
11642 self.transact(window, cx, |this, window, cx| {
11643 let edits = this.change_selections(Default::default(), window, cx, |s| {
11644 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11645 s.move_with(|display_map, selection| {
11646 if !selection.is_empty() {
11647 return;
11648 }
11649
11650 let mut head = selection.head();
11651 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11652 if head.column() == display_map.line_len(head.row()) {
11653 transpose_offset = display_map
11654 .buffer_snapshot
11655 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11656 }
11657
11658 if transpose_offset == 0 {
11659 return;
11660 }
11661
11662 *head.column_mut() += 1;
11663 head = display_map.clip_point(head, Bias::Right);
11664 let goal = SelectionGoal::HorizontalPosition(
11665 display_map
11666 .x_for_display_point(head, text_layout_details)
11667 .into(),
11668 );
11669 selection.collapse_to(head, goal);
11670
11671 let transpose_start = display_map
11672 .buffer_snapshot
11673 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11674 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
11675 let transpose_end = display_map
11676 .buffer_snapshot
11677 .clip_offset(transpose_offset + 1, Bias::Right);
11678 if let Some(ch) =
11679 display_map.buffer_snapshot.chars_at(transpose_start).next()
11680 {
11681 edits.push((transpose_start..transpose_offset, String::new()));
11682 edits.push((transpose_end..transpose_end, ch.to_string()));
11683 }
11684 }
11685 });
11686 edits
11687 });
11688 this.buffer
11689 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11690 let selections = this.selections.all::<usize>(cx);
11691 this.change_selections(Default::default(), window, cx, |s| {
11692 s.select(selections);
11693 });
11694 });
11695 }
11696
11697 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
11698 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11699 if self.mode.is_single_line() {
11700 cx.propagate();
11701 return;
11702 }
11703
11704 self.rewrap_impl(RewrapOptions::default(), cx)
11705 }
11706
11707 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
11708 let buffer = self.buffer.read(cx).snapshot(cx);
11709 let selections = self.selections.all::<Point>(cx);
11710
11711 // Split selections to respect paragraph, indent, and comment prefix boundaries.
11712 let wrap_ranges = selections.into_iter().flat_map(|selection| {
11713 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
11714 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11715 .peekable();
11716
11717 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
11718 row
11719 } else {
11720 return Vec::new();
11721 };
11722
11723 let language_settings = buffer.language_settings_at(selection.head(), cx);
11724 let language_scope = buffer.language_scope_at(selection.head());
11725
11726 let indent_and_prefix_for_row =
11727 |row: u32| -> (IndentSize, Option<String>, Option<String>) {
11728 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
11729 let (comment_prefix, rewrap_prefix) =
11730 if let Some(language_scope) = &language_scope {
11731 let indent_end = Point::new(row, indent.len);
11732 let comment_prefix = language_scope
11733 .line_comment_prefixes()
11734 .iter()
11735 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11736 .map(|prefix| prefix.to_string());
11737 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11738 let line_text_after_indent = buffer
11739 .text_for_range(indent_end..line_end)
11740 .collect::<String>();
11741 let rewrap_prefix = language_scope
11742 .rewrap_prefixes()
11743 .iter()
11744 .find_map(|prefix_regex| {
11745 prefix_regex.find(&line_text_after_indent).map(|mat| {
11746 if mat.start() == 0 {
11747 Some(mat.as_str().to_string())
11748 } else {
11749 None
11750 }
11751 })
11752 })
11753 .flatten();
11754 (comment_prefix, rewrap_prefix)
11755 } else {
11756 (None, None)
11757 };
11758 (indent, comment_prefix, rewrap_prefix)
11759 };
11760
11761 let mut ranges = Vec::new();
11762 let from_empty_selection = selection.is_empty();
11763
11764 let mut current_range_start = first_row;
11765 let mut prev_row = first_row;
11766 let (
11767 mut current_range_indent,
11768 mut current_range_comment_prefix,
11769 mut current_range_rewrap_prefix,
11770 ) = indent_and_prefix_for_row(first_row);
11771
11772 for row in non_blank_rows_iter.skip(1) {
11773 let has_paragraph_break = row > prev_row + 1;
11774
11775 let (row_indent, row_comment_prefix, row_rewrap_prefix) =
11776 indent_and_prefix_for_row(row);
11777
11778 let has_indent_change = row_indent != current_range_indent;
11779 let has_comment_change = row_comment_prefix != current_range_comment_prefix;
11780
11781 let has_boundary_change = has_comment_change
11782 || row_rewrap_prefix.is_some()
11783 || (has_indent_change && current_range_comment_prefix.is_some());
11784
11785 if has_paragraph_break || has_boundary_change {
11786 ranges.push((
11787 language_settings.clone(),
11788 Point::new(current_range_start, 0)
11789 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
11790 current_range_indent,
11791 current_range_comment_prefix.clone(),
11792 current_range_rewrap_prefix.clone(),
11793 from_empty_selection,
11794 ));
11795 current_range_start = row;
11796 current_range_indent = row_indent;
11797 current_range_comment_prefix = row_comment_prefix;
11798 current_range_rewrap_prefix = row_rewrap_prefix;
11799 }
11800 prev_row = row;
11801 }
11802
11803 ranges.push((
11804 language_settings.clone(),
11805 Point::new(current_range_start, 0)
11806 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
11807 current_range_indent,
11808 current_range_comment_prefix,
11809 current_range_rewrap_prefix,
11810 from_empty_selection,
11811 ));
11812
11813 ranges
11814 });
11815
11816 let mut edits = Vec::new();
11817 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
11818
11819 for (
11820 language_settings,
11821 wrap_range,
11822 indent_size,
11823 comment_prefix,
11824 rewrap_prefix,
11825 from_empty_selection,
11826 ) in wrap_ranges
11827 {
11828 let mut start_row = wrap_range.start.row;
11829 let mut end_row = wrap_range.end.row;
11830
11831 // Skip selections that overlap with a range that has already been rewrapped.
11832 let selection_range = start_row..end_row;
11833 if rewrapped_row_ranges
11834 .iter()
11835 .any(|range| range.overlaps(&selection_range))
11836 {
11837 continue;
11838 }
11839
11840 let tab_size = language_settings.tab_size;
11841
11842 let indent_prefix = indent_size.chars().collect::<String>();
11843 let mut line_prefix = indent_prefix.clone();
11844 let mut inside_comment = false;
11845 if let Some(prefix) = &comment_prefix {
11846 line_prefix.push_str(prefix);
11847 inside_comment = true;
11848 }
11849 if let Some(prefix) = &rewrap_prefix {
11850 line_prefix.push_str(prefix);
11851 }
11852
11853 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
11854 RewrapBehavior::InComments => inside_comment,
11855 RewrapBehavior::InSelections => !wrap_range.is_empty(),
11856 RewrapBehavior::Anywhere => true,
11857 };
11858
11859 let should_rewrap = options.override_language_settings
11860 || allow_rewrap_based_on_language
11861 || self.hard_wrap.is_some();
11862 if !should_rewrap {
11863 continue;
11864 }
11865
11866 if from_empty_selection {
11867 'expand_upwards: while start_row > 0 {
11868 let prev_row = start_row - 1;
11869 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
11870 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
11871 && !buffer.is_line_blank(MultiBufferRow(prev_row))
11872 {
11873 start_row = prev_row;
11874 } else {
11875 break 'expand_upwards;
11876 }
11877 }
11878
11879 'expand_downwards: while end_row < buffer.max_point().row {
11880 let next_row = end_row + 1;
11881 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
11882 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
11883 && !buffer.is_line_blank(MultiBufferRow(next_row))
11884 {
11885 end_row = next_row;
11886 } else {
11887 break 'expand_downwards;
11888 }
11889 }
11890 }
11891
11892 let start = Point::new(start_row, 0);
11893 let start_offset = start.to_offset(&buffer);
11894 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
11895 let selection_text = buffer.text_for_range(start..end).collect::<String>();
11896 let Some(lines_without_prefixes) = selection_text
11897 .lines()
11898 .enumerate()
11899 .map(|(ix, line)| {
11900 let line_trimmed = line.trim_start();
11901 if rewrap_prefix.is_some() && ix > 0 {
11902 Ok(line_trimmed)
11903 } else {
11904 line_trimmed
11905 .strip_prefix(&line_prefix.trim_start())
11906 .with_context(|| {
11907 format!("line did not start with prefix {line_prefix:?}: {line:?}")
11908 })
11909 }
11910 })
11911 .collect::<Result<Vec<_>, _>>()
11912 .log_err()
11913 else {
11914 continue;
11915 };
11916
11917 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
11918 buffer
11919 .language_settings_at(Point::new(start_row, 0), cx)
11920 .preferred_line_length as usize
11921 });
11922
11923 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
11924 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
11925 } else {
11926 line_prefix.clone()
11927 };
11928
11929 let wrapped_text = wrap_with_prefix(
11930 line_prefix,
11931 subsequent_lines_prefix,
11932 lines_without_prefixes.join("\n"),
11933 wrap_column,
11934 tab_size,
11935 options.preserve_existing_whitespace,
11936 );
11937
11938 // TODO: should always use char-based diff while still supporting cursor behavior that
11939 // matches vim.
11940 let mut diff_options = DiffOptions::default();
11941 if options.override_language_settings {
11942 diff_options.max_word_diff_len = 0;
11943 diff_options.max_word_diff_line_count = 0;
11944 } else {
11945 diff_options.max_word_diff_len = usize::MAX;
11946 diff_options.max_word_diff_line_count = usize::MAX;
11947 }
11948
11949 for (old_range, new_text) in
11950 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
11951 {
11952 let edit_start = buffer.anchor_after(start_offset + old_range.start);
11953 let edit_end = buffer.anchor_after(start_offset + old_range.end);
11954 edits.push((edit_start..edit_end, new_text));
11955 }
11956
11957 rewrapped_row_ranges.push(start_row..=end_row);
11958 }
11959
11960 self.buffer
11961 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11962 }
11963
11964 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
11965 let mut text = String::new();
11966 let buffer = self.buffer.read(cx).snapshot(cx);
11967 let mut selections = self.selections.all::<Point>(cx);
11968 let mut clipboard_selections = Vec::with_capacity(selections.len());
11969 {
11970 let max_point = buffer.max_point();
11971 let mut is_first = true;
11972 for selection in &mut selections {
11973 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11974 if is_entire_line {
11975 selection.start = Point::new(selection.start.row, 0);
11976 if !selection.is_empty() && selection.end.column == 0 {
11977 selection.end = cmp::min(max_point, selection.end);
11978 } else {
11979 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
11980 }
11981 selection.goal = SelectionGoal::None;
11982 }
11983 if is_first {
11984 is_first = false;
11985 } else {
11986 text += "\n";
11987 }
11988 let mut len = 0;
11989 for chunk in buffer.text_for_range(selection.start..selection.end) {
11990 text.push_str(chunk);
11991 len += chunk.len();
11992 }
11993 clipboard_selections.push(ClipboardSelection {
11994 len,
11995 is_entire_line,
11996 first_line_indent: buffer
11997 .indent_size_for_line(MultiBufferRow(selection.start.row))
11998 .len,
11999 });
12000 }
12001 }
12002
12003 self.transact(window, cx, |this, window, cx| {
12004 this.change_selections(Default::default(), window, cx, |s| {
12005 s.select(selections);
12006 });
12007 this.insert("", window, cx);
12008 });
12009 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12010 }
12011
12012 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12013 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12014 let item = self.cut_common(window, cx);
12015 cx.write_to_clipboard(item);
12016 }
12017
12018 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12019 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12020 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12021 s.move_with(|snapshot, sel| {
12022 if sel.is_empty() {
12023 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
12024 }
12025 });
12026 });
12027 let item = self.cut_common(window, cx);
12028 cx.set_global(KillRing(item))
12029 }
12030
12031 pub fn kill_ring_yank(
12032 &mut self,
12033 _: &KillRingYank,
12034 window: &mut Window,
12035 cx: &mut Context<Self>,
12036 ) {
12037 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12038 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12039 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12040 (kill_ring.text().to_string(), kill_ring.metadata_json())
12041 } else {
12042 return;
12043 }
12044 } else {
12045 return;
12046 };
12047 self.do_paste(&text, metadata, false, window, cx);
12048 }
12049
12050 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12051 self.do_copy(true, cx);
12052 }
12053
12054 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12055 self.do_copy(false, cx);
12056 }
12057
12058 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12059 let selections = self.selections.all::<Point>(cx);
12060 let buffer = self.buffer.read(cx).read(cx);
12061 let mut text = String::new();
12062
12063 let mut clipboard_selections = Vec::with_capacity(selections.len());
12064 {
12065 let max_point = buffer.max_point();
12066 let mut is_first = true;
12067 for selection in &selections {
12068 let mut start = selection.start;
12069 let mut end = selection.end;
12070 let is_entire_line = selection.is_empty() || self.selections.line_mode;
12071 if is_entire_line {
12072 start = Point::new(start.row, 0);
12073 end = cmp::min(max_point, Point::new(end.row + 1, 0));
12074 }
12075
12076 let mut trimmed_selections = Vec::new();
12077 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12078 let row = MultiBufferRow(start.row);
12079 let first_indent = buffer.indent_size_for_line(row);
12080 if first_indent.len == 0 || start.column > first_indent.len {
12081 trimmed_selections.push(start..end);
12082 } else {
12083 trimmed_selections.push(
12084 Point::new(row.0, first_indent.len)
12085 ..Point::new(row.0, buffer.line_len(row)),
12086 );
12087 for row in start.row + 1..=end.row {
12088 let mut line_len = buffer.line_len(MultiBufferRow(row));
12089 if row == end.row {
12090 line_len = end.column;
12091 }
12092 if line_len == 0 {
12093 trimmed_selections
12094 .push(Point::new(row, 0)..Point::new(row, line_len));
12095 continue;
12096 }
12097 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12098 if row_indent_size.len >= first_indent.len {
12099 trimmed_selections.push(
12100 Point::new(row, first_indent.len)..Point::new(row, line_len),
12101 );
12102 } else {
12103 trimmed_selections.clear();
12104 trimmed_selections.push(start..end);
12105 break;
12106 }
12107 }
12108 }
12109 } else {
12110 trimmed_selections.push(start..end);
12111 }
12112
12113 for trimmed_range in trimmed_selections {
12114 if is_first {
12115 is_first = false;
12116 } else {
12117 text += "\n";
12118 }
12119 let mut len = 0;
12120 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12121 text.push_str(chunk);
12122 len += chunk.len();
12123 }
12124 clipboard_selections.push(ClipboardSelection {
12125 len,
12126 is_entire_line,
12127 first_line_indent: buffer
12128 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12129 .len,
12130 });
12131 }
12132 }
12133 }
12134
12135 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12136 text,
12137 clipboard_selections,
12138 ));
12139 }
12140
12141 pub fn do_paste(
12142 &mut self,
12143 text: &String,
12144 clipboard_selections: Option<Vec<ClipboardSelection>>,
12145 handle_entire_lines: bool,
12146 window: &mut Window,
12147 cx: &mut Context<Self>,
12148 ) {
12149 if self.read_only(cx) {
12150 return;
12151 }
12152
12153 let clipboard_text = Cow::Borrowed(text);
12154
12155 self.transact(window, cx, |this, window, cx| {
12156 if let Some(mut clipboard_selections) = clipboard_selections {
12157 let old_selections = this.selections.all::<usize>(cx);
12158 let all_selections_were_entire_line =
12159 clipboard_selections.iter().all(|s| s.is_entire_line);
12160 let first_selection_indent_column =
12161 clipboard_selections.first().map(|s| s.first_line_indent);
12162 if clipboard_selections.len() != old_selections.len() {
12163 clipboard_selections.drain(..);
12164 }
12165 let cursor_offset = this.selections.last::<usize>(cx).head();
12166 let mut auto_indent_on_paste = true;
12167
12168 this.buffer.update(cx, |buffer, cx| {
12169 let snapshot = buffer.read(cx);
12170 auto_indent_on_paste = snapshot
12171 .language_settings_at(cursor_offset, cx)
12172 .auto_indent_on_paste;
12173
12174 let mut start_offset = 0;
12175 let mut edits = Vec::new();
12176 let mut original_indent_columns = Vec::new();
12177 for (ix, selection) in old_selections.iter().enumerate() {
12178 let to_insert;
12179 let entire_line;
12180 let original_indent_column;
12181 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12182 let end_offset = start_offset + clipboard_selection.len;
12183 to_insert = &clipboard_text[start_offset..end_offset];
12184 entire_line = clipboard_selection.is_entire_line;
12185 start_offset = end_offset + 1;
12186 original_indent_column = Some(clipboard_selection.first_line_indent);
12187 } else {
12188 to_insert = clipboard_text.as_str();
12189 entire_line = all_selections_were_entire_line;
12190 original_indent_column = first_selection_indent_column
12191 }
12192
12193 // If the corresponding selection was empty when this slice of the
12194 // clipboard text was written, then the entire line containing the
12195 // selection was copied. If this selection is also currently empty,
12196 // then paste the line before the current line of the buffer.
12197 let range = if selection.is_empty() && handle_entire_lines && entire_line {
12198 let column = selection.start.to_point(&snapshot).column as usize;
12199 let line_start = selection.start - column;
12200 line_start..line_start
12201 } else {
12202 selection.range()
12203 };
12204
12205 edits.push((range, to_insert));
12206 original_indent_columns.push(original_indent_column);
12207 }
12208 drop(snapshot);
12209
12210 buffer.edit(
12211 edits,
12212 if auto_indent_on_paste {
12213 Some(AutoindentMode::Block {
12214 original_indent_columns,
12215 })
12216 } else {
12217 None
12218 },
12219 cx,
12220 );
12221 });
12222
12223 let selections = this.selections.all::<usize>(cx);
12224 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12225 } else {
12226 this.insert(&clipboard_text, window, cx);
12227 }
12228 });
12229 }
12230
12231 pub fn diff_clipboard_with_selection(
12232 &mut self,
12233 _: &DiffClipboardWithSelection,
12234 window: &mut Window,
12235 cx: &mut Context<Self>,
12236 ) {
12237 let selections = self.selections.all::<usize>(cx);
12238
12239 if selections.is_empty() {
12240 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12241 return;
12242 };
12243
12244 let clipboard_text = match cx.read_from_clipboard() {
12245 Some(item) => match item.entries().first() {
12246 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
12247 _ => None,
12248 },
12249 None => None,
12250 };
12251
12252 let Some(clipboard_text) = clipboard_text else {
12253 log::warn!("Clipboard doesn't contain text.");
12254 return;
12255 };
12256
12257 window.dispatch_action(
12258 Box::new(DiffClipboardWithSelectionData {
12259 clipboard_text,
12260 editor: cx.entity(),
12261 }),
12262 cx,
12263 );
12264 }
12265
12266 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12267 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12268 if let Some(item) = cx.read_from_clipboard() {
12269 let entries = item.entries();
12270
12271 match entries.first() {
12272 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12273 // of all the pasted entries.
12274 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12275 .do_paste(
12276 clipboard_string.text(),
12277 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12278 true,
12279 window,
12280 cx,
12281 ),
12282 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12283 }
12284 }
12285 }
12286
12287 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12288 if self.read_only(cx) {
12289 return;
12290 }
12291
12292 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12293
12294 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12295 if let Some((selections, _)) =
12296 self.selection_history.transaction(transaction_id).cloned()
12297 {
12298 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12299 s.select_anchors(selections.to_vec());
12300 });
12301 } else {
12302 log::error!(
12303 "No entry in selection_history found for undo. \
12304 This may correspond to a bug where undo does not update the selection. \
12305 If this is occurring, please add details to \
12306 https://github.com/zed-industries/zed/issues/22692"
12307 );
12308 }
12309 self.request_autoscroll(Autoscroll::fit(), cx);
12310 self.unmark_text(window, cx);
12311 self.refresh_edit_prediction(true, false, window, cx);
12312 cx.emit(EditorEvent::Edited { transaction_id });
12313 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12314 }
12315 }
12316
12317 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12318 if self.read_only(cx) {
12319 return;
12320 }
12321
12322 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12323
12324 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12325 if let Some((_, Some(selections))) =
12326 self.selection_history.transaction(transaction_id).cloned()
12327 {
12328 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12329 s.select_anchors(selections.to_vec());
12330 });
12331 } else {
12332 log::error!(
12333 "No entry in selection_history found for redo. \
12334 This may correspond to a bug where undo does not update the selection. \
12335 If this is occurring, please add details to \
12336 https://github.com/zed-industries/zed/issues/22692"
12337 );
12338 }
12339 self.request_autoscroll(Autoscroll::fit(), cx);
12340 self.unmark_text(window, cx);
12341 self.refresh_edit_prediction(true, false, window, cx);
12342 cx.emit(EditorEvent::Edited { transaction_id });
12343 }
12344 }
12345
12346 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12347 self.buffer
12348 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12349 }
12350
12351 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12352 self.buffer
12353 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12354 }
12355
12356 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
12357 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12358 self.change_selections(Default::default(), window, cx, |s| {
12359 s.move_with(|map, selection| {
12360 let cursor = if selection.is_empty() {
12361 movement::left(map, selection.start)
12362 } else {
12363 selection.start
12364 };
12365 selection.collapse_to(cursor, SelectionGoal::None);
12366 });
12367 })
12368 }
12369
12370 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12371 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12372 self.change_selections(Default::default(), window, cx, |s| {
12373 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12374 })
12375 }
12376
12377 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
12378 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12379 self.change_selections(Default::default(), window, cx, |s| {
12380 s.move_with(|map, selection| {
12381 let cursor = if selection.is_empty() {
12382 movement::right(map, selection.end)
12383 } else {
12384 selection.end
12385 };
12386 selection.collapse_to(cursor, SelectionGoal::None)
12387 });
12388 })
12389 }
12390
12391 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12392 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12393 self.change_selections(Default::default(), window, cx, |s| {
12394 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
12395 })
12396 }
12397
12398 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
12399 if self.take_rename(true, window, cx).is_some() {
12400 return;
12401 }
12402
12403 if self.mode.is_single_line() {
12404 cx.propagate();
12405 return;
12406 }
12407
12408 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12409
12410 let text_layout_details = &self.text_layout_details(window);
12411 let selection_count = self.selections.count();
12412 let first_selection = self.selections.first_anchor();
12413
12414 self.change_selections(Default::default(), window, cx, |s| {
12415 s.move_with(|map, selection| {
12416 if !selection.is_empty() {
12417 selection.goal = SelectionGoal::None;
12418 }
12419 let (cursor, goal) = movement::up(
12420 map,
12421 selection.start,
12422 selection.goal,
12423 false,
12424 text_layout_details,
12425 );
12426 selection.collapse_to(cursor, goal);
12427 });
12428 });
12429
12430 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12431 {
12432 cx.propagate();
12433 }
12434 }
12435
12436 pub fn move_up_by_lines(
12437 &mut self,
12438 action: &MoveUpByLines,
12439 window: &mut Window,
12440 cx: &mut Context<Self>,
12441 ) {
12442 if self.take_rename(true, window, cx).is_some() {
12443 return;
12444 }
12445
12446 if self.mode.is_single_line() {
12447 cx.propagate();
12448 return;
12449 }
12450
12451 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12452
12453 let text_layout_details = &self.text_layout_details(window);
12454
12455 self.change_selections(Default::default(), window, cx, |s| {
12456 s.move_with(|map, selection| {
12457 if !selection.is_empty() {
12458 selection.goal = SelectionGoal::None;
12459 }
12460 let (cursor, goal) = movement::up_by_rows(
12461 map,
12462 selection.start,
12463 action.lines,
12464 selection.goal,
12465 false,
12466 text_layout_details,
12467 );
12468 selection.collapse_to(cursor, goal);
12469 });
12470 })
12471 }
12472
12473 pub fn move_down_by_lines(
12474 &mut self,
12475 action: &MoveDownByLines,
12476 window: &mut Window,
12477 cx: &mut Context<Self>,
12478 ) {
12479 if self.take_rename(true, window, cx).is_some() {
12480 return;
12481 }
12482
12483 if self.mode.is_single_line() {
12484 cx.propagate();
12485 return;
12486 }
12487
12488 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12489
12490 let text_layout_details = &self.text_layout_details(window);
12491
12492 self.change_selections(Default::default(), window, cx, |s| {
12493 s.move_with(|map, selection| {
12494 if !selection.is_empty() {
12495 selection.goal = SelectionGoal::None;
12496 }
12497 let (cursor, goal) = movement::down_by_rows(
12498 map,
12499 selection.start,
12500 action.lines,
12501 selection.goal,
12502 false,
12503 text_layout_details,
12504 );
12505 selection.collapse_to(cursor, goal);
12506 });
12507 })
12508 }
12509
12510 pub fn select_down_by_lines(
12511 &mut self,
12512 action: &SelectDownByLines,
12513 window: &mut Window,
12514 cx: &mut Context<Self>,
12515 ) {
12516 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12517 let text_layout_details = &self.text_layout_details(window);
12518 self.change_selections(Default::default(), window, cx, |s| {
12519 s.move_heads_with(|map, head, goal| {
12520 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
12521 })
12522 })
12523 }
12524
12525 pub fn select_up_by_lines(
12526 &mut self,
12527 action: &SelectUpByLines,
12528 window: &mut Window,
12529 cx: &mut Context<Self>,
12530 ) {
12531 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12532 let text_layout_details = &self.text_layout_details(window);
12533 self.change_selections(Default::default(), window, cx, |s| {
12534 s.move_heads_with(|map, head, goal| {
12535 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
12536 })
12537 })
12538 }
12539
12540 pub fn select_page_up(
12541 &mut self,
12542 _: &SelectPageUp,
12543 window: &mut Window,
12544 cx: &mut Context<Self>,
12545 ) {
12546 let Some(row_count) = self.visible_row_count() else {
12547 return;
12548 };
12549
12550 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12551
12552 let text_layout_details = &self.text_layout_details(window);
12553
12554 self.change_selections(Default::default(), window, cx, |s| {
12555 s.move_heads_with(|map, head, goal| {
12556 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
12557 })
12558 })
12559 }
12560
12561 pub fn move_page_up(
12562 &mut self,
12563 action: &MovePageUp,
12564 window: &mut Window,
12565 cx: &mut Context<Self>,
12566 ) {
12567 if self.take_rename(true, window, cx).is_some() {
12568 return;
12569 }
12570
12571 if self
12572 .context_menu
12573 .borrow_mut()
12574 .as_mut()
12575 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
12576 .unwrap_or(false)
12577 {
12578 return;
12579 }
12580
12581 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12582 cx.propagate();
12583 return;
12584 }
12585
12586 let Some(row_count) = self.visible_row_count() else {
12587 return;
12588 };
12589
12590 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12591
12592 let effects = if action.center_cursor {
12593 SelectionEffects::scroll(Autoscroll::center())
12594 } else {
12595 SelectionEffects::default()
12596 };
12597
12598 let text_layout_details = &self.text_layout_details(window);
12599
12600 self.change_selections(effects, window, cx, |s| {
12601 s.move_with(|map, selection| {
12602 if !selection.is_empty() {
12603 selection.goal = SelectionGoal::None;
12604 }
12605 let (cursor, goal) = movement::up_by_rows(
12606 map,
12607 selection.end,
12608 row_count,
12609 selection.goal,
12610 false,
12611 text_layout_details,
12612 );
12613 selection.collapse_to(cursor, goal);
12614 });
12615 });
12616 }
12617
12618 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
12619 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12620 let text_layout_details = &self.text_layout_details(window);
12621 self.change_selections(Default::default(), window, cx, |s| {
12622 s.move_heads_with(|map, head, goal| {
12623 movement::up(map, head, goal, false, text_layout_details)
12624 })
12625 })
12626 }
12627
12628 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
12629 self.take_rename(true, window, cx);
12630
12631 if self.mode.is_single_line() {
12632 cx.propagate();
12633 return;
12634 }
12635
12636 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12637
12638 let text_layout_details = &self.text_layout_details(window);
12639 let selection_count = self.selections.count();
12640 let first_selection = self.selections.first_anchor();
12641
12642 self.change_selections(Default::default(), window, cx, |s| {
12643 s.move_with(|map, selection| {
12644 if !selection.is_empty() {
12645 selection.goal = SelectionGoal::None;
12646 }
12647 let (cursor, goal) = movement::down(
12648 map,
12649 selection.end,
12650 selection.goal,
12651 false,
12652 text_layout_details,
12653 );
12654 selection.collapse_to(cursor, goal);
12655 });
12656 });
12657
12658 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12659 {
12660 cx.propagate();
12661 }
12662 }
12663
12664 pub fn select_page_down(
12665 &mut self,
12666 _: &SelectPageDown,
12667 window: &mut Window,
12668 cx: &mut Context<Self>,
12669 ) {
12670 let Some(row_count) = self.visible_row_count() else {
12671 return;
12672 };
12673
12674 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12675
12676 let text_layout_details = &self.text_layout_details(window);
12677
12678 self.change_selections(Default::default(), window, cx, |s| {
12679 s.move_heads_with(|map, head, goal| {
12680 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
12681 })
12682 })
12683 }
12684
12685 pub fn move_page_down(
12686 &mut self,
12687 action: &MovePageDown,
12688 window: &mut Window,
12689 cx: &mut Context<Self>,
12690 ) {
12691 if self.take_rename(true, window, cx).is_some() {
12692 return;
12693 }
12694
12695 if self
12696 .context_menu
12697 .borrow_mut()
12698 .as_mut()
12699 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
12700 .unwrap_or(false)
12701 {
12702 return;
12703 }
12704
12705 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12706 cx.propagate();
12707 return;
12708 }
12709
12710 let Some(row_count) = self.visible_row_count() else {
12711 return;
12712 };
12713
12714 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12715
12716 let effects = if action.center_cursor {
12717 SelectionEffects::scroll(Autoscroll::center())
12718 } else {
12719 SelectionEffects::default()
12720 };
12721
12722 let text_layout_details = &self.text_layout_details(window);
12723 self.change_selections(effects, window, cx, |s| {
12724 s.move_with(|map, selection| {
12725 if !selection.is_empty() {
12726 selection.goal = SelectionGoal::None;
12727 }
12728 let (cursor, goal) = movement::down_by_rows(
12729 map,
12730 selection.end,
12731 row_count,
12732 selection.goal,
12733 false,
12734 text_layout_details,
12735 );
12736 selection.collapse_to(cursor, goal);
12737 });
12738 });
12739 }
12740
12741 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
12742 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12743 let text_layout_details = &self.text_layout_details(window);
12744 self.change_selections(Default::default(), window, cx, |s| {
12745 s.move_heads_with(|map, head, goal| {
12746 movement::down(map, head, goal, false, text_layout_details)
12747 })
12748 });
12749 }
12750
12751 pub fn context_menu_first(
12752 &mut self,
12753 _: &ContextMenuFirst,
12754 window: &mut Window,
12755 cx: &mut Context<Self>,
12756 ) {
12757 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12758 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
12759 }
12760 }
12761
12762 pub fn context_menu_prev(
12763 &mut self,
12764 _: &ContextMenuPrevious,
12765 window: &mut Window,
12766 cx: &mut Context<Self>,
12767 ) {
12768 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12769 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
12770 }
12771 }
12772
12773 pub fn context_menu_next(
12774 &mut self,
12775 _: &ContextMenuNext,
12776 window: &mut Window,
12777 cx: &mut Context<Self>,
12778 ) {
12779 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12780 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
12781 }
12782 }
12783
12784 pub fn context_menu_last(
12785 &mut self,
12786 _: &ContextMenuLast,
12787 window: &mut Window,
12788 cx: &mut Context<Self>,
12789 ) {
12790 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12791 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
12792 }
12793 }
12794
12795 pub fn signature_help_prev(
12796 &mut self,
12797 _: &SignatureHelpPrevious,
12798 _: &mut Window,
12799 cx: &mut Context<Self>,
12800 ) {
12801 if let Some(popover) = self.signature_help_state.popover_mut() {
12802 if popover.current_signature == 0 {
12803 popover.current_signature = popover.signatures.len() - 1;
12804 } else {
12805 popover.current_signature -= 1;
12806 }
12807 cx.notify();
12808 }
12809 }
12810
12811 pub fn signature_help_next(
12812 &mut self,
12813 _: &SignatureHelpNext,
12814 _: &mut Window,
12815 cx: &mut Context<Self>,
12816 ) {
12817 if let Some(popover) = self.signature_help_state.popover_mut() {
12818 if popover.current_signature + 1 == popover.signatures.len() {
12819 popover.current_signature = 0;
12820 } else {
12821 popover.current_signature += 1;
12822 }
12823 cx.notify();
12824 }
12825 }
12826
12827 pub fn move_to_previous_word_start(
12828 &mut self,
12829 _: &MoveToPreviousWordStart,
12830 window: &mut Window,
12831 cx: &mut Context<Self>,
12832 ) {
12833 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12834 self.change_selections(Default::default(), window, cx, |s| {
12835 s.move_cursors_with(|map, head, _| {
12836 (
12837 movement::previous_word_start(map, head),
12838 SelectionGoal::None,
12839 )
12840 });
12841 })
12842 }
12843
12844 pub fn move_to_previous_subword_start(
12845 &mut self,
12846 _: &MoveToPreviousSubwordStart,
12847 window: &mut Window,
12848 cx: &mut Context<Self>,
12849 ) {
12850 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12851 self.change_selections(Default::default(), window, cx, |s| {
12852 s.move_cursors_with(|map, head, _| {
12853 (
12854 movement::previous_subword_start(map, head),
12855 SelectionGoal::None,
12856 )
12857 });
12858 })
12859 }
12860
12861 pub fn select_to_previous_word_start(
12862 &mut self,
12863 _: &SelectToPreviousWordStart,
12864 window: &mut Window,
12865 cx: &mut Context<Self>,
12866 ) {
12867 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12868 self.change_selections(Default::default(), window, cx, |s| {
12869 s.move_heads_with(|map, head, _| {
12870 (
12871 movement::previous_word_start(map, head),
12872 SelectionGoal::None,
12873 )
12874 });
12875 })
12876 }
12877
12878 pub fn select_to_previous_subword_start(
12879 &mut self,
12880 _: &SelectToPreviousSubwordStart,
12881 window: &mut Window,
12882 cx: &mut Context<Self>,
12883 ) {
12884 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12885 self.change_selections(Default::default(), window, cx, |s| {
12886 s.move_heads_with(|map, head, _| {
12887 (
12888 movement::previous_subword_start(map, head),
12889 SelectionGoal::None,
12890 )
12891 });
12892 })
12893 }
12894
12895 pub fn delete_to_previous_word_start(
12896 &mut self,
12897 action: &DeleteToPreviousWordStart,
12898 window: &mut Window,
12899 cx: &mut Context<Self>,
12900 ) {
12901 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12902 self.transact(window, cx, |this, window, cx| {
12903 this.select_autoclose_pair(window, cx);
12904 this.change_selections(Default::default(), window, cx, |s| {
12905 s.move_with(|map, selection| {
12906 if selection.is_empty() {
12907 let cursor = if action.ignore_newlines {
12908 movement::previous_word_start(map, selection.head())
12909 } else {
12910 movement::previous_word_start_or_newline(map, selection.head())
12911 };
12912 selection.set_head(cursor, SelectionGoal::None);
12913 }
12914 });
12915 });
12916 this.insert("", window, cx);
12917 });
12918 }
12919
12920 pub fn delete_to_previous_subword_start(
12921 &mut self,
12922 _: &DeleteToPreviousSubwordStart,
12923 window: &mut Window,
12924 cx: &mut Context<Self>,
12925 ) {
12926 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12927 self.transact(window, cx, |this, window, cx| {
12928 this.select_autoclose_pair(window, cx);
12929 this.change_selections(Default::default(), window, cx, |s| {
12930 s.move_with(|map, selection| {
12931 if selection.is_empty() {
12932 let cursor = movement::previous_subword_start(map, selection.head());
12933 selection.set_head(cursor, SelectionGoal::None);
12934 }
12935 });
12936 });
12937 this.insert("", window, cx);
12938 });
12939 }
12940
12941 pub fn move_to_next_word_end(
12942 &mut self,
12943 _: &MoveToNextWordEnd,
12944 window: &mut Window,
12945 cx: &mut Context<Self>,
12946 ) {
12947 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12948 self.change_selections(Default::default(), window, cx, |s| {
12949 s.move_cursors_with(|map, head, _| {
12950 (movement::next_word_end(map, head), SelectionGoal::None)
12951 });
12952 })
12953 }
12954
12955 pub fn move_to_next_subword_end(
12956 &mut self,
12957 _: &MoveToNextSubwordEnd,
12958 window: &mut Window,
12959 cx: &mut Context<Self>,
12960 ) {
12961 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12962 self.change_selections(Default::default(), window, cx, |s| {
12963 s.move_cursors_with(|map, head, _| {
12964 (movement::next_subword_end(map, head), SelectionGoal::None)
12965 });
12966 })
12967 }
12968
12969 pub fn select_to_next_word_end(
12970 &mut self,
12971 _: &SelectToNextWordEnd,
12972 window: &mut Window,
12973 cx: &mut Context<Self>,
12974 ) {
12975 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12976 self.change_selections(Default::default(), window, cx, |s| {
12977 s.move_heads_with(|map, head, _| {
12978 (movement::next_word_end(map, head), SelectionGoal::None)
12979 });
12980 })
12981 }
12982
12983 pub fn select_to_next_subword_end(
12984 &mut self,
12985 _: &SelectToNextSubwordEnd,
12986 window: &mut Window,
12987 cx: &mut Context<Self>,
12988 ) {
12989 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12990 self.change_selections(Default::default(), window, cx, |s| {
12991 s.move_heads_with(|map, head, _| {
12992 (movement::next_subword_end(map, head), SelectionGoal::None)
12993 });
12994 })
12995 }
12996
12997 pub fn delete_to_next_word_end(
12998 &mut self,
12999 action: &DeleteToNextWordEnd,
13000 window: &mut Window,
13001 cx: &mut Context<Self>,
13002 ) {
13003 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13004 self.transact(window, cx, |this, window, cx| {
13005 this.change_selections(Default::default(), window, cx, |s| {
13006 s.move_with(|map, selection| {
13007 if selection.is_empty() {
13008 let cursor = if action.ignore_newlines {
13009 movement::next_word_end(map, selection.head())
13010 } else {
13011 movement::next_word_end_or_newline(map, selection.head())
13012 };
13013 selection.set_head(cursor, SelectionGoal::None);
13014 }
13015 });
13016 });
13017 this.insert("", window, cx);
13018 });
13019 }
13020
13021 pub fn delete_to_next_subword_end(
13022 &mut self,
13023 _: &DeleteToNextSubwordEnd,
13024 window: &mut Window,
13025 cx: &mut Context<Self>,
13026 ) {
13027 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13028 self.transact(window, cx, |this, window, cx| {
13029 this.change_selections(Default::default(), window, cx, |s| {
13030 s.move_with(|map, selection| {
13031 if selection.is_empty() {
13032 let cursor = movement::next_subword_end(map, selection.head());
13033 selection.set_head(cursor, SelectionGoal::None);
13034 }
13035 });
13036 });
13037 this.insert("", window, cx);
13038 });
13039 }
13040
13041 pub fn move_to_beginning_of_line(
13042 &mut self,
13043 action: &MoveToBeginningOfLine,
13044 window: &mut Window,
13045 cx: &mut Context<Self>,
13046 ) {
13047 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13048 self.change_selections(Default::default(), window, cx, |s| {
13049 s.move_cursors_with(|map, head, _| {
13050 (
13051 movement::indented_line_beginning(
13052 map,
13053 head,
13054 action.stop_at_soft_wraps,
13055 action.stop_at_indent,
13056 ),
13057 SelectionGoal::None,
13058 )
13059 });
13060 })
13061 }
13062
13063 pub fn select_to_beginning_of_line(
13064 &mut self,
13065 action: &SelectToBeginningOfLine,
13066 window: &mut Window,
13067 cx: &mut Context<Self>,
13068 ) {
13069 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13070 self.change_selections(Default::default(), window, cx, |s| {
13071 s.move_heads_with(|map, head, _| {
13072 (
13073 movement::indented_line_beginning(
13074 map,
13075 head,
13076 action.stop_at_soft_wraps,
13077 action.stop_at_indent,
13078 ),
13079 SelectionGoal::None,
13080 )
13081 });
13082 });
13083 }
13084
13085 pub fn delete_to_beginning_of_line(
13086 &mut self,
13087 action: &DeleteToBeginningOfLine,
13088 window: &mut Window,
13089 cx: &mut Context<Self>,
13090 ) {
13091 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13092 self.transact(window, cx, |this, window, cx| {
13093 this.change_selections(Default::default(), window, cx, |s| {
13094 s.move_with(|_, selection| {
13095 selection.reversed = true;
13096 });
13097 });
13098
13099 this.select_to_beginning_of_line(
13100 &SelectToBeginningOfLine {
13101 stop_at_soft_wraps: false,
13102 stop_at_indent: action.stop_at_indent,
13103 },
13104 window,
13105 cx,
13106 );
13107 this.backspace(&Backspace, window, cx);
13108 });
13109 }
13110
13111 pub fn move_to_end_of_line(
13112 &mut self,
13113 action: &MoveToEndOfLine,
13114 window: &mut Window,
13115 cx: &mut Context<Self>,
13116 ) {
13117 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13118 self.change_selections(Default::default(), window, cx, |s| {
13119 s.move_cursors_with(|map, head, _| {
13120 (
13121 movement::line_end(map, head, action.stop_at_soft_wraps),
13122 SelectionGoal::None,
13123 )
13124 });
13125 })
13126 }
13127
13128 pub fn select_to_end_of_line(
13129 &mut self,
13130 action: &SelectToEndOfLine,
13131 window: &mut Window,
13132 cx: &mut Context<Self>,
13133 ) {
13134 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13135 self.change_selections(Default::default(), window, cx, |s| {
13136 s.move_heads_with(|map, head, _| {
13137 (
13138 movement::line_end(map, head, action.stop_at_soft_wraps),
13139 SelectionGoal::None,
13140 )
13141 });
13142 })
13143 }
13144
13145 pub fn delete_to_end_of_line(
13146 &mut self,
13147 _: &DeleteToEndOfLine,
13148 window: &mut Window,
13149 cx: &mut Context<Self>,
13150 ) {
13151 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13152 self.transact(window, cx, |this, window, cx| {
13153 this.select_to_end_of_line(
13154 &SelectToEndOfLine {
13155 stop_at_soft_wraps: false,
13156 },
13157 window,
13158 cx,
13159 );
13160 this.delete(&Delete, window, cx);
13161 });
13162 }
13163
13164 pub fn cut_to_end_of_line(
13165 &mut self,
13166 _: &CutToEndOfLine,
13167 window: &mut Window,
13168 cx: &mut Context<Self>,
13169 ) {
13170 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13171 self.transact(window, cx, |this, window, cx| {
13172 this.select_to_end_of_line(
13173 &SelectToEndOfLine {
13174 stop_at_soft_wraps: false,
13175 },
13176 window,
13177 cx,
13178 );
13179 this.cut(&Cut, window, cx);
13180 });
13181 }
13182
13183 pub fn move_to_start_of_paragraph(
13184 &mut self,
13185 _: &MoveToStartOfParagraph,
13186 window: &mut Window,
13187 cx: &mut Context<Self>,
13188 ) {
13189 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13190 cx.propagate();
13191 return;
13192 }
13193 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13194 self.change_selections(Default::default(), window, cx, |s| {
13195 s.move_with(|map, selection| {
13196 selection.collapse_to(
13197 movement::start_of_paragraph(map, selection.head(), 1),
13198 SelectionGoal::None,
13199 )
13200 });
13201 })
13202 }
13203
13204 pub fn move_to_end_of_paragraph(
13205 &mut self,
13206 _: &MoveToEndOfParagraph,
13207 window: &mut Window,
13208 cx: &mut Context<Self>,
13209 ) {
13210 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13211 cx.propagate();
13212 return;
13213 }
13214 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13215 self.change_selections(Default::default(), window, cx, |s| {
13216 s.move_with(|map, selection| {
13217 selection.collapse_to(
13218 movement::end_of_paragraph(map, selection.head(), 1),
13219 SelectionGoal::None,
13220 )
13221 });
13222 })
13223 }
13224
13225 pub fn select_to_start_of_paragraph(
13226 &mut self,
13227 _: &SelectToStartOfParagraph,
13228 window: &mut Window,
13229 cx: &mut Context<Self>,
13230 ) {
13231 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13232 cx.propagate();
13233 return;
13234 }
13235 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13236 self.change_selections(Default::default(), window, cx, |s| {
13237 s.move_heads_with(|map, head, _| {
13238 (
13239 movement::start_of_paragraph(map, head, 1),
13240 SelectionGoal::None,
13241 )
13242 });
13243 })
13244 }
13245
13246 pub fn select_to_end_of_paragraph(
13247 &mut self,
13248 _: &SelectToEndOfParagraph,
13249 window: &mut Window,
13250 cx: &mut Context<Self>,
13251 ) {
13252 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13253 cx.propagate();
13254 return;
13255 }
13256 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13257 self.change_selections(Default::default(), window, cx, |s| {
13258 s.move_heads_with(|map, head, _| {
13259 (
13260 movement::end_of_paragraph(map, head, 1),
13261 SelectionGoal::None,
13262 )
13263 });
13264 })
13265 }
13266
13267 pub fn move_to_start_of_excerpt(
13268 &mut self,
13269 _: &MoveToStartOfExcerpt,
13270 window: &mut Window,
13271 cx: &mut Context<Self>,
13272 ) {
13273 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13274 cx.propagate();
13275 return;
13276 }
13277 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13278 self.change_selections(Default::default(), window, cx, |s| {
13279 s.move_with(|map, selection| {
13280 selection.collapse_to(
13281 movement::start_of_excerpt(
13282 map,
13283 selection.head(),
13284 workspace::searchable::Direction::Prev,
13285 ),
13286 SelectionGoal::None,
13287 )
13288 });
13289 })
13290 }
13291
13292 pub fn move_to_start_of_next_excerpt(
13293 &mut self,
13294 _: &MoveToStartOfNextExcerpt,
13295 window: &mut Window,
13296 cx: &mut Context<Self>,
13297 ) {
13298 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13299 cx.propagate();
13300 return;
13301 }
13302
13303 self.change_selections(Default::default(), window, cx, |s| {
13304 s.move_with(|map, selection| {
13305 selection.collapse_to(
13306 movement::start_of_excerpt(
13307 map,
13308 selection.head(),
13309 workspace::searchable::Direction::Next,
13310 ),
13311 SelectionGoal::None,
13312 )
13313 });
13314 })
13315 }
13316
13317 pub fn move_to_end_of_excerpt(
13318 &mut self,
13319 _: &MoveToEndOfExcerpt,
13320 window: &mut Window,
13321 cx: &mut Context<Self>,
13322 ) {
13323 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13324 cx.propagate();
13325 return;
13326 }
13327 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13328 self.change_selections(Default::default(), window, cx, |s| {
13329 s.move_with(|map, selection| {
13330 selection.collapse_to(
13331 movement::end_of_excerpt(
13332 map,
13333 selection.head(),
13334 workspace::searchable::Direction::Next,
13335 ),
13336 SelectionGoal::None,
13337 )
13338 });
13339 })
13340 }
13341
13342 pub fn move_to_end_of_previous_excerpt(
13343 &mut self,
13344 _: &MoveToEndOfPreviousExcerpt,
13345 window: &mut Window,
13346 cx: &mut Context<Self>,
13347 ) {
13348 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13349 cx.propagate();
13350 return;
13351 }
13352 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13353 self.change_selections(Default::default(), window, cx, |s| {
13354 s.move_with(|map, selection| {
13355 selection.collapse_to(
13356 movement::end_of_excerpt(
13357 map,
13358 selection.head(),
13359 workspace::searchable::Direction::Prev,
13360 ),
13361 SelectionGoal::None,
13362 )
13363 });
13364 })
13365 }
13366
13367 pub fn select_to_start_of_excerpt(
13368 &mut self,
13369 _: &SelectToStartOfExcerpt,
13370 window: &mut Window,
13371 cx: &mut Context<Self>,
13372 ) {
13373 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13374 cx.propagate();
13375 return;
13376 }
13377 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13378 self.change_selections(Default::default(), window, cx, |s| {
13379 s.move_heads_with(|map, head, _| {
13380 (
13381 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13382 SelectionGoal::None,
13383 )
13384 });
13385 })
13386 }
13387
13388 pub fn select_to_start_of_next_excerpt(
13389 &mut self,
13390 _: &SelectToStartOfNextExcerpt,
13391 window: &mut Window,
13392 cx: &mut Context<Self>,
13393 ) {
13394 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13395 cx.propagate();
13396 return;
13397 }
13398 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13399 self.change_selections(Default::default(), window, cx, |s| {
13400 s.move_heads_with(|map, head, _| {
13401 (
13402 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
13403 SelectionGoal::None,
13404 )
13405 });
13406 })
13407 }
13408
13409 pub fn select_to_end_of_excerpt(
13410 &mut self,
13411 _: &SelectToEndOfExcerpt,
13412 window: &mut Window,
13413 cx: &mut Context<Self>,
13414 ) {
13415 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13416 cx.propagate();
13417 return;
13418 }
13419 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13420 self.change_selections(Default::default(), window, cx, |s| {
13421 s.move_heads_with(|map, head, _| {
13422 (
13423 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
13424 SelectionGoal::None,
13425 )
13426 });
13427 })
13428 }
13429
13430 pub fn select_to_end_of_previous_excerpt(
13431 &mut self,
13432 _: &SelectToEndOfPreviousExcerpt,
13433 window: &mut Window,
13434 cx: &mut Context<Self>,
13435 ) {
13436 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13437 cx.propagate();
13438 return;
13439 }
13440 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13441 self.change_selections(Default::default(), window, cx, |s| {
13442 s.move_heads_with(|map, head, _| {
13443 (
13444 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13445 SelectionGoal::None,
13446 )
13447 });
13448 })
13449 }
13450
13451 pub fn move_to_beginning(
13452 &mut self,
13453 _: &MoveToBeginning,
13454 window: &mut Window,
13455 cx: &mut Context<Self>,
13456 ) {
13457 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13458 cx.propagate();
13459 return;
13460 }
13461 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13462 self.change_selections(Default::default(), window, cx, |s| {
13463 s.select_ranges(vec![0..0]);
13464 });
13465 }
13466
13467 pub fn select_to_beginning(
13468 &mut self,
13469 _: &SelectToBeginning,
13470 window: &mut Window,
13471 cx: &mut Context<Self>,
13472 ) {
13473 let mut selection = self.selections.last::<Point>(cx);
13474 selection.set_head(Point::zero(), SelectionGoal::None);
13475 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13476 self.change_selections(Default::default(), window, cx, |s| {
13477 s.select(vec![selection]);
13478 });
13479 }
13480
13481 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
13482 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13483 cx.propagate();
13484 return;
13485 }
13486 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13487 let cursor = self.buffer.read(cx).read(cx).len();
13488 self.change_selections(Default::default(), window, cx, |s| {
13489 s.select_ranges(vec![cursor..cursor])
13490 });
13491 }
13492
13493 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
13494 self.nav_history = nav_history;
13495 }
13496
13497 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
13498 self.nav_history.as_ref()
13499 }
13500
13501 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
13502 self.push_to_nav_history(
13503 self.selections.newest_anchor().head(),
13504 None,
13505 false,
13506 true,
13507 cx,
13508 );
13509 }
13510
13511 fn push_to_nav_history(
13512 &mut self,
13513 cursor_anchor: Anchor,
13514 new_position: Option<Point>,
13515 is_deactivate: bool,
13516 always: bool,
13517 cx: &mut Context<Self>,
13518 ) {
13519 if let Some(nav_history) = self.nav_history.as_mut() {
13520 let buffer = self.buffer.read(cx).read(cx);
13521 let cursor_position = cursor_anchor.to_point(&buffer);
13522 let scroll_state = self.scroll_manager.anchor();
13523 let scroll_top_row = scroll_state.top_row(&buffer);
13524 drop(buffer);
13525
13526 if let Some(new_position) = new_position {
13527 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
13528 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
13529 return;
13530 }
13531 }
13532
13533 nav_history.push(
13534 Some(NavigationData {
13535 cursor_anchor,
13536 cursor_position,
13537 scroll_anchor: scroll_state,
13538 scroll_top_row,
13539 }),
13540 cx,
13541 );
13542 cx.emit(EditorEvent::PushedToNavHistory {
13543 anchor: cursor_anchor,
13544 is_deactivate,
13545 })
13546 }
13547 }
13548
13549 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
13550 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13551 let buffer = self.buffer.read(cx).snapshot(cx);
13552 let mut selection = self.selections.first::<usize>(cx);
13553 selection.set_head(buffer.len(), SelectionGoal::None);
13554 self.change_selections(Default::default(), window, cx, |s| {
13555 s.select(vec![selection]);
13556 });
13557 }
13558
13559 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
13560 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13561 let end = self.buffer.read(cx).read(cx).len();
13562 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13563 s.select_ranges(vec![0..end]);
13564 });
13565 }
13566
13567 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
13568 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13569 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13570 let mut selections = self.selections.all::<Point>(cx);
13571 let max_point = display_map.buffer_snapshot.max_point();
13572 for selection in &mut selections {
13573 let rows = selection.spanned_rows(true, &display_map);
13574 selection.start = Point::new(rows.start.0, 0);
13575 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
13576 selection.reversed = false;
13577 }
13578 self.change_selections(Default::default(), window, cx, |s| {
13579 s.select(selections);
13580 });
13581 }
13582
13583 pub fn split_selection_into_lines(
13584 &mut self,
13585 _: &SplitSelectionIntoLines,
13586 window: &mut Window,
13587 cx: &mut Context<Self>,
13588 ) {
13589 let selections = self
13590 .selections
13591 .all::<Point>(cx)
13592 .into_iter()
13593 .map(|selection| selection.start..selection.end)
13594 .collect::<Vec<_>>();
13595 self.unfold_ranges(&selections, true, true, cx);
13596
13597 let mut new_selection_ranges = Vec::new();
13598 {
13599 let buffer = self.buffer.read(cx).read(cx);
13600 for selection in selections {
13601 for row in selection.start.row..selection.end.row {
13602 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
13603 new_selection_ranges.push(cursor..cursor);
13604 }
13605
13606 let is_multiline_selection = selection.start.row != selection.end.row;
13607 // Don't insert last one if it's a multi-line selection ending at the start of a line,
13608 // so this action feels more ergonomic when paired with other selection operations
13609 let should_skip_last = is_multiline_selection && selection.end.column == 0;
13610 if !should_skip_last {
13611 new_selection_ranges.push(selection.end..selection.end);
13612 }
13613 }
13614 }
13615 self.change_selections(Default::default(), window, cx, |s| {
13616 s.select_ranges(new_selection_ranges);
13617 });
13618 }
13619
13620 pub fn add_selection_above(
13621 &mut self,
13622 _: &AddSelectionAbove,
13623 window: &mut Window,
13624 cx: &mut Context<Self>,
13625 ) {
13626 self.add_selection(true, window, cx);
13627 }
13628
13629 pub fn add_selection_below(
13630 &mut self,
13631 _: &AddSelectionBelow,
13632 window: &mut Window,
13633 cx: &mut Context<Self>,
13634 ) {
13635 self.add_selection(false, window, cx);
13636 }
13637
13638 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
13639 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13640
13641 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13642 let all_selections = self.selections.all::<Point>(cx);
13643 let text_layout_details = self.text_layout_details(window);
13644
13645 let (mut columnar_selections, new_selections_to_columnarize) = {
13646 if let Some(state) = self.add_selections_state.as_ref() {
13647 let columnar_selection_ids: HashSet<_> = state
13648 .groups
13649 .iter()
13650 .flat_map(|group| group.stack.iter())
13651 .copied()
13652 .collect();
13653
13654 all_selections
13655 .into_iter()
13656 .partition(|s| columnar_selection_ids.contains(&s.id))
13657 } else {
13658 (Vec::new(), all_selections)
13659 }
13660 };
13661
13662 let mut state = self
13663 .add_selections_state
13664 .take()
13665 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
13666
13667 for selection in new_selections_to_columnarize {
13668 let range = selection.display_range(&display_map).sorted();
13669 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
13670 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
13671 let positions = start_x.min(end_x)..start_x.max(end_x);
13672 let mut stack = Vec::new();
13673 for row in range.start.row().0..=range.end.row().0 {
13674 if let Some(selection) = self.selections.build_columnar_selection(
13675 &display_map,
13676 DisplayRow(row),
13677 &positions,
13678 selection.reversed,
13679 &text_layout_details,
13680 ) {
13681 stack.push(selection.id);
13682 columnar_selections.push(selection);
13683 }
13684 }
13685 if !stack.is_empty() {
13686 if above {
13687 stack.reverse();
13688 }
13689 state.groups.push(AddSelectionsGroup { above, stack });
13690 }
13691 }
13692
13693 let mut final_selections = Vec::new();
13694 let end_row = if above {
13695 DisplayRow(0)
13696 } else {
13697 display_map.max_point().row()
13698 };
13699
13700 let mut last_added_item_per_group = HashMap::default();
13701 for group in state.groups.iter_mut() {
13702 if let Some(last_id) = group.stack.last() {
13703 last_added_item_per_group.insert(*last_id, group);
13704 }
13705 }
13706
13707 for selection in columnar_selections {
13708 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
13709 if above == group.above {
13710 let range = selection.display_range(&display_map).sorted();
13711 debug_assert_eq!(range.start.row(), range.end.row());
13712 let mut row = range.start.row();
13713 let positions =
13714 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
13715 px(start)..px(end)
13716 } else {
13717 let start_x =
13718 display_map.x_for_display_point(range.start, &text_layout_details);
13719 let end_x =
13720 display_map.x_for_display_point(range.end, &text_layout_details);
13721 start_x.min(end_x)..start_x.max(end_x)
13722 };
13723
13724 let mut maybe_new_selection = None;
13725 while row != end_row {
13726 if above {
13727 row.0 -= 1;
13728 } else {
13729 row.0 += 1;
13730 }
13731 if let Some(new_selection) = self.selections.build_columnar_selection(
13732 &display_map,
13733 row,
13734 &positions,
13735 selection.reversed,
13736 &text_layout_details,
13737 ) {
13738 maybe_new_selection = Some(new_selection);
13739 break;
13740 }
13741 }
13742
13743 if let Some(new_selection) = maybe_new_selection {
13744 group.stack.push(new_selection.id);
13745 if above {
13746 final_selections.push(new_selection);
13747 final_selections.push(selection);
13748 } else {
13749 final_selections.push(selection);
13750 final_selections.push(new_selection);
13751 }
13752 } else {
13753 final_selections.push(selection);
13754 }
13755 } else {
13756 group.stack.pop();
13757 }
13758 } else {
13759 final_selections.push(selection);
13760 }
13761 }
13762
13763 self.change_selections(Default::default(), window, cx, |s| {
13764 s.select(final_selections);
13765 });
13766
13767 let final_selection_ids: HashSet<_> = self
13768 .selections
13769 .all::<Point>(cx)
13770 .iter()
13771 .map(|s| s.id)
13772 .collect();
13773 state.groups.retain_mut(|group| {
13774 // selections might get merged above so we remove invalid items from stacks
13775 group.stack.retain(|id| final_selection_ids.contains(id));
13776
13777 // single selection in stack can be treated as initial state
13778 group.stack.len() > 1
13779 });
13780
13781 if !state.groups.is_empty() {
13782 self.add_selections_state = Some(state);
13783 }
13784 }
13785
13786 fn select_match_ranges(
13787 &mut self,
13788 range: Range<usize>,
13789 reversed: bool,
13790 replace_newest: bool,
13791 auto_scroll: Option<Autoscroll>,
13792 window: &mut Window,
13793 cx: &mut Context<Editor>,
13794 ) {
13795 self.unfold_ranges(
13796 std::slice::from_ref(&range),
13797 false,
13798 auto_scroll.is_some(),
13799 cx,
13800 );
13801 let effects = if let Some(scroll) = auto_scroll {
13802 SelectionEffects::scroll(scroll)
13803 } else {
13804 SelectionEffects::no_scroll()
13805 };
13806 self.change_selections(effects, window, cx, |s| {
13807 if replace_newest {
13808 s.delete(s.newest_anchor().id);
13809 }
13810 if reversed {
13811 s.insert_range(range.end..range.start);
13812 } else {
13813 s.insert_range(range);
13814 }
13815 });
13816 }
13817
13818 pub fn select_next_match_internal(
13819 &mut self,
13820 display_map: &DisplaySnapshot,
13821 replace_newest: bool,
13822 autoscroll: Option<Autoscroll>,
13823 window: &mut Window,
13824 cx: &mut Context<Self>,
13825 ) -> Result<()> {
13826 let buffer = &display_map.buffer_snapshot;
13827 let mut selections = self.selections.all::<usize>(cx);
13828 if let Some(mut select_next_state) = self.select_next_state.take() {
13829 let query = &select_next_state.query;
13830 if !select_next_state.done {
13831 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13832 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13833 let mut next_selected_range = None;
13834
13835 let bytes_after_last_selection =
13836 buffer.bytes_in_range(last_selection.end..buffer.len());
13837 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
13838 let query_matches = query
13839 .stream_find_iter(bytes_after_last_selection)
13840 .map(|result| (last_selection.end, result))
13841 .chain(
13842 query
13843 .stream_find_iter(bytes_before_first_selection)
13844 .map(|result| (0, result)),
13845 );
13846
13847 for (start_offset, query_match) in query_matches {
13848 let query_match = query_match.unwrap(); // can only fail due to I/O
13849 let offset_range =
13850 start_offset + query_match.start()..start_offset + query_match.end();
13851
13852 if !select_next_state.wordwise
13853 || (!buffer.is_inside_word(offset_range.start, false)
13854 && !buffer.is_inside_word(offset_range.end, false))
13855 {
13856 // TODO: This is n^2, because we might check all the selections
13857 if !selections
13858 .iter()
13859 .any(|selection| selection.range().overlaps(&offset_range))
13860 {
13861 next_selected_range = Some(offset_range);
13862 break;
13863 }
13864 }
13865 }
13866
13867 if let Some(next_selected_range) = next_selected_range {
13868 self.select_match_ranges(
13869 next_selected_range,
13870 last_selection.reversed,
13871 replace_newest,
13872 autoscroll,
13873 window,
13874 cx,
13875 );
13876 } else {
13877 select_next_state.done = true;
13878 }
13879 }
13880
13881 self.select_next_state = Some(select_next_state);
13882 } else {
13883 let mut only_carets = true;
13884 let mut same_text_selected = true;
13885 let mut selected_text = None;
13886
13887 let mut selections_iter = selections.iter().peekable();
13888 while let Some(selection) = selections_iter.next() {
13889 if selection.start != selection.end {
13890 only_carets = false;
13891 }
13892
13893 if same_text_selected {
13894 if selected_text.is_none() {
13895 selected_text =
13896 Some(buffer.text_for_range(selection.range()).collect::<String>());
13897 }
13898
13899 if let Some(next_selection) = selections_iter.peek() {
13900 if next_selection.range().len() == selection.range().len() {
13901 let next_selected_text = buffer
13902 .text_for_range(next_selection.range())
13903 .collect::<String>();
13904 if Some(next_selected_text) != selected_text {
13905 same_text_selected = false;
13906 selected_text = None;
13907 }
13908 } else {
13909 same_text_selected = false;
13910 selected_text = None;
13911 }
13912 }
13913 }
13914 }
13915
13916 if only_carets {
13917 for selection in &mut selections {
13918 let (word_range, _) = buffer.surrounding_word(selection.start, false);
13919 selection.start = word_range.start;
13920 selection.end = word_range.end;
13921 selection.goal = SelectionGoal::None;
13922 selection.reversed = false;
13923 self.select_match_ranges(
13924 selection.start..selection.end,
13925 selection.reversed,
13926 replace_newest,
13927 autoscroll,
13928 window,
13929 cx,
13930 );
13931 }
13932
13933 if selections.len() == 1 {
13934 let selection = selections
13935 .last()
13936 .expect("ensured that there's only one selection");
13937 let query = buffer
13938 .text_for_range(selection.start..selection.end)
13939 .collect::<String>();
13940 let is_empty = query.is_empty();
13941 let select_state = SelectNextState {
13942 query: AhoCorasick::new(&[query])?,
13943 wordwise: true,
13944 done: is_empty,
13945 };
13946 self.select_next_state = Some(select_state);
13947 } else {
13948 self.select_next_state = None;
13949 }
13950 } else if let Some(selected_text) = selected_text {
13951 self.select_next_state = Some(SelectNextState {
13952 query: AhoCorasick::new(&[selected_text])?,
13953 wordwise: false,
13954 done: false,
13955 });
13956 self.select_next_match_internal(
13957 display_map,
13958 replace_newest,
13959 autoscroll,
13960 window,
13961 cx,
13962 )?;
13963 }
13964 }
13965 Ok(())
13966 }
13967
13968 pub fn select_all_matches(
13969 &mut self,
13970 _action: &SelectAllMatches,
13971 window: &mut Window,
13972 cx: &mut Context<Self>,
13973 ) -> Result<()> {
13974 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13975
13976 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13977
13978 self.select_next_match_internal(&display_map, false, None, window, cx)?;
13979 let Some(select_next_state) = self.select_next_state.as_mut() else {
13980 return Ok(());
13981 };
13982 if select_next_state.done {
13983 return Ok(());
13984 }
13985
13986 let mut new_selections = Vec::new();
13987
13988 let reversed = self.selections.oldest::<usize>(cx).reversed;
13989 let buffer = &display_map.buffer_snapshot;
13990 let query_matches = select_next_state
13991 .query
13992 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
13993
13994 for query_match in query_matches.into_iter() {
13995 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
13996 let offset_range = if reversed {
13997 query_match.end()..query_match.start()
13998 } else {
13999 query_match.start()..query_match.end()
14000 };
14001
14002 if !select_next_state.wordwise
14003 || (!buffer.is_inside_word(offset_range.start, false)
14004 && !buffer.is_inside_word(offset_range.end, false))
14005 {
14006 new_selections.push(offset_range.start..offset_range.end);
14007 }
14008 }
14009
14010 select_next_state.done = true;
14011
14012 if new_selections.is_empty() {
14013 log::error!("bug: new_selections is empty in select_all_matches");
14014 return Ok(());
14015 }
14016
14017 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14018 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14019 selections.select_ranges(new_selections)
14020 });
14021
14022 Ok(())
14023 }
14024
14025 pub fn select_next(
14026 &mut self,
14027 action: &SelectNext,
14028 window: &mut Window,
14029 cx: &mut Context<Self>,
14030 ) -> Result<()> {
14031 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14032 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14033 self.select_next_match_internal(
14034 &display_map,
14035 action.replace_newest,
14036 Some(Autoscroll::newest()),
14037 window,
14038 cx,
14039 )?;
14040 Ok(())
14041 }
14042
14043 pub fn select_previous(
14044 &mut self,
14045 action: &SelectPrevious,
14046 window: &mut Window,
14047 cx: &mut Context<Self>,
14048 ) -> Result<()> {
14049 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14050 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14051 let buffer = &display_map.buffer_snapshot;
14052 let mut selections = self.selections.all::<usize>(cx);
14053 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14054 let query = &select_prev_state.query;
14055 if !select_prev_state.done {
14056 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14057 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14058 let mut next_selected_range = None;
14059 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14060 let bytes_before_last_selection =
14061 buffer.reversed_bytes_in_range(0..last_selection.start);
14062 let bytes_after_first_selection =
14063 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14064 let query_matches = query
14065 .stream_find_iter(bytes_before_last_selection)
14066 .map(|result| (last_selection.start, result))
14067 .chain(
14068 query
14069 .stream_find_iter(bytes_after_first_selection)
14070 .map(|result| (buffer.len(), result)),
14071 );
14072 for (end_offset, query_match) in query_matches {
14073 let query_match = query_match.unwrap(); // can only fail due to I/O
14074 let offset_range =
14075 end_offset - query_match.end()..end_offset - query_match.start();
14076
14077 if !select_prev_state.wordwise
14078 || (!buffer.is_inside_word(offset_range.start, false)
14079 && !buffer.is_inside_word(offset_range.end, false))
14080 {
14081 next_selected_range = Some(offset_range);
14082 break;
14083 }
14084 }
14085
14086 if let Some(next_selected_range) = next_selected_range {
14087 self.select_match_ranges(
14088 next_selected_range,
14089 last_selection.reversed,
14090 action.replace_newest,
14091 Some(Autoscroll::newest()),
14092 window,
14093 cx,
14094 );
14095 } else {
14096 select_prev_state.done = true;
14097 }
14098 }
14099
14100 self.select_prev_state = Some(select_prev_state);
14101 } else {
14102 let mut only_carets = true;
14103 let mut same_text_selected = true;
14104 let mut selected_text = None;
14105
14106 let mut selections_iter = selections.iter().peekable();
14107 while let Some(selection) = selections_iter.next() {
14108 if selection.start != selection.end {
14109 only_carets = false;
14110 }
14111
14112 if same_text_selected {
14113 if selected_text.is_none() {
14114 selected_text =
14115 Some(buffer.text_for_range(selection.range()).collect::<String>());
14116 }
14117
14118 if let Some(next_selection) = selections_iter.peek() {
14119 if next_selection.range().len() == selection.range().len() {
14120 let next_selected_text = buffer
14121 .text_for_range(next_selection.range())
14122 .collect::<String>();
14123 if Some(next_selected_text) != selected_text {
14124 same_text_selected = false;
14125 selected_text = None;
14126 }
14127 } else {
14128 same_text_selected = false;
14129 selected_text = None;
14130 }
14131 }
14132 }
14133 }
14134
14135 if only_carets {
14136 for selection in &mut selections {
14137 let (word_range, _) = buffer.surrounding_word(selection.start, false);
14138 selection.start = word_range.start;
14139 selection.end = word_range.end;
14140 selection.goal = SelectionGoal::None;
14141 selection.reversed = false;
14142 self.select_match_ranges(
14143 selection.start..selection.end,
14144 selection.reversed,
14145 action.replace_newest,
14146 Some(Autoscroll::newest()),
14147 window,
14148 cx,
14149 );
14150 }
14151 if selections.len() == 1 {
14152 let selection = selections
14153 .last()
14154 .expect("ensured that there's only one selection");
14155 let query = buffer
14156 .text_for_range(selection.start..selection.end)
14157 .collect::<String>();
14158 let is_empty = query.is_empty();
14159 let select_state = SelectNextState {
14160 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
14161 wordwise: true,
14162 done: is_empty,
14163 };
14164 self.select_prev_state = Some(select_state);
14165 } else {
14166 self.select_prev_state = None;
14167 }
14168 } else if let Some(selected_text) = selected_text {
14169 self.select_prev_state = Some(SelectNextState {
14170 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
14171 wordwise: false,
14172 done: false,
14173 });
14174 self.select_previous(action, window, cx)?;
14175 }
14176 }
14177 Ok(())
14178 }
14179
14180 pub fn find_next_match(
14181 &mut self,
14182 _: &FindNextMatch,
14183 window: &mut Window,
14184 cx: &mut Context<Self>,
14185 ) -> Result<()> {
14186 let selections = self.selections.disjoint_anchors();
14187 match selections.first() {
14188 Some(first) if selections.len() >= 2 => {
14189 self.change_selections(Default::default(), window, cx, |s| {
14190 s.select_ranges([first.range()]);
14191 });
14192 }
14193 _ => self.select_next(
14194 &SelectNext {
14195 replace_newest: true,
14196 },
14197 window,
14198 cx,
14199 )?,
14200 }
14201 Ok(())
14202 }
14203
14204 pub fn find_previous_match(
14205 &mut self,
14206 _: &FindPreviousMatch,
14207 window: &mut Window,
14208 cx: &mut Context<Self>,
14209 ) -> Result<()> {
14210 let selections = self.selections.disjoint_anchors();
14211 match selections.last() {
14212 Some(last) if selections.len() >= 2 => {
14213 self.change_selections(Default::default(), window, cx, |s| {
14214 s.select_ranges([last.range()]);
14215 });
14216 }
14217 _ => self.select_previous(
14218 &SelectPrevious {
14219 replace_newest: true,
14220 },
14221 window,
14222 cx,
14223 )?,
14224 }
14225 Ok(())
14226 }
14227
14228 pub fn toggle_comments(
14229 &mut self,
14230 action: &ToggleComments,
14231 window: &mut Window,
14232 cx: &mut Context<Self>,
14233 ) {
14234 if self.read_only(cx) {
14235 return;
14236 }
14237 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14238 let text_layout_details = &self.text_layout_details(window);
14239 self.transact(window, cx, |this, window, cx| {
14240 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
14241 let mut edits = Vec::new();
14242 let mut selection_edit_ranges = Vec::new();
14243 let mut last_toggled_row = None;
14244 let snapshot = this.buffer.read(cx).read(cx);
14245 let empty_str: Arc<str> = Arc::default();
14246 let mut suffixes_inserted = Vec::new();
14247 let ignore_indent = action.ignore_indent;
14248
14249 fn comment_prefix_range(
14250 snapshot: &MultiBufferSnapshot,
14251 row: MultiBufferRow,
14252 comment_prefix: &str,
14253 comment_prefix_whitespace: &str,
14254 ignore_indent: bool,
14255 ) -> Range<Point> {
14256 let indent_size = if ignore_indent {
14257 0
14258 } else {
14259 snapshot.indent_size_for_line(row).len
14260 };
14261
14262 let start = Point::new(row.0, indent_size);
14263
14264 let mut line_bytes = snapshot
14265 .bytes_in_range(start..snapshot.max_point())
14266 .flatten()
14267 .copied();
14268
14269 // If this line currently begins with the line comment prefix, then record
14270 // the range containing the prefix.
14271 if line_bytes
14272 .by_ref()
14273 .take(comment_prefix.len())
14274 .eq(comment_prefix.bytes())
14275 {
14276 // Include any whitespace that matches the comment prefix.
14277 let matching_whitespace_len = line_bytes
14278 .zip(comment_prefix_whitespace.bytes())
14279 .take_while(|(a, b)| a == b)
14280 .count() as u32;
14281 let end = Point::new(
14282 start.row,
14283 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14284 );
14285 start..end
14286 } else {
14287 start..start
14288 }
14289 }
14290
14291 fn comment_suffix_range(
14292 snapshot: &MultiBufferSnapshot,
14293 row: MultiBufferRow,
14294 comment_suffix: &str,
14295 comment_suffix_has_leading_space: bool,
14296 ) -> Range<Point> {
14297 let end = Point::new(row.0, snapshot.line_len(row));
14298 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14299
14300 let mut line_end_bytes = snapshot
14301 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14302 .flatten()
14303 .copied();
14304
14305 let leading_space_len = if suffix_start_column > 0
14306 && line_end_bytes.next() == Some(b' ')
14307 && comment_suffix_has_leading_space
14308 {
14309 1
14310 } else {
14311 0
14312 };
14313
14314 // If this line currently begins with the line comment prefix, then record
14315 // the range containing the prefix.
14316 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
14317 let start = Point::new(end.row, suffix_start_column - leading_space_len);
14318 start..end
14319 } else {
14320 end..end
14321 }
14322 }
14323
14324 // TODO: Handle selections that cross excerpts
14325 for selection in &mut selections {
14326 let start_column = snapshot
14327 .indent_size_for_line(MultiBufferRow(selection.start.row))
14328 .len;
14329 let language = if let Some(language) =
14330 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
14331 {
14332 language
14333 } else {
14334 continue;
14335 };
14336
14337 selection_edit_ranges.clear();
14338
14339 // If multiple selections contain a given row, avoid processing that
14340 // row more than once.
14341 let mut start_row = MultiBufferRow(selection.start.row);
14342 if last_toggled_row == Some(start_row) {
14343 start_row = start_row.next_row();
14344 }
14345 let end_row =
14346 if selection.end.row > selection.start.row && selection.end.column == 0 {
14347 MultiBufferRow(selection.end.row - 1)
14348 } else {
14349 MultiBufferRow(selection.end.row)
14350 };
14351 last_toggled_row = Some(end_row);
14352
14353 if start_row > end_row {
14354 continue;
14355 }
14356
14357 // If the language has line comments, toggle those.
14358 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
14359
14360 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
14361 if ignore_indent {
14362 full_comment_prefixes = full_comment_prefixes
14363 .into_iter()
14364 .map(|s| Arc::from(s.trim_end()))
14365 .collect();
14366 }
14367
14368 if !full_comment_prefixes.is_empty() {
14369 let first_prefix = full_comment_prefixes
14370 .first()
14371 .expect("prefixes is non-empty");
14372 let prefix_trimmed_lengths = full_comment_prefixes
14373 .iter()
14374 .map(|p| p.trim_end_matches(' ').len())
14375 .collect::<SmallVec<[usize; 4]>>();
14376
14377 let mut all_selection_lines_are_comments = true;
14378
14379 for row in start_row.0..=end_row.0 {
14380 let row = MultiBufferRow(row);
14381 if start_row < end_row && snapshot.is_line_blank(row) {
14382 continue;
14383 }
14384
14385 let prefix_range = full_comment_prefixes
14386 .iter()
14387 .zip(prefix_trimmed_lengths.iter().copied())
14388 .map(|(prefix, trimmed_prefix_len)| {
14389 comment_prefix_range(
14390 snapshot.deref(),
14391 row,
14392 &prefix[..trimmed_prefix_len],
14393 &prefix[trimmed_prefix_len..],
14394 ignore_indent,
14395 )
14396 })
14397 .max_by_key(|range| range.end.column - range.start.column)
14398 .expect("prefixes is non-empty");
14399
14400 if prefix_range.is_empty() {
14401 all_selection_lines_are_comments = false;
14402 }
14403
14404 selection_edit_ranges.push(prefix_range);
14405 }
14406
14407 if all_selection_lines_are_comments {
14408 edits.extend(
14409 selection_edit_ranges
14410 .iter()
14411 .cloned()
14412 .map(|range| (range, empty_str.clone())),
14413 );
14414 } else {
14415 let min_column = selection_edit_ranges
14416 .iter()
14417 .map(|range| range.start.column)
14418 .min()
14419 .unwrap_or(0);
14420 edits.extend(selection_edit_ranges.iter().map(|range| {
14421 let position = Point::new(range.start.row, min_column);
14422 (position..position, first_prefix.clone())
14423 }));
14424 }
14425 } else if let Some(BlockCommentConfig {
14426 start: full_comment_prefix,
14427 end: comment_suffix,
14428 ..
14429 }) = language.block_comment()
14430 {
14431 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
14432 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
14433 let prefix_range = comment_prefix_range(
14434 snapshot.deref(),
14435 start_row,
14436 comment_prefix,
14437 comment_prefix_whitespace,
14438 ignore_indent,
14439 );
14440 let suffix_range = comment_suffix_range(
14441 snapshot.deref(),
14442 end_row,
14443 comment_suffix.trim_start_matches(' '),
14444 comment_suffix.starts_with(' '),
14445 );
14446
14447 if prefix_range.is_empty() || suffix_range.is_empty() {
14448 edits.push((
14449 prefix_range.start..prefix_range.start,
14450 full_comment_prefix.clone(),
14451 ));
14452 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
14453 suffixes_inserted.push((end_row, comment_suffix.len()));
14454 } else {
14455 edits.push((prefix_range, empty_str.clone()));
14456 edits.push((suffix_range, empty_str.clone()));
14457 }
14458 } else {
14459 continue;
14460 }
14461 }
14462
14463 drop(snapshot);
14464 this.buffer.update(cx, |buffer, cx| {
14465 buffer.edit(edits, None, cx);
14466 });
14467
14468 // Adjust selections so that they end before any comment suffixes that
14469 // were inserted.
14470 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
14471 let mut selections = this.selections.all::<Point>(cx);
14472 let snapshot = this.buffer.read(cx).read(cx);
14473 for selection in &mut selections {
14474 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
14475 match row.cmp(&MultiBufferRow(selection.end.row)) {
14476 Ordering::Less => {
14477 suffixes_inserted.next();
14478 continue;
14479 }
14480 Ordering::Greater => break,
14481 Ordering::Equal => {
14482 if selection.end.column == snapshot.line_len(row) {
14483 if selection.is_empty() {
14484 selection.start.column -= suffix_len as u32;
14485 }
14486 selection.end.column -= suffix_len as u32;
14487 }
14488 break;
14489 }
14490 }
14491 }
14492 }
14493
14494 drop(snapshot);
14495 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
14496
14497 let selections = this.selections.all::<Point>(cx);
14498 let selections_on_single_row = selections.windows(2).all(|selections| {
14499 selections[0].start.row == selections[1].start.row
14500 && selections[0].end.row == selections[1].end.row
14501 && selections[0].start.row == selections[0].end.row
14502 });
14503 let selections_selecting = selections
14504 .iter()
14505 .any(|selection| selection.start != selection.end);
14506 let advance_downwards = action.advance_downwards
14507 && selections_on_single_row
14508 && !selections_selecting
14509 && !matches!(this.mode, EditorMode::SingleLine { .. });
14510
14511 if advance_downwards {
14512 let snapshot = this.buffer.read(cx).snapshot(cx);
14513
14514 this.change_selections(Default::default(), window, cx, |s| {
14515 s.move_cursors_with(|display_snapshot, display_point, _| {
14516 let mut point = display_point.to_point(display_snapshot);
14517 point.row += 1;
14518 point = snapshot.clip_point(point, Bias::Left);
14519 let display_point = point.to_display_point(display_snapshot);
14520 let goal = SelectionGoal::HorizontalPosition(
14521 display_snapshot
14522 .x_for_display_point(display_point, text_layout_details)
14523 .into(),
14524 );
14525 (display_point, goal)
14526 })
14527 });
14528 }
14529 });
14530 }
14531
14532 pub fn select_enclosing_symbol(
14533 &mut self,
14534 _: &SelectEnclosingSymbol,
14535 window: &mut Window,
14536 cx: &mut Context<Self>,
14537 ) {
14538 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14539
14540 let buffer = self.buffer.read(cx).snapshot(cx);
14541 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
14542
14543 fn update_selection(
14544 selection: &Selection<usize>,
14545 buffer_snap: &MultiBufferSnapshot,
14546 ) -> Option<Selection<usize>> {
14547 let cursor = selection.head();
14548 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
14549 for symbol in symbols.iter().rev() {
14550 let start = symbol.range.start.to_offset(buffer_snap);
14551 let end = symbol.range.end.to_offset(buffer_snap);
14552 let new_range = start..end;
14553 if start < selection.start || end > selection.end {
14554 return Some(Selection {
14555 id: selection.id,
14556 start: new_range.start,
14557 end: new_range.end,
14558 goal: SelectionGoal::None,
14559 reversed: selection.reversed,
14560 });
14561 }
14562 }
14563 None
14564 }
14565
14566 let mut selected_larger_symbol = false;
14567 let new_selections = old_selections
14568 .iter()
14569 .map(|selection| match update_selection(selection, &buffer) {
14570 Some(new_selection) => {
14571 if new_selection.range() != selection.range() {
14572 selected_larger_symbol = true;
14573 }
14574 new_selection
14575 }
14576 None => selection.clone(),
14577 })
14578 .collect::<Vec<_>>();
14579
14580 if selected_larger_symbol {
14581 self.change_selections(Default::default(), window, cx, |s| {
14582 s.select(new_selections);
14583 });
14584 }
14585 }
14586
14587 pub fn select_larger_syntax_node(
14588 &mut self,
14589 _: &SelectLargerSyntaxNode,
14590 window: &mut Window,
14591 cx: &mut Context<Self>,
14592 ) {
14593 let Some(visible_row_count) = self.visible_row_count() else {
14594 return;
14595 };
14596 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
14597 if old_selections.is_empty() {
14598 return;
14599 }
14600
14601 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14602
14603 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14604 let buffer = self.buffer.read(cx).snapshot(cx);
14605
14606 let mut selected_larger_node = false;
14607 let mut new_selections = old_selections
14608 .iter()
14609 .map(|selection| {
14610 let old_range = selection.start..selection.end;
14611
14612 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
14613 // manually select word at selection
14614 if ["string_content", "inline"].contains(&node.kind()) {
14615 let (word_range, _) = buffer.surrounding_word(old_range.start, false);
14616 // ignore if word is already selected
14617 if !word_range.is_empty() && old_range != word_range {
14618 let (last_word_range, _) =
14619 buffer.surrounding_word(old_range.end, false);
14620 // only select word if start and end point belongs to same word
14621 if word_range == last_word_range {
14622 selected_larger_node = true;
14623 return Selection {
14624 id: selection.id,
14625 start: word_range.start,
14626 end: word_range.end,
14627 goal: SelectionGoal::None,
14628 reversed: selection.reversed,
14629 };
14630 }
14631 }
14632 }
14633 }
14634
14635 let mut new_range = old_range.clone();
14636 while let Some((_node, containing_range)) =
14637 buffer.syntax_ancestor(new_range.clone())
14638 {
14639 new_range = match containing_range {
14640 MultiOrSingleBufferOffsetRange::Single(_) => break,
14641 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14642 };
14643 if !display_map.intersects_fold(new_range.start)
14644 && !display_map.intersects_fold(new_range.end)
14645 {
14646 break;
14647 }
14648 }
14649
14650 selected_larger_node |= new_range != old_range;
14651 Selection {
14652 id: selection.id,
14653 start: new_range.start,
14654 end: new_range.end,
14655 goal: SelectionGoal::None,
14656 reversed: selection.reversed,
14657 }
14658 })
14659 .collect::<Vec<_>>();
14660
14661 if !selected_larger_node {
14662 return; // don't put this call in the history
14663 }
14664
14665 // scroll based on transformation done to the last selection created by the user
14666 let (last_old, last_new) = old_selections
14667 .last()
14668 .zip(new_selections.last().cloned())
14669 .expect("old_selections isn't empty");
14670
14671 // revert selection
14672 let is_selection_reversed = {
14673 let should_newest_selection_be_reversed = last_old.start != last_new.start;
14674 new_selections.last_mut().expect("checked above").reversed =
14675 should_newest_selection_be_reversed;
14676 should_newest_selection_be_reversed
14677 };
14678
14679 if selected_larger_node {
14680 self.select_syntax_node_history.disable_clearing = true;
14681 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14682 s.select(new_selections.clone());
14683 });
14684 self.select_syntax_node_history.disable_clearing = false;
14685 }
14686
14687 let start_row = last_new.start.to_display_point(&display_map).row().0;
14688 let end_row = last_new.end.to_display_point(&display_map).row().0;
14689 let selection_height = end_row - start_row + 1;
14690 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
14691
14692 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
14693 let scroll_behavior = if fits_on_the_screen {
14694 self.request_autoscroll(Autoscroll::fit(), cx);
14695 SelectSyntaxNodeScrollBehavior::FitSelection
14696 } else if is_selection_reversed {
14697 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14698 SelectSyntaxNodeScrollBehavior::CursorTop
14699 } else {
14700 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14701 SelectSyntaxNodeScrollBehavior::CursorBottom
14702 };
14703
14704 self.select_syntax_node_history.push((
14705 old_selections,
14706 scroll_behavior,
14707 is_selection_reversed,
14708 ));
14709 }
14710
14711 pub fn select_smaller_syntax_node(
14712 &mut self,
14713 _: &SelectSmallerSyntaxNode,
14714 window: &mut Window,
14715 cx: &mut Context<Self>,
14716 ) {
14717 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14718
14719 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
14720 self.select_syntax_node_history.pop()
14721 {
14722 if let Some(selection) = selections.last_mut() {
14723 selection.reversed = is_selection_reversed;
14724 }
14725
14726 self.select_syntax_node_history.disable_clearing = true;
14727 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14728 s.select(selections.to_vec());
14729 });
14730 self.select_syntax_node_history.disable_clearing = false;
14731
14732 match scroll_behavior {
14733 SelectSyntaxNodeScrollBehavior::CursorTop => {
14734 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14735 }
14736 SelectSyntaxNodeScrollBehavior::FitSelection => {
14737 self.request_autoscroll(Autoscroll::fit(), cx);
14738 }
14739 SelectSyntaxNodeScrollBehavior::CursorBottom => {
14740 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14741 }
14742 }
14743 }
14744 }
14745
14746 pub fn unwrap_syntax_node(
14747 &mut self,
14748 _: &UnwrapSyntaxNode,
14749 window: &mut Window,
14750 cx: &mut Context<Self>,
14751 ) {
14752 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14753
14754 let buffer = self.buffer.read(cx).snapshot(cx);
14755 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
14756
14757 let edits = old_selections
14758 .iter()
14759 // only consider the first selection for now
14760 .take(1)
14761 .map(|selection| {
14762 // Only requires two branches once if-let-chains stabilize (#53667)
14763 let selection_range = if !selection.is_empty() {
14764 selection.range()
14765 } else if let Some((_, ancestor_range)) =
14766 buffer.syntax_ancestor(selection.start..selection.end)
14767 {
14768 match ancestor_range {
14769 MultiOrSingleBufferOffsetRange::Single(range) => range,
14770 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14771 }
14772 } else {
14773 selection.range()
14774 };
14775
14776 let mut new_range = selection_range.clone();
14777 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(new_range.clone()) {
14778 new_range = match ancestor_range {
14779 MultiOrSingleBufferOffsetRange::Single(range) => range,
14780 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14781 };
14782 if new_range.start < selection_range.start
14783 || new_range.end > selection_range.end
14784 {
14785 break;
14786 }
14787 }
14788
14789 (selection, selection_range, new_range)
14790 })
14791 .collect::<Vec<_>>();
14792
14793 self.transact(window, cx, |editor, window, cx| {
14794 for (_, child, parent) in &edits {
14795 let text = buffer.text_for_range(child.clone()).collect::<String>();
14796 editor.replace_text_in_range(Some(parent.clone()), &text, window, cx);
14797 }
14798
14799 editor.change_selections(
14800 SelectionEffects::scroll(Autoscroll::fit()),
14801 window,
14802 cx,
14803 |s| {
14804 s.select(
14805 edits
14806 .iter()
14807 .map(|(s, old, new)| Selection {
14808 id: s.id,
14809 start: new.start,
14810 end: new.start + old.len(),
14811 goal: SelectionGoal::None,
14812 reversed: s.reversed,
14813 })
14814 .collect(),
14815 );
14816 },
14817 );
14818 });
14819 }
14820
14821 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
14822 if !EditorSettings::get_global(cx).gutter.runnables {
14823 self.clear_tasks();
14824 return Task::ready(());
14825 }
14826 let project = self.project.as_ref().map(Entity::downgrade);
14827 let task_sources = self.lsp_task_sources(cx);
14828 let multi_buffer = self.buffer.downgrade();
14829 cx.spawn_in(window, async move |editor, cx| {
14830 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
14831 let Some(project) = project.and_then(|p| p.upgrade()) else {
14832 return;
14833 };
14834 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
14835 this.display_map.update(cx, |map, cx| map.snapshot(cx))
14836 }) else {
14837 return;
14838 };
14839
14840 let hide_runnables = project
14841 .update(cx, |project, cx| {
14842 // Do not display any test indicators in non-dev server remote projects.
14843 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
14844 })
14845 .unwrap_or(true);
14846 if hide_runnables {
14847 return;
14848 }
14849 let new_rows =
14850 cx.background_spawn({
14851 let snapshot = display_snapshot.clone();
14852 async move {
14853 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
14854 }
14855 })
14856 .await;
14857 let Ok(lsp_tasks) =
14858 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
14859 else {
14860 return;
14861 };
14862 let lsp_tasks = lsp_tasks.await;
14863
14864 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
14865 lsp_tasks
14866 .into_iter()
14867 .flat_map(|(kind, tasks)| {
14868 tasks.into_iter().filter_map(move |(location, task)| {
14869 Some((kind.clone(), location?, task))
14870 })
14871 })
14872 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
14873 let buffer = location.target.buffer;
14874 let buffer_snapshot = buffer.read(cx).snapshot();
14875 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
14876 |(excerpt_id, snapshot, _)| {
14877 if snapshot.remote_id() == buffer_snapshot.remote_id() {
14878 display_snapshot
14879 .buffer_snapshot
14880 .anchor_in_excerpt(excerpt_id, location.target.range.start)
14881 } else {
14882 None
14883 }
14884 },
14885 );
14886 if let Some(offset) = offset {
14887 let task_buffer_range =
14888 location.target.range.to_point(&buffer_snapshot);
14889 let context_buffer_range =
14890 task_buffer_range.to_offset(&buffer_snapshot);
14891 let context_range = BufferOffset(context_buffer_range.start)
14892 ..BufferOffset(context_buffer_range.end);
14893
14894 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
14895 .or_insert_with(|| RunnableTasks {
14896 templates: Vec::new(),
14897 offset,
14898 column: task_buffer_range.start.column,
14899 extra_variables: HashMap::default(),
14900 context_range,
14901 })
14902 .templates
14903 .push((kind, task.original_task().clone()));
14904 }
14905
14906 acc
14907 })
14908 }) else {
14909 return;
14910 };
14911
14912 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
14913 buffer.language_settings(cx).tasks.prefer_lsp
14914 }) else {
14915 return;
14916 };
14917
14918 let rows = Self::runnable_rows(
14919 project,
14920 display_snapshot,
14921 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
14922 new_rows,
14923 cx.clone(),
14924 )
14925 .await;
14926 editor
14927 .update(cx, |editor, _| {
14928 editor.clear_tasks();
14929 for (key, mut value) in rows {
14930 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
14931 value.templates.extend(lsp_tasks.templates);
14932 }
14933
14934 editor.insert_tasks(key, value);
14935 }
14936 for (key, value) in lsp_tasks_by_rows {
14937 editor.insert_tasks(key, value);
14938 }
14939 })
14940 .ok();
14941 })
14942 }
14943 fn fetch_runnable_ranges(
14944 snapshot: &DisplaySnapshot,
14945 range: Range<Anchor>,
14946 ) -> Vec<language::RunnableRange> {
14947 snapshot.buffer_snapshot.runnable_ranges(range).collect()
14948 }
14949
14950 fn runnable_rows(
14951 project: Entity<Project>,
14952 snapshot: DisplaySnapshot,
14953 prefer_lsp: bool,
14954 runnable_ranges: Vec<RunnableRange>,
14955 cx: AsyncWindowContext,
14956 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
14957 cx.spawn(async move |cx| {
14958 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
14959 for mut runnable in runnable_ranges {
14960 let Some(tasks) = cx
14961 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
14962 .ok()
14963 else {
14964 continue;
14965 };
14966 let mut tasks = tasks.await;
14967
14968 if prefer_lsp {
14969 tasks.retain(|(task_kind, _)| {
14970 !matches!(task_kind, TaskSourceKind::Language { .. })
14971 });
14972 }
14973 if tasks.is_empty() {
14974 continue;
14975 }
14976
14977 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
14978 let Some(row) = snapshot
14979 .buffer_snapshot
14980 .buffer_line_for_row(MultiBufferRow(point.row))
14981 .map(|(_, range)| range.start.row)
14982 else {
14983 continue;
14984 };
14985
14986 let context_range =
14987 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
14988 runnable_rows.push((
14989 (runnable.buffer_id, row),
14990 RunnableTasks {
14991 templates: tasks,
14992 offset: snapshot
14993 .buffer_snapshot
14994 .anchor_before(runnable.run_range.start),
14995 context_range,
14996 column: point.column,
14997 extra_variables: runnable.extra_captures,
14998 },
14999 ));
15000 }
15001 runnable_rows
15002 })
15003 }
15004
15005 fn templates_with_tags(
15006 project: &Entity<Project>,
15007 runnable: &mut Runnable,
15008 cx: &mut App,
15009 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
15010 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
15011 let (worktree_id, file) = project
15012 .buffer_for_id(runnable.buffer, cx)
15013 .and_then(|buffer| buffer.read(cx).file())
15014 .map(|file| (file.worktree_id(cx), file.clone()))
15015 .unzip();
15016
15017 (
15018 project.task_store().read(cx).task_inventory().cloned(),
15019 worktree_id,
15020 file,
15021 )
15022 });
15023
15024 let tags = mem::take(&mut runnable.tags);
15025 let language = runnable.language.clone();
15026 cx.spawn(async move |cx| {
15027 let mut templates_with_tags = Vec::new();
15028 if let Some(inventory) = inventory {
15029 for RunnableTag(tag) in tags {
15030 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
15031 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
15032 }) else {
15033 return templates_with_tags;
15034 };
15035 templates_with_tags.extend(new_tasks.await.into_iter().filter(
15036 move |(_, template)| {
15037 template.tags.iter().any(|source_tag| source_tag == &tag)
15038 },
15039 ));
15040 }
15041 }
15042 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
15043
15044 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
15045 // Strongest source wins; if we have worktree tag binding, prefer that to
15046 // global and language bindings;
15047 // if we have a global binding, prefer that to language binding.
15048 let first_mismatch = templates_with_tags
15049 .iter()
15050 .position(|(tag_source, _)| tag_source != leading_tag_source);
15051 if let Some(index) = first_mismatch {
15052 templates_with_tags.truncate(index);
15053 }
15054 }
15055
15056 templates_with_tags
15057 })
15058 }
15059
15060 pub fn move_to_enclosing_bracket(
15061 &mut self,
15062 _: &MoveToEnclosingBracket,
15063 window: &mut Window,
15064 cx: &mut Context<Self>,
15065 ) {
15066 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15067 self.change_selections(Default::default(), window, cx, |s| {
15068 s.move_offsets_with(|snapshot, selection| {
15069 let Some(enclosing_bracket_ranges) =
15070 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
15071 else {
15072 return;
15073 };
15074
15075 let mut best_length = usize::MAX;
15076 let mut best_inside = false;
15077 let mut best_in_bracket_range = false;
15078 let mut best_destination = None;
15079 for (open, close) in enclosing_bracket_ranges {
15080 let close = close.to_inclusive();
15081 let length = close.end() - open.start;
15082 let inside = selection.start >= open.end && selection.end <= *close.start();
15083 let in_bracket_range = open.to_inclusive().contains(&selection.head())
15084 || close.contains(&selection.head());
15085
15086 // If best is next to a bracket and current isn't, skip
15087 if !in_bracket_range && best_in_bracket_range {
15088 continue;
15089 }
15090
15091 // Prefer smaller lengths unless best is inside and current isn't
15092 if length > best_length && (best_inside || !inside) {
15093 continue;
15094 }
15095
15096 best_length = length;
15097 best_inside = inside;
15098 best_in_bracket_range = in_bracket_range;
15099 best_destination = Some(
15100 if close.contains(&selection.start) && close.contains(&selection.end) {
15101 if inside { open.end } else { open.start }
15102 } else if inside {
15103 *close.start()
15104 } else {
15105 *close.end()
15106 },
15107 );
15108 }
15109
15110 if let Some(destination) = best_destination {
15111 selection.collapse_to(destination, SelectionGoal::None);
15112 }
15113 })
15114 });
15115 }
15116
15117 pub fn undo_selection(
15118 &mut self,
15119 _: &UndoSelection,
15120 window: &mut Window,
15121 cx: &mut Context<Self>,
15122 ) {
15123 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15124 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
15125 self.selection_history.mode = SelectionHistoryMode::Undoing;
15126 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15127 this.end_selection(window, cx);
15128 this.change_selections(
15129 SelectionEffects::scroll(Autoscroll::newest()),
15130 window,
15131 cx,
15132 |s| s.select_anchors(entry.selections.to_vec()),
15133 );
15134 });
15135 self.selection_history.mode = SelectionHistoryMode::Normal;
15136
15137 self.select_next_state = entry.select_next_state;
15138 self.select_prev_state = entry.select_prev_state;
15139 self.add_selections_state = entry.add_selections_state;
15140 }
15141 }
15142
15143 pub fn redo_selection(
15144 &mut self,
15145 _: &RedoSelection,
15146 window: &mut Window,
15147 cx: &mut Context<Self>,
15148 ) {
15149 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15150 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
15151 self.selection_history.mode = SelectionHistoryMode::Redoing;
15152 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15153 this.end_selection(window, cx);
15154 this.change_selections(
15155 SelectionEffects::scroll(Autoscroll::newest()),
15156 window,
15157 cx,
15158 |s| s.select_anchors(entry.selections.to_vec()),
15159 );
15160 });
15161 self.selection_history.mode = SelectionHistoryMode::Normal;
15162
15163 self.select_next_state = entry.select_next_state;
15164 self.select_prev_state = entry.select_prev_state;
15165 self.add_selections_state = entry.add_selections_state;
15166 }
15167 }
15168
15169 pub fn expand_excerpts(
15170 &mut self,
15171 action: &ExpandExcerpts,
15172 _: &mut Window,
15173 cx: &mut Context<Self>,
15174 ) {
15175 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
15176 }
15177
15178 pub fn expand_excerpts_down(
15179 &mut self,
15180 action: &ExpandExcerptsDown,
15181 _: &mut Window,
15182 cx: &mut Context<Self>,
15183 ) {
15184 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
15185 }
15186
15187 pub fn expand_excerpts_up(
15188 &mut self,
15189 action: &ExpandExcerptsUp,
15190 _: &mut Window,
15191 cx: &mut Context<Self>,
15192 ) {
15193 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
15194 }
15195
15196 pub fn expand_excerpts_for_direction(
15197 &mut self,
15198 lines: u32,
15199 direction: ExpandExcerptDirection,
15200
15201 cx: &mut Context<Self>,
15202 ) {
15203 let selections = self.selections.disjoint_anchors();
15204
15205 let lines = if lines == 0 {
15206 EditorSettings::get_global(cx).expand_excerpt_lines
15207 } else {
15208 lines
15209 };
15210
15211 self.buffer.update(cx, |buffer, cx| {
15212 let snapshot = buffer.snapshot(cx);
15213 let mut excerpt_ids = selections
15214 .iter()
15215 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
15216 .collect::<Vec<_>>();
15217 excerpt_ids.sort();
15218 excerpt_ids.dedup();
15219 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
15220 })
15221 }
15222
15223 pub fn expand_excerpt(
15224 &mut self,
15225 excerpt: ExcerptId,
15226 direction: ExpandExcerptDirection,
15227 window: &mut Window,
15228 cx: &mut Context<Self>,
15229 ) {
15230 let current_scroll_position = self.scroll_position(cx);
15231 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
15232 let mut should_scroll_up = false;
15233
15234 if direction == ExpandExcerptDirection::Down {
15235 let multi_buffer = self.buffer.read(cx);
15236 let snapshot = multi_buffer.snapshot(cx);
15237 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
15238 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
15239 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
15240 let buffer_snapshot = buffer.read(cx).snapshot();
15241 let excerpt_end_row =
15242 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
15243 let last_row = buffer_snapshot.max_point().row;
15244 let lines_below = last_row.saturating_sub(excerpt_end_row);
15245 should_scroll_up = lines_below >= lines_to_expand;
15246 }
15247 }
15248 }
15249 }
15250
15251 self.buffer.update(cx, |buffer, cx| {
15252 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
15253 });
15254
15255 if should_scroll_up {
15256 let new_scroll_position =
15257 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
15258 self.set_scroll_position(new_scroll_position, window, cx);
15259 }
15260 }
15261
15262 pub fn go_to_singleton_buffer_point(
15263 &mut self,
15264 point: Point,
15265 window: &mut Window,
15266 cx: &mut Context<Self>,
15267 ) {
15268 self.go_to_singleton_buffer_range(point..point, window, cx);
15269 }
15270
15271 pub fn go_to_singleton_buffer_range(
15272 &mut self,
15273 range: Range<Point>,
15274 window: &mut Window,
15275 cx: &mut Context<Self>,
15276 ) {
15277 let multibuffer = self.buffer().read(cx);
15278 let Some(buffer) = multibuffer.as_singleton() else {
15279 return;
15280 };
15281 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
15282 return;
15283 };
15284 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
15285 return;
15286 };
15287 self.change_selections(
15288 SelectionEffects::default().nav_history(true),
15289 window,
15290 cx,
15291 |s| s.select_anchor_ranges([start..end]),
15292 );
15293 }
15294
15295 pub fn go_to_diagnostic(
15296 &mut self,
15297 action: &GoToDiagnostic,
15298 window: &mut Window,
15299 cx: &mut Context<Self>,
15300 ) {
15301 if !self.diagnostics_enabled() {
15302 return;
15303 }
15304 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15305 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
15306 }
15307
15308 pub fn go_to_prev_diagnostic(
15309 &mut self,
15310 action: &GoToPreviousDiagnostic,
15311 window: &mut Window,
15312 cx: &mut Context<Self>,
15313 ) {
15314 if !self.diagnostics_enabled() {
15315 return;
15316 }
15317 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15318 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
15319 }
15320
15321 pub fn go_to_diagnostic_impl(
15322 &mut self,
15323 direction: Direction,
15324 severity: GoToDiagnosticSeverityFilter,
15325 window: &mut Window,
15326 cx: &mut Context<Self>,
15327 ) {
15328 let buffer = self.buffer.read(cx).snapshot(cx);
15329 let selection = self.selections.newest::<usize>(cx);
15330
15331 let mut active_group_id = None;
15332 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
15333 if active_group.active_range.start.to_offset(&buffer) == selection.start {
15334 active_group_id = Some(active_group.group_id);
15335 }
15336 }
15337
15338 fn filtered(
15339 snapshot: EditorSnapshot,
15340 severity: GoToDiagnosticSeverityFilter,
15341 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
15342 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
15343 diagnostics
15344 .filter(move |entry| severity.matches(entry.diagnostic.severity))
15345 .filter(|entry| entry.range.start != entry.range.end)
15346 .filter(|entry| !entry.diagnostic.is_unnecessary)
15347 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
15348 }
15349
15350 let snapshot = self.snapshot(window, cx);
15351 let before = filtered(
15352 snapshot.clone(),
15353 severity,
15354 buffer
15355 .diagnostics_in_range(0..selection.start)
15356 .filter(|entry| entry.range.start <= selection.start),
15357 );
15358 let after = filtered(
15359 snapshot,
15360 severity,
15361 buffer
15362 .diagnostics_in_range(selection.start..buffer.len())
15363 .filter(|entry| entry.range.start >= selection.start),
15364 );
15365
15366 let mut found: Option<DiagnosticEntry<usize>> = None;
15367 if direction == Direction::Prev {
15368 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
15369 {
15370 for diagnostic in prev_diagnostics.into_iter().rev() {
15371 if diagnostic.range.start != selection.start
15372 || active_group_id
15373 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
15374 {
15375 found = Some(diagnostic);
15376 break 'outer;
15377 }
15378 }
15379 }
15380 } else {
15381 for diagnostic in after.chain(before) {
15382 if diagnostic.range.start != selection.start
15383 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
15384 {
15385 found = Some(diagnostic);
15386 break;
15387 }
15388 }
15389 }
15390 let Some(next_diagnostic) = found else {
15391 return;
15392 };
15393
15394 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
15395 return;
15396 };
15397 self.change_selections(Default::default(), window, cx, |s| {
15398 s.select_ranges(vec![
15399 next_diagnostic.range.start..next_diagnostic.range.start,
15400 ])
15401 });
15402 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
15403 self.refresh_edit_prediction(false, true, window, cx);
15404 }
15405
15406 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
15407 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15408 let snapshot = self.snapshot(window, cx);
15409 let selection = self.selections.newest::<Point>(cx);
15410 self.go_to_hunk_before_or_after_position(
15411 &snapshot,
15412 selection.head(),
15413 Direction::Next,
15414 window,
15415 cx,
15416 );
15417 }
15418
15419 pub fn go_to_hunk_before_or_after_position(
15420 &mut self,
15421 snapshot: &EditorSnapshot,
15422 position: Point,
15423 direction: Direction,
15424 window: &mut Window,
15425 cx: &mut Context<Editor>,
15426 ) {
15427 let row = if direction == Direction::Next {
15428 self.hunk_after_position(snapshot, position)
15429 .map(|hunk| hunk.row_range.start)
15430 } else {
15431 self.hunk_before_position(snapshot, position)
15432 };
15433
15434 if let Some(row) = row {
15435 let destination = Point::new(row.0, 0);
15436 let autoscroll = Autoscroll::center();
15437
15438 self.unfold_ranges(&[destination..destination], false, false, cx);
15439 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
15440 s.select_ranges([destination..destination]);
15441 });
15442 }
15443 }
15444
15445 fn hunk_after_position(
15446 &mut self,
15447 snapshot: &EditorSnapshot,
15448 position: Point,
15449 ) -> Option<MultiBufferDiffHunk> {
15450 snapshot
15451 .buffer_snapshot
15452 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
15453 .find(|hunk| hunk.row_range.start.0 > position.row)
15454 .or_else(|| {
15455 snapshot
15456 .buffer_snapshot
15457 .diff_hunks_in_range(Point::zero()..position)
15458 .find(|hunk| hunk.row_range.end.0 < position.row)
15459 })
15460 }
15461
15462 fn go_to_prev_hunk(
15463 &mut self,
15464 _: &GoToPreviousHunk,
15465 window: &mut Window,
15466 cx: &mut Context<Self>,
15467 ) {
15468 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15469 let snapshot = self.snapshot(window, cx);
15470 let selection = self.selections.newest::<Point>(cx);
15471 self.go_to_hunk_before_or_after_position(
15472 &snapshot,
15473 selection.head(),
15474 Direction::Prev,
15475 window,
15476 cx,
15477 );
15478 }
15479
15480 fn hunk_before_position(
15481 &mut self,
15482 snapshot: &EditorSnapshot,
15483 position: Point,
15484 ) -> Option<MultiBufferRow> {
15485 snapshot
15486 .buffer_snapshot
15487 .diff_hunk_before(position)
15488 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
15489 }
15490
15491 fn go_to_next_change(
15492 &mut self,
15493 _: &GoToNextChange,
15494 window: &mut Window,
15495 cx: &mut Context<Self>,
15496 ) {
15497 if let Some(selections) = self
15498 .change_list
15499 .next_change(1, Direction::Next)
15500 .map(|s| s.to_vec())
15501 {
15502 self.change_selections(Default::default(), window, cx, |s| {
15503 let map = s.display_map();
15504 s.select_display_ranges(selections.iter().map(|a| {
15505 let point = a.to_display_point(&map);
15506 point..point
15507 }))
15508 })
15509 }
15510 }
15511
15512 fn go_to_previous_change(
15513 &mut self,
15514 _: &GoToPreviousChange,
15515 window: &mut Window,
15516 cx: &mut Context<Self>,
15517 ) {
15518 if let Some(selections) = self
15519 .change_list
15520 .next_change(1, Direction::Prev)
15521 .map(|s| s.to_vec())
15522 {
15523 self.change_selections(Default::default(), window, cx, |s| {
15524 let map = s.display_map();
15525 s.select_display_ranges(selections.iter().map(|a| {
15526 let point = a.to_display_point(&map);
15527 point..point
15528 }))
15529 })
15530 }
15531 }
15532
15533 fn go_to_line<T: 'static>(
15534 &mut self,
15535 position: Anchor,
15536 highlight_color: Option<Hsla>,
15537 window: &mut Window,
15538 cx: &mut Context<Self>,
15539 ) {
15540 let snapshot = self.snapshot(window, cx).display_snapshot;
15541 let position = position.to_point(&snapshot.buffer_snapshot);
15542 let start = snapshot
15543 .buffer_snapshot
15544 .clip_point(Point::new(position.row, 0), Bias::Left);
15545 let end = start + Point::new(1, 0);
15546 let start = snapshot.buffer_snapshot.anchor_before(start);
15547 let end = snapshot.buffer_snapshot.anchor_before(end);
15548
15549 self.highlight_rows::<T>(
15550 start..end,
15551 highlight_color
15552 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
15553 Default::default(),
15554 cx,
15555 );
15556
15557 if self.buffer.read(cx).is_singleton() {
15558 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
15559 }
15560 }
15561
15562 pub fn go_to_definition(
15563 &mut self,
15564 _: &GoToDefinition,
15565 window: &mut Window,
15566 cx: &mut Context<Self>,
15567 ) -> Task<Result<Navigated>> {
15568 let definition =
15569 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
15570 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
15571 cx.spawn_in(window, async move |editor, cx| {
15572 if definition.await? == Navigated::Yes {
15573 return Ok(Navigated::Yes);
15574 }
15575 match fallback_strategy {
15576 GoToDefinitionFallback::None => Ok(Navigated::No),
15577 GoToDefinitionFallback::FindAllReferences => {
15578 match editor.update_in(cx, |editor, window, cx| {
15579 editor.find_all_references(&FindAllReferences, window, cx)
15580 })? {
15581 Some(references) => references.await,
15582 None => Ok(Navigated::No),
15583 }
15584 }
15585 }
15586 })
15587 }
15588
15589 pub fn go_to_declaration(
15590 &mut self,
15591 _: &GoToDeclaration,
15592 window: &mut Window,
15593 cx: &mut Context<Self>,
15594 ) -> Task<Result<Navigated>> {
15595 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
15596 }
15597
15598 pub fn go_to_declaration_split(
15599 &mut self,
15600 _: &GoToDeclaration,
15601 window: &mut Window,
15602 cx: &mut Context<Self>,
15603 ) -> Task<Result<Navigated>> {
15604 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
15605 }
15606
15607 pub fn go_to_implementation(
15608 &mut self,
15609 _: &GoToImplementation,
15610 window: &mut Window,
15611 cx: &mut Context<Self>,
15612 ) -> Task<Result<Navigated>> {
15613 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
15614 }
15615
15616 pub fn go_to_implementation_split(
15617 &mut self,
15618 _: &GoToImplementationSplit,
15619 window: &mut Window,
15620 cx: &mut Context<Self>,
15621 ) -> Task<Result<Navigated>> {
15622 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
15623 }
15624
15625 pub fn go_to_type_definition(
15626 &mut self,
15627 _: &GoToTypeDefinition,
15628 window: &mut Window,
15629 cx: &mut Context<Self>,
15630 ) -> Task<Result<Navigated>> {
15631 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
15632 }
15633
15634 pub fn go_to_definition_split(
15635 &mut self,
15636 _: &GoToDefinitionSplit,
15637 window: &mut Window,
15638 cx: &mut Context<Self>,
15639 ) -> Task<Result<Navigated>> {
15640 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
15641 }
15642
15643 pub fn go_to_type_definition_split(
15644 &mut self,
15645 _: &GoToTypeDefinitionSplit,
15646 window: &mut Window,
15647 cx: &mut Context<Self>,
15648 ) -> Task<Result<Navigated>> {
15649 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
15650 }
15651
15652 fn go_to_definition_of_kind(
15653 &mut self,
15654 kind: GotoDefinitionKind,
15655 split: bool,
15656 window: &mut Window,
15657 cx: &mut Context<Self>,
15658 ) -> Task<Result<Navigated>> {
15659 let Some(provider) = self.semantics_provider.clone() else {
15660 return Task::ready(Ok(Navigated::No));
15661 };
15662 let head = self.selections.newest::<usize>(cx).head();
15663 let buffer = self.buffer.read(cx);
15664 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
15665 text_anchor
15666 } else {
15667 return Task::ready(Ok(Navigated::No));
15668 };
15669
15670 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
15671 return Task::ready(Ok(Navigated::No));
15672 };
15673
15674 cx.spawn_in(window, async move |editor, cx| {
15675 let definitions = definitions.await?;
15676 let navigated = editor
15677 .update_in(cx, |editor, window, cx| {
15678 editor.navigate_to_hover_links(
15679 Some(kind),
15680 definitions
15681 .into_iter()
15682 .filter(|location| {
15683 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
15684 })
15685 .map(HoverLink::Text)
15686 .collect::<Vec<_>>(),
15687 split,
15688 window,
15689 cx,
15690 )
15691 })?
15692 .await?;
15693 anyhow::Ok(navigated)
15694 })
15695 }
15696
15697 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
15698 let selection = self.selections.newest_anchor();
15699 let head = selection.head();
15700 let tail = selection.tail();
15701
15702 let Some((buffer, start_position)) =
15703 self.buffer.read(cx).text_anchor_for_position(head, cx)
15704 else {
15705 return;
15706 };
15707
15708 let end_position = if head != tail {
15709 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
15710 return;
15711 };
15712 Some(pos)
15713 } else {
15714 None
15715 };
15716
15717 let url_finder = cx.spawn_in(window, async move |editor, cx| {
15718 let url = if let Some(end_pos) = end_position {
15719 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
15720 } else {
15721 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
15722 };
15723
15724 if let Some(url) = url {
15725 editor.update(cx, |_, cx| {
15726 cx.open_url(&url);
15727 })
15728 } else {
15729 Ok(())
15730 }
15731 });
15732
15733 url_finder.detach();
15734 }
15735
15736 pub fn open_selected_filename(
15737 &mut self,
15738 _: &OpenSelectedFilename,
15739 window: &mut Window,
15740 cx: &mut Context<Self>,
15741 ) {
15742 let Some(workspace) = self.workspace() else {
15743 return;
15744 };
15745
15746 let position = self.selections.newest_anchor().head();
15747
15748 let Some((buffer, buffer_position)) =
15749 self.buffer.read(cx).text_anchor_for_position(position, cx)
15750 else {
15751 return;
15752 };
15753
15754 let project = self.project.clone();
15755
15756 cx.spawn_in(window, async move |_, cx| {
15757 let result = find_file(&buffer, project, buffer_position, cx).await;
15758
15759 if let Some((_, path)) = result {
15760 workspace
15761 .update_in(cx, |workspace, window, cx| {
15762 workspace.open_resolved_path(path, window, cx)
15763 })?
15764 .await?;
15765 }
15766 anyhow::Ok(())
15767 })
15768 .detach();
15769 }
15770
15771 pub(crate) fn navigate_to_hover_links(
15772 &mut self,
15773 kind: Option<GotoDefinitionKind>,
15774 mut definitions: Vec<HoverLink>,
15775 split: bool,
15776 window: &mut Window,
15777 cx: &mut Context<Editor>,
15778 ) -> Task<Result<Navigated>> {
15779 // If there is one definition, just open it directly
15780 if definitions.len() == 1 {
15781 let definition = definitions.pop().unwrap();
15782
15783 enum TargetTaskResult {
15784 Location(Option<Location>),
15785 AlreadyNavigated,
15786 }
15787
15788 let target_task = match definition {
15789 HoverLink::Text(link) => {
15790 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
15791 }
15792 HoverLink::InlayHint(lsp_location, server_id) => {
15793 let computation =
15794 self.compute_target_location(lsp_location, server_id, window, cx);
15795 cx.background_spawn(async move {
15796 let location = computation.await?;
15797 Ok(TargetTaskResult::Location(location))
15798 })
15799 }
15800 HoverLink::Url(url) => {
15801 cx.open_url(&url);
15802 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
15803 }
15804 HoverLink::File(path) => {
15805 if let Some(workspace) = self.workspace() {
15806 cx.spawn_in(window, async move |_, cx| {
15807 workspace
15808 .update_in(cx, |workspace, window, cx| {
15809 workspace.open_resolved_path(path, window, cx)
15810 })?
15811 .await
15812 .map(|_| TargetTaskResult::AlreadyNavigated)
15813 })
15814 } else {
15815 Task::ready(Ok(TargetTaskResult::Location(None)))
15816 }
15817 }
15818 };
15819 cx.spawn_in(window, async move |editor, cx| {
15820 let target = match target_task.await.context("target resolution task")? {
15821 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
15822 TargetTaskResult::Location(None) => return Ok(Navigated::No),
15823 TargetTaskResult::Location(Some(target)) => target,
15824 };
15825
15826 editor.update_in(cx, |editor, window, cx| {
15827 let Some(workspace) = editor.workspace() else {
15828 return Navigated::No;
15829 };
15830 let pane = workspace.read(cx).active_pane().clone();
15831
15832 let range = target.range.to_point(target.buffer.read(cx));
15833 let range = editor.range_for_match(&range);
15834 let range = collapse_multiline_range(range);
15835
15836 if !split
15837 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
15838 {
15839 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
15840 } else {
15841 window.defer(cx, move |window, cx| {
15842 let target_editor: Entity<Self> =
15843 workspace.update(cx, |workspace, cx| {
15844 let pane = if split {
15845 workspace.adjacent_pane(window, cx)
15846 } else {
15847 workspace.active_pane().clone()
15848 };
15849
15850 workspace.open_project_item(
15851 pane,
15852 target.buffer.clone(),
15853 true,
15854 true,
15855 window,
15856 cx,
15857 )
15858 });
15859 target_editor.update(cx, |target_editor, cx| {
15860 // When selecting a definition in a different buffer, disable the nav history
15861 // to avoid creating a history entry at the previous cursor location.
15862 pane.update(cx, |pane, _| pane.disable_history());
15863 target_editor.go_to_singleton_buffer_range(range, window, cx);
15864 pane.update(cx, |pane, _| pane.enable_history());
15865 });
15866 });
15867 }
15868 Navigated::Yes
15869 })
15870 })
15871 } else if !definitions.is_empty() {
15872 cx.spawn_in(window, async move |editor, cx| {
15873 let (title, location_tasks, workspace) = editor
15874 .update_in(cx, |editor, window, cx| {
15875 let tab_kind = match kind {
15876 Some(GotoDefinitionKind::Implementation) => "Implementations",
15877 _ => "Definitions",
15878 };
15879 let title = definitions
15880 .iter()
15881 .find_map(|definition| match definition {
15882 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
15883 let buffer = origin.buffer.read(cx);
15884 format!(
15885 "{} for {}",
15886 tab_kind,
15887 buffer
15888 .text_for_range(origin.range.clone())
15889 .collect::<String>()
15890 )
15891 }),
15892 HoverLink::InlayHint(_, _) => None,
15893 HoverLink::Url(_) => None,
15894 HoverLink::File(_) => None,
15895 })
15896 .unwrap_or(tab_kind.to_string());
15897 let location_tasks = definitions
15898 .into_iter()
15899 .map(|definition| match definition {
15900 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
15901 HoverLink::InlayHint(lsp_location, server_id) => editor
15902 .compute_target_location(lsp_location, server_id, window, cx),
15903 HoverLink::Url(_) => Task::ready(Ok(None)),
15904 HoverLink::File(_) => Task::ready(Ok(None)),
15905 })
15906 .collect::<Vec<_>>();
15907 (title, location_tasks, editor.workspace().clone())
15908 })
15909 .context("location tasks preparation")?;
15910
15911 let locations: Vec<Location> = future::join_all(location_tasks)
15912 .await
15913 .into_iter()
15914 .filter_map(|location| location.transpose())
15915 .collect::<Result<_>>()
15916 .context("location tasks")?;
15917
15918 if locations.is_empty() {
15919 return Ok(Navigated::No);
15920 }
15921
15922 let Some(workspace) = workspace else {
15923 return Ok(Navigated::No);
15924 };
15925
15926 let opened = workspace
15927 .update_in(cx, |workspace, window, cx| {
15928 Self::open_locations_in_multibuffer(
15929 workspace,
15930 locations,
15931 title,
15932 split,
15933 MultibufferSelectionMode::First,
15934 window,
15935 cx,
15936 )
15937 })
15938 .ok();
15939
15940 anyhow::Ok(Navigated::from_bool(opened.is_some()))
15941 })
15942 } else {
15943 Task::ready(Ok(Navigated::No))
15944 }
15945 }
15946
15947 fn compute_target_location(
15948 &self,
15949 lsp_location: lsp::Location,
15950 server_id: LanguageServerId,
15951 window: &mut Window,
15952 cx: &mut Context<Self>,
15953 ) -> Task<anyhow::Result<Option<Location>>> {
15954 let Some(project) = self.project.clone() else {
15955 return Task::ready(Ok(None));
15956 };
15957
15958 cx.spawn_in(window, async move |editor, cx| {
15959 let location_task = editor.update(cx, |_, cx| {
15960 project.update(cx, |project, cx| {
15961 let language_server_name = project
15962 .language_server_statuses(cx)
15963 .find(|(id, _)| server_id == *id)
15964 .map(|(_, status)| status.name.clone());
15965 language_server_name.map(|language_server_name| {
15966 project.open_local_buffer_via_lsp(
15967 lsp_location.uri.clone(),
15968 server_id,
15969 language_server_name,
15970 cx,
15971 )
15972 })
15973 })
15974 })?;
15975 let location = match location_task {
15976 Some(task) => Some({
15977 let target_buffer_handle = task.await.context("open local buffer")?;
15978 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
15979 let target_start = target_buffer
15980 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
15981 let target_end = target_buffer
15982 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
15983 target_buffer.anchor_after(target_start)
15984 ..target_buffer.anchor_before(target_end)
15985 })?;
15986 Location {
15987 buffer: target_buffer_handle,
15988 range,
15989 }
15990 }),
15991 None => None,
15992 };
15993 Ok(location)
15994 })
15995 }
15996
15997 pub fn find_all_references(
15998 &mut self,
15999 _: &FindAllReferences,
16000 window: &mut Window,
16001 cx: &mut Context<Self>,
16002 ) -> Option<Task<Result<Navigated>>> {
16003 let selection = self.selections.newest::<usize>(cx);
16004 let multi_buffer = self.buffer.read(cx);
16005 let head = selection.head();
16006
16007 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16008 let head_anchor = multi_buffer_snapshot.anchor_at(
16009 head,
16010 if head < selection.tail() {
16011 Bias::Right
16012 } else {
16013 Bias::Left
16014 },
16015 );
16016
16017 match self
16018 .find_all_references_task_sources
16019 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16020 {
16021 Ok(_) => {
16022 log::info!(
16023 "Ignoring repeated FindAllReferences invocation with the position of already running task"
16024 );
16025 return None;
16026 }
16027 Err(i) => {
16028 self.find_all_references_task_sources.insert(i, head_anchor);
16029 }
16030 }
16031
16032 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
16033 let workspace = self.workspace()?;
16034 let project = workspace.read(cx).project().clone();
16035 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
16036 Some(cx.spawn_in(window, async move |editor, cx| {
16037 let _cleanup = cx.on_drop(&editor, move |editor, _| {
16038 if let Ok(i) = editor
16039 .find_all_references_task_sources
16040 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16041 {
16042 editor.find_all_references_task_sources.remove(i);
16043 }
16044 });
16045
16046 let locations = references.await?;
16047 if locations.is_empty() {
16048 return anyhow::Ok(Navigated::No);
16049 }
16050
16051 workspace.update_in(cx, |workspace, window, cx| {
16052 let title = locations
16053 .first()
16054 .as_ref()
16055 .map(|location| {
16056 let buffer = location.buffer.read(cx);
16057 format!(
16058 "References to `{}`",
16059 buffer
16060 .text_for_range(location.range.clone())
16061 .collect::<String>()
16062 )
16063 })
16064 .unwrap();
16065 Self::open_locations_in_multibuffer(
16066 workspace,
16067 locations,
16068 title,
16069 false,
16070 MultibufferSelectionMode::First,
16071 window,
16072 cx,
16073 );
16074 Navigated::Yes
16075 })
16076 }))
16077 }
16078
16079 /// Opens a multibuffer with the given project locations in it
16080 pub fn open_locations_in_multibuffer(
16081 workspace: &mut Workspace,
16082 mut locations: Vec<Location>,
16083 title: String,
16084 split: bool,
16085 multibuffer_selection_mode: MultibufferSelectionMode,
16086 window: &mut Window,
16087 cx: &mut Context<Workspace>,
16088 ) {
16089 if locations.is_empty() {
16090 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
16091 return;
16092 }
16093
16094 // If there are multiple definitions, open them in a multibuffer
16095 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
16096 let mut locations = locations.into_iter().peekable();
16097 let mut ranges: Vec<Range<Anchor>> = Vec::new();
16098 let capability = workspace.project().read(cx).capability();
16099
16100 let excerpt_buffer = cx.new(|cx| {
16101 let mut multibuffer = MultiBuffer::new(capability);
16102 while let Some(location) = locations.next() {
16103 let buffer = location.buffer.read(cx);
16104 let mut ranges_for_buffer = Vec::new();
16105 let range = location.range.to_point(buffer);
16106 ranges_for_buffer.push(range.clone());
16107
16108 while let Some(next_location) = locations.peek() {
16109 if next_location.buffer == location.buffer {
16110 ranges_for_buffer.push(next_location.range.to_point(buffer));
16111 locations.next();
16112 } else {
16113 break;
16114 }
16115 }
16116
16117 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
16118 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
16119 PathKey::for_buffer(&location.buffer, cx),
16120 location.buffer.clone(),
16121 ranges_for_buffer,
16122 DEFAULT_MULTIBUFFER_CONTEXT,
16123 cx,
16124 );
16125 ranges.extend(new_ranges)
16126 }
16127
16128 multibuffer.with_title(title)
16129 });
16130
16131 let editor = cx.new(|cx| {
16132 Editor::for_multibuffer(
16133 excerpt_buffer,
16134 Some(workspace.project().clone()),
16135 window,
16136 cx,
16137 )
16138 });
16139 editor.update(cx, |editor, cx| {
16140 match multibuffer_selection_mode {
16141 MultibufferSelectionMode::First => {
16142 if let Some(first_range) = ranges.first() {
16143 editor.change_selections(
16144 SelectionEffects::no_scroll(),
16145 window,
16146 cx,
16147 |selections| {
16148 selections.clear_disjoint();
16149 selections
16150 .select_anchor_ranges(std::iter::once(first_range.clone()));
16151 },
16152 );
16153 }
16154 editor.highlight_background::<Self>(
16155 &ranges,
16156 |theme| theme.colors().editor_highlighted_line_background,
16157 cx,
16158 );
16159 }
16160 MultibufferSelectionMode::All => {
16161 editor.change_selections(
16162 SelectionEffects::no_scroll(),
16163 window,
16164 cx,
16165 |selections| {
16166 selections.clear_disjoint();
16167 selections.select_anchor_ranges(ranges);
16168 },
16169 );
16170 }
16171 }
16172 editor.register_buffers_with_language_servers(cx);
16173 });
16174
16175 let item = Box::new(editor);
16176 let item_id = item.item_id();
16177
16178 if split {
16179 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
16180 } else {
16181 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
16182 let (preview_item_id, preview_item_idx) =
16183 workspace.active_pane().read_with(cx, |pane, _| {
16184 (pane.preview_item_id(), pane.preview_item_idx())
16185 });
16186
16187 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
16188
16189 if let Some(preview_item_id) = preview_item_id {
16190 workspace.active_pane().update(cx, |pane, cx| {
16191 pane.remove_item(preview_item_id, false, false, window, cx);
16192 });
16193 }
16194 } else {
16195 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
16196 }
16197 }
16198 workspace.active_pane().update(cx, |pane, cx| {
16199 pane.set_preview_item_id(Some(item_id), cx);
16200 });
16201 }
16202
16203 pub fn rename(
16204 &mut self,
16205 _: &Rename,
16206 window: &mut Window,
16207 cx: &mut Context<Self>,
16208 ) -> Option<Task<Result<()>>> {
16209 use language::ToOffset as _;
16210
16211 let provider = self.semantics_provider.clone()?;
16212 let selection = self.selections.newest_anchor().clone();
16213 let (cursor_buffer, cursor_buffer_position) = self
16214 .buffer
16215 .read(cx)
16216 .text_anchor_for_position(selection.head(), cx)?;
16217 let (tail_buffer, cursor_buffer_position_end) = self
16218 .buffer
16219 .read(cx)
16220 .text_anchor_for_position(selection.tail(), cx)?;
16221 if tail_buffer != cursor_buffer {
16222 return None;
16223 }
16224
16225 let snapshot = cursor_buffer.read(cx).snapshot();
16226 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
16227 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
16228 let prepare_rename = provider
16229 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
16230 .unwrap_or_else(|| Task::ready(Ok(None)));
16231 drop(snapshot);
16232
16233 Some(cx.spawn_in(window, async move |this, cx| {
16234 let rename_range = if let Some(range) = prepare_rename.await? {
16235 Some(range)
16236 } else {
16237 this.update(cx, |this, cx| {
16238 let buffer = this.buffer.read(cx).snapshot(cx);
16239 let mut buffer_highlights = this
16240 .document_highlights_for_position(selection.head(), &buffer)
16241 .filter(|highlight| {
16242 highlight.start.excerpt_id == selection.head().excerpt_id
16243 && highlight.end.excerpt_id == selection.head().excerpt_id
16244 });
16245 buffer_highlights
16246 .next()
16247 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
16248 })?
16249 };
16250 if let Some(rename_range) = rename_range {
16251 this.update_in(cx, |this, window, cx| {
16252 let snapshot = cursor_buffer.read(cx).snapshot();
16253 let rename_buffer_range = rename_range.to_offset(&snapshot);
16254 let cursor_offset_in_rename_range =
16255 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
16256 let cursor_offset_in_rename_range_end =
16257 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
16258
16259 this.take_rename(false, window, cx);
16260 let buffer = this.buffer.read(cx).read(cx);
16261 let cursor_offset = selection.head().to_offset(&buffer);
16262 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
16263 let rename_end = rename_start + rename_buffer_range.len();
16264 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
16265 let mut old_highlight_id = None;
16266 let old_name: Arc<str> = buffer
16267 .chunks(rename_start..rename_end, true)
16268 .map(|chunk| {
16269 if old_highlight_id.is_none() {
16270 old_highlight_id = chunk.syntax_highlight_id;
16271 }
16272 chunk.text
16273 })
16274 .collect::<String>()
16275 .into();
16276
16277 drop(buffer);
16278
16279 // Position the selection in the rename editor so that it matches the current selection.
16280 this.show_local_selections = false;
16281 let rename_editor = cx.new(|cx| {
16282 let mut editor = Editor::single_line(window, cx);
16283 editor.buffer.update(cx, |buffer, cx| {
16284 buffer.edit([(0..0, old_name.clone())], None, cx)
16285 });
16286 let rename_selection_range = match cursor_offset_in_rename_range
16287 .cmp(&cursor_offset_in_rename_range_end)
16288 {
16289 Ordering::Equal => {
16290 editor.select_all(&SelectAll, window, cx);
16291 return editor;
16292 }
16293 Ordering::Less => {
16294 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
16295 }
16296 Ordering::Greater => {
16297 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
16298 }
16299 };
16300 if rename_selection_range.end > old_name.len() {
16301 editor.select_all(&SelectAll, window, cx);
16302 } else {
16303 editor.change_selections(Default::default(), window, cx, |s| {
16304 s.select_ranges([rename_selection_range]);
16305 });
16306 }
16307 editor
16308 });
16309 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
16310 if e == &EditorEvent::Focused {
16311 cx.emit(EditorEvent::FocusedIn)
16312 }
16313 })
16314 .detach();
16315
16316 let write_highlights =
16317 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
16318 let read_highlights =
16319 this.clear_background_highlights::<DocumentHighlightRead>(cx);
16320 let ranges = write_highlights
16321 .iter()
16322 .flat_map(|(_, ranges)| ranges.iter())
16323 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
16324 .cloned()
16325 .collect();
16326
16327 this.highlight_text::<Rename>(
16328 ranges,
16329 HighlightStyle {
16330 fade_out: Some(0.6),
16331 ..Default::default()
16332 },
16333 cx,
16334 );
16335 let rename_focus_handle = rename_editor.focus_handle(cx);
16336 window.focus(&rename_focus_handle);
16337 let block_id = this.insert_blocks(
16338 [BlockProperties {
16339 style: BlockStyle::Flex,
16340 placement: BlockPlacement::Below(range.start),
16341 height: Some(1),
16342 render: Arc::new({
16343 let rename_editor = rename_editor.clone();
16344 move |cx: &mut BlockContext| {
16345 let mut text_style = cx.editor_style.text.clone();
16346 if let Some(highlight_style) = old_highlight_id
16347 .and_then(|h| h.style(&cx.editor_style.syntax))
16348 {
16349 text_style = text_style.highlight(highlight_style);
16350 }
16351 div()
16352 .block_mouse_except_scroll()
16353 .pl(cx.anchor_x)
16354 .child(EditorElement::new(
16355 &rename_editor,
16356 EditorStyle {
16357 background: cx.theme().system().transparent,
16358 local_player: cx.editor_style.local_player,
16359 text: text_style,
16360 scrollbar_width: cx.editor_style.scrollbar_width,
16361 syntax: cx.editor_style.syntax.clone(),
16362 status: cx.editor_style.status.clone(),
16363 inlay_hints_style: HighlightStyle {
16364 font_weight: Some(FontWeight::BOLD),
16365 ..make_inlay_hints_style(cx.app)
16366 },
16367 edit_prediction_styles: make_suggestion_styles(
16368 cx.app,
16369 ),
16370 ..EditorStyle::default()
16371 },
16372 ))
16373 .into_any_element()
16374 }
16375 }),
16376 priority: 0,
16377 }],
16378 Some(Autoscroll::fit()),
16379 cx,
16380 )[0];
16381 this.pending_rename = Some(RenameState {
16382 range,
16383 old_name,
16384 editor: rename_editor,
16385 block_id,
16386 });
16387 })?;
16388 }
16389
16390 Ok(())
16391 }))
16392 }
16393
16394 pub fn confirm_rename(
16395 &mut self,
16396 _: &ConfirmRename,
16397 window: &mut Window,
16398 cx: &mut Context<Self>,
16399 ) -> Option<Task<Result<()>>> {
16400 let rename = self.take_rename(false, window, cx)?;
16401 let workspace = self.workspace()?.downgrade();
16402 let (buffer, start) = self
16403 .buffer
16404 .read(cx)
16405 .text_anchor_for_position(rename.range.start, cx)?;
16406 let (end_buffer, _) = self
16407 .buffer
16408 .read(cx)
16409 .text_anchor_for_position(rename.range.end, cx)?;
16410 if buffer != end_buffer {
16411 return None;
16412 }
16413
16414 let old_name = rename.old_name;
16415 let new_name = rename.editor.read(cx).text(cx);
16416
16417 let rename = self.semantics_provider.as_ref()?.perform_rename(
16418 &buffer,
16419 start,
16420 new_name.clone(),
16421 cx,
16422 )?;
16423
16424 Some(cx.spawn_in(window, async move |editor, cx| {
16425 let project_transaction = rename.await?;
16426 Self::open_project_transaction(
16427 &editor,
16428 workspace,
16429 project_transaction,
16430 format!("Rename: {} → {}", old_name, new_name),
16431 cx,
16432 )
16433 .await?;
16434
16435 editor.update(cx, |editor, cx| {
16436 editor.refresh_document_highlights(cx);
16437 })?;
16438 Ok(())
16439 }))
16440 }
16441
16442 fn take_rename(
16443 &mut self,
16444 moving_cursor: bool,
16445 window: &mut Window,
16446 cx: &mut Context<Self>,
16447 ) -> Option<RenameState> {
16448 let rename = self.pending_rename.take()?;
16449 if rename.editor.focus_handle(cx).is_focused(window) {
16450 window.focus(&self.focus_handle);
16451 }
16452
16453 self.remove_blocks(
16454 [rename.block_id].into_iter().collect(),
16455 Some(Autoscroll::fit()),
16456 cx,
16457 );
16458 self.clear_highlights::<Rename>(cx);
16459 self.show_local_selections = true;
16460
16461 if moving_cursor {
16462 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
16463 editor.selections.newest::<usize>(cx).head()
16464 });
16465
16466 // Update the selection to match the position of the selection inside
16467 // the rename editor.
16468 let snapshot = self.buffer.read(cx).read(cx);
16469 let rename_range = rename.range.to_offset(&snapshot);
16470 let cursor_in_editor = snapshot
16471 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
16472 .min(rename_range.end);
16473 drop(snapshot);
16474
16475 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16476 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
16477 });
16478 } else {
16479 self.refresh_document_highlights(cx);
16480 }
16481
16482 Some(rename)
16483 }
16484
16485 pub fn pending_rename(&self) -> Option<&RenameState> {
16486 self.pending_rename.as_ref()
16487 }
16488
16489 fn format(
16490 &mut self,
16491 _: &Format,
16492 window: &mut Window,
16493 cx: &mut Context<Self>,
16494 ) -> Option<Task<Result<()>>> {
16495 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16496
16497 let project = match &self.project {
16498 Some(project) => project.clone(),
16499 None => return None,
16500 };
16501
16502 Some(self.perform_format(
16503 project,
16504 FormatTrigger::Manual,
16505 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
16506 window,
16507 cx,
16508 ))
16509 }
16510
16511 fn format_selections(
16512 &mut self,
16513 _: &FormatSelections,
16514 window: &mut Window,
16515 cx: &mut Context<Self>,
16516 ) -> Option<Task<Result<()>>> {
16517 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16518
16519 let project = match &self.project {
16520 Some(project) => project.clone(),
16521 None => return None,
16522 };
16523
16524 let ranges = self
16525 .selections
16526 .all_adjusted(cx)
16527 .into_iter()
16528 .map(|selection| selection.range())
16529 .collect_vec();
16530
16531 Some(self.perform_format(
16532 project,
16533 FormatTrigger::Manual,
16534 FormatTarget::Ranges(ranges),
16535 window,
16536 cx,
16537 ))
16538 }
16539
16540 fn perform_format(
16541 &mut self,
16542 project: Entity<Project>,
16543 trigger: FormatTrigger,
16544 target: FormatTarget,
16545 window: &mut Window,
16546 cx: &mut Context<Self>,
16547 ) -> Task<Result<()>> {
16548 let buffer = self.buffer.clone();
16549 let (buffers, target) = match target {
16550 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
16551 FormatTarget::Ranges(selection_ranges) => {
16552 let multi_buffer = buffer.read(cx);
16553 let snapshot = multi_buffer.read(cx);
16554 let mut buffers = HashSet::default();
16555 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
16556 BTreeMap::new();
16557 for selection_range in selection_ranges {
16558 for (buffer, buffer_range, _) in
16559 snapshot.range_to_buffer_ranges(selection_range)
16560 {
16561 let buffer_id = buffer.remote_id();
16562 let start = buffer.anchor_before(buffer_range.start);
16563 let end = buffer.anchor_after(buffer_range.end);
16564 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
16565 buffer_id_to_ranges
16566 .entry(buffer_id)
16567 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
16568 .or_insert_with(|| vec![start..end]);
16569 }
16570 }
16571 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
16572 }
16573 };
16574
16575 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
16576 let selections_prev = transaction_id_prev
16577 .and_then(|transaction_id_prev| {
16578 // default to selections as they were after the last edit, if we have them,
16579 // instead of how they are now.
16580 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
16581 // will take you back to where you made the last edit, instead of staying where you scrolled
16582 self.selection_history
16583 .transaction(transaction_id_prev)
16584 .map(|t| t.0.clone())
16585 })
16586 .unwrap_or_else(|| {
16587 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
16588 self.selections.disjoint_anchors()
16589 });
16590
16591 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
16592 let format = project.update(cx, |project, cx| {
16593 project.format(buffers, target, true, trigger, cx)
16594 });
16595
16596 cx.spawn_in(window, async move |editor, cx| {
16597 let transaction = futures::select_biased! {
16598 transaction = format.log_err().fuse() => transaction,
16599 () = timeout => {
16600 log::warn!("timed out waiting for formatting");
16601 None
16602 }
16603 };
16604
16605 buffer
16606 .update(cx, |buffer, cx| {
16607 if let Some(transaction) = transaction {
16608 if !buffer.is_singleton() {
16609 buffer.push_transaction(&transaction.0, cx);
16610 }
16611 }
16612 cx.notify();
16613 })
16614 .ok();
16615
16616 if let Some(transaction_id_now) =
16617 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
16618 {
16619 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
16620 if has_new_transaction {
16621 _ = editor.update(cx, |editor, _| {
16622 editor
16623 .selection_history
16624 .insert_transaction(transaction_id_now, selections_prev);
16625 });
16626 }
16627 }
16628
16629 Ok(())
16630 })
16631 }
16632
16633 fn organize_imports(
16634 &mut self,
16635 _: &OrganizeImports,
16636 window: &mut Window,
16637 cx: &mut Context<Self>,
16638 ) -> Option<Task<Result<()>>> {
16639 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16640 let project = match &self.project {
16641 Some(project) => project.clone(),
16642 None => return None,
16643 };
16644 Some(self.perform_code_action_kind(
16645 project,
16646 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
16647 window,
16648 cx,
16649 ))
16650 }
16651
16652 fn perform_code_action_kind(
16653 &mut self,
16654 project: Entity<Project>,
16655 kind: CodeActionKind,
16656 window: &mut Window,
16657 cx: &mut Context<Self>,
16658 ) -> Task<Result<()>> {
16659 let buffer = self.buffer.clone();
16660 let buffers = buffer.read(cx).all_buffers();
16661 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
16662 let apply_action = project.update(cx, |project, cx| {
16663 project.apply_code_action_kind(buffers, kind, true, cx)
16664 });
16665 cx.spawn_in(window, async move |_, cx| {
16666 let transaction = futures::select_biased! {
16667 () = timeout => {
16668 log::warn!("timed out waiting for executing code action");
16669 None
16670 }
16671 transaction = apply_action.log_err().fuse() => transaction,
16672 };
16673 buffer
16674 .update(cx, |buffer, cx| {
16675 // check if we need this
16676 if let Some(transaction) = transaction {
16677 if !buffer.is_singleton() {
16678 buffer.push_transaction(&transaction.0, cx);
16679 }
16680 }
16681 cx.notify();
16682 })
16683 .ok();
16684 Ok(())
16685 })
16686 }
16687
16688 pub fn restart_language_server(
16689 &mut self,
16690 _: &RestartLanguageServer,
16691 _: &mut Window,
16692 cx: &mut Context<Self>,
16693 ) {
16694 if let Some(project) = self.project.clone() {
16695 self.buffer.update(cx, |multi_buffer, cx| {
16696 project.update(cx, |project, cx| {
16697 project.restart_language_servers_for_buffers(
16698 multi_buffer.all_buffers().into_iter().collect(),
16699 HashSet::default(),
16700 cx,
16701 );
16702 });
16703 })
16704 }
16705 }
16706
16707 pub fn stop_language_server(
16708 &mut self,
16709 _: &StopLanguageServer,
16710 _: &mut Window,
16711 cx: &mut Context<Self>,
16712 ) {
16713 if let Some(project) = self.project.clone() {
16714 self.buffer.update(cx, |multi_buffer, cx| {
16715 project.update(cx, |project, cx| {
16716 project.stop_language_servers_for_buffers(
16717 multi_buffer.all_buffers().into_iter().collect(),
16718 HashSet::default(),
16719 cx,
16720 );
16721 cx.emit(project::Event::RefreshInlayHints);
16722 });
16723 });
16724 }
16725 }
16726
16727 fn cancel_language_server_work(
16728 workspace: &mut Workspace,
16729 _: &actions::CancelLanguageServerWork,
16730 _: &mut Window,
16731 cx: &mut Context<Workspace>,
16732 ) {
16733 let project = workspace.project();
16734 let buffers = workspace
16735 .active_item(cx)
16736 .and_then(|item| item.act_as::<Editor>(cx))
16737 .map_or(HashSet::default(), |editor| {
16738 editor.read(cx).buffer.read(cx).all_buffers()
16739 });
16740 project.update(cx, |project, cx| {
16741 project.cancel_language_server_work_for_buffers(buffers, cx);
16742 });
16743 }
16744
16745 fn show_character_palette(
16746 &mut self,
16747 _: &ShowCharacterPalette,
16748 window: &mut Window,
16749 _: &mut Context<Self>,
16750 ) {
16751 window.show_character_palette();
16752 }
16753
16754 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
16755 if !self.diagnostics_enabled() {
16756 return;
16757 }
16758
16759 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
16760 let buffer = self.buffer.read(cx).snapshot(cx);
16761 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
16762 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
16763 let is_valid = buffer
16764 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
16765 .any(|entry| {
16766 entry.diagnostic.is_primary
16767 && !entry.range.is_empty()
16768 && entry.range.start == primary_range_start
16769 && entry.diagnostic.message == active_diagnostics.active_message
16770 });
16771
16772 if !is_valid {
16773 self.dismiss_diagnostics(cx);
16774 }
16775 }
16776 }
16777
16778 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
16779 match &self.active_diagnostics {
16780 ActiveDiagnostic::Group(group) => Some(group),
16781 _ => None,
16782 }
16783 }
16784
16785 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
16786 if !self.diagnostics_enabled() {
16787 return;
16788 }
16789 self.dismiss_diagnostics(cx);
16790 self.active_diagnostics = ActiveDiagnostic::All;
16791 }
16792
16793 fn activate_diagnostics(
16794 &mut self,
16795 buffer_id: BufferId,
16796 diagnostic: DiagnosticEntry<usize>,
16797 window: &mut Window,
16798 cx: &mut Context<Self>,
16799 ) {
16800 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16801 return;
16802 }
16803 self.dismiss_diagnostics(cx);
16804 let snapshot = self.snapshot(window, cx);
16805 let buffer = self.buffer.read(cx).snapshot(cx);
16806 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
16807 return;
16808 };
16809
16810 let diagnostic_group = buffer
16811 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
16812 .collect::<Vec<_>>();
16813
16814 let blocks =
16815 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
16816
16817 let blocks = self.display_map.update(cx, |display_map, cx| {
16818 display_map.insert_blocks(blocks, cx).into_iter().collect()
16819 });
16820 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
16821 active_range: buffer.anchor_before(diagnostic.range.start)
16822 ..buffer.anchor_after(diagnostic.range.end),
16823 active_message: diagnostic.diagnostic.message.clone(),
16824 group_id: diagnostic.diagnostic.group_id,
16825 blocks,
16826 });
16827 cx.notify();
16828 }
16829
16830 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
16831 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16832 return;
16833 };
16834
16835 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
16836 if let ActiveDiagnostic::Group(group) = prev {
16837 self.display_map.update(cx, |display_map, cx| {
16838 display_map.remove_blocks(group.blocks, cx);
16839 });
16840 cx.notify();
16841 }
16842 }
16843
16844 /// Disable inline diagnostics rendering for this editor.
16845 pub fn disable_inline_diagnostics(&mut self) {
16846 self.inline_diagnostics_enabled = false;
16847 self.inline_diagnostics_update = Task::ready(());
16848 self.inline_diagnostics.clear();
16849 }
16850
16851 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
16852 self.diagnostics_enabled = false;
16853 self.dismiss_diagnostics(cx);
16854 self.inline_diagnostics_update = Task::ready(());
16855 self.inline_diagnostics.clear();
16856 }
16857
16858 pub fn diagnostics_enabled(&self) -> bool {
16859 self.diagnostics_enabled && self.mode.is_full()
16860 }
16861
16862 pub fn inline_diagnostics_enabled(&self) -> bool {
16863 self.inline_diagnostics_enabled && self.diagnostics_enabled()
16864 }
16865
16866 pub fn show_inline_diagnostics(&self) -> bool {
16867 self.show_inline_diagnostics
16868 }
16869
16870 pub fn toggle_inline_diagnostics(
16871 &mut self,
16872 _: &ToggleInlineDiagnostics,
16873 window: &mut Window,
16874 cx: &mut Context<Editor>,
16875 ) {
16876 self.show_inline_diagnostics = !self.show_inline_diagnostics;
16877 self.refresh_inline_diagnostics(false, window, cx);
16878 }
16879
16880 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
16881 self.diagnostics_max_severity = severity;
16882 self.display_map.update(cx, |display_map, _| {
16883 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
16884 });
16885 }
16886
16887 pub fn toggle_diagnostics(
16888 &mut self,
16889 _: &ToggleDiagnostics,
16890 window: &mut Window,
16891 cx: &mut Context<Editor>,
16892 ) {
16893 if !self.diagnostics_enabled() {
16894 return;
16895 }
16896
16897 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16898 EditorSettings::get_global(cx)
16899 .diagnostics_max_severity
16900 .filter(|severity| severity != &DiagnosticSeverity::Off)
16901 .unwrap_or(DiagnosticSeverity::Hint)
16902 } else {
16903 DiagnosticSeverity::Off
16904 };
16905 self.set_max_diagnostics_severity(new_severity, cx);
16906 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16907 self.active_diagnostics = ActiveDiagnostic::None;
16908 self.inline_diagnostics_update = Task::ready(());
16909 self.inline_diagnostics.clear();
16910 } else {
16911 self.refresh_inline_diagnostics(false, window, cx);
16912 }
16913
16914 cx.notify();
16915 }
16916
16917 pub fn toggle_minimap(
16918 &mut self,
16919 _: &ToggleMinimap,
16920 window: &mut Window,
16921 cx: &mut Context<Editor>,
16922 ) {
16923 if self.supports_minimap(cx) {
16924 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
16925 }
16926 }
16927
16928 fn refresh_inline_diagnostics(
16929 &mut self,
16930 debounce: bool,
16931 window: &mut Window,
16932 cx: &mut Context<Self>,
16933 ) {
16934 let max_severity = ProjectSettings::get_global(cx)
16935 .diagnostics
16936 .inline
16937 .max_severity
16938 .unwrap_or(self.diagnostics_max_severity);
16939
16940 if !self.inline_diagnostics_enabled()
16941 || !self.show_inline_diagnostics
16942 || max_severity == DiagnosticSeverity::Off
16943 {
16944 self.inline_diagnostics_update = Task::ready(());
16945 self.inline_diagnostics.clear();
16946 return;
16947 }
16948
16949 let debounce_ms = ProjectSettings::get_global(cx)
16950 .diagnostics
16951 .inline
16952 .update_debounce_ms;
16953 let debounce = if debounce && debounce_ms > 0 {
16954 Some(Duration::from_millis(debounce_ms))
16955 } else {
16956 None
16957 };
16958 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
16959 if let Some(debounce) = debounce {
16960 cx.background_executor().timer(debounce).await;
16961 }
16962 let Some(snapshot) = editor.upgrade().and_then(|editor| {
16963 editor
16964 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
16965 .ok()
16966 }) else {
16967 return;
16968 };
16969
16970 let new_inline_diagnostics = cx
16971 .background_spawn(async move {
16972 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
16973 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
16974 let message = diagnostic_entry
16975 .diagnostic
16976 .message
16977 .split_once('\n')
16978 .map(|(line, _)| line)
16979 .map(SharedString::new)
16980 .unwrap_or_else(|| {
16981 SharedString::from(diagnostic_entry.diagnostic.message)
16982 });
16983 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
16984 let (Ok(i) | Err(i)) = inline_diagnostics
16985 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
16986 inline_diagnostics.insert(
16987 i,
16988 (
16989 start_anchor,
16990 InlineDiagnostic {
16991 message,
16992 group_id: diagnostic_entry.diagnostic.group_id,
16993 start: diagnostic_entry.range.start.to_point(&snapshot),
16994 is_primary: diagnostic_entry.diagnostic.is_primary,
16995 severity: diagnostic_entry.diagnostic.severity,
16996 },
16997 ),
16998 );
16999 }
17000 inline_diagnostics
17001 })
17002 .await;
17003
17004 editor
17005 .update(cx, |editor, cx| {
17006 editor.inline_diagnostics = new_inline_diagnostics;
17007 cx.notify();
17008 })
17009 .ok();
17010 });
17011 }
17012
17013 fn pull_diagnostics(
17014 &mut self,
17015 buffer_id: Option<BufferId>,
17016 window: &Window,
17017 cx: &mut Context<Self>,
17018 ) -> Option<()> {
17019 if !self.mode().is_full() {
17020 return None;
17021 }
17022 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
17023 .diagnostics
17024 .lsp_pull_diagnostics;
17025 if !pull_diagnostics_settings.enabled {
17026 return None;
17027 }
17028 let project = self.project.as_ref()?.downgrade();
17029 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
17030 let mut buffers = self.buffer.read(cx).all_buffers();
17031 if let Some(buffer_id) = buffer_id {
17032 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
17033 }
17034
17035 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
17036 cx.background_executor().timer(debounce).await;
17037
17038 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
17039 buffers
17040 .into_iter()
17041 .filter_map(|buffer| {
17042 project
17043 .update(cx, |project, cx| {
17044 project.lsp_store().update(cx, |lsp_store, cx| {
17045 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
17046 })
17047 })
17048 .ok()
17049 })
17050 .collect::<FuturesUnordered<_>>()
17051 }) else {
17052 return;
17053 };
17054
17055 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
17056 match pull_task {
17057 Ok(()) => {
17058 if editor
17059 .update_in(cx, |editor, window, cx| {
17060 editor.update_diagnostics_state(window, cx);
17061 })
17062 .is_err()
17063 {
17064 return;
17065 }
17066 }
17067 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
17068 }
17069 }
17070 });
17071
17072 Some(())
17073 }
17074
17075 pub fn set_selections_from_remote(
17076 &mut self,
17077 selections: Vec<Selection<Anchor>>,
17078 pending_selection: Option<Selection<Anchor>>,
17079 window: &mut Window,
17080 cx: &mut Context<Self>,
17081 ) {
17082 let old_cursor_position = self.selections.newest_anchor().head();
17083 self.selections.change_with(cx, |s| {
17084 s.select_anchors(selections);
17085 if let Some(pending_selection) = pending_selection {
17086 s.set_pending(pending_selection, SelectMode::Character);
17087 } else {
17088 s.clear_pending();
17089 }
17090 });
17091 self.selections_did_change(
17092 false,
17093 &old_cursor_position,
17094 SelectionEffects::default(),
17095 window,
17096 cx,
17097 );
17098 }
17099
17100 pub fn transact(
17101 &mut self,
17102 window: &mut Window,
17103 cx: &mut Context<Self>,
17104 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
17105 ) -> Option<TransactionId> {
17106 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17107 this.start_transaction_at(Instant::now(), window, cx);
17108 update(this, window, cx);
17109 this.end_transaction_at(Instant::now(), cx)
17110 })
17111 }
17112
17113 pub fn start_transaction_at(
17114 &mut self,
17115 now: Instant,
17116 window: &mut Window,
17117 cx: &mut Context<Self>,
17118 ) -> Option<TransactionId> {
17119 self.end_selection(window, cx);
17120 if let Some(tx_id) = self
17121 .buffer
17122 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
17123 {
17124 self.selection_history
17125 .insert_transaction(tx_id, self.selections.disjoint_anchors());
17126 cx.emit(EditorEvent::TransactionBegun {
17127 transaction_id: tx_id,
17128 });
17129 Some(tx_id)
17130 } else {
17131 None
17132 }
17133 }
17134
17135 pub fn end_transaction_at(
17136 &mut self,
17137 now: Instant,
17138 cx: &mut Context<Self>,
17139 ) -> Option<TransactionId> {
17140 if let Some(transaction_id) = self
17141 .buffer
17142 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
17143 {
17144 if let Some((_, end_selections)) =
17145 self.selection_history.transaction_mut(transaction_id)
17146 {
17147 *end_selections = Some(self.selections.disjoint_anchors());
17148 } else {
17149 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
17150 }
17151
17152 cx.emit(EditorEvent::Edited { transaction_id });
17153 Some(transaction_id)
17154 } else {
17155 None
17156 }
17157 }
17158
17159 pub fn modify_transaction_selection_history(
17160 &mut self,
17161 transaction_id: TransactionId,
17162 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
17163 ) -> bool {
17164 self.selection_history
17165 .transaction_mut(transaction_id)
17166 .map(modify)
17167 .is_some()
17168 }
17169
17170 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
17171 if self.selection_mark_mode {
17172 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17173 s.move_with(|_, sel| {
17174 sel.collapse_to(sel.head(), SelectionGoal::None);
17175 });
17176 })
17177 }
17178 self.selection_mark_mode = true;
17179 cx.notify();
17180 }
17181
17182 pub fn swap_selection_ends(
17183 &mut self,
17184 _: &actions::SwapSelectionEnds,
17185 window: &mut Window,
17186 cx: &mut Context<Self>,
17187 ) {
17188 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17189 s.move_with(|_, sel| {
17190 if sel.start != sel.end {
17191 sel.reversed = !sel.reversed
17192 }
17193 });
17194 });
17195 self.request_autoscroll(Autoscroll::newest(), cx);
17196 cx.notify();
17197 }
17198
17199 pub fn toggle_focus(
17200 workspace: &mut Workspace,
17201 _: &actions::ToggleFocus,
17202 window: &mut Window,
17203 cx: &mut Context<Workspace>,
17204 ) {
17205 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
17206 return;
17207 };
17208 workspace.activate_item(&item, true, true, window, cx);
17209 }
17210
17211 pub fn toggle_fold(
17212 &mut self,
17213 _: &actions::ToggleFold,
17214 window: &mut Window,
17215 cx: &mut Context<Self>,
17216 ) {
17217 if self.is_singleton(cx) {
17218 let selection = self.selections.newest::<Point>(cx);
17219
17220 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17221 let range = if selection.is_empty() {
17222 let point = selection.head().to_display_point(&display_map);
17223 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17224 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17225 .to_point(&display_map);
17226 start..end
17227 } else {
17228 selection.range()
17229 };
17230 if display_map.folds_in_range(range).next().is_some() {
17231 self.unfold_lines(&Default::default(), window, cx)
17232 } else {
17233 self.fold(&Default::default(), window, cx)
17234 }
17235 } else {
17236 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17237 let buffer_ids: HashSet<_> = self
17238 .selections
17239 .disjoint_anchor_ranges()
17240 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17241 .collect();
17242
17243 let should_unfold = buffer_ids
17244 .iter()
17245 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17246
17247 for buffer_id in buffer_ids {
17248 if should_unfold {
17249 self.unfold_buffer(buffer_id, cx);
17250 } else {
17251 self.fold_buffer(buffer_id, cx);
17252 }
17253 }
17254 }
17255 }
17256
17257 pub fn toggle_fold_recursive(
17258 &mut self,
17259 _: &actions::ToggleFoldRecursive,
17260 window: &mut Window,
17261 cx: &mut Context<Self>,
17262 ) {
17263 let selection = self.selections.newest::<Point>(cx);
17264
17265 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17266 let range = if selection.is_empty() {
17267 let point = selection.head().to_display_point(&display_map);
17268 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17269 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17270 .to_point(&display_map);
17271 start..end
17272 } else {
17273 selection.range()
17274 };
17275 if display_map.folds_in_range(range).next().is_some() {
17276 self.unfold_recursive(&Default::default(), window, cx)
17277 } else {
17278 self.fold_recursive(&Default::default(), window, cx)
17279 }
17280 }
17281
17282 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
17283 if self.is_singleton(cx) {
17284 let mut to_fold = Vec::new();
17285 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17286 let selections = self.selections.all_adjusted(cx);
17287
17288 for selection in selections {
17289 let range = selection.range().sorted();
17290 let buffer_start_row = range.start.row;
17291
17292 if range.start.row != range.end.row {
17293 let mut found = false;
17294 let mut row = range.start.row;
17295 while row <= range.end.row {
17296 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
17297 {
17298 found = true;
17299 row = crease.range().end.row + 1;
17300 to_fold.push(crease);
17301 } else {
17302 row += 1
17303 }
17304 }
17305 if found {
17306 continue;
17307 }
17308 }
17309
17310 for row in (0..=range.start.row).rev() {
17311 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17312 if crease.range().end.row >= buffer_start_row {
17313 to_fold.push(crease);
17314 if row <= range.start.row {
17315 break;
17316 }
17317 }
17318 }
17319 }
17320 }
17321
17322 self.fold_creases(to_fold, true, window, cx);
17323 } else {
17324 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17325 let buffer_ids = self
17326 .selections
17327 .disjoint_anchor_ranges()
17328 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17329 .collect::<HashSet<_>>();
17330 for buffer_id in buffer_ids {
17331 self.fold_buffer(buffer_id, cx);
17332 }
17333 }
17334 }
17335
17336 pub fn toggle_fold_all(
17337 &mut self,
17338 _: &actions::ToggleFoldAll,
17339 window: &mut Window,
17340 cx: &mut Context<Self>,
17341 ) {
17342 if self.buffer.read(cx).is_singleton() {
17343 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17344 let has_folds = display_map
17345 .folds_in_range(0..display_map.buffer_snapshot.len())
17346 .next()
17347 .is_some();
17348
17349 if has_folds {
17350 self.unfold_all(&actions::UnfoldAll, window, cx);
17351 } else {
17352 self.fold_all(&actions::FoldAll, window, cx);
17353 }
17354 } else {
17355 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
17356 let should_unfold = buffer_ids
17357 .iter()
17358 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17359
17360 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17361 editor
17362 .update_in(cx, |editor, _, cx| {
17363 for buffer_id in buffer_ids {
17364 if should_unfold {
17365 editor.unfold_buffer(buffer_id, cx);
17366 } else {
17367 editor.fold_buffer(buffer_id, cx);
17368 }
17369 }
17370 })
17371 .ok();
17372 });
17373 }
17374 }
17375
17376 fn fold_at_level(
17377 &mut self,
17378 fold_at: &FoldAtLevel,
17379 window: &mut Window,
17380 cx: &mut Context<Self>,
17381 ) {
17382 if !self.buffer.read(cx).is_singleton() {
17383 return;
17384 }
17385
17386 let fold_at_level = fold_at.0;
17387 let snapshot = self.buffer.read(cx).snapshot(cx);
17388 let mut to_fold = Vec::new();
17389 let mut stack = vec![(0, snapshot.max_row().0, 1)];
17390
17391 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
17392 while start_row < end_row {
17393 match self
17394 .snapshot(window, cx)
17395 .crease_for_buffer_row(MultiBufferRow(start_row))
17396 {
17397 Some(crease) => {
17398 let nested_start_row = crease.range().start.row + 1;
17399 let nested_end_row = crease.range().end.row;
17400
17401 if current_level < fold_at_level {
17402 stack.push((nested_start_row, nested_end_row, current_level + 1));
17403 } else if current_level == fold_at_level {
17404 to_fold.push(crease);
17405 }
17406
17407 start_row = nested_end_row + 1;
17408 }
17409 None => start_row += 1,
17410 }
17411 }
17412 }
17413
17414 self.fold_creases(to_fold, true, window, cx);
17415 }
17416
17417 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
17418 if self.buffer.read(cx).is_singleton() {
17419 let mut fold_ranges = Vec::new();
17420 let snapshot = self.buffer.read(cx).snapshot(cx);
17421
17422 for row in 0..snapshot.max_row().0 {
17423 if let Some(foldable_range) = self
17424 .snapshot(window, cx)
17425 .crease_for_buffer_row(MultiBufferRow(row))
17426 {
17427 fold_ranges.push(foldable_range);
17428 }
17429 }
17430
17431 self.fold_creases(fold_ranges, true, window, cx);
17432 } else {
17433 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17434 editor
17435 .update_in(cx, |editor, _, cx| {
17436 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17437 editor.fold_buffer(buffer_id, cx);
17438 }
17439 })
17440 .ok();
17441 });
17442 }
17443 }
17444
17445 pub fn fold_function_bodies(
17446 &mut self,
17447 _: &actions::FoldFunctionBodies,
17448 window: &mut Window,
17449 cx: &mut Context<Self>,
17450 ) {
17451 let snapshot = self.buffer.read(cx).snapshot(cx);
17452
17453 let ranges = snapshot
17454 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
17455 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
17456 .collect::<Vec<_>>();
17457
17458 let creases = ranges
17459 .into_iter()
17460 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
17461 .collect();
17462
17463 self.fold_creases(creases, true, window, cx);
17464 }
17465
17466 pub fn fold_recursive(
17467 &mut self,
17468 _: &actions::FoldRecursive,
17469 window: &mut Window,
17470 cx: &mut Context<Self>,
17471 ) {
17472 let mut to_fold = Vec::new();
17473 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17474 let selections = self.selections.all_adjusted(cx);
17475
17476 for selection in selections {
17477 let range = selection.range().sorted();
17478 let buffer_start_row = range.start.row;
17479
17480 if range.start.row != range.end.row {
17481 let mut found = false;
17482 for row in range.start.row..=range.end.row {
17483 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17484 found = true;
17485 to_fold.push(crease);
17486 }
17487 }
17488 if found {
17489 continue;
17490 }
17491 }
17492
17493 for row in (0..=range.start.row).rev() {
17494 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17495 if crease.range().end.row >= buffer_start_row {
17496 to_fold.push(crease);
17497 } else {
17498 break;
17499 }
17500 }
17501 }
17502 }
17503
17504 self.fold_creases(to_fold, true, window, cx);
17505 }
17506
17507 pub fn fold_at(
17508 &mut self,
17509 buffer_row: MultiBufferRow,
17510 window: &mut Window,
17511 cx: &mut Context<Self>,
17512 ) {
17513 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17514
17515 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
17516 let autoscroll = self
17517 .selections
17518 .all::<Point>(cx)
17519 .iter()
17520 .any(|selection| crease.range().overlaps(&selection.range()));
17521
17522 self.fold_creases(vec![crease], autoscroll, window, cx);
17523 }
17524 }
17525
17526 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
17527 if self.is_singleton(cx) {
17528 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17529 let buffer = &display_map.buffer_snapshot;
17530 let selections = self.selections.all::<Point>(cx);
17531 let ranges = selections
17532 .iter()
17533 .map(|s| {
17534 let range = s.display_range(&display_map).sorted();
17535 let mut start = range.start.to_point(&display_map);
17536 let mut end = range.end.to_point(&display_map);
17537 start.column = 0;
17538 end.column = buffer.line_len(MultiBufferRow(end.row));
17539 start..end
17540 })
17541 .collect::<Vec<_>>();
17542
17543 self.unfold_ranges(&ranges, true, true, cx);
17544 } else {
17545 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17546 let buffer_ids = self
17547 .selections
17548 .disjoint_anchor_ranges()
17549 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17550 .collect::<HashSet<_>>();
17551 for buffer_id in buffer_ids {
17552 self.unfold_buffer(buffer_id, cx);
17553 }
17554 }
17555 }
17556
17557 pub fn unfold_recursive(
17558 &mut self,
17559 _: &UnfoldRecursive,
17560 _window: &mut Window,
17561 cx: &mut Context<Self>,
17562 ) {
17563 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17564 let selections = self.selections.all::<Point>(cx);
17565 let ranges = selections
17566 .iter()
17567 .map(|s| {
17568 let mut range = s.display_range(&display_map).sorted();
17569 *range.start.column_mut() = 0;
17570 *range.end.column_mut() = display_map.line_len(range.end.row());
17571 let start = range.start.to_point(&display_map);
17572 let end = range.end.to_point(&display_map);
17573 start..end
17574 })
17575 .collect::<Vec<_>>();
17576
17577 self.unfold_ranges(&ranges, true, true, cx);
17578 }
17579
17580 pub fn unfold_at(
17581 &mut self,
17582 buffer_row: MultiBufferRow,
17583 _window: &mut Window,
17584 cx: &mut Context<Self>,
17585 ) {
17586 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17587
17588 let intersection_range = Point::new(buffer_row.0, 0)
17589 ..Point::new(
17590 buffer_row.0,
17591 display_map.buffer_snapshot.line_len(buffer_row),
17592 );
17593
17594 let autoscroll = self
17595 .selections
17596 .all::<Point>(cx)
17597 .iter()
17598 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
17599
17600 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
17601 }
17602
17603 pub fn unfold_all(
17604 &mut self,
17605 _: &actions::UnfoldAll,
17606 _window: &mut Window,
17607 cx: &mut Context<Self>,
17608 ) {
17609 if self.buffer.read(cx).is_singleton() {
17610 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17611 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
17612 } else {
17613 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
17614 editor
17615 .update(cx, |editor, cx| {
17616 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17617 editor.unfold_buffer(buffer_id, cx);
17618 }
17619 })
17620 .ok();
17621 });
17622 }
17623 }
17624
17625 pub fn fold_selected_ranges(
17626 &mut self,
17627 _: &FoldSelectedRanges,
17628 window: &mut Window,
17629 cx: &mut Context<Self>,
17630 ) {
17631 let selections = self.selections.all_adjusted(cx);
17632 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17633 let ranges = selections
17634 .into_iter()
17635 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
17636 .collect::<Vec<_>>();
17637 self.fold_creases(ranges, true, window, cx);
17638 }
17639
17640 pub fn fold_ranges<T: ToOffset + Clone>(
17641 &mut self,
17642 ranges: Vec<Range<T>>,
17643 auto_scroll: bool,
17644 window: &mut Window,
17645 cx: &mut Context<Self>,
17646 ) {
17647 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17648 let ranges = ranges
17649 .into_iter()
17650 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
17651 .collect::<Vec<_>>();
17652 self.fold_creases(ranges, auto_scroll, window, cx);
17653 }
17654
17655 pub fn fold_creases<T: ToOffset + Clone>(
17656 &mut self,
17657 creases: Vec<Crease<T>>,
17658 auto_scroll: bool,
17659 _window: &mut Window,
17660 cx: &mut Context<Self>,
17661 ) {
17662 if creases.is_empty() {
17663 return;
17664 }
17665
17666 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
17667
17668 if auto_scroll {
17669 self.request_autoscroll(Autoscroll::fit(), cx);
17670 }
17671
17672 cx.notify();
17673
17674 self.scrollbar_marker_state.dirty = true;
17675 self.folds_did_change(cx);
17676 }
17677
17678 /// Removes any folds whose ranges intersect any of the given ranges.
17679 pub fn unfold_ranges<T: ToOffset + Clone>(
17680 &mut self,
17681 ranges: &[Range<T>],
17682 inclusive: bool,
17683 auto_scroll: bool,
17684 cx: &mut Context<Self>,
17685 ) {
17686 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17687 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
17688 });
17689 self.folds_did_change(cx);
17690 }
17691
17692 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17693 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
17694 return;
17695 }
17696 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17697 self.display_map.update(cx, |display_map, cx| {
17698 display_map.fold_buffers([buffer_id], cx)
17699 });
17700 cx.emit(EditorEvent::BufferFoldToggled {
17701 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
17702 folded: true,
17703 });
17704 cx.notify();
17705 }
17706
17707 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17708 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
17709 return;
17710 }
17711 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17712 self.display_map.update(cx, |display_map, cx| {
17713 display_map.unfold_buffers([buffer_id], cx);
17714 });
17715 cx.emit(EditorEvent::BufferFoldToggled {
17716 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
17717 folded: false,
17718 });
17719 cx.notify();
17720 }
17721
17722 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
17723 self.display_map.read(cx).is_buffer_folded(buffer)
17724 }
17725
17726 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
17727 self.display_map.read(cx).folded_buffers()
17728 }
17729
17730 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17731 self.display_map.update(cx, |display_map, cx| {
17732 display_map.disable_header_for_buffer(buffer_id, cx);
17733 });
17734 cx.notify();
17735 }
17736
17737 /// Removes any folds with the given ranges.
17738 pub fn remove_folds_with_type<T: ToOffset + Clone>(
17739 &mut self,
17740 ranges: &[Range<T>],
17741 type_id: TypeId,
17742 auto_scroll: bool,
17743 cx: &mut Context<Self>,
17744 ) {
17745 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17746 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
17747 });
17748 self.folds_did_change(cx);
17749 }
17750
17751 fn remove_folds_with<T: ToOffset + Clone>(
17752 &mut self,
17753 ranges: &[Range<T>],
17754 auto_scroll: bool,
17755 cx: &mut Context<Self>,
17756 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
17757 ) {
17758 if ranges.is_empty() {
17759 return;
17760 }
17761
17762 let mut buffers_affected = HashSet::default();
17763 let multi_buffer = self.buffer().read(cx);
17764 for range in ranges {
17765 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
17766 buffers_affected.insert(buffer.read(cx).remote_id());
17767 };
17768 }
17769
17770 self.display_map.update(cx, update);
17771
17772 if auto_scroll {
17773 self.request_autoscroll(Autoscroll::fit(), cx);
17774 }
17775
17776 cx.notify();
17777 self.scrollbar_marker_state.dirty = true;
17778 self.active_indent_guides_state.dirty = true;
17779 }
17780
17781 pub fn update_renderer_widths(
17782 &mut self,
17783 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
17784 cx: &mut Context<Self>,
17785 ) -> bool {
17786 self.display_map
17787 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
17788 }
17789
17790 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
17791 self.display_map.read(cx).fold_placeholder.clone()
17792 }
17793
17794 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
17795 self.buffer.update(cx, |buffer, cx| {
17796 buffer.set_all_diff_hunks_expanded(cx);
17797 });
17798 }
17799
17800 pub fn expand_all_diff_hunks(
17801 &mut self,
17802 _: &ExpandAllDiffHunks,
17803 _window: &mut Window,
17804 cx: &mut Context<Self>,
17805 ) {
17806 self.buffer.update(cx, |buffer, cx| {
17807 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
17808 });
17809 }
17810
17811 pub fn toggle_selected_diff_hunks(
17812 &mut self,
17813 _: &ToggleSelectedDiffHunks,
17814 _window: &mut Window,
17815 cx: &mut Context<Self>,
17816 ) {
17817 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17818 self.toggle_diff_hunks_in_ranges(ranges, cx);
17819 }
17820
17821 pub fn diff_hunks_in_ranges<'a>(
17822 &'a self,
17823 ranges: &'a [Range<Anchor>],
17824 buffer: &'a MultiBufferSnapshot,
17825 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
17826 ranges.iter().flat_map(move |range| {
17827 let end_excerpt_id = range.end.excerpt_id;
17828 let range = range.to_point(buffer);
17829 let mut peek_end = range.end;
17830 if range.end.row < buffer.max_row().0 {
17831 peek_end = Point::new(range.end.row + 1, 0);
17832 }
17833 buffer
17834 .diff_hunks_in_range(range.start..peek_end)
17835 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
17836 })
17837 }
17838
17839 pub fn has_stageable_diff_hunks_in_ranges(
17840 &self,
17841 ranges: &[Range<Anchor>],
17842 snapshot: &MultiBufferSnapshot,
17843 ) -> bool {
17844 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
17845 hunks.any(|hunk| hunk.status().has_secondary_hunk())
17846 }
17847
17848 pub fn toggle_staged_selected_diff_hunks(
17849 &mut self,
17850 _: &::git::ToggleStaged,
17851 _: &mut Window,
17852 cx: &mut Context<Self>,
17853 ) {
17854 let snapshot = self.buffer.read(cx).snapshot(cx);
17855 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17856 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
17857 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17858 }
17859
17860 pub fn set_render_diff_hunk_controls(
17861 &mut self,
17862 render_diff_hunk_controls: RenderDiffHunkControlsFn,
17863 cx: &mut Context<Self>,
17864 ) {
17865 self.render_diff_hunk_controls = render_diff_hunk_controls;
17866 cx.notify();
17867 }
17868
17869 pub fn stage_and_next(
17870 &mut self,
17871 _: &::git::StageAndNext,
17872 window: &mut Window,
17873 cx: &mut Context<Self>,
17874 ) {
17875 self.do_stage_or_unstage_and_next(true, window, cx);
17876 }
17877
17878 pub fn unstage_and_next(
17879 &mut self,
17880 _: &::git::UnstageAndNext,
17881 window: &mut Window,
17882 cx: &mut Context<Self>,
17883 ) {
17884 self.do_stage_or_unstage_and_next(false, window, cx);
17885 }
17886
17887 pub fn stage_or_unstage_diff_hunks(
17888 &mut self,
17889 stage: bool,
17890 ranges: Vec<Range<Anchor>>,
17891 cx: &mut Context<Self>,
17892 ) {
17893 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
17894 cx.spawn(async move |this, cx| {
17895 task.await?;
17896 this.update(cx, |this, cx| {
17897 let snapshot = this.buffer.read(cx).snapshot(cx);
17898 let chunk_by = this
17899 .diff_hunks_in_ranges(&ranges, &snapshot)
17900 .chunk_by(|hunk| hunk.buffer_id);
17901 for (buffer_id, hunks) in &chunk_by {
17902 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
17903 }
17904 })
17905 })
17906 .detach_and_log_err(cx);
17907 }
17908
17909 fn save_buffers_for_ranges_if_needed(
17910 &mut self,
17911 ranges: &[Range<Anchor>],
17912 cx: &mut Context<Editor>,
17913 ) -> Task<Result<()>> {
17914 let multibuffer = self.buffer.read(cx);
17915 let snapshot = multibuffer.read(cx);
17916 let buffer_ids: HashSet<_> = ranges
17917 .iter()
17918 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
17919 .collect();
17920 drop(snapshot);
17921
17922 let mut buffers = HashSet::default();
17923 for buffer_id in buffer_ids {
17924 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
17925 let buffer = buffer_entity.read(cx);
17926 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
17927 {
17928 buffers.insert(buffer_entity);
17929 }
17930 }
17931 }
17932
17933 if let Some(project) = &self.project {
17934 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
17935 } else {
17936 Task::ready(Ok(()))
17937 }
17938 }
17939
17940 fn do_stage_or_unstage_and_next(
17941 &mut self,
17942 stage: bool,
17943 window: &mut Window,
17944 cx: &mut Context<Self>,
17945 ) {
17946 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
17947
17948 if ranges.iter().any(|range| range.start != range.end) {
17949 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17950 return;
17951 }
17952
17953 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17954 let snapshot = self.snapshot(window, cx);
17955 let position = self.selections.newest::<Point>(cx).head();
17956 let mut row = snapshot
17957 .buffer_snapshot
17958 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
17959 .find(|hunk| hunk.row_range.start.0 > position.row)
17960 .map(|hunk| hunk.row_range.start);
17961
17962 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
17963 // Outside of the project diff editor, wrap around to the beginning.
17964 if !all_diff_hunks_expanded {
17965 row = row.or_else(|| {
17966 snapshot
17967 .buffer_snapshot
17968 .diff_hunks_in_range(Point::zero()..position)
17969 .find(|hunk| hunk.row_range.end.0 < position.row)
17970 .map(|hunk| hunk.row_range.start)
17971 });
17972 }
17973
17974 if let Some(row) = row {
17975 let destination = Point::new(row.0, 0);
17976 let autoscroll = Autoscroll::center();
17977
17978 self.unfold_ranges(&[destination..destination], false, false, cx);
17979 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17980 s.select_ranges([destination..destination]);
17981 });
17982 }
17983 }
17984
17985 fn do_stage_or_unstage(
17986 &self,
17987 stage: bool,
17988 buffer_id: BufferId,
17989 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
17990 cx: &mut App,
17991 ) -> Option<()> {
17992 let project = self.project.as_ref()?;
17993 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
17994 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
17995 let buffer_snapshot = buffer.read(cx).snapshot();
17996 let file_exists = buffer_snapshot
17997 .file()
17998 .is_some_and(|file| file.disk_state().exists());
17999 diff.update(cx, |diff, cx| {
18000 diff.stage_or_unstage_hunks(
18001 stage,
18002 &hunks
18003 .map(|hunk| buffer_diff::DiffHunk {
18004 buffer_range: hunk.buffer_range,
18005 diff_base_byte_range: hunk.diff_base_byte_range,
18006 secondary_status: hunk.secondary_status,
18007 range: Point::zero()..Point::zero(), // unused
18008 })
18009 .collect::<Vec<_>>(),
18010 &buffer_snapshot,
18011 file_exists,
18012 cx,
18013 )
18014 });
18015 None
18016 }
18017
18018 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
18019 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
18020 self.buffer
18021 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
18022 }
18023
18024 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
18025 self.buffer.update(cx, |buffer, cx| {
18026 let ranges = vec![Anchor::min()..Anchor::max()];
18027 if !buffer.all_diff_hunks_expanded()
18028 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
18029 {
18030 buffer.collapse_diff_hunks(ranges, cx);
18031 true
18032 } else {
18033 false
18034 }
18035 })
18036 }
18037
18038 fn toggle_diff_hunks_in_ranges(
18039 &mut self,
18040 ranges: Vec<Range<Anchor>>,
18041 cx: &mut Context<Editor>,
18042 ) {
18043 self.buffer.update(cx, |buffer, cx| {
18044 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
18045 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
18046 })
18047 }
18048
18049 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
18050 self.buffer.update(cx, |buffer, cx| {
18051 let snapshot = buffer.snapshot(cx);
18052 let excerpt_id = range.end.excerpt_id;
18053 let point_range = range.to_point(&snapshot);
18054 let expand = !buffer.single_hunk_is_expanded(range, cx);
18055 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
18056 })
18057 }
18058
18059 pub(crate) fn apply_all_diff_hunks(
18060 &mut self,
18061 _: &ApplyAllDiffHunks,
18062 window: &mut Window,
18063 cx: &mut Context<Self>,
18064 ) {
18065 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18066
18067 let buffers = self.buffer.read(cx).all_buffers();
18068 for branch_buffer in buffers {
18069 branch_buffer.update(cx, |branch_buffer, cx| {
18070 branch_buffer.merge_into_base(Vec::new(), cx);
18071 });
18072 }
18073
18074 if let Some(project) = self.project.clone() {
18075 self.save(
18076 SaveOptions {
18077 format: true,
18078 autosave: false,
18079 },
18080 project,
18081 window,
18082 cx,
18083 )
18084 .detach_and_log_err(cx);
18085 }
18086 }
18087
18088 pub(crate) fn apply_selected_diff_hunks(
18089 &mut self,
18090 _: &ApplyDiffHunk,
18091 window: &mut Window,
18092 cx: &mut Context<Self>,
18093 ) {
18094 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18095 let snapshot = self.snapshot(window, cx);
18096 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
18097 let mut ranges_by_buffer = HashMap::default();
18098 self.transact(window, cx, |editor, _window, cx| {
18099 for hunk in hunks {
18100 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
18101 ranges_by_buffer
18102 .entry(buffer.clone())
18103 .or_insert_with(Vec::new)
18104 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
18105 }
18106 }
18107
18108 for (buffer, ranges) in ranges_by_buffer {
18109 buffer.update(cx, |buffer, cx| {
18110 buffer.merge_into_base(ranges, cx);
18111 });
18112 }
18113 });
18114
18115 if let Some(project) = self.project.clone() {
18116 self.save(
18117 SaveOptions {
18118 format: true,
18119 autosave: false,
18120 },
18121 project,
18122 window,
18123 cx,
18124 )
18125 .detach_and_log_err(cx);
18126 }
18127 }
18128
18129 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
18130 if hovered != self.gutter_hovered {
18131 self.gutter_hovered = hovered;
18132 cx.notify();
18133 }
18134 }
18135
18136 pub fn insert_blocks(
18137 &mut self,
18138 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
18139 autoscroll: Option<Autoscroll>,
18140 cx: &mut Context<Self>,
18141 ) -> Vec<CustomBlockId> {
18142 let blocks = self
18143 .display_map
18144 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
18145 if let Some(autoscroll) = autoscroll {
18146 self.request_autoscroll(autoscroll, cx);
18147 }
18148 cx.notify();
18149 blocks
18150 }
18151
18152 pub fn resize_blocks(
18153 &mut self,
18154 heights: HashMap<CustomBlockId, u32>,
18155 autoscroll: Option<Autoscroll>,
18156 cx: &mut Context<Self>,
18157 ) {
18158 self.display_map
18159 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
18160 if let Some(autoscroll) = autoscroll {
18161 self.request_autoscroll(autoscroll, cx);
18162 }
18163 cx.notify();
18164 }
18165
18166 pub fn replace_blocks(
18167 &mut self,
18168 renderers: HashMap<CustomBlockId, RenderBlock>,
18169 autoscroll: Option<Autoscroll>,
18170 cx: &mut Context<Self>,
18171 ) {
18172 self.display_map
18173 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
18174 if let Some(autoscroll) = autoscroll {
18175 self.request_autoscroll(autoscroll, cx);
18176 }
18177 cx.notify();
18178 }
18179
18180 pub fn remove_blocks(
18181 &mut self,
18182 block_ids: HashSet<CustomBlockId>,
18183 autoscroll: Option<Autoscroll>,
18184 cx: &mut Context<Self>,
18185 ) {
18186 self.display_map.update(cx, |display_map, cx| {
18187 display_map.remove_blocks(block_ids, cx)
18188 });
18189 if let Some(autoscroll) = autoscroll {
18190 self.request_autoscroll(autoscroll, cx);
18191 }
18192 cx.notify();
18193 }
18194
18195 pub fn row_for_block(
18196 &self,
18197 block_id: CustomBlockId,
18198 cx: &mut Context<Self>,
18199 ) -> Option<DisplayRow> {
18200 self.display_map
18201 .update(cx, |map, cx| map.row_for_block(block_id, cx))
18202 }
18203
18204 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
18205 self.focused_block = Some(focused_block);
18206 }
18207
18208 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
18209 self.focused_block.take()
18210 }
18211
18212 pub fn insert_creases(
18213 &mut self,
18214 creases: impl IntoIterator<Item = Crease<Anchor>>,
18215 cx: &mut Context<Self>,
18216 ) -> Vec<CreaseId> {
18217 self.display_map
18218 .update(cx, |map, cx| map.insert_creases(creases, cx))
18219 }
18220
18221 pub fn remove_creases(
18222 &mut self,
18223 ids: impl IntoIterator<Item = CreaseId>,
18224 cx: &mut Context<Self>,
18225 ) -> Vec<(CreaseId, Range<Anchor>)> {
18226 self.display_map
18227 .update(cx, |map, cx| map.remove_creases(ids, cx))
18228 }
18229
18230 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
18231 self.display_map
18232 .update(cx, |map, cx| map.snapshot(cx))
18233 .longest_row()
18234 }
18235
18236 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
18237 self.display_map
18238 .update(cx, |map, cx| map.snapshot(cx))
18239 .max_point()
18240 }
18241
18242 pub fn text(&self, cx: &App) -> String {
18243 self.buffer.read(cx).read(cx).text()
18244 }
18245
18246 pub fn is_empty(&self, cx: &App) -> bool {
18247 self.buffer.read(cx).read(cx).is_empty()
18248 }
18249
18250 pub fn text_option(&self, cx: &App) -> Option<String> {
18251 let text = self.text(cx);
18252 let text = text.trim();
18253
18254 if text.is_empty() {
18255 return None;
18256 }
18257
18258 Some(text.to_string())
18259 }
18260
18261 pub fn set_text(
18262 &mut self,
18263 text: impl Into<Arc<str>>,
18264 window: &mut Window,
18265 cx: &mut Context<Self>,
18266 ) {
18267 self.transact(window, cx, |this, _, cx| {
18268 this.buffer
18269 .read(cx)
18270 .as_singleton()
18271 .expect("you can only call set_text on editors for singleton buffers")
18272 .update(cx, |buffer, cx| buffer.set_text(text, cx));
18273 });
18274 }
18275
18276 pub fn display_text(&self, cx: &mut App) -> String {
18277 self.display_map
18278 .update(cx, |map, cx| map.snapshot(cx))
18279 .text()
18280 }
18281
18282 fn create_minimap(
18283 &self,
18284 minimap_settings: MinimapSettings,
18285 window: &mut Window,
18286 cx: &mut Context<Self>,
18287 ) -> Option<Entity<Self>> {
18288 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
18289 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
18290 }
18291
18292 fn initialize_new_minimap(
18293 &self,
18294 minimap_settings: MinimapSettings,
18295 window: &mut Window,
18296 cx: &mut Context<Self>,
18297 ) -> Entity<Self> {
18298 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
18299
18300 let mut minimap = Editor::new_internal(
18301 EditorMode::Minimap {
18302 parent: cx.weak_entity(),
18303 },
18304 self.buffer.clone(),
18305 None,
18306 Some(self.display_map.clone()),
18307 window,
18308 cx,
18309 );
18310 minimap.scroll_manager.clone_state(&self.scroll_manager);
18311 minimap.set_text_style_refinement(TextStyleRefinement {
18312 font_size: Some(MINIMAP_FONT_SIZE),
18313 font_weight: Some(MINIMAP_FONT_WEIGHT),
18314 ..Default::default()
18315 });
18316 minimap.update_minimap_configuration(minimap_settings, cx);
18317 cx.new(|_| minimap)
18318 }
18319
18320 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
18321 let current_line_highlight = minimap_settings
18322 .current_line_highlight
18323 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
18324 self.set_current_line_highlight(Some(current_line_highlight));
18325 }
18326
18327 pub fn minimap(&self) -> Option<&Entity<Self>> {
18328 self.minimap
18329 .as_ref()
18330 .filter(|_| self.minimap_visibility.visible())
18331 }
18332
18333 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
18334 let mut wrap_guides = smallvec![];
18335
18336 if self.show_wrap_guides == Some(false) {
18337 return wrap_guides;
18338 }
18339
18340 let settings = self.buffer.read(cx).language_settings(cx);
18341 if settings.show_wrap_guides {
18342 match self.soft_wrap_mode(cx) {
18343 SoftWrap::Column(soft_wrap) => {
18344 wrap_guides.push((soft_wrap as usize, true));
18345 }
18346 SoftWrap::Bounded(soft_wrap) => {
18347 wrap_guides.push((soft_wrap as usize, true));
18348 }
18349 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
18350 }
18351 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
18352 }
18353
18354 wrap_guides
18355 }
18356
18357 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
18358 let settings = self.buffer.read(cx).language_settings(cx);
18359 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
18360 match mode {
18361 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
18362 SoftWrap::None
18363 }
18364 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
18365 language_settings::SoftWrap::PreferredLineLength => {
18366 SoftWrap::Column(settings.preferred_line_length)
18367 }
18368 language_settings::SoftWrap::Bounded => {
18369 SoftWrap::Bounded(settings.preferred_line_length)
18370 }
18371 }
18372 }
18373
18374 pub fn set_soft_wrap_mode(
18375 &mut self,
18376 mode: language_settings::SoftWrap,
18377
18378 cx: &mut Context<Self>,
18379 ) {
18380 self.soft_wrap_mode_override = Some(mode);
18381 cx.notify();
18382 }
18383
18384 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
18385 self.hard_wrap = hard_wrap;
18386 cx.notify();
18387 }
18388
18389 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
18390 self.text_style_refinement = Some(style);
18391 }
18392
18393 /// called by the Element so we know what style we were most recently rendered with.
18394 pub(crate) fn set_style(
18395 &mut self,
18396 style: EditorStyle,
18397 window: &mut Window,
18398 cx: &mut Context<Self>,
18399 ) {
18400 // We intentionally do not inform the display map about the minimap style
18401 // so that wrapping is not recalculated and stays consistent for the editor
18402 // and its linked minimap.
18403 if !self.mode.is_minimap() {
18404 let rem_size = window.rem_size();
18405 self.display_map.update(cx, |map, cx| {
18406 map.set_font(
18407 style.text.font(),
18408 style.text.font_size.to_pixels(rem_size),
18409 cx,
18410 )
18411 });
18412 }
18413 self.style = Some(style);
18414 }
18415
18416 pub fn style(&self) -> Option<&EditorStyle> {
18417 self.style.as_ref()
18418 }
18419
18420 // Called by the element. This method is not designed to be called outside of the editor
18421 // element's layout code because it does not notify when rewrapping is computed synchronously.
18422 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
18423 self.display_map
18424 .update(cx, |map, cx| map.set_wrap_width(width, cx))
18425 }
18426
18427 pub fn set_soft_wrap(&mut self) {
18428 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
18429 }
18430
18431 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
18432 if self.soft_wrap_mode_override.is_some() {
18433 self.soft_wrap_mode_override.take();
18434 } else {
18435 let soft_wrap = match self.soft_wrap_mode(cx) {
18436 SoftWrap::GitDiff => return,
18437 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
18438 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
18439 language_settings::SoftWrap::None
18440 }
18441 };
18442 self.soft_wrap_mode_override = Some(soft_wrap);
18443 }
18444 cx.notify();
18445 }
18446
18447 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
18448 let Some(workspace) = self.workspace() else {
18449 return;
18450 };
18451 let fs = workspace.read(cx).app_state().fs.clone();
18452 let current_show = TabBarSettings::get_global(cx).show;
18453 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
18454 setting.show = Some(!current_show);
18455 });
18456 }
18457
18458 pub fn toggle_indent_guides(
18459 &mut self,
18460 _: &ToggleIndentGuides,
18461 _: &mut Window,
18462 cx: &mut Context<Self>,
18463 ) {
18464 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
18465 self.buffer
18466 .read(cx)
18467 .language_settings(cx)
18468 .indent_guides
18469 .enabled
18470 });
18471 self.show_indent_guides = Some(!currently_enabled);
18472 cx.notify();
18473 }
18474
18475 fn should_show_indent_guides(&self) -> Option<bool> {
18476 self.show_indent_guides
18477 }
18478
18479 pub fn toggle_line_numbers(
18480 &mut self,
18481 _: &ToggleLineNumbers,
18482 _: &mut Window,
18483 cx: &mut Context<Self>,
18484 ) {
18485 let mut editor_settings = EditorSettings::get_global(cx).clone();
18486 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
18487 EditorSettings::override_global(editor_settings, cx);
18488 }
18489
18490 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
18491 if let Some(show_line_numbers) = self.show_line_numbers {
18492 return show_line_numbers;
18493 }
18494 EditorSettings::get_global(cx).gutter.line_numbers
18495 }
18496
18497 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
18498 self.use_relative_line_numbers
18499 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
18500 }
18501
18502 pub fn toggle_relative_line_numbers(
18503 &mut self,
18504 _: &ToggleRelativeLineNumbers,
18505 _: &mut Window,
18506 cx: &mut Context<Self>,
18507 ) {
18508 let is_relative = self.should_use_relative_line_numbers(cx);
18509 self.set_relative_line_number(Some(!is_relative), cx)
18510 }
18511
18512 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
18513 self.use_relative_line_numbers = is_relative;
18514 cx.notify();
18515 }
18516
18517 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
18518 self.show_gutter = show_gutter;
18519 cx.notify();
18520 }
18521
18522 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
18523 self.show_scrollbars = ScrollbarAxes {
18524 horizontal: show,
18525 vertical: show,
18526 };
18527 cx.notify();
18528 }
18529
18530 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18531 self.show_scrollbars.vertical = show;
18532 cx.notify();
18533 }
18534
18535 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18536 self.show_scrollbars.horizontal = show;
18537 cx.notify();
18538 }
18539
18540 pub fn set_minimap_visibility(
18541 &mut self,
18542 minimap_visibility: MinimapVisibility,
18543 window: &mut Window,
18544 cx: &mut Context<Self>,
18545 ) {
18546 if self.minimap_visibility != minimap_visibility {
18547 if minimap_visibility.visible() && self.minimap.is_none() {
18548 let minimap_settings = EditorSettings::get_global(cx).minimap;
18549 self.minimap =
18550 self.create_minimap(minimap_settings.with_show_override(), window, cx);
18551 }
18552 self.minimap_visibility = minimap_visibility;
18553 cx.notify();
18554 }
18555 }
18556
18557 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18558 self.set_show_scrollbars(false, cx);
18559 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
18560 }
18561
18562 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18563 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
18564 }
18565
18566 /// Normally the text in full mode and auto height editors is padded on the
18567 /// left side by roughly half a character width for improved hit testing.
18568 ///
18569 /// Use this method to disable this for cases where this is not wanted (e.g.
18570 /// if you want to align the editor text with some other text above or below)
18571 /// or if you want to add this padding to single-line editors.
18572 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
18573 self.offset_content = offset_content;
18574 cx.notify();
18575 }
18576
18577 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
18578 self.show_line_numbers = Some(show_line_numbers);
18579 cx.notify();
18580 }
18581
18582 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
18583 self.disable_expand_excerpt_buttons = true;
18584 cx.notify();
18585 }
18586
18587 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
18588 self.show_git_diff_gutter = Some(show_git_diff_gutter);
18589 cx.notify();
18590 }
18591
18592 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
18593 self.show_code_actions = Some(show_code_actions);
18594 cx.notify();
18595 }
18596
18597 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
18598 self.show_runnables = Some(show_runnables);
18599 cx.notify();
18600 }
18601
18602 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
18603 self.show_breakpoints = Some(show_breakpoints);
18604 cx.notify();
18605 }
18606
18607 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
18608 if self.display_map.read(cx).masked != masked {
18609 self.display_map.update(cx, |map, _| map.masked = masked);
18610 }
18611 cx.notify()
18612 }
18613
18614 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
18615 self.show_wrap_guides = Some(show_wrap_guides);
18616 cx.notify();
18617 }
18618
18619 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
18620 self.show_indent_guides = Some(show_indent_guides);
18621 cx.notify();
18622 }
18623
18624 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
18625 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
18626 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
18627 if let Some(dir) = file.abs_path(cx).parent() {
18628 return Some(dir.to_owned());
18629 }
18630 }
18631
18632 if let Some(project_path) = buffer.read(cx).project_path(cx) {
18633 return Some(project_path.path.to_path_buf());
18634 }
18635 }
18636
18637 None
18638 }
18639
18640 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
18641 self.active_excerpt(cx)?
18642 .1
18643 .read(cx)
18644 .file()
18645 .and_then(|f| f.as_local())
18646 }
18647
18648 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18649 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18650 let buffer = buffer.read(cx);
18651 if let Some(project_path) = buffer.project_path(cx) {
18652 let project = self.project.as_ref()?.read(cx);
18653 project.absolute_path(&project_path, cx)
18654 } else {
18655 buffer
18656 .file()
18657 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
18658 }
18659 })
18660 }
18661
18662 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18663 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18664 let project_path = buffer.read(cx).project_path(cx)?;
18665 let project = self.project.as_ref()?.read(cx);
18666 let entry = project.entry_for_path(&project_path, cx)?;
18667 let path = entry.path.to_path_buf();
18668 Some(path)
18669 })
18670 }
18671
18672 pub fn reveal_in_finder(
18673 &mut self,
18674 _: &RevealInFileManager,
18675 _window: &mut Window,
18676 cx: &mut Context<Self>,
18677 ) {
18678 if let Some(target) = self.target_file(cx) {
18679 cx.reveal_path(&target.abs_path(cx));
18680 }
18681 }
18682
18683 pub fn copy_path(
18684 &mut self,
18685 _: &zed_actions::workspace::CopyPath,
18686 _window: &mut Window,
18687 cx: &mut Context<Self>,
18688 ) {
18689 if let Some(path) = self.target_file_abs_path(cx) {
18690 if let Some(path) = path.to_str() {
18691 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18692 }
18693 }
18694 }
18695
18696 pub fn copy_relative_path(
18697 &mut self,
18698 _: &zed_actions::workspace::CopyRelativePath,
18699 _window: &mut Window,
18700 cx: &mut Context<Self>,
18701 ) {
18702 if let Some(path) = self.target_file_path(cx) {
18703 if let Some(path) = path.to_str() {
18704 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18705 }
18706 }
18707 }
18708
18709 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
18710 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
18711 buffer.read(cx).project_path(cx)
18712 } else {
18713 None
18714 }
18715 }
18716
18717 // Returns true if the editor handled a go-to-line request
18718 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
18719 maybe!({
18720 let breakpoint_store = self.breakpoint_store.as_ref()?;
18721
18722 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
18723 else {
18724 self.clear_row_highlights::<ActiveDebugLine>();
18725 return None;
18726 };
18727
18728 let position = active_stack_frame.position;
18729 let buffer_id = position.buffer_id?;
18730 let snapshot = self
18731 .project
18732 .as_ref()?
18733 .read(cx)
18734 .buffer_for_id(buffer_id, cx)?
18735 .read(cx)
18736 .snapshot();
18737
18738 let mut handled = false;
18739 for (id, ExcerptRange { context, .. }) in
18740 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
18741 {
18742 if context.start.cmp(&position, &snapshot).is_ge()
18743 || context.end.cmp(&position, &snapshot).is_lt()
18744 {
18745 continue;
18746 }
18747 let snapshot = self.buffer.read(cx).snapshot(cx);
18748 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
18749
18750 handled = true;
18751 self.clear_row_highlights::<ActiveDebugLine>();
18752
18753 self.go_to_line::<ActiveDebugLine>(
18754 multibuffer_anchor,
18755 Some(cx.theme().colors().editor_debugger_active_line_background),
18756 window,
18757 cx,
18758 );
18759
18760 cx.notify();
18761 }
18762
18763 handled.then_some(())
18764 })
18765 .is_some()
18766 }
18767
18768 pub fn copy_file_name_without_extension(
18769 &mut self,
18770 _: &CopyFileNameWithoutExtension,
18771 _: &mut Window,
18772 cx: &mut Context<Self>,
18773 ) {
18774 if let Some(file) = self.target_file(cx) {
18775 if let Some(file_stem) = file.path().file_stem() {
18776 if let Some(name) = file_stem.to_str() {
18777 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18778 }
18779 }
18780 }
18781 }
18782
18783 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
18784 if let Some(file) = self.target_file(cx) {
18785 if let Some(file_name) = file.path().file_name() {
18786 if let Some(name) = file_name.to_str() {
18787 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18788 }
18789 }
18790 }
18791 }
18792
18793 pub fn toggle_git_blame(
18794 &mut self,
18795 _: &::git::Blame,
18796 window: &mut Window,
18797 cx: &mut Context<Self>,
18798 ) {
18799 self.show_git_blame_gutter = !self.show_git_blame_gutter;
18800
18801 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
18802 self.start_git_blame(true, window, cx);
18803 }
18804
18805 cx.notify();
18806 }
18807
18808 pub fn toggle_git_blame_inline(
18809 &mut self,
18810 _: &ToggleGitBlameInline,
18811 window: &mut Window,
18812 cx: &mut Context<Self>,
18813 ) {
18814 self.toggle_git_blame_inline_internal(true, window, cx);
18815 cx.notify();
18816 }
18817
18818 pub fn open_git_blame_commit(
18819 &mut self,
18820 _: &OpenGitBlameCommit,
18821 window: &mut Window,
18822 cx: &mut Context<Self>,
18823 ) {
18824 self.open_git_blame_commit_internal(window, cx);
18825 }
18826
18827 fn open_git_blame_commit_internal(
18828 &mut self,
18829 window: &mut Window,
18830 cx: &mut Context<Self>,
18831 ) -> Option<()> {
18832 let blame = self.blame.as_ref()?;
18833 let snapshot = self.snapshot(window, cx);
18834 let cursor = self.selections.newest::<Point>(cx).head();
18835 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
18836 let blame_entry = blame
18837 .update(cx, |blame, cx| {
18838 blame
18839 .blame_for_rows(
18840 &[RowInfo {
18841 buffer_id: Some(buffer.remote_id()),
18842 buffer_row: Some(point.row),
18843 ..Default::default()
18844 }],
18845 cx,
18846 )
18847 .next()
18848 })
18849 .flatten()?;
18850 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
18851 let repo = blame.read(cx).repository(cx)?;
18852 let workspace = self.workspace()?.downgrade();
18853 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
18854 None
18855 }
18856
18857 pub fn git_blame_inline_enabled(&self) -> bool {
18858 self.git_blame_inline_enabled
18859 }
18860
18861 pub fn toggle_selection_menu(
18862 &mut self,
18863 _: &ToggleSelectionMenu,
18864 _: &mut Window,
18865 cx: &mut Context<Self>,
18866 ) {
18867 self.show_selection_menu = self
18868 .show_selection_menu
18869 .map(|show_selections_menu| !show_selections_menu)
18870 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
18871
18872 cx.notify();
18873 }
18874
18875 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
18876 self.show_selection_menu
18877 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
18878 }
18879
18880 fn start_git_blame(
18881 &mut self,
18882 user_triggered: bool,
18883 window: &mut Window,
18884 cx: &mut Context<Self>,
18885 ) {
18886 if let Some(project) = self.project.as_ref() {
18887 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
18888 return;
18889 };
18890
18891 if buffer.read(cx).file().is_none() {
18892 return;
18893 }
18894
18895 let focused = self.focus_handle(cx).contains_focused(window, cx);
18896
18897 let project = project.clone();
18898 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
18899 self.blame_subscription =
18900 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
18901 self.blame = Some(blame);
18902 }
18903 }
18904
18905 fn toggle_git_blame_inline_internal(
18906 &mut self,
18907 user_triggered: bool,
18908 window: &mut Window,
18909 cx: &mut Context<Self>,
18910 ) {
18911 if self.git_blame_inline_enabled {
18912 self.git_blame_inline_enabled = false;
18913 self.show_git_blame_inline = false;
18914 self.show_git_blame_inline_delay_task.take();
18915 } else {
18916 self.git_blame_inline_enabled = true;
18917 self.start_git_blame_inline(user_triggered, window, cx);
18918 }
18919
18920 cx.notify();
18921 }
18922
18923 fn start_git_blame_inline(
18924 &mut self,
18925 user_triggered: bool,
18926 window: &mut Window,
18927 cx: &mut Context<Self>,
18928 ) {
18929 self.start_git_blame(user_triggered, window, cx);
18930
18931 if ProjectSettings::get_global(cx)
18932 .git
18933 .inline_blame_delay()
18934 .is_some()
18935 {
18936 self.start_inline_blame_timer(window, cx);
18937 } else {
18938 self.show_git_blame_inline = true
18939 }
18940 }
18941
18942 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
18943 self.blame.as_ref()
18944 }
18945
18946 pub fn show_git_blame_gutter(&self) -> bool {
18947 self.show_git_blame_gutter
18948 }
18949
18950 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
18951 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
18952 }
18953
18954 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
18955 self.show_git_blame_inline
18956 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
18957 && !self.newest_selection_head_on_empty_line(cx)
18958 && self.has_blame_entries(cx)
18959 }
18960
18961 fn has_blame_entries(&self, cx: &App) -> bool {
18962 self.blame()
18963 .map_or(false, |blame| blame.read(cx).has_generated_entries())
18964 }
18965
18966 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
18967 let cursor_anchor = self.selections.newest_anchor().head();
18968
18969 let snapshot = self.buffer.read(cx).snapshot(cx);
18970 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
18971
18972 snapshot.line_len(buffer_row) == 0
18973 }
18974
18975 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
18976 let buffer_and_selection = maybe!({
18977 let selection = self.selections.newest::<Point>(cx);
18978 let selection_range = selection.range();
18979
18980 let multi_buffer = self.buffer().read(cx);
18981 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18982 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
18983
18984 let (buffer, range, _) = if selection.reversed {
18985 buffer_ranges.first()
18986 } else {
18987 buffer_ranges.last()
18988 }?;
18989
18990 let selection = text::ToPoint::to_point(&range.start, &buffer).row
18991 ..text::ToPoint::to_point(&range.end, &buffer).row;
18992 Some((
18993 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
18994 selection,
18995 ))
18996 });
18997
18998 let Some((buffer, selection)) = buffer_and_selection else {
18999 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
19000 };
19001
19002 let Some(project) = self.project.as_ref() else {
19003 return Task::ready(Err(anyhow!("editor does not have project")));
19004 };
19005
19006 project.update(cx, |project, cx| {
19007 project.get_permalink_to_line(&buffer, selection, cx)
19008 })
19009 }
19010
19011 pub fn copy_permalink_to_line(
19012 &mut self,
19013 _: &CopyPermalinkToLine,
19014 window: &mut Window,
19015 cx: &mut Context<Self>,
19016 ) {
19017 let permalink_task = self.get_permalink_to_line(cx);
19018 let workspace = self.workspace();
19019
19020 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19021 Ok(permalink) => {
19022 cx.update(|_, cx| {
19023 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
19024 })
19025 .ok();
19026 }
19027 Err(err) => {
19028 let message = format!("Failed to copy permalink: {err}");
19029
19030 anyhow::Result::<()>::Err(err).log_err();
19031
19032 if let Some(workspace) = workspace {
19033 workspace
19034 .update_in(cx, |workspace, _, cx| {
19035 struct CopyPermalinkToLine;
19036
19037 workspace.show_toast(
19038 Toast::new(
19039 NotificationId::unique::<CopyPermalinkToLine>(),
19040 message,
19041 ),
19042 cx,
19043 )
19044 })
19045 .ok();
19046 }
19047 }
19048 })
19049 .detach();
19050 }
19051
19052 pub fn copy_file_location(
19053 &mut self,
19054 _: &CopyFileLocation,
19055 _: &mut Window,
19056 cx: &mut Context<Self>,
19057 ) {
19058 let selection = self.selections.newest::<Point>(cx).start.row + 1;
19059 if let Some(file) = self.target_file(cx) {
19060 if let Some(path) = file.path().to_str() {
19061 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
19062 }
19063 }
19064 }
19065
19066 pub fn open_permalink_to_line(
19067 &mut self,
19068 _: &OpenPermalinkToLine,
19069 window: &mut Window,
19070 cx: &mut Context<Self>,
19071 ) {
19072 let permalink_task = self.get_permalink_to_line(cx);
19073 let workspace = self.workspace();
19074
19075 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19076 Ok(permalink) => {
19077 cx.update(|_, cx| {
19078 cx.open_url(permalink.as_ref());
19079 })
19080 .ok();
19081 }
19082 Err(err) => {
19083 let message = format!("Failed to open permalink: {err}");
19084
19085 anyhow::Result::<()>::Err(err).log_err();
19086
19087 if let Some(workspace) = workspace {
19088 workspace
19089 .update(cx, |workspace, cx| {
19090 struct OpenPermalinkToLine;
19091
19092 workspace.show_toast(
19093 Toast::new(
19094 NotificationId::unique::<OpenPermalinkToLine>(),
19095 message,
19096 ),
19097 cx,
19098 )
19099 })
19100 .ok();
19101 }
19102 }
19103 })
19104 .detach();
19105 }
19106
19107 pub fn insert_uuid_v4(
19108 &mut self,
19109 _: &InsertUuidV4,
19110 window: &mut Window,
19111 cx: &mut Context<Self>,
19112 ) {
19113 self.insert_uuid(UuidVersion::V4, window, cx);
19114 }
19115
19116 pub fn insert_uuid_v7(
19117 &mut self,
19118 _: &InsertUuidV7,
19119 window: &mut Window,
19120 cx: &mut Context<Self>,
19121 ) {
19122 self.insert_uuid(UuidVersion::V7, window, cx);
19123 }
19124
19125 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
19126 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19127 self.transact(window, cx, |this, window, cx| {
19128 let edits = this
19129 .selections
19130 .all::<Point>(cx)
19131 .into_iter()
19132 .map(|selection| {
19133 let uuid = match version {
19134 UuidVersion::V4 => uuid::Uuid::new_v4(),
19135 UuidVersion::V7 => uuid::Uuid::now_v7(),
19136 };
19137
19138 (selection.range(), uuid.to_string())
19139 });
19140 this.edit(edits, cx);
19141 this.refresh_edit_prediction(true, false, window, cx);
19142 });
19143 }
19144
19145 pub fn open_selections_in_multibuffer(
19146 &mut self,
19147 _: &OpenSelectionsInMultibuffer,
19148 window: &mut Window,
19149 cx: &mut Context<Self>,
19150 ) {
19151 let multibuffer = self.buffer.read(cx);
19152
19153 let Some(buffer) = multibuffer.as_singleton() else {
19154 return;
19155 };
19156
19157 let Some(workspace) = self.workspace() else {
19158 return;
19159 };
19160
19161 let title = multibuffer.title(cx).to_string();
19162
19163 let locations = self
19164 .selections
19165 .all_anchors(cx)
19166 .into_iter()
19167 .map(|selection| Location {
19168 buffer: buffer.clone(),
19169 range: selection.start.text_anchor..selection.end.text_anchor,
19170 })
19171 .collect::<Vec<_>>();
19172
19173 cx.spawn_in(window, async move |_, cx| {
19174 workspace.update_in(cx, |workspace, window, cx| {
19175 Self::open_locations_in_multibuffer(
19176 workspace,
19177 locations,
19178 format!("Selections for '{title}'"),
19179 false,
19180 MultibufferSelectionMode::All,
19181 window,
19182 cx,
19183 );
19184 })
19185 })
19186 .detach();
19187 }
19188
19189 /// Adds a row highlight for the given range. If a row has multiple highlights, the
19190 /// last highlight added will be used.
19191 ///
19192 /// If the range ends at the beginning of a line, then that line will not be highlighted.
19193 pub fn highlight_rows<T: 'static>(
19194 &mut self,
19195 range: Range<Anchor>,
19196 color: Hsla,
19197 options: RowHighlightOptions,
19198 cx: &mut Context<Self>,
19199 ) {
19200 let snapshot = self.buffer().read(cx).snapshot(cx);
19201 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19202 let ix = row_highlights.binary_search_by(|highlight| {
19203 Ordering::Equal
19204 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
19205 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
19206 });
19207
19208 if let Err(mut ix) = ix {
19209 let index = post_inc(&mut self.highlight_order);
19210
19211 // If this range intersects with the preceding highlight, then merge it with
19212 // the preceding highlight. Otherwise insert a new highlight.
19213 let mut merged = false;
19214 if ix > 0 {
19215 let prev_highlight = &mut row_highlights[ix - 1];
19216 if prev_highlight
19217 .range
19218 .end
19219 .cmp(&range.start, &snapshot)
19220 .is_ge()
19221 {
19222 ix -= 1;
19223 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
19224 prev_highlight.range.end = range.end;
19225 }
19226 merged = true;
19227 prev_highlight.index = index;
19228 prev_highlight.color = color;
19229 prev_highlight.options = options;
19230 }
19231 }
19232
19233 if !merged {
19234 row_highlights.insert(
19235 ix,
19236 RowHighlight {
19237 range: range.clone(),
19238 index,
19239 color,
19240 options,
19241 type_id: TypeId::of::<T>(),
19242 },
19243 );
19244 }
19245
19246 // If any of the following highlights intersect with this one, merge them.
19247 while let Some(next_highlight) = row_highlights.get(ix + 1) {
19248 let highlight = &row_highlights[ix];
19249 if next_highlight
19250 .range
19251 .start
19252 .cmp(&highlight.range.end, &snapshot)
19253 .is_le()
19254 {
19255 if next_highlight
19256 .range
19257 .end
19258 .cmp(&highlight.range.end, &snapshot)
19259 .is_gt()
19260 {
19261 row_highlights[ix].range.end = next_highlight.range.end;
19262 }
19263 row_highlights.remove(ix + 1);
19264 } else {
19265 break;
19266 }
19267 }
19268 }
19269 }
19270
19271 /// Remove any highlighted row ranges of the given type that intersect the
19272 /// given ranges.
19273 pub fn remove_highlighted_rows<T: 'static>(
19274 &mut self,
19275 ranges_to_remove: Vec<Range<Anchor>>,
19276 cx: &mut Context<Self>,
19277 ) {
19278 let snapshot = self.buffer().read(cx).snapshot(cx);
19279 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19280 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19281 row_highlights.retain(|highlight| {
19282 while let Some(range_to_remove) = ranges_to_remove.peek() {
19283 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
19284 Ordering::Less | Ordering::Equal => {
19285 ranges_to_remove.next();
19286 }
19287 Ordering::Greater => {
19288 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
19289 Ordering::Less | Ordering::Equal => {
19290 return false;
19291 }
19292 Ordering::Greater => break,
19293 }
19294 }
19295 }
19296 }
19297
19298 true
19299 })
19300 }
19301
19302 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
19303 pub fn clear_row_highlights<T: 'static>(&mut self) {
19304 self.highlighted_rows.remove(&TypeId::of::<T>());
19305 }
19306
19307 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
19308 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
19309 self.highlighted_rows
19310 .get(&TypeId::of::<T>())
19311 .map_or(&[] as &[_], |vec| vec.as_slice())
19312 .iter()
19313 .map(|highlight| (highlight.range.clone(), highlight.color))
19314 }
19315
19316 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
19317 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
19318 /// Allows to ignore certain kinds of highlights.
19319 pub fn highlighted_display_rows(
19320 &self,
19321 window: &mut Window,
19322 cx: &mut App,
19323 ) -> BTreeMap<DisplayRow, LineHighlight> {
19324 let snapshot = self.snapshot(window, cx);
19325 let mut used_highlight_orders = HashMap::default();
19326 self.highlighted_rows
19327 .iter()
19328 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
19329 .fold(
19330 BTreeMap::<DisplayRow, LineHighlight>::new(),
19331 |mut unique_rows, highlight| {
19332 let start = highlight.range.start.to_display_point(&snapshot);
19333 let end = highlight.range.end.to_display_point(&snapshot);
19334 let start_row = start.row().0;
19335 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
19336 && end.column() == 0
19337 {
19338 end.row().0.saturating_sub(1)
19339 } else {
19340 end.row().0
19341 };
19342 for row in start_row..=end_row {
19343 let used_index =
19344 used_highlight_orders.entry(row).or_insert(highlight.index);
19345 if highlight.index >= *used_index {
19346 *used_index = highlight.index;
19347 unique_rows.insert(
19348 DisplayRow(row),
19349 LineHighlight {
19350 include_gutter: highlight.options.include_gutter,
19351 border: None,
19352 background: highlight.color.into(),
19353 type_id: Some(highlight.type_id),
19354 },
19355 );
19356 }
19357 }
19358 unique_rows
19359 },
19360 )
19361 }
19362
19363 pub fn highlighted_display_row_for_autoscroll(
19364 &self,
19365 snapshot: &DisplaySnapshot,
19366 ) -> Option<DisplayRow> {
19367 self.highlighted_rows
19368 .values()
19369 .flat_map(|highlighted_rows| highlighted_rows.iter())
19370 .filter_map(|highlight| {
19371 if highlight.options.autoscroll {
19372 Some(highlight.range.start.to_display_point(snapshot).row())
19373 } else {
19374 None
19375 }
19376 })
19377 .min()
19378 }
19379
19380 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
19381 self.highlight_background::<SearchWithinRange>(
19382 ranges,
19383 |colors| colors.colors().editor_document_highlight_read_background,
19384 cx,
19385 )
19386 }
19387
19388 pub fn set_breadcrumb_header(&mut self, new_header: String) {
19389 self.breadcrumb_header = Some(new_header);
19390 }
19391
19392 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
19393 self.clear_background_highlights::<SearchWithinRange>(cx);
19394 }
19395
19396 pub fn highlight_background<T: 'static>(
19397 &mut self,
19398 ranges: &[Range<Anchor>],
19399 color_fetcher: fn(&Theme) -> Hsla,
19400 cx: &mut Context<Self>,
19401 ) {
19402 self.background_highlights.insert(
19403 HighlightKey::Type(TypeId::of::<T>()),
19404 (color_fetcher, Arc::from(ranges)),
19405 );
19406 self.scrollbar_marker_state.dirty = true;
19407 cx.notify();
19408 }
19409
19410 pub fn highlight_background_key<T: 'static>(
19411 &mut self,
19412 key: usize,
19413 ranges: &[Range<Anchor>],
19414 color_fetcher: fn(&Theme) -> Hsla,
19415 cx: &mut Context<Self>,
19416 ) {
19417 self.background_highlights.insert(
19418 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19419 (color_fetcher, Arc::from(ranges)),
19420 );
19421 self.scrollbar_marker_state.dirty = true;
19422 cx.notify();
19423 }
19424
19425 pub fn clear_background_highlights<T: 'static>(
19426 &mut self,
19427 cx: &mut Context<Self>,
19428 ) -> Option<BackgroundHighlight> {
19429 let text_highlights = self
19430 .background_highlights
19431 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
19432 if !text_highlights.1.is_empty() {
19433 self.scrollbar_marker_state.dirty = true;
19434 cx.notify();
19435 }
19436 Some(text_highlights)
19437 }
19438
19439 pub fn highlight_gutter<T: 'static>(
19440 &mut self,
19441 ranges: impl Into<Vec<Range<Anchor>>>,
19442 color_fetcher: fn(&App) -> Hsla,
19443 cx: &mut Context<Self>,
19444 ) {
19445 self.gutter_highlights
19446 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
19447 cx.notify();
19448 }
19449
19450 pub fn clear_gutter_highlights<T: 'static>(
19451 &mut self,
19452 cx: &mut Context<Self>,
19453 ) -> Option<GutterHighlight> {
19454 cx.notify();
19455 self.gutter_highlights.remove(&TypeId::of::<T>())
19456 }
19457
19458 pub fn insert_gutter_highlight<T: 'static>(
19459 &mut self,
19460 range: Range<Anchor>,
19461 color_fetcher: fn(&App) -> Hsla,
19462 cx: &mut Context<Self>,
19463 ) {
19464 let snapshot = self.buffer().read(cx).snapshot(cx);
19465 let mut highlights = self
19466 .gutter_highlights
19467 .remove(&TypeId::of::<T>())
19468 .map(|(_, highlights)| highlights)
19469 .unwrap_or_default();
19470 let ix = highlights.binary_search_by(|highlight| {
19471 Ordering::Equal
19472 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
19473 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
19474 });
19475 if let Err(ix) = ix {
19476 highlights.insert(ix, range);
19477 }
19478 self.gutter_highlights
19479 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
19480 }
19481
19482 pub fn remove_gutter_highlights<T: 'static>(
19483 &mut self,
19484 ranges_to_remove: Vec<Range<Anchor>>,
19485 cx: &mut Context<Self>,
19486 ) {
19487 let snapshot = self.buffer().read(cx).snapshot(cx);
19488 let Some((color_fetcher, mut gutter_highlights)) =
19489 self.gutter_highlights.remove(&TypeId::of::<T>())
19490 else {
19491 return;
19492 };
19493 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19494 gutter_highlights.retain(|highlight| {
19495 while let Some(range_to_remove) = ranges_to_remove.peek() {
19496 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
19497 Ordering::Less | Ordering::Equal => {
19498 ranges_to_remove.next();
19499 }
19500 Ordering::Greater => {
19501 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
19502 Ordering::Less | Ordering::Equal => {
19503 return false;
19504 }
19505 Ordering::Greater => break,
19506 }
19507 }
19508 }
19509 }
19510
19511 true
19512 });
19513 self.gutter_highlights
19514 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
19515 }
19516
19517 #[cfg(feature = "test-support")]
19518 pub fn all_text_highlights(
19519 &self,
19520 window: &mut Window,
19521 cx: &mut Context<Self>,
19522 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
19523 let snapshot = self.snapshot(window, cx);
19524 self.display_map.update(cx, |display_map, _| {
19525 display_map
19526 .all_text_highlights()
19527 .map(|highlight| {
19528 let (style, ranges) = highlight.as_ref();
19529 (
19530 *style,
19531 ranges
19532 .iter()
19533 .map(|range| range.clone().to_display_points(&snapshot))
19534 .collect(),
19535 )
19536 })
19537 .collect()
19538 })
19539 }
19540
19541 #[cfg(feature = "test-support")]
19542 pub fn all_text_background_highlights(
19543 &self,
19544 window: &mut Window,
19545 cx: &mut Context<Self>,
19546 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19547 let snapshot = self.snapshot(window, cx);
19548 let buffer = &snapshot.buffer_snapshot;
19549 let start = buffer.anchor_before(0);
19550 let end = buffer.anchor_after(buffer.len());
19551 self.background_highlights_in_range(start..end, &snapshot, cx.theme())
19552 }
19553
19554 #[cfg(feature = "test-support")]
19555 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
19556 let snapshot = self.buffer().read(cx).snapshot(cx);
19557
19558 let highlights = self
19559 .background_highlights
19560 .get(&HighlightKey::Type(TypeId::of::<
19561 items::BufferSearchHighlights,
19562 >()));
19563
19564 if let Some((_color, ranges)) = highlights {
19565 ranges
19566 .iter()
19567 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
19568 .collect_vec()
19569 } else {
19570 vec![]
19571 }
19572 }
19573
19574 fn document_highlights_for_position<'a>(
19575 &'a self,
19576 position: Anchor,
19577 buffer: &'a MultiBufferSnapshot,
19578 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
19579 let read_highlights = self
19580 .background_highlights
19581 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
19582 .map(|h| &h.1);
19583 let write_highlights = self
19584 .background_highlights
19585 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
19586 .map(|h| &h.1);
19587 let left_position = position.bias_left(buffer);
19588 let right_position = position.bias_right(buffer);
19589 read_highlights
19590 .into_iter()
19591 .chain(write_highlights)
19592 .flat_map(move |ranges| {
19593 let start_ix = match ranges.binary_search_by(|probe| {
19594 let cmp = probe.end.cmp(&left_position, buffer);
19595 if cmp.is_ge() {
19596 Ordering::Greater
19597 } else {
19598 Ordering::Less
19599 }
19600 }) {
19601 Ok(i) | Err(i) => i,
19602 };
19603
19604 ranges[start_ix..]
19605 .iter()
19606 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
19607 })
19608 }
19609
19610 pub fn has_background_highlights<T: 'static>(&self) -> bool {
19611 self.background_highlights
19612 .get(&HighlightKey::Type(TypeId::of::<T>()))
19613 .map_or(false, |(_, highlights)| !highlights.is_empty())
19614 }
19615
19616 pub fn background_highlights_in_range(
19617 &self,
19618 search_range: Range<Anchor>,
19619 display_snapshot: &DisplaySnapshot,
19620 theme: &Theme,
19621 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19622 let mut results = Vec::new();
19623 for (color_fetcher, ranges) in self.background_highlights.values() {
19624 let color = color_fetcher(theme);
19625 let start_ix = match ranges.binary_search_by(|probe| {
19626 let cmp = probe
19627 .end
19628 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19629 if cmp.is_gt() {
19630 Ordering::Greater
19631 } else {
19632 Ordering::Less
19633 }
19634 }) {
19635 Ok(i) | Err(i) => i,
19636 };
19637 for range in &ranges[start_ix..] {
19638 if range
19639 .start
19640 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19641 .is_ge()
19642 {
19643 break;
19644 }
19645
19646 let start = range.start.to_display_point(display_snapshot);
19647 let end = range.end.to_display_point(display_snapshot);
19648 results.push((start..end, color))
19649 }
19650 }
19651 results
19652 }
19653
19654 pub fn background_highlight_row_ranges<T: 'static>(
19655 &self,
19656 search_range: Range<Anchor>,
19657 display_snapshot: &DisplaySnapshot,
19658 count: usize,
19659 ) -> Vec<RangeInclusive<DisplayPoint>> {
19660 let mut results = Vec::new();
19661 let Some((_, ranges)) = self
19662 .background_highlights
19663 .get(&HighlightKey::Type(TypeId::of::<T>()))
19664 else {
19665 return vec![];
19666 };
19667
19668 let start_ix = match ranges.binary_search_by(|probe| {
19669 let cmp = probe
19670 .end
19671 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19672 if cmp.is_gt() {
19673 Ordering::Greater
19674 } else {
19675 Ordering::Less
19676 }
19677 }) {
19678 Ok(i) | Err(i) => i,
19679 };
19680 let mut push_region = |start: Option<Point>, end: Option<Point>| {
19681 if let (Some(start_display), Some(end_display)) = (start, end) {
19682 results.push(
19683 start_display.to_display_point(display_snapshot)
19684 ..=end_display.to_display_point(display_snapshot),
19685 );
19686 }
19687 };
19688 let mut start_row: Option<Point> = None;
19689 let mut end_row: Option<Point> = None;
19690 if ranges.len() > count {
19691 return Vec::new();
19692 }
19693 for range in &ranges[start_ix..] {
19694 if range
19695 .start
19696 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19697 .is_ge()
19698 {
19699 break;
19700 }
19701 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
19702 if let Some(current_row) = &end_row {
19703 if end.row == current_row.row {
19704 continue;
19705 }
19706 }
19707 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
19708 if start_row.is_none() {
19709 assert_eq!(end_row, None);
19710 start_row = Some(start);
19711 end_row = Some(end);
19712 continue;
19713 }
19714 if let Some(current_end) = end_row.as_mut() {
19715 if start.row > current_end.row + 1 {
19716 push_region(start_row, end_row);
19717 start_row = Some(start);
19718 end_row = Some(end);
19719 } else {
19720 // Merge two hunks.
19721 *current_end = end;
19722 }
19723 } else {
19724 unreachable!();
19725 }
19726 }
19727 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
19728 push_region(start_row, end_row);
19729 results
19730 }
19731
19732 pub fn gutter_highlights_in_range(
19733 &self,
19734 search_range: Range<Anchor>,
19735 display_snapshot: &DisplaySnapshot,
19736 cx: &App,
19737 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19738 let mut results = Vec::new();
19739 for (color_fetcher, ranges) in self.gutter_highlights.values() {
19740 let color = color_fetcher(cx);
19741 let start_ix = match ranges.binary_search_by(|probe| {
19742 let cmp = probe
19743 .end
19744 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19745 if cmp.is_gt() {
19746 Ordering::Greater
19747 } else {
19748 Ordering::Less
19749 }
19750 }) {
19751 Ok(i) | Err(i) => i,
19752 };
19753 for range in &ranges[start_ix..] {
19754 if range
19755 .start
19756 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19757 .is_ge()
19758 {
19759 break;
19760 }
19761
19762 let start = range.start.to_display_point(display_snapshot);
19763 let end = range.end.to_display_point(display_snapshot);
19764 results.push((start..end, color))
19765 }
19766 }
19767 results
19768 }
19769
19770 /// Get the text ranges corresponding to the redaction query
19771 pub fn redacted_ranges(
19772 &self,
19773 search_range: Range<Anchor>,
19774 display_snapshot: &DisplaySnapshot,
19775 cx: &App,
19776 ) -> Vec<Range<DisplayPoint>> {
19777 display_snapshot
19778 .buffer_snapshot
19779 .redacted_ranges(search_range, |file| {
19780 if let Some(file) = file {
19781 file.is_private()
19782 && EditorSettings::get(
19783 Some(SettingsLocation {
19784 worktree_id: file.worktree_id(cx),
19785 path: file.path().as_ref(),
19786 }),
19787 cx,
19788 )
19789 .redact_private_values
19790 } else {
19791 false
19792 }
19793 })
19794 .map(|range| {
19795 range.start.to_display_point(display_snapshot)
19796 ..range.end.to_display_point(display_snapshot)
19797 })
19798 .collect()
19799 }
19800
19801 pub fn highlight_text_key<T: 'static>(
19802 &mut self,
19803 key: usize,
19804 ranges: Vec<Range<Anchor>>,
19805 style: HighlightStyle,
19806 cx: &mut Context<Self>,
19807 ) {
19808 self.display_map.update(cx, |map, _| {
19809 map.highlight_text(
19810 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19811 ranges,
19812 style,
19813 );
19814 });
19815 cx.notify();
19816 }
19817
19818 pub fn highlight_text<T: 'static>(
19819 &mut self,
19820 ranges: Vec<Range<Anchor>>,
19821 style: HighlightStyle,
19822 cx: &mut Context<Self>,
19823 ) {
19824 self.display_map.update(cx, |map, _| {
19825 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
19826 });
19827 cx.notify();
19828 }
19829
19830 pub(crate) fn highlight_inlays<T: 'static>(
19831 &mut self,
19832 highlights: Vec<InlayHighlight>,
19833 style: HighlightStyle,
19834 cx: &mut Context<Self>,
19835 ) {
19836 self.display_map.update(cx, |map, _| {
19837 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
19838 });
19839 cx.notify();
19840 }
19841
19842 pub fn text_highlights<'a, T: 'static>(
19843 &'a self,
19844 cx: &'a App,
19845 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
19846 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
19847 }
19848
19849 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
19850 let cleared = self
19851 .display_map
19852 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
19853 if cleared {
19854 cx.notify();
19855 }
19856 }
19857
19858 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
19859 (self.read_only(cx) || self.blink_manager.read(cx).visible())
19860 && self.focus_handle.is_focused(window)
19861 }
19862
19863 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
19864 self.show_cursor_when_unfocused = is_enabled;
19865 cx.notify();
19866 }
19867
19868 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
19869 cx.notify();
19870 }
19871
19872 fn on_debug_session_event(
19873 &mut self,
19874 _session: Entity<Session>,
19875 event: &SessionEvent,
19876 cx: &mut Context<Self>,
19877 ) {
19878 match event {
19879 SessionEvent::InvalidateInlineValue => {
19880 self.refresh_inline_values(cx);
19881 }
19882 _ => {}
19883 }
19884 }
19885
19886 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
19887 let Some(project) = self.project.clone() else {
19888 return;
19889 };
19890
19891 if !self.inline_value_cache.enabled {
19892 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
19893 self.splice_inlays(&inlays, Vec::new(), cx);
19894 return;
19895 }
19896
19897 let current_execution_position = self
19898 .highlighted_rows
19899 .get(&TypeId::of::<ActiveDebugLine>())
19900 .and_then(|lines| lines.last().map(|line| line.range.end));
19901
19902 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
19903 let inline_values = editor
19904 .update(cx, |editor, cx| {
19905 let Some(current_execution_position) = current_execution_position else {
19906 return Some(Task::ready(Ok(Vec::new())));
19907 };
19908
19909 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
19910 let snapshot = buffer.snapshot(cx);
19911
19912 let excerpt = snapshot.excerpt_containing(
19913 current_execution_position..current_execution_position,
19914 )?;
19915
19916 editor.buffer.read(cx).buffer(excerpt.buffer_id())
19917 })?;
19918
19919 let range =
19920 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
19921
19922 project.inline_values(buffer, range, cx)
19923 })
19924 .ok()
19925 .flatten()?
19926 .await
19927 .context("refreshing debugger inlays")
19928 .log_err()?;
19929
19930 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
19931
19932 for (buffer_id, inline_value) in inline_values
19933 .into_iter()
19934 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
19935 {
19936 buffer_inline_values
19937 .entry(buffer_id)
19938 .or_default()
19939 .push(inline_value);
19940 }
19941
19942 editor
19943 .update(cx, |editor, cx| {
19944 let snapshot = editor.buffer.read(cx).snapshot(cx);
19945 let mut new_inlays = Vec::default();
19946
19947 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
19948 let buffer_id = buffer_snapshot.remote_id();
19949 buffer_inline_values
19950 .get(&buffer_id)
19951 .into_iter()
19952 .flatten()
19953 .for_each(|hint| {
19954 let inlay = Inlay::debugger(
19955 post_inc(&mut editor.next_inlay_id),
19956 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
19957 hint.text(),
19958 );
19959 if !inlay.text.chars().contains(&'\n') {
19960 new_inlays.push(inlay);
19961 }
19962 });
19963 }
19964
19965 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
19966 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
19967
19968 editor.splice_inlays(&inlay_ids, new_inlays, cx);
19969 })
19970 .ok()?;
19971 Some(())
19972 });
19973 }
19974
19975 fn on_buffer_event(
19976 &mut self,
19977 multibuffer: &Entity<MultiBuffer>,
19978 event: &multi_buffer::Event,
19979 window: &mut Window,
19980 cx: &mut Context<Self>,
19981 ) {
19982 match event {
19983 multi_buffer::Event::Edited {
19984 singleton_buffer_edited,
19985 edited_buffer,
19986 } => {
19987 self.scrollbar_marker_state.dirty = true;
19988 self.active_indent_guides_state.dirty = true;
19989 self.refresh_active_diagnostics(cx);
19990 self.refresh_code_actions(window, cx);
19991 self.refresh_selected_text_highlights(true, window, cx);
19992 self.refresh_single_line_folds(window, cx);
19993 refresh_matching_bracket_highlights(self, window, cx);
19994 if self.has_active_edit_prediction() {
19995 self.update_visible_edit_prediction(window, cx);
19996 }
19997 if let Some(project) = self.project.as_ref() {
19998 if let Some(edited_buffer) = edited_buffer {
19999 project.update(cx, |project, cx| {
20000 self.registered_buffers
20001 .entry(edited_buffer.read(cx).remote_id())
20002 .or_insert_with(|| {
20003 project
20004 .register_buffer_with_language_servers(&edited_buffer, cx)
20005 });
20006 });
20007 }
20008 }
20009 cx.emit(EditorEvent::BufferEdited);
20010 cx.emit(SearchEvent::MatchesInvalidated);
20011
20012 if let Some(buffer) = edited_buffer {
20013 self.update_lsp_data(false, Some(buffer.read(cx).remote_id()), window, cx);
20014 }
20015
20016 if *singleton_buffer_edited {
20017 if let Some(buffer) = edited_buffer {
20018 if buffer.read(cx).file().is_none() {
20019 cx.emit(EditorEvent::TitleChanged);
20020 }
20021 }
20022 if let Some(project) = &self.project {
20023 #[allow(clippy::mutable_key_type)]
20024 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
20025 multibuffer
20026 .all_buffers()
20027 .into_iter()
20028 .filter_map(|buffer| {
20029 buffer.update(cx, |buffer, cx| {
20030 let language = buffer.language()?;
20031 let should_discard = project.update(cx, |project, cx| {
20032 project.is_local()
20033 && !project.has_language_servers_for(buffer, cx)
20034 });
20035 should_discard.not().then_some(language.clone())
20036 })
20037 })
20038 .collect::<HashSet<_>>()
20039 });
20040 if !languages_affected.is_empty() {
20041 self.refresh_inlay_hints(
20042 InlayHintRefreshReason::BufferEdited(languages_affected),
20043 cx,
20044 );
20045 }
20046 }
20047 }
20048
20049 let Some(project) = &self.project else { return };
20050 let (telemetry, is_via_ssh) = {
20051 let project = project.read(cx);
20052 let telemetry = project.client().telemetry().clone();
20053 let is_via_ssh = project.is_via_ssh();
20054 (telemetry, is_via_ssh)
20055 };
20056 refresh_linked_ranges(self, window, cx);
20057 telemetry.log_edit_event("editor", is_via_ssh);
20058 }
20059 multi_buffer::Event::ExcerptsAdded {
20060 buffer,
20061 predecessor,
20062 excerpts,
20063 } => {
20064 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20065 let buffer_id = buffer.read(cx).remote_id();
20066 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
20067 if let Some(project) = &self.project {
20068 update_uncommitted_diff_for_buffer(
20069 cx.entity(),
20070 project,
20071 [buffer.clone()],
20072 self.buffer.clone(),
20073 cx,
20074 )
20075 .detach();
20076 }
20077 }
20078 self.update_lsp_data(false, Some(buffer_id), window, cx);
20079 cx.emit(EditorEvent::ExcerptsAdded {
20080 buffer: buffer.clone(),
20081 predecessor: *predecessor,
20082 excerpts: excerpts.clone(),
20083 });
20084 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20085 }
20086 multi_buffer::Event::ExcerptsRemoved {
20087 ids,
20088 removed_buffer_ids,
20089 } => {
20090 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
20091 let buffer = self.buffer.read(cx);
20092 self.registered_buffers
20093 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
20094 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20095 cx.emit(EditorEvent::ExcerptsRemoved {
20096 ids: ids.clone(),
20097 removed_buffer_ids: removed_buffer_ids.clone(),
20098 });
20099 }
20100 multi_buffer::Event::ExcerptsEdited {
20101 excerpt_ids,
20102 buffer_ids,
20103 } => {
20104 self.display_map.update(cx, |map, cx| {
20105 map.unfold_buffers(buffer_ids.iter().copied(), cx)
20106 });
20107 cx.emit(EditorEvent::ExcerptsEdited {
20108 ids: excerpt_ids.clone(),
20109 });
20110 }
20111 multi_buffer::Event::ExcerptsExpanded { ids } => {
20112 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20113 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
20114 }
20115 multi_buffer::Event::Reparsed(buffer_id) => {
20116 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20117 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20118
20119 cx.emit(EditorEvent::Reparsed(*buffer_id));
20120 }
20121 multi_buffer::Event::DiffHunksToggled => {
20122 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20123 }
20124 multi_buffer::Event::LanguageChanged(buffer_id) => {
20125 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
20126 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20127 cx.emit(EditorEvent::Reparsed(*buffer_id));
20128 cx.notify();
20129 }
20130 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
20131 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
20132 multi_buffer::Event::FileHandleChanged
20133 | multi_buffer::Event::Reloaded
20134 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
20135 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
20136 multi_buffer::Event::DiagnosticsUpdated => {
20137 self.update_diagnostics_state(window, cx);
20138 }
20139 _ => {}
20140 };
20141 }
20142
20143 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
20144 if !self.diagnostics_enabled() {
20145 return;
20146 }
20147 self.refresh_active_diagnostics(cx);
20148 self.refresh_inline_diagnostics(true, window, cx);
20149 self.scrollbar_marker_state.dirty = true;
20150 cx.notify();
20151 }
20152
20153 pub fn start_temporary_diff_override(&mut self) {
20154 self.load_diff_task.take();
20155 self.temporary_diff_override = true;
20156 }
20157
20158 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
20159 self.temporary_diff_override = false;
20160 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
20161 self.buffer.update(cx, |buffer, cx| {
20162 buffer.set_all_diff_hunks_collapsed(cx);
20163 });
20164
20165 if let Some(project) = self.project.clone() {
20166 self.load_diff_task = Some(
20167 update_uncommitted_diff_for_buffer(
20168 cx.entity(),
20169 &project,
20170 self.buffer.read(cx).all_buffers(),
20171 self.buffer.clone(),
20172 cx,
20173 )
20174 .shared(),
20175 );
20176 }
20177 }
20178
20179 fn on_display_map_changed(
20180 &mut self,
20181 _: Entity<DisplayMap>,
20182 _: &mut Window,
20183 cx: &mut Context<Self>,
20184 ) {
20185 cx.notify();
20186 }
20187
20188 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20189 if self.diagnostics_enabled() {
20190 let new_severity = EditorSettings::get_global(cx)
20191 .diagnostics_max_severity
20192 .unwrap_or(DiagnosticSeverity::Hint);
20193 self.set_max_diagnostics_severity(new_severity, cx);
20194 }
20195 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20196 self.update_edit_prediction_settings(cx);
20197 self.refresh_edit_prediction(true, false, window, cx);
20198 self.refresh_inline_values(cx);
20199 self.refresh_inlay_hints(
20200 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
20201 self.selections.newest_anchor().head(),
20202 &self.buffer.read(cx).snapshot(cx),
20203 cx,
20204 )),
20205 cx,
20206 );
20207
20208 let old_cursor_shape = self.cursor_shape;
20209
20210 {
20211 let editor_settings = EditorSettings::get_global(cx);
20212 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
20213 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
20214 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
20215 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
20216 }
20217
20218 if old_cursor_shape != self.cursor_shape {
20219 cx.emit(EditorEvent::CursorShapeChanged);
20220 }
20221
20222 let project_settings = ProjectSettings::get_global(cx);
20223 self.serialize_dirty_buffers =
20224 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
20225
20226 if self.mode.is_full() {
20227 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
20228 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
20229 if self.show_inline_diagnostics != show_inline_diagnostics {
20230 self.show_inline_diagnostics = show_inline_diagnostics;
20231 self.refresh_inline_diagnostics(false, window, cx);
20232 }
20233
20234 if self.git_blame_inline_enabled != inline_blame_enabled {
20235 self.toggle_git_blame_inline_internal(false, window, cx);
20236 }
20237
20238 let minimap_settings = EditorSettings::get_global(cx).minimap;
20239 if self.minimap_visibility != MinimapVisibility::Disabled {
20240 if self.minimap_visibility.settings_visibility()
20241 != minimap_settings.minimap_enabled()
20242 {
20243 self.set_minimap_visibility(
20244 MinimapVisibility::for_mode(self.mode(), cx),
20245 window,
20246 cx,
20247 );
20248 } else if let Some(minimap_entity) = self.minimap.as_ref() {
20249 minimap_entity.update(cx, |minimap_editor, cx| {
20250 minimap_editor.update_minimap_configuration(minimap_settings, cx)
20251 })
20252 }
20253 }
20254 }
20255
20256 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
20257 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
20258 }) {
20259 if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
20260 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
20261 }
20262 self.refresh_colors(false, None, window, cx);
20263 }
20264
20265 cx.notify();
20266 }
20267
20268 pub fn set_searchable(&mut self, searchable: bool) {
20269 self.searchable = searchable;
20270 }
20271
20272 pub fn searchable(&self) -> bool {
20273 self.searchable
20274 }
20275
20276 fn open_proposed_changes_editor(
20277 &mut self,
20278 _: &OpenProposedChangesEditor,
20279 window: &mut Window,
20280 cx: &mut Context<Self>,
20281 ) {
20282 let Some(workspace) = self.workspace() else {
20283 cx.propagate();
20284 return;
20285 };
20286
20287 let selections = self.selections.all::<usize>(cx);
20288 let multi_buffer = self.buffer.read(cx);
20289 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20290 let mut new_selections_by_buffer = HashMap::default();
20291 for selection in selections {
20292 for (buffer, range, _) in
20293 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
20294 {
20295 let mut range = range.to_point(buffer);
20296 range.start.column = 0;
20297 range.end.column = buffer.line_len(range.end.row);
20298 new_selections_by_buffer
20299 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
20300 .or_insert(Vec::new())
20301 .push(range)
20302 }
20303 }
20304
20305 let proposed_changes_buffers = new_selections_by_buffer
20306 .into_iter()
20307 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
20308 .collect::<Vec<_>>();
20309 let proposed_changes_editor = cx.new(|cx| {
20310 ProposedChangesEditor::new(
20311 "Proposed changes",
20312 proposed_changes_buffers,
20313 self.project.clone(),
20314 window,
20315 cx,
20316 )
20317 });
20318
20319 window.defer(cx, move |window, cx| {
20320 workspace.update(cx, |workspace, cx| {
20321 workspace.active_pane().update(cx, |pane, cx| {
20322 pane.add_item(
20323 Box::new(proposed_changes_editor),
20324 true,
20325 true,
20326 None,
20327 window,
20328 cx,
20329 );
20330 });
20331 });
20332 });
20333 }
20334
20335 pub fn open_excerpts_in_split(
20336 &mut self,
20337 _: &OpenExcerptsSplit,
20338 window: &mut Window,
20339 cx: &mut Context<Self>,
20340 ) {
20341 self.open_excerpts_common(None, true, window, cx)
20342 }
20343
20344 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
20345 self.open_excerpts_common(None, false, window, cx)
20346 }
20347
20348 fn open_excerpts_common(
20349 &mut self,
20350 jump_data: Option<JumpData>,
20351 split: bool,
20352 window: &mut Window,
20353 cx: &mut Context<Self>,
20354 ) {
20355 let Some(workspace) = self.workspace() else {
20356 cx.propagate();
20357 return;
20358 };
20359
20360 if self.buffer.read(cx).is_singleton() {
20361 cx.propagate();
20362 return;
20363 }
20364
20365 let mut new_selections_by_buffer = HashMap::default();
20366 match &jump_data {
20367 Some(JumpData::MultiBufferPoint {
20368 excerpt_id,
20369 position,
20370 anchor,
20371 line_offset_from_top,
20372 }) => {
20373 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20374 if let Some(buffer) = multi_buffer_snapshot
20375 .buffer_id_for_excerpt(*excerpt_id)
20376 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
20377 {
20378 let buffer_snapshot = buffer.read(cx).snapshot();
20379 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
20380 language::ToPoint::to_point(anchor, &buffer_snapshot)
20381 } else {
20382 buffer_snapshot.clip_point(*position, Bias::Left)
20383 };
20384 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
20385 new_selections_by_buffer.insert(
20386 buffer,
20387 (
20388 vec![jump_to_offset..jump_to_offset],
20389 Some(*line_offset_from_top),
20390 ),
20391 );
20392 }
20393 }
20394 Some(JumpData::MultiBufferRow {
20395 row,
20396 line_offset_from_top,
20397 }) => {
20398 let point = MultiBufferPoint::new(row.0, 0);
20399 if let Some((buffer, buffer_point, _)) =
20400 self.buffer.read(cx).point_to_buffer_point(point, cx)
20401 {
20402 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
20403 new_selections_by_buffer
20404 .entry(buffer)
20405 .or_insert((Vec::new(), Some(*line_offset_from_top)))
20406 .0
20407 .push(buffer_offset..buffer_offset)
20408 }
20409 }
20410 None => {
20411 let selections = self.selections.all::<usize>(cx);
20412 let multi_buffer = self.buffer.read(cx);
20413 for selection in selections {
20414 for (snapshot, range, _, anchor) in multi_buffer
20415 .snapshot(cx)
20416 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
20417 {
20418 if let Some(anchor) = anchor {
20419 // selection is in a deleted hunk
20420 let Some(buffer_id) = anchor.buffer_id else {
20421 continue;
20422 };
20423 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
20424 continue;
20425 };
20426 let offset = text::ToOffset::to_offset(
20427 &anchor.text_anchor,
20428 &buffer_handle.read(cx).snapshot(),
20429 );
20430 let range = offset..offset;
20431 new_selections_by_buffer
20432 .entry(buffer_handle)
20433 .or_insert((Vec::new(), None))
20434 .0
20435 .push(range)
20436 } else {
20437 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
20438 else {
20439 continue;
20440 };
20441 new_selections_by_buffer
20442 .entry(buffer_handle)
20443 .or_insert((Vec::new(), None))
20444 .0
20445 .push(range)
20446 }
20447 }
20448 }
20449 }
20450 }
20451
20452 new_selections_by_buffer
20453 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
20454
20455 if new_selections_by_buffer.is_empty() {
20456 return;
20457 }
20458
20459 // We defer the pane interaction because we ourselves are a workspace item
20460 // and activating a new item causes the pane to call a method on us reentrantly,
20461 // which panics if we're on the stack.
20462 window.defer(cx, move |window, cx| {
20463 workspace.update(cx, |workspace, cx| {
20464 let pane = if split {
20465 workspace.adjacent_pane(window, cx)
20466 } else {
20467 workspace.active_pane().clone()
20468 };
20469
20470 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
20471 let editor = buffer
20472 .read(cx)
20473 .file()
20474 .is_none()
20475 .then(|| {
20476 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
20477 // so `workspace.open_project_item` will never find them, always opening a new editor.
20478 // Instead, we try to activate the existing editor in the pane first.
20479 let (editor, pane_item_index) =
20480 pane.read(cx).items().enumerate().find_map(|(i, item)| {
20481 let editor = item.downcast::<Editor>()?;
20482 let singleton_buffer =
20483 editor.read(cx).buffer().read(cx).as_singleton()?;
20484 if singleton_buffer == buffer {
20485 Some((editor, i))
20486 } else {
20487 None
20488 }
20489 })?;
20490 pane.update(cx, |pane, cx| {
20491 pane.activate_item(pane_item_index, true, true, window, cx)
20492 });
20493 Some(editor)
20494 })
20495 .flatten()
20496 .unwrap_or_else(|| {
20497 workspace.open_project_item::<Self>(
20498 pane.clone(),
20499 buffer,
20500 true,
20501 true,
20502 window,
20503 cx,
20504 )
20505 });
20506
20507 editor.update(cx, |editor, cx| {
20508 let autoscroll = match scroll_offset {
20509 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
20510 None => Autoscroll::newest(),
20511 };
20512 let nav_history = editor.nav_history.take();
20513 editor.change_selections(
20514 SelectionEffects::scroll(autoscroll),
20515 window,
20516 cx,
20517 |s| {
20518 s.select_ranges(ranges);
20519 },
20520 );
20521 editor.nav_history = nav_history;
20522 });
20523 }
20524 })
20525 });
20526 }
20527
20528 // For now, don't allow opening excerpts in buffers that aren't backed by
20529 // regular project files.
20530 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
20531 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
20532 }
20533
20534 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
20535 let snapshot = self.buffer.read(cx).read(cx);
20536 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
20537 Some(
20538 ranges
20539 .iter()
20540 .map(move |range| {
20541 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
20542 })
20543 .collect(),
20544 )
20545 }
20546
20547 fn selection_replacement_ranges(
20548 &self,
20549 range: Range<OffsetUtf16>,
20550 cx: &mut App,
20551 ) -> Vec<Range<OffsetUtf16>> {
20552 let selections = self.selections.all::<OffsetUtf16>(cx);
20553 let newest_selection = selections
20554 .iter()
20555 .max_by_key(|selection| selection.id)
20556 .unwrap();
20557 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
20558 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
20559 let snapshot = self.buffer.read(cx).read(cx);
20560 selections
20561 .into_iter()
20562 .map(|mut selection| {
20563 selection.start.0 =
20564 (selection.start.0 as isize).saturating_add(start_delta) as usize;
20565 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
20566 snapshot.clip_offset_utf16(selection.start, Bias::Left)
20567 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
20568 })
20569 .collect()
20570 }
20571
20572 fn report_editor_event(
20573 &self,
20574 event_type: &'static str,
20575 file_extension: Option<String>,
20576 cx: &App,
20577 ) {
20578 if cfg!(any(test, feature = "test-support")) {
20579 return;
20580 }
20581
20582 let Some(project) = &self.project else { return };
20583
20584 // If None, we are in a file without an extension
20585 let file = self
20586 .buffer
20587 .read(cx)
20588 .as_singleton()
20589 .and_then(|b| b.read(cx).file());
20590 let file_extension = file_extension.or(file
20591 .as_ref()
20592 .and_then(|file| Path::new(file.file_name(cx)).extension())
20593 .and_then(|e| e.to_str())
20594 .map(|a| a.to_string()));
20595
20596 let vim_mode = vim_enabled(cx);
20597
20598 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
20599 let copilot_enabled = edit_predictions_provider
20600 == language::language_settings::EditPredictionProvider::Copilot;
20601 let copilot_enabled_for_language = self
20602 .buffer
20603 .read(cx)
20604 .language_settings(cx)
20605 .show_edit_predictions;
20606
20607 let project = project.read(cx);
20608 telemetry::event!(
20609 event_type,
20610 file_extension,
20611 vim_mode,
20612 copilot_enabled,
20613 copilot_enabled_for_language,
20614 edit_predictions_provider,
20615 is_via_ssh = project.is_via_ssh(),
20616 );
20617 }
20618
20619 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
20620 /// with each line being an array of {text, highlight} objects.
20621 fn copy_highlight_json(
20622 &mut self,
20623 _: &CopyHighlightJson,
20624 window: &mut Window,
20625 cx: &mut Context<Self>,
20626 ) {
20627 #[derive(Serialize)]
20628 struct Chunk<'a> {
20629 text: String,
20630 highlight: Option<&'a str>,
20631 }
20632
20633 let snapshot = self.buffer.read(cx).snapshot(cx);
20634 let range = self
20635 .selected_text_range(false, window, cx)
20636 .and_then(|selection| {
20637 if selection.range.is_empty() {
20638 None
20639 } else {
20640 Some(selection.range)
20641 }
20642 })
20643 .unwrap_or_else(|| 0..snapshot.len());
20644
20645 let chunks = snapshot.chunks(range, true);
20646 let mut lines = Vec::new();
20647 let mut line: VecDeque<Chunk> = VecDeque::new();
20648
20649 let Some(style) = self.style.as_ref() else {
20650 return;
20651 };
20652
20653 for chunk in chunks {
20654 let highlight = chunk
20655 .syntax_highlight_id
20656 .and_then(|id| id.name(&style.syntax));
20657 let mut chunk_lines = chunk.text.split('\n').peekable();
20658 while let Some(text) = chunk_lines.next() {
20659 let mut merged_with_last_token = false;
20660 if let Some(last_token) = line.back_mut() {
20661 if last_token.highlight == highlight {
20662 last_token.text.push_str(text);
20663 merged_with_last_token = true;
20664 }
20665 }
20666
20667 if !merged_with_last_token {
20668 line.push_back(Chunk {
20669 text: text.into(),
20670 highlight,
20671 });
20672 }
20673
20674 if chunk_lines.peek().is_some() {
20675 if line.len() > 1 && line.front().unwrap().text.is_empty() {
20676 line.pop_front();
20677 }
20678 if line.len() > 1 && line.back().unwrap().text.is_empty() {
20679 line.pop_back();
20680 }
20681
20682 lines.push(mem::take(&mut line));
20683 }
20684 }
20685 }
20686
20687 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
20688 return;
20689 };
20690 cx.write_to_clipboard(ClipboardItem::new_string(lines));
20691 }
20692
20693 pub fn open_context_menu(
20694 &mut self,
20695 _: &OpenContextMenu,
20696 window: &mut Window,
20697 cx: &mut Context<Self>,
20698 ) {
20699 self.request_autoscroll(Autoscroll::newest(), cx);
20700 let position = self.selections.newest_display(cx).start;
20701 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
20702 }
20703
20704 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
20705 &self.inlay_hint_cache
20706 }
20707
20708 pub fn replay_insert_event(
20709 &mut self,
20710 text: &str,
20711 relative_utf16_range: Option<Range<isize>>,
20712 window: &mut Window,
20713 cx: &mut Context<Self>,
20714 ) {
20715 if !self.input_enabled {
20716 cx.emit(EditorEvent::InputIgnored { text: text.into() });
20717 return;
20718 }
20719 if let Some(relative_utf16_range) = relative_utf16_range {
20720 let selections = self.selections.all::<OffsetUtf16>(cx);
20721 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20722 let new_ranges = selections.into_iter().map(|range| {
20723 let start = OffsetUtf16(
20724 range
20725 .head()
20726 .0
20727 .saturating_add_signed(relative_utf16_range.start),
20728 );
20729 let end = OffsetUtf16(
20730 range
20731 .head()
20732 .0
20733 .saturating_add_signed(relative_utf16_range.end),
20734 );
20735 start..end
20736 });
20737 s.select_ranges(new_ranges);
20738 });
20739 }
20740
20741 self.handle_input(text, window, cx);
20742 }
20743
20744 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
20745 let Some(provider) = self.semantics_provider.as_ref() else {
20746 return false;
20747 };
20748
20749 let mut supports = false;
20750 self.buffer().update(cx, |this, cx| {
20751 this.for_each_buffer(|buffer| {
20752 supports |= provider.supports_inlay_hints(buffer, cx);
20753 });
20754 });
20755
20756 supports
20757 }
20758
20759 pub fn is_focused(&self, window: &Window) -> bool {
20760 self.focus_handle.is_focused(window)
20761 }
20762
20763 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20764 cx.emit(EditorEvent::Focused);
20765
20766 if let Some(descendant) = self
20767 .last_focused_descendant
20768 .take()
20769 .and_then(|descendant| descendant.upgrade())
20770 {
20771 window.focus(&descendant);
20772 } else {
20773 if let Some(blame) = self.blame.as_ref() {
20774 blame.update(cx, GitBlame::focus)
20775 }
20776
20777 self.blink_manager.update(cx, BlinkManager::enable);
20778 self.show_cursor_names(window, cx);
20779 self.buffer.update(cx, |buffer, cx| {
20780 buffer.finalize_last_transaction(cx);
20781 if self.leader_id.is_none() {
20782 buffer.set_active_selections(
20783 &self.selections.disjoint_anchors(),
20784 self.selections.line_mode,
20785 self.cursor_shape,
20786 cx,
20787 );
20788 }
20789 });
20790 }
20791 }
20792
20793 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
20794 cx.emit(EditorEvent::FocusedIn)
20795 }
20796
20797 fn handle_focus_out(
20798 &mut self,
20799 event: FocusOutEvent,
20800 _window: &mut Window,
20801 cx: &mut Context<Self>,
20802 ) {
20803 if event.blurred != self.focus_handle {
20804 self.last_focused_descendant = Some(event.blurred);
20805 }
20806 self.selection_drag_state = SelectionDragState::None;
20807 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
20808 }
20809
20810 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20811 self.blink_manager.update(cx, BlinkManager::disable);
20812 self.buffer
20813 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
20814
20815 if let Some(blame) = self.blame.as_ref() {
20816 blame.update(cx, GitBlame::blur)
20817 }
20818 if !self.hover_state.focused(window, cx) {
20819 hide_hover(self, cx);
20820 }
20821 if !self
20822 .context_menu
20823 .borrow()
20824 .as_ref()
20825 .is_some_and(|context_menu| context_menu.focused(window, cx))
20826 {
20827 self.hide_context_menu(window, cx);
20828 }
20829 self.discard_edit_prediction(false, cx);
20830 cx.emit(EditorEvent::Blurred);
20831 cx.notify();
20832 }
20833
20834 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20835 let mut pending: String = window
20836 .pending_input_keystrokes()
20837 .into_iter()
20838 .flatten()
20839 .filter_map(|keystroke| {
20840 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
20841 keystroke.key_char.clone()
20842 } else {
20843 None
20844 }
20845 })
20846 .collect();
20847
20848 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
20849 pending = "".to_string();
20850 }
20851
20852 let existing_pending = self
20853 .text_highlights::<PendingInput>(cx)
20854 .map(|(_, ranges)| ranges.iter().cloned().collect::<Vec<_>>());
20855 if existing_pending.is_none() && pending.is_empty() {
20856 return;
20857 }
20858 let transaction =
20859 self.transact(window, cx, |this, window, cx| {
20860 let selections = this.selections.all::<usize>(cx);
20861 let edits = selections
20862 .iter()
20863 .map(|selection| (selection.end..selection.end, pending.clone()));
20864 this.edit(edits, cx);
20865 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20866 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
20867 sel.start + ix * pending.len()..sel.end + ix * pending.len()
20868 }));
20869 });
20870 if let Some(existing_ranges) = existing_pending {
20871 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
20872 this.edit(edits, cx);
20873 }
20874 });
20875
20876 let snapshot = self.snapshot(window, cx);
20877 let ranges = self
20878 .selections
20879 .all::<usize>(cx)
20880 .into_iter()
20881 .map(|selection| {
20882 snapshot.buffer_snapshot.anchor_after(selection.end)
20883 ..snapshot
20884 .buffer_snapshot
20885 .anchor_before(selection.end + pending.len())
20886 })
20887 .collect();
20888
20889 if pending.is_empty() {
20890 self.clear_highlights::<PendingInput>(cx);
20891 } else {
20892 self.highlight_text::<PendingInput>(
20893 ranges,
20894 HighlightStyle {
20895 underline: Some(UnderlineStyle {
20896 thickness: px(1.),
20897 color: None,
20898 wavy: false,
20899 }),
20900 ..Default::default()
20901 },
20902 cx,
20903 );
20904 }
20905
20906 self.ime_transaction = self.ime_transaction.or(transaction);
20907 if let Some(transaction) = self.ime_transaction {
20908 self.buffer.update(cx, |buffer, cx| {
20909 buffer.group_until_transaction(transaction, cx);
20910 });
20911 }
20912
20913 if self.text_highlights::<PendingInput>(cx).is_none() {
20914 self.ime_transaction.take();
20915 }
20916 }
20917
20918 pub fn register_action_renderer(
20919 &mut self,
20920 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
20921 ) -> Subscription {
20922 let id = self.next_editor_action_id.post_inc();
20923 self.editor_actions
20924 .borrow_mut()
20925 .insert(id, Box::new(listener));
20926
20927 let editor_actions = self.editor_actions.clone();
20928 Subscription::new(move || {
20929 editor_actions.borrow_mut().remove(&id);
20930 })
20931 }
20932
20933 pub fn register_action<A: Action>(
20934 &mut self,
20935 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
20936 ) -> Subscription {
20937 let id = self.next_editor_action_id.post_inc();
20938 let listener = Arc::new(listener);
20939 self.editor_actions.borrow_mut().insert(
20940 id,
20941 Box::new(move |_, window, _| {
20942 let listener = listener.clone();
20943 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
20944 let action = action.downcast_ref().unwrap();
20945 if phase == DispatchPhase::Bubble {
20946 listener(action, window, cx)
20947 }
20948 })
20949 }),
20950 );
20951
20952 let editor_actions = self.editor_actions.clone();
20953 Subscription::new(move || {
20954 editor_actions.borrow_mut().remove(&id);
20955 })
20956 }
20957
20958 pub fn file_header_size(&self) -> u32 {
20959 FILE_HEADER_HEIGHT
20960 }
20961
20962 pub fn restore(
20963 &mut self,
20964 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
20965 window: &mut Window,
20966 cx: &mut Context<Self>,
20967 ) {
20968 let workspace = self.workspace();
20969 let project = self.project.as_ref();
20970 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
20971 let mut tasks = Vec::new();
20972 for (buffer_id, changes) in revert_changes {
20973 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
20974 buffer.update(cx, |buffer, cx| {
20975 buffer.edit(
20976 changes
20977 .into_iter()
20978 .map(|(range, text)| (range, text.to_string())),
20979 None,
20980 cx,
20981 );
20982 });
20983
20984 if let Some(project) =
20985 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
20986 {
20987 project.update(cx, |project, cx| {
20988 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
20989 })
20990 }
20991 }
20992 }
20993 tasks
20994 });
20995 cx.spawn_in(window, async move |_, cx| {
20996 for (buffer, task) in save_tasks {
20997 let result = task.await;
20998 if result.is_err() {
20999 let Some(path) = buffer
21000 .read_with(cx, |buffer, cx| buffer.project_path(cx))
21001 .ok()
21002 else {
21003 continue;
21004 };
21005 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
21006 let Some(task) = cx
21007 .update_window_entity(&workspace, |workspace, window, cx| {
21008 workspace
21009 .open_path_preview(path, None, false, false, false, window, cx)
21010 })
21011 .ok()
21012 else {
21013 continue;
21014 };
21015 task.await.log_err();
21016 }
21017 }
21018 }
21019 })
21020 .detach();
21021 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21022 selections.refresh()
21023 });
21024 }
21025
21026 pub fn to_pixel_point(
21027 &self,
21028 source: multi_buffer::Anchor,
21029 editor_snapshot: &EditorSnapshot,
21030 window: &mut Window,
21031 ) -> Option<gpui::Point<Pixels>> {
21032 let source_point = source.to_display_point(editor_snapshot);
21033 self.display_to_pixel_point(source_point, editor_snapshot, window)
21034 }
21035
21036 pub fn display_to_pixel_point(
21037 &self,
21038 source: DisplayPoint,
21039 editor_snapshot: &EditorSnapshot,
21040 window: &mut Window,
21041 ) -> Option<gpui::Point<Pixels>> {
21042 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
21043 let text_layout_details = self.text_layout_details(window);
21044 let scroll_top = text_layout_details
21045 .scroll_anchor
21046 .scroll_position(editor_snapshot)
21047 .y;
21048
21049 if source.row().as_f32() < scroll_top.floor() {
21050 return None;
21051 }
21052 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
21053 let source_y = line_height * (source.row().as_f32() - scroll_top);
21054 Some(gpui::Point::new(source_x, source_y))
21055 }
21056
21057 pub fn has_visible_completions_menu(&self) -> bool {
21058 !self.edit_prediction_preview_is_active()
21059 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
21060 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
21061 })
21062 }
21063
21064 pub fn register_addon<T: Addon>(&mut self, instance: T) {
21065 if self.mode.is_minimap() {
21066 return;
21067 }
21068 self.addons
21069 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
21070 }
21071
21072 pub fn unregister_addon<T: Addon>(&mut self) {
21073 self.addons.remove(&std::any::TypeId::of::<T>());
21074 }
21075
21076 pub fn addon<T: Addon>(&self) -> Option<&T> {
21077 let type_id = std::any::TypeId::of::<T>();
21078 self.addons
21079 .get(&type_id)
21080 .and_then(|item| item.to_any().downcast_ref::<T>())
21081 }
21082
21083 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
21084 let type_id = std::any::TypeId::of::<T>();
21085 self.addons
21086 .get_mut(&type_id)
21087 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
21088 }
21089
21090 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
21091 let text_layout_details = self.text_layout_details(window);
21092 let style = &text_layout_details.editor_style;
21093 let font_id = window.text_system().resolve_font(&style.text.font());
21094 let font_size = style.text.font_size.to_pixels(window.rem_size());
21095 let line_height = style.text.line_height_in_pixels(window.rem_size());
21096 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
21097 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
21098
21099 CharacterDimensions {
21100 em_width,
21101 em_advance,
21102 line_height,
21103 }
21104 }
21105
21106 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
21107 self.load_diff_task.clone()
21108 }
21109
21110 fn read_metadata_from_db(
21111 &mut self,
21112 item_id: u64,
21113 workspace_id: WorkspaceId,
21114 window: &mut Window,
21115 cx: &mut Context<Editor>,
21116 ) {
21117 if self.is_singleton(cx)
21118 && !self.mode.is_minimap()
21119 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
21120 {
21121 let buffer_snapshot = OnceCell::new();
21122
21123 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
21124 if !folds.is_empty() {
21125 let snapshot =
21126 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21127 self.fold_ranges(
21128 folds
21129 .into_iter()
21130 .map(|(start, end)| {
21131 snapshot.clip_offset(start, Bias::Left)
21132 ..snapshot.clip_offset(end, Bias::Right)
21133 })
21134 .collect(),
21135 false,
21136 window,
21137 cx,
21138 );
21139 }
21140 }
21141
21142 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
21143 if !selections.is_empty() {
21144 let snapshot =
21145 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21146 // skip adding the initial selection to selection history
21147 self.selection_history.mode = SelectionHistoryMode::Skipping;
21148 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21149 s.select_ranges(selections.into_iter().map(|(start, end)| {
21150 snapshot.clip_offset(start, Bias::Left)
21151 ..snapshot.clip_offset(end, Bias::Right)
21152 }));
21153 });
21154 self.selection_history.mode = SelectionHistoryMode::Normal;
21155 }
21156 };
21157 }
21158
21159 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
21160 }
21161
21162 fn update_lsp_data(
21163 &mut self,
21164 ignore_cache: bool,
21165 for_buffer: Option<BufferId>,
21166 window: &mut Window,
21167 cx: &mut Context<'_, Self>,
21168 ) {
21169 self.pull_diagnostics(for_buffer, window, cx);
21170 self.refresh_colors(ignore_cache, for_buffer, window, cx);
21171 }
21172}
21173
21174fn vim_enabled(cx: &App) -> bool {
21175 cx.global::<SettingsStore>()
21176 .raw_user_settings()
21177 .get("vim_mode")
21178 == Some(&serde_json::Value::Bool(true))
21179}
21180
21181fn process_completion_for_edit(
21182 completion: &Completion,
21183 intent: CompletionIntent,
21184 buffer: &Entity<Buffer>,
21185 cursor_position: &text::Anchor,
21186 cx: &mut Context<Editor>,
21187) -> CompletionEdit {
21188 let buffer = buffer.read(cx);
21189 let buffer_snapshot = buffer.snapshot();
21190 let (snippet, new_text) = if completion.is_snippet() {
21191 // Workaround for typescript language server issues so that methods don't expand within
21192 // strings and functions with type expressions. The previous point is used because the query
21193 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
21194 let mut snippet_source = completion.new_text.clone();
21195 let mut previous_point = text::ToPoint::to_point(cursor_position, buffer);
21196 previous_point.column = previous_point.column.saturating_sub(1);
21197 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point) {
21198 if scope.prefers_label_for_snippet_in_completion() {
21199 if let Some(label) = completion.label() {
21200 if matches!(
21201 completion.kind(),
21202 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
21203 ) {
21204 snippet_source = label;
21205 }
21206 }
21207 }
21208 }
21209 match Snippet::parse(&snippet_source).log_err() {
21210 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
21211 None => (None, completion.new_text.clone()),
21212 }
21213 } else {
21214 (None, completion.new_text.clone())
21215 };
21216
21217 let mut range_to_replace = {
21218 let replace_range = &completion.replace_range;
21219 if let CompletionSource::Lsp {
21220 insert_range: Some(insert_range),
21221 ..
21222 } = &completion.source
21223 {
21224 debug_assert_eq!(
21225 insert_range.start, replace_range.start,
21226 "insert_range and replace_range should start at the same position"
21227 );
21228 debug_assert!(
21229 insert_range
21230 .start
21231 .cmp(&cursor_position, &buffer_snapshot)
21232 .is_le(),
21233 "insert_range should start before or at cursor position"
21234 );
21235 debug_assert!(
21236 replace_range
21237 .start
21238 .cmp(&cursor_position, &buffer_snapshot)
21239 .is_le(),
21240 "replace_range should start before or at cursor position"
21241 );
21242
21243 let should_replace = match intent {
21244 CompletionIntent::CompleteWithInsert => false,
21245 CompletionIntent::CompleteWithReplace => true,
21246 CompletionIntent::Complete | CompletionIntent::Compose => {
21247 let insert_mode =
21248 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
21249 .completions
21250 .lsp_insert_mode;
21251 match insert_mode {
21252 LspInsertMode::Insert => false,
21253 LspInsertMode::Replace => true,
21254 LspInsertMode::ReplaceSubsequence => {
21255 let mut text_to_replace = buffer.chars_for_range(
21256 buffer.anchor_before(replace_range.start)
21257 ..buffer.anchor_after(replace_range.end),
21258 );
21259 let mut current_needle = text_to_replace.next();
21260 for haystack_ch in completion.label.text.chars() {
21261 if let Some(needle_ch) = current_needle {
21262 if haystack_ch.eq_ignore_ascii_case(&needle_ch) {
21263 current_needle = text_to_replace.next();
21264 }
21265 }
21266 }
21267 current_needle.is_none()
21268 }
21269 LspInsertMode::ReplaceSuffix => {
21270 if replace_range
21271 .end
21272 .cmp(&cursor_position, &buffer_snapshot)
21273 .is_gt()
21274 {
21275 let range_after_cursor = *cursor_position..replace_range.end;
21276 let text_after_cursor = buffer
21277 .text_for_range(
21278 buffer.anchor_before(range_after_cursor.start)
21279 ..buffer.anchor_after(range_after_cursor.end),
21280 )
21281 .collect::<String>()
21282 .to_ascii_lowercase();
21283 completion
21284 .label
21285 .text
21286 .to_ascii_lowercase()
21287 .ends_with(&text_after_cursor)
21288 } else {
21289 true
21290 }
21291 }
21292 }
21293 }
21294 };
21295
21296 if should_replace {
21297 replace_range.clone()
21298 } else {
21299 insert_range.clone()
21300 }
21301 } else {
21302 replace_range.clone()
21303 }
21304 };
21305
21306 if range_to_replace
21307 .end
21308 .cmp(&cursor_position, &buffer_snapshot)
21309 .is_lt()
21310 {
21311 range_to_replace.end = *cursor_position;
21312 }
21313
21314 CompletionEdit {
21315 new_text,
21316 replace_range: range_to_replace.to_offset(&buffer),
21317 snippet,
21318 }
21319}
21320
21321struct CompletionEdit {
21322 new_text: String,
21323 replace_range: Range<usize>,
21324 snippet: Option<Snippet>,
21325}
21326
21327fn insert_extra_newline_brackets(
21328 buffer: &MultiBufferSnapshot,
21329 range: Range<usize>,
21330 language: &language::LanguageScope,
21331) -> bool {
21332 let leading_whitespace_len = buffer
21333 .reversed_chars_at(range.start)
21334 .take_while(|c| c.is_whitespace() && *c != '\n')
21335 .map(|c| c.len_utf8())
21336 .sum::<usize>();
21337 let trailing_whitespace_len = buffer
21338 .chars_at(range.end)
21339 .take_while(|c| c.is_whitespace() && *c != '\n')
21340 .map(|c| c.len_utf8())
21341 .sum::<usize>();
21342 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
21343
21344 language.brackets().any(|(pair, enabled)| {
21345 let pair_start = pair.start.trim_end();
21346 let pair_end = pair.end.trim_start();
21347
21348 enabled
21349 && pair.newline
21350 && buffer.contains_str_at(range.end, pair_end)
21351 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
21352 })
21353}
21354
21355fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
21356 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
21357 [(buffer, range, _)] => (*buffer, range.clone()),
21358 _ => return false,
21359 };
21360 let pair = {
21361 let mut result: Option<BracketMatch> = None;
21362
21363 for pair in buffer
21364 .all_bracket_ranges(range.clone())
21365 .filter(move |pair| {
21366 pair.open_range.start <= range.start && pair.close_range.end >= range.end
21367 })
21368 {
21369 let len = pair.close_range.end - pair.open_range.start;
21370
21371 if let Some(existing) = &result {
21372 let existing_len = existing.close_range.end - existing.open_range.start;
21373 if len > existing_len {
21374 continue;
21375 }
21376 }
21377
21378 result = Some(pair);
21379 }
21380
21381 result
21382 };
21383 let Some(pair) = pair else {
21384 return false;
21385 };
21386 pair.newline_only
21387 && buffer
21388 .chars_for_range(pair.open_range.end..range.start)
21389 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
21390 .all(|c| c.is_whitespace() && c != '\n')
21391}
21392
21393fn update_uncommitted_diff_for_buffer(
21394 editor: Entity<Editor>,
21395 project: &Entity<Project>,
21396 buffers: impl IntoIterator<Item = Entity<Buffer>>,
21397 buffer: Entity<MultiBuffer>,
21398 cx: &mut App,
21399) -> Task<()> {
21400 let mut tasks = Vec::new();
21401 project.update(cx, |project, cx| {
21402 for buffer in buffers {
21403 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
21404 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
21405 }
21406 }
21407 });
21408 cx.spawn(async move |cx| {
21409 let diffs = future::join_all(tasks).await;
21410 if editor
21411 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
21412 .unwrap_or(false)
21413 {
21414 return;
21415 }
21416
21417 buffer
21418 .update(cx, |buffer, cx| {
21419 for diff in diffs.into_iter().flatten() {
21420 buffer.add_diff(diff, cx);
21421 }
21422 })
21423 .ok();
21424 })
21425}
21426
21427fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
21428 let tab_size = tab_size.get() as usize;
21429 let mut width = offset;
21430
21431 for ch in text.chars() {
21432 width += if ch == '\t' {
21433 tab_size - (width % tab_size)
21434 } else {
21435 1
21436 };
21437 }
21438
21439 width - offset
21440}
21441
21442#[cfg(test)]
21443mod tests {
21444 use super::*;
21445
21446 #[test]
21447 fn test_string_size_with_expanded_tabs() {
21448 let nz = |val| NonZeroU32::new(val).unwrap();
21449 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
21450 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
21451 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
21452 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
21453 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
21454 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
21455 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
21456 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
21457 }
21458}
21459
21460/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
21461struct WordBreakingTokenizer<'a> {
21462 input: &'a str,
21463}
21464
21465impl<'a> WordBreakingTokenizer<'a> {
21466 fn new(input: &'a str) -> Self {
21467 Self { input }
21468 }
21469}
21470
21471fn is_char_ideographic(ch: char) -> bool {
21472 use unicode_script::Script::*;
21473 use unicode_script::UnicodeScript;
21474 matches!(ch.script(), Han | Tangut | Yi)
21475}
21476
21477fn is_grapheme_ideographic(text: &str) -> bool {
21478 text.chars().any(is_char_ideographic)
21479}
21480
21481fn is_grapheme_whitespace(text: &str) -> bool {
21482 text.chars().any(|x| x.is_whitespace())
21483}
21484
21485fn should_stay_with_preceding_ideograph(text: &str) -> bool {
21486 text.chars().next().map_or(false, |ch| {
21487 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
21488 })
21489}
21490
21491#[derive(PartialEq, Eq, Debug, Clone, Copy)]
21492enum WordBreakToken<'a> {
21493 Word { token: &'a str, grapheme_len: usize },
21494 InlineWhitespace { token: &'a str, grapheme_len: usize },
21495 Newline,
21496}
21497
21498impl<'a> Iterator for WordBreakingTokenizer<'a> {
21499 /// Yields a span, the count of graphemes in the token, and whether it was
21500 /// whitespace. Note that it also breaks at word boundaries.
21501 type Item = WordBreakToken<'a>;
21502
21503 fn next(&mut self) -> Option<Self::Item> {
21504 use unicode_segmentation::UnicodeSegmentation;
21505 if self.input.is_empty() {
21506 return None;
21507 }
21508
21509 let mut iter = self.input.graphemes(true).peekable();
21510 let mut offset = 0;
21511 let mut grapheme_len = 0;
21512 if let Some(first_grapheme) = iter.next() {
21513 let is_newline = first_grapheme == "\n";
21514 let is_whitespace = is_grapheme_whitespace(first_grapheme);
21515 offset += first_grapheme.len();
21516 grapheme_len += 1;
21517 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
21518 if let Some(grapheme) = iter.peek().copied() {
21519 if should_stay_with_preceding_ideograph(grapheme) {
21520 offset += grapheme.len();
21521 grapheme_len += 1;
21522 }
21523 }
21524 } else {
21525 let mut words = self.input[offset..].split_word_bound_indices().peekable();
21526 let mut next_word_bound = words.peek().copied();
21527 if next_word_bound.map_or(false, |(i, _)| i == 0) {
21528 next_word_bound = words.next();
21529 }
21530 while let Some(grapheme) = iter.peek().copied() {
21531 if next_word_bound.map_or(false, |(i, _)| i == offset) {
21532 break;
21533 };
21534 if is_grapheme_whitespace(grapheme) != is_whitespace
21535 || (grapheme == "\n") != is_newline
21536 {
21537 break;
21538 };
21539 offset += grapheme.len();
21540 grapheme_len += 1;
21541 iter.next();
21542 }
21543 }
21544 let token = &self.input[..offset];
21545 self.input = &self.input[offset..];
21546 if token == "\n" {
21547 Some(WordBreakToken::Newline)
21548 } else if is_whitespace {
21549 Some(WordBreakToken::InlineWhitespace {
21550 token,
21551 grapheme_len,
21552 })
21553 } else {
21554 Some(WordBreakToken::Word {
21555 token,
21556 grapheme_len,
21557 })
21558 }
21559 } else {
21560 None
21561 }
21562 }
21563}
21564
21565#[test]
21566fn test_word_breaking_tokenizer() {
21567 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
21568 ("", &[]),
21569 (" ", &[whitespace(" ", 2)]),
21570 ("Ʒ", &[word("Ʒ", 1)]),
21571 ("Ǽ", &[word("Ǽ", 1)]),
21572 ("⋑", &[word("⋑", 1)]),
21573 ("⋑⋑", &[word("⋑⋑", 2)]),
21574 (
21575 "原理,进而",
21576 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
21577 ),
21578 (
21579 "hello world",
21580 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
21581 ),
21582 (
21583 "hello, world",
21584 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
21585 ),
21586 (
21587 " hello world",
21588 &[
21589 whitespace(" ", 2),
21590 word("hello", 5),
21591 whitespace(" ", 1),
21592 word("world", 5),
21593 ],
21594 ),
21595 (
21596 "这是什么 \n 钢笔",
21597 &[
21598 word("这", 1),
21599 word("是", 1),
21600 word("什", 1),
21601 word("么", 1),
21602 whitespace(" ", 1),
21603 newline(),
21604 whitespace(" ", 1),
21605 word("钢", 1),
21606 word("笔", 1),
21607 ],
21608 ),
21609 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
21610 ];
21611
21612 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21613 WordBreakToken::Word {
21614 token,
21615 grapheme_len,
21616 }
21617 }
21618
21619 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21620 WordBreakToken::InlineWhitespace {
21621 token,
21622 grapheme_len,
21623 }
21624 }
21625
21626 fn newline() -> WordBreakToken<'static> {
21627 WordBreakToken::Newline
21628 }
21629
21630 for (input, result) in tests {
21631 assert_eq!(
21632 WordBreakingTokenizer::new(input)
21633 .collect::<Vec<_>>()
21634 .as_slice(),
21635 *result,
21636 );
21637 }
21638}
21639
21640fn wrap_with_prefix(
21641 first_line_prefix: String,
21642 subsequent_lines_prefix: String,
21643 unwrapped_text: String,
21644 wrap_column: usize,
21645 tab_size: NonZeroU32,
21646 preserve_existing_whitespace: bool,
21647) -> String {
21648 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
21649 let subsequent_lines_prefix_len =
21650 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
21651 let mut wrapped_text = String::new();
21652 let mut current_line = first_line_prefix.clone();
21653 let mut is_first_line = true;
21654
21655 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
21656 let mut current_line_len = first_line_prefix_len;
21657 let mut in_whitespace = false;
21658 for token in tokenizer {
21659 let have_preceding_whitespace = in_whitespace;
21660 match token {
21661 WordBreakToken::Word {
21662 token,
21663 grapheme_len,
21664 } => {
21665 in_whitespace = false;
21666 let current_prefix_len = if is_first_line {
21667 first_line_prefix_len
21668 } else {
21669 subsequent_lines_prefix_len
21670 };
21671 if current_line_len + grapheme_len > wrap_column
21672 && current_line_len != current_prefix_len
21673 {
21674 wrapped_text.push_str(current_line.trim_end());
21675 wrapped_text.push('\n');
21676 is_first_line = false;
21677 current_line = subsequent_lines_prefix.clone();
21678 current_line_len = subsequent_lines_prefix_len;
21679 }
21680 current_line.push_str(token);
21681 current_line_len += grapheme_len;
21682 }
21683 WordBreakToken::InlineWhitespace {
21684 mut token,
21685 mut grapheme_len,
21686 } => {
21687 in_whitespace = true;
21688 if have_preceding_whitespace && !preserve_existing_whitespace {
21689 continue;
21690 }
21691 if !preserve_existing_whitespace {
21692 token = " ";
21693 grapheme_len = 1;
21694 }
21695 let current_prefix_len = if is_first_line {
21696 first_line_prefix_len
21697 } else {
21698 subsequent_lines_prefix_len
21699 };
21700 if current_line_len + grapheme_len > wrap_column {
21701 wrapped_text.push_str(current_line.trim_end());
21702 wrapped_text.push('\n');
21703 is_first_line = false;
21704 current_line = subsequent_lines_prefix.clone();
21705 current_line_len = subsequent_lines_prefix_len;
21706 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
21707 current_line.push_str(token);
21708 current_line_len += grapheme_len;
21709 }
21710 }
21711 WordBreakToken::Newline => {
21712 in_whitespace = true;
21713 let current_prefix_len = if is_first_line {
21714 first_line_prefix_len
21715 } else {
21716 subsequent_lines_prefix_len
21717 };
21718 if preserve_existing_whitespace {
21719 wrapped_text.push_str(current_line.trim_end());
21720 wrapped_text.push('\n');
21721 is_first_line = false;
21722 current_line = subsequent_lines_prefix.clone();
21723 current_line_len = subsequent_lines_prefix_len;
21724 } else if have_preceding_whitespace {
21725 continue;
21726 } else if current_line_len + 1 > wrap_column
21727 && current_line_len != current_prefix_len
21728 {
21729 wrapped_text.push_str(current_line.trim_end());
21730 wrapped_text.push('\n');
21731 is_first_line = false;
21732 current_line = subsequent_lines_prefix.clone();
21733 current_line_len = subsequent_lines_prefix_len;
21734 } else if current_line_len != current_prefix_len {
21735 current_line.push(' ');
21736 current_line_len += 1;
21737 }
21738 }
21739 }
21740 }
21741
21742 if !current_line.is_empty() {
21743 wrapped_text.push_str(¤t_line);
21744 }
21745 wrapped_text
21746}
21747
21748#[test]
21749fn test_wrap_with_prefix() {
21750 assert_eq!(
21751 wrap_with_prefix(
21752 "# ".to_string(),
21753 "# ".to_string(),
21754 "abcdefg".to_string(),
21755 4,
21756 NonZeroU32::new(4).unwrap(),
21757 false,
21758 ),
21759 "# abcdefg"
21760 );
21761 assert_eq!(
21762 wrap_with_prefix(
21763 "".to_string(),
21764 "".to_string(),
21765 "\thello world".to_string(),
21766 8,
21767 NonZeroU32::new(4).unwrap(),
21768 false,
21769 ),
21770 "hello\nworld"
21771 );
21772 assert_eq!(
21773 wrap_with_prefix(
21774 "// ".to_string(),
21775 "// ".to_string(),
21776 "xx \nyy zz aa bb cc".to_string(),
21777 12,
21778 NonZeroU32::new(4).unwrap(),
21779 false,
21780 ),
21781 "// xx yy zz\n// aa bb cc"
21782 );
21783 assert_eq!(
21784 wrap_with_prefix(
21785 String::new(),
21786 String::new(),
21787 "这是什么 \n 钢笔".to_string(),
21788 3,
21789 NonZeroU32::new(4).unwrap(),
21790 false,
21791 ),
21792 "这是什\n么 钢\n笔"
21793 );
21794}
21795
21796pub trait CollaborationHub {
21797 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
21798 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
21799 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
21800}
21801
21802impl CollaborationHub for Entity<Project> {
21803 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
21804 self.read(cx).collaborators()
21805 }
21806
21807 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
21808 self.read(cx).user_store().read(cx).participant_indices()
21809 }
21810
21811 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
21812 let this = self.read(cx);
21813 let user_ids = this.collaborators().values().map(|c| c.user_id);
21814 this.user_store().read(cx).participant_names(user_ids, cx)
21815 }
21816}
21817
21818pub trait SemanticsProvider {
21819 fn hover(
21820 &self,
21821 buffer: &Entity<Buffer>,
21822 position: text::Anchor,
21823 cx: &mut App,
21824 ) -> Option<Task<Vec<project::Hover>>>;
21825
21826 fn inline_values(
21827 &self,
21828 buffer_handle: Entity<Buffer>,
21829 range: Range<text::Anchor>,
21830 cx: &mut App,
21831 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21832
21833 fn inlay_hints(
21834 &self,
21835 buffer_handle: Entity<Buffer>,
21836 range: Range<text::Anchor>,
21837 cx: &mut App,
21838 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21839
21840 fn resolve_inlay_hint(
21841 &self,
21842 hint: InlayHint,
21843 buffer_handle: Entity<Buffer>,
21844 server_id: LanguageServerId,
21845 cx: &mut App,
21846 ) -> Option<Task<anyhow::Result<InlayHint>>>;
21847
21848 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
21849
21850 fn document_highlights(
21851 &self,
21852 buffer: &Entity<Buffer>,
21853 position: text::Anchor,
21854 cx: &mut App,
21855 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
21856
21857 fn definitions(
21858 &self,
21859 buffer: &Entity<Buffer>,
21860 position: text::Anchor,
21861 kind: GotoDefinitionKind,
21862 cx: &mut App,
21863 ) -> Option<Task<Result<Vec<LocationLink>>>>;
21864
21865 fn range_for_rename(
21866 &self,
21867 buffer: &Entity<Buffer>,
21868 position: text::Anchor,
21869 cx: &mut App,
21870 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
21871
21872 fn perform_rename(
21873 &self,
21874 buffer: &Entity<Buffer>,
21875 position: text::Anchor,
21876 new_name: String,
21877 cx: &mut App,
21878 ) -> Option<Task<Result<ProjectTransaction>>>;
21879}
21880
21881pub trait CompletionProvider {
21882 fn completions(
21883 &self,
21884 excerpt_id: ExcerptId,
21885 buffer: &Entity<Buffer>,
21886 buffer_position: text::Anchor,
21887 trigger: CompletionContext,
21888 window: &mut Window,
21889 cx: &mut Context<Editor>,
21890 ) -> Task<Result<Vec<CompletionResponse>>>;
21891
21892 fn resolve_completions(
21893 &self,
21894 _buffer: Entity<Buffer>,
21895 _completion_indices: Vec<usize>,
21896 _completions: Rc<RefCell<Box<[Completion]>>>,
21897 _cx: &mut Context<Editor>,
21898 ) -> Task<Result<bool>> {
21899 Task::ready(Ok(false))
21900 }
21901
21902 fn apply_additional_edits_for_completion(
21903 &self,
21904 _buffer: Entity<Buffer>,
21905 _completions: Rc<RefCell<Box<[Completion]>>>,
21906 _completion_index: usize,
21907 _push_to_history: bool,
21908 _cx: &mut Context<Editor>,
21909 ) -> Task<Result<Option<language::Transaction>>> {
21910 Task::ready(Ok(None))
21911 }
21912
21913 fn is_completion_trigger(
21914 &self,
21915 buffer: &Entity<Buffer>,
21916 position: language::Anchor,
21917 text: &str,
21918 trigger_in_words: bool,
21919 menu_is_open: bool,
21920 cx: &mut Context<Editor>,
21921 ) -> bool;
21922
21923 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
21924
21925 fn sort_completions(&self) -> bool {
21926 true
21927 }
21928
21929 fn filter_completions(&self) -> bool {
21930 true
21931 }
21932}
21933
21934pub trait CodeActionProvider {
21935 fn id(&self) -> Arc<str>;
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
21945 fn apply_code_action(
21946 &self,
21947 buffer_handle: Entity<Buffer>,
21948 action: CodeAction,
21949 excerpt_id: ExcerptId,
21950 push_to_history: bool,
21951 window: &mut Window,
21952 cx: &mut App,
21953 ) -> Task<Result<ProjectTransaction>>;
21954}
21955
21956impl CodeActionProvider for Entity<Project> {
21957 fn id(&self) -> Arc<str> {
21958 "project".into()
21959 }
21960
21961 fn code_actions(
21962 &self,
21963 buffer: &Entity<Buffer>,
21964 range: Range<text::Anchor>,
21965 _window: &mut Window,
21966 cx: &mut App,
21967 ) -> Task<Result<Vec<CodeAction>>> {
21968 self.update(cx, |project, cx| {
21969 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
21970 let code_actions = project.code_actions(buffer, range, None, cx);
21971 cx.background_spawn(async move {
21972 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
21973 Ok(code_lens_actions
21974 .context("code lens fetch")?
21975 .into_iter()
21976 .chain(code_actions.context("code action fetch")?)
21977 .collect())
21978 })
21979 })
21980 }
21981
21982 fn apply_code_action(
21983 &self,
21984 buffer_handle: Entity<Buffer>,
21985 action: CodeAction,
21986 _excerpt_id: ExcerptId,
21987 push_to_history: bool,
21988 _window: &mut Window,
21989 cx: &mut App,
21990 ) -> Task<Result<ProjectTransaction>> {
21991 self.update(cx, |project, cx| {
21992 project.apply_code_action(buffer_handle, action, push_to_history, cx)
21993 })
21994 }
21995}
21996
21997fn snippet_completions(
21998 project: &Project,
21999 buffer: &Entity<Buffer>,
22000 buffer_position: text::Anchor,
22001 cx: &mut App,
22002) -> Task<Result<CompletionResponse>> {
22003 let languages = buffer.read(cx).languages_at(buffer_position);
22004 let snippet_store = project.snippets().read(cx);
22005
22006 let scopes: Vec<_> = languages
22007 .iter()
22008 .filter_map(|language| {
22009 let language_name = language.lsp_id();
22010 let snippets = snippet_store.snippets_for(Some(language_name), cx);
22011
22012 if snippets.is_empty() {
22013 None
22014 } else {
22015 Some((language.default_scope(), snippets))
22016 }
22017 })
22018 .collect();
22019
22020 if scopes.is_empty() {
22021 return Task::ready(Ok(CompletionResponse {
22022 completions: vec![],
22023 is_incomplete: false,
22024 }));
22025 }
22026
22027 let snapshot = buffer.read(cx).text_snapshot();
22028 let chars: String = snapshot
22029 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
22030 .collect();
22031 let executor = cx.background_executor().clone();
22032
22033 cx.background_spawn(async move {
22034 let mut is_incomplete = false;
22035 let mut completions: Vec<Completion> = Vec::new();
22036 for (scope, snippets) in scopes.into_iter() {
22037 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
22038 let mut last_word = chars
22039 .chars()
22040 .take_while(|c| classifier.is_word(*c))
22041 .collect::<String>();
22042 last_word = last_word.chars().rev().collect();
22043
22044 if last_word.is_empty() {
22045 return Ok(CompletionResponse {
22046 completions: vec![],
22047 is_incomplete: true,
22048 });
22049 }
22050
22051 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
22052 let to_lsp = |point: &text::Anchor| {
22053 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
22054 point_to_lsp(end)
22055 };
22056 let lsp_end = to_lsp(&buffer_position);
22057
22058 let candidates = snippets
22059 .iter()
22060 .enumerate()
22061 .flat_map(|(ix, snippet)| {
22062 snippet
22063 .prefix
22064 .iter()
22065 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
22066 })
22067 .collect::<Vec<StringMatchCandidate>>();
22068
22069 const MAX_RESULTS: usize = 100;
22070 let mut matches = fuzzy::match_strings(
22071 &candidates,
22072 &last_word,
22073 last_word.chars().any(|c| c.is_uppercase()),
22074 true,
22075 MAX_RESULTS,
22076 &Default::default(),
22077 executor.clone(),
22078 )
22079 .await;
22080
22081 if matches.len() >= MAX_RESULTS {
22082 is_incomplete = true;
22083 }
22084
22085 // Remove all candidates where the query's start does not match the start of any word in the candidate
22086 if let Some(query_start) = last_word.chars().next() {
22087 matches.retain(|string_match| {
22088 split_words(&string_match.string).any(|word| {
22089 // Check that the first codepoint of the word as lowercase matches the first
22090 // codepoint of the query as lowercase
22091 word.chars()
22092 .flat_map(|codepoint| codepoint.to_lowercase())
22093 .zip(query_start.to_lowercase())
22094 .all(|(word_cp, query_cp)| word_cp == query_cp)
22095 })
22096 });
22097 }
22098
22099 let matched_strings = matches
22100 .into_iter()
22101 .map(|m| m.string)
22102 .collect::<HashSet<_>>();
22103
22104 completions.extend(snippets.iter().filter_map(|snippet| {
22105 let matching_prefix = snippet
22106 .prefix
22107 .iter()
22108 .find(|prefix| matched_strings.contains(*prefix))?;
22109 let start = as_offset - last_word.len();
22110 let start = snapshot.anchor_before(start);
22111 let range = start..buffer_position;
22112 let lsp_start = to_lsp(&start);
22113 let lsp_range = lsp::Range {
22114 start: lsp_start,
22115 end: lsp_end,
22116 };
22117 Some(Completion {
22118 replace_range: range,
22119 new_text: snippet.body.clone(),
22120 source: CompletionSource::Lsp {
22121 insert_range: None,
22122 server_id: LanguageServerId(usize::MAX),
22123 resolved: true,
22124 lsp_completion: Box::new(lsp::CompletionItem {
22125 label: snippet.prefix.first().unwrap().clone(),
22126 kind: Some(CompletionItemKind::SNIPPET),
22127 label_details: snippet.description.as_ref().map(|description| {
22128 lsp::CompletionItemLabelDetails {
22129 detail: Some(description.clone()),
22130 description: None,
22131 }
22132 }),
22133 insert_text_format: Some(InsertTextFormat::SNIPPET),
22134 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22135 lsp::InsertReplaceEdit {
22136 new_text: snippet.body.clone(),
22137 insert: lsp_range,
22138 replace: lsp_range,
22139 },
22140 )),
22141 filter_text: Some(snippet.body.clone()),
22142 sort_text: Some(char::MAX.to_string()),
22143 ..lsp::CompletionItem::default()
22144 }),
22145 lsp_defaults: None,
22146 },
22147 label: CodeLabel {
22148 text: matching_prefix.clone(),
22149 runs: Vec::new(),
22150 filter_range: 0..matching_prefix.len(),
22151 },
22152 icon_path: None,
22153 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
22154 single_line: snippet.name.clone().into(),
22155 plain_text: snippet
22156 .description
22157 .clone()
22158 .map(|description| description.into()),
22159 }),
22160 insert_text_mode: None,
22161 confirm: None,
22162 })
22163 }))
22164 }
22165
22166 Ok(CompletionResponse {
22167 completions,
22168 is_incomplete,
22169 })
22170 })
22171}
22172
22173impl CompletionProvider for Entity<Project> {
22174 fn completions(
22175 &self,
22176 _excerpt_id: ExcerptId,
22177 buffer: &Entity<Buffer>,
22178 buffer_position: text::Anchor,
22179 options: CompletionContext,
22180 _window: &mut Window,
22181 cx: &mut Context<Editor>,
22182 ) -> Task<Result<Vec<CompletionResponse>>> {
22183 self.update(cx, |project, cx| {
22184 let snippets = snippet_completions(project, buffer, buffer_position, cx);
22185 let project_completions = project.completions(buffer, buffer_position, options, cx);
22186 cx.background_spawn(async move {
22187 let mut responses = project_completions.await?;
22188 let snippets = snippets.await?;
22189 if !snippets.completions.is_empty() {
22190 responses.push(snippets);
22191 }
22192 Ok(responses)
22193 })
22194 })
22195 }
22196
22197 fn resolve_completions(
22198 &self,
22199 buffer: Entity<Buffer>,
22200 completion_indices: Vec<usize>,
22201 completions: Rc<RefCell<Box<[Completion]>>>,
22202 cx: &mut Context<Editor>,
22203 ) -> Task<Result<bool>> {
22204 self.update(cx, |project, cx| {
22205 project.lsp_store().update(cx, |lsp_store, cx| {
22206 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
22207 })
22208 })
22209 }
22210
22211 fn apply_additional_edits_for_completion(
22212 &self,
22213 buffer: Entity<Buffer>,
22214 completions: Rc<RefCell<Box<[Completion]>>>,
22215 completion_index: usize,
22216 push_to_history: bool,
22217 cx: &mut Context<Editor>,
22218 ) -> Task<Result<Option<language::Transaction>>> {
22219 self.update(cx, |project, cx| {
22220 project.lsp_store().update(cx, |lsp_store, cx| {
22221 lsp_store.apply_additional_edits_for_completion(
22222 buffer,
22223 completions,
22224 completion_index,
22225 push_to_history,
22226 cx,
22227 )
22228 })
22229 })
22230 }
22231
22232 fn is_completion_trigger(
22233 &self,
22234 buffer: &Entity<Buffer>,
22235 position: language::Anchor,
22236 text: &str,
22237 trigger_in_words: bool,
22238 menu_is_open: bool,
22239 cx: &mut Context<Editor>,
22240 ) -> bool {
22241 let mut chars = text.chars();
22242 let char = if let Some(char) = chars.next() {
22243 char
22244 } else {
22245 return false;
22246 };
22247 if chars.next().is_some() {
22248 return false;
22249 }
22250
22251 let buffer = buffer.read(cx);
22252 let snapshot = buffer.snapshot();
22253 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
22254 return false;
22255 }
22256 let classifier = snapshot.char_classifier_at(position).for_completion(true);
22257 if trigger_in_words && classifier.is_word(char) {
22258 return true;
22259 }
22260
22261 buffer.completion_triggers().contains(text)
22262 }
22263}
22264
22265impl SemanticsProvider for Entity<Project> {
22266 fn hover(
22267 &self,
22268 buffer: &Entity<Buffer>,
22269 position: text::Anchor,
22270 cx: &mut App,
22271 ) -> Option<Task<Vec<project::Hover>>> {
22272 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
22273 }
22274
22275 fn document_highlights(
22276 &self,
22277 buffer: &Entity<Buffer>,
22278 position: text::Anchor,
22279 cx: &mut App,
22280 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
22281 Some(self.update(cx, |project, cx| {
22282 project.document_highlights(buffer, position, cx)
22283 }))
22284 }
22285
22286 fn definitions(
22287 &self,
22288 buffer: &Entity<Buffer>,
22289 position: text::Anchor,
22290 kind: GotoDefinitionKind,
22291 cx: &mut App,
22292 ) -> Option<Task<Result<Vec<LocationLink>>>> {
22293 Some(self.update(cx, |project, cx| match kind {
22294 GotoDefinitionKind::Symbol => project.definitions(&buffer, position, cx),
22295 GotoDefinitionKind::Declaration => project.declarations(&buffer, position, cx),
22296 GotoDefinitionKind::Type => project.type_definitions(&buffer, position, cx),
22297 GotoDefinitionKind::Implementation => project.implementations(&buffer, position, cx),
22298 }))
22299 }
22300
22301 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
22302 self.update(cx, |project, cx| {
22303 if project
22304 .active_debug_session(cx)
22305 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
22306 {
22307 return true;
22308 }
22309
22310 buffer.update(cx, |buffer, cx| {
22311 project.any_language_server_supports_inlay_hints(buffer, cx)
22312 })
22313 })
22314 }
22315
22316 fn inline_values(
22317 &self,
22318 buffer_handle: Entity<Buffer>,
22319 range: Range<text::Anchor>,
22320 cx: &mut App,
22321 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22322 self.update(cx, |project, cx| {
22323 let (session, active_stack_frame) = project.active_debug_session(cx)?;
22324
22325 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
22326 })
22327 }
22328
22329 fn inlay_hints(
22330 &self,
22331 buffer_handle: Entity<Buffer>,
22332 range: Range<text::Anchor>,
22333 cx: &mut App,
22334 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22335 Some(self.update(cx, |project, cx| {
22336 project.inlay_hints(buffer_handle, range, cx)
22337 }))
22338 }
22339
22340 fn resolve_inlay_hint(
22341 &self,
22342 hint: InlayHint,
22343 buffer_handle: Entity<Buffer>,
22344 server_id: LanguageServerId,
22345 cx: &mut App,
22346 ) -> Option<Task<anyhow::Result<InlayHint>>> {
22347 Some(self.update(cx, |project, cx| {
22348 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
22349 }))
22350 }
22351
22352 fn range_for_rename(
22353 &self,
22354 buffer: &Entity<Buffer>,
22355 position: text::Anchor,
22356 cx: &mut App,
22357 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
22358 Some(self.update(cx, |project, cx| {
22359 let buffer = buffer.clone();
22360 let task = project.prepare_rename(buffer.clone(), position, cx);
22361 cx.spawn(async move |_, cx| {
22362 Ok(match task.await? {
22363 PrepareRenameResponse::Success(range) => Some(range),
22364 PrepareRenameResponse::InvalidPosition => None,
22365 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
22366 // Fallback on using TreeSitter info to determine identifier range
22367 buffer.read_with(cx, |buffer, _| {
22368 let snapshot = buffer.snapshot();
22369 let (range, kind) = snapshot.surrounding_word(position, false);
22370 if kind != Some(CharKind::Word) {
22371 return None;
22372 }
22373 Some(
22374 snapshot.anchor_before(range.start)
22375 ..snapshot.anchor_after(range.end),
22376 )
22377 })?
22378 }
22379 })
22380 })
22381 }))
22382 }
22383
22384 fn perform_rename(
22385 &self,
22386 buffer: &Entity<Buffer>,
22387 position: text::Anchor,
22388 new_name: String,
22389 cx: &mut App,
22390 ) -> Option<Task<Result<ProjectTransaction>>> {
22391 Some(self.update(cx, |project, cx| {
22392 project.perform_rename(buffer.clone(), position, new_name, cx)
22393 }))
22394 }
22395}
22396
22397fn inlay_hint_settings(
22398 location: Anchor,
22399 snapshot: &MultiBufferSnapshot,
22400 cx: &mut Context<Editor>,
22401) -> InlayHintSettings {
22402 let file = snapshot.file_at(location);
22403 let language = snapshot.language_at(location).map(|l| l.name());
22404 language_settings(language, file, cx).inlay_hints
22405}
22406
22407fn consume_contiguous_rows(
22408 contiguous_row_selections: &mut Vec<Selection<Point>>,
22409 selection: &Selection<Point>,
22410 display_map: &DisplaySnapshot,
22411 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
22412) -> (MultiBufferRow, MultiBufferRow) {
22413 contiguous_row_selections.push(selection.clone());
22414 let start_row = starting_row(selection, display_map);
22415 let mut end_row = ending_row(selection, display_map);
22416
22417 while let Some(next_selection) = selections.peek() {
22418 if next_selection.start.row <= end_row.0 {
22419 end_row = ending_row(next_selection, display_map);
22420 contiguous_row_selections.push(selections.next().unwrap().clone());
22421 } else {
22422 break;
22423 }
22424 }
22425 (start_row, end_row)
22426}
22427
22428fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22429 if selection.start.column > 0 {
22430 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
22431 } else {
22432 MultiBufferRow(selection.start.row)
22433 }
22434}
22435
22436fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22437 if next_selection.end.column > 0 || next_selection.is_empty() {
22438 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
22439 } else {
22440 MultiBufferRow(next_selection.end.row)
22441 }
22442}
22443
22444impl EditorSnapshot {
22445 pub fn remote_selections_in_range<'a>(
22446 &'a self,
22447 range: &'a Range<Anchor>,
22448 collaboration_hub: &dyn CollaborationHub,
22449 cx: &'a App,
22450 ) -> impl 'a + Iterator<Item = RemoteSelection> {
22451 let participant_names = collaboration_hub.user_names(cx);
22452 let participant_indices = collaboration_hub.user_participant_indices(cx);
22453 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
22454 let collaborators_by_replica_id = collaborators_by_peer_id
22455 .values()
22456 .map(|collaborator| (collaborator.replica_id, collaborator))
22457 .collect::<HashMap<_, _>>();
22458 self.buffer_snapshot
22459 .selections_in_range(range, false)
22460 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
22461 if replica_id == AGENT_REPLICA_ID {
22462 Some(RemoteSelection {
22463 replica_id,
22464 selection,
22465 cursor_shape,
22466 line_mode,
22467 collaborator_id: CollaboratorId::Agent,
22468 user_name: Some("Agent".into()),
22469 color: cx.theme().players().agent(),
22470 })
22471 } else {
22472 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
22473 let participant_index = participant_indices.get(&collaborator.user_id).copied();
22474 let user_name = participant_names.get(&collaborator.user_id).cloned();
22475 Some(RemoteSelection {
22476 replica_id,
22477 selection,
22478 cursor_shape,
22479 line_mode,
22480 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
22481 user_name,
22482 color: if let Some(index) = participant_index {
22483 cx.theme().players().color_for_participant(index.0)
22484 } else {
22485 cx.theme().players().absent()
22486 },
22487 })
22488 }
22489 })
22490 }
22491
22492 pub fn hunks_for_ranges(
22493 &self,
22494 ranges: impl IntoIterator<Item = Range<Point>>,
22495 ) -> Vec<MultiBufferDiffHunk> {
22496 let mut hunks = Vec::new();
22497 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
22498 HashMap::default();
22499 for query_range in ranges {
22500 let query_rows =
22501 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
22502 for hunk in self.buffer_snapshot.diff_hunks_in_range(
22503 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
22504 ) {
22505 // Include deleted hunks that are adjacent to the query range, because
22506 // otherwise they would be missed.
22507 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
22508 if hunk.status().is_deleted() {
22509 intersects_range |= hunk.row_range.start == query_rows.end;
22510 intersects_range |= hunk.row_range.end == query_rows.start;
22511 }
22512 if intersects_range {
22513 if !processed_buffer_rows
22514 .entry(hunk.buffer_id)
22515 .or_default()
22516 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
22517 {
22518 continue;
22519 }
22520 hunks.push(hunk);
22521 }
22522 }
22523 }
22524
22525 hunks
22526 }
22527
22528 fn display_diff_hunks_for_rows<'a>(
22529 &'a self,
22530 display_rows: Range<DisplayRow>,
22531 folded_buffers: &'a HashSet<BufferId>,
22532 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
22533 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
22534 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
22535
22536 self.buffer_snapshot
22537 .diff_hunks_in_range(buffer_start..buffer_end)
22538 .filter_map(|hunk| {
22539 if folded_buffers.contains(&hunk.buffer_id) {
22540 return None;
22541 }
22542
22543 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
22544 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
22545
22546 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
22547 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
22548
22549 let display_hunk = if hunk_display_start.column() != 0 {
22550 DisplayDiffHunk::Folded {
22551 display_row: hunk_display_start.row(),
22552 }
22553 } else {
22554 let mut end_row = hunk_display_end.row();
22555 if hunk_display_end.column() > 0 {
22556 end_row.0 += 1;
22557 }
22558 let is_created_file = hunk.is_created_file();
22559 DisplayDiffHunk::Unfolded {
22560 status: hunk.status(),
22561 diff_base_byte_range: hunk.diff_base_byte_range,
22562 display_row_range: hunk_display_start.row()..end_row,
22563 multi_buffer_range: Anchor::range_in_buffer(
22564 hunk.excerpt_id,
22565 hunk.buffer_id,
22566 hunk.buffer_range,
22567 ),
22568 is_created_file,
22569 }
22570 };
22571
22572 Some(display_hunk)
22573 })
22574 }
22575
22576 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
22577 self.display_snapshot.buffer_snapshot.language_at(position)
22578 }
22579
22580 pub fn is_focused(&self) -> bool {
22581 self.is_focused
22582 }
22583
22584 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
22585 self.placeholder_text.as_ref()
22586 }
22587
22588 pub fn scroll_position(&self) -> gpui::Point<f32> {
22589 self.scroll_anchor.scroll_position(&self.display_snapshot)
22590 }
22591
22592 fn gutter_dimensions(
22593 &self,
22594 font_id: FontId,
22595 font_size: Pixels,
22596 max_line_number_width: Pixels,
22597 cx: &App,
22598 ) -> Option<GutterDimensions> {
22599 if !self.show_gutter {
22600 return None;
22601 }
22602
22603 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
22604 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
22605
22606 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
22607 matches!(
22608 ProjectSettings::get_global(cx).git.git_gutter,
22609 Some(GitGutterSetting::TrackedFiles)
22610 )
22611 });
22612 let gutter_settings = EditorSettings::get_global(cx).gutter;
22613 let show_line_numbers = self
22614 .show_line_numbers
22615 .unwrap_or(gutter_settings.line_numbers);
22616 let line_gutter_width = if show_line_numbers {
22617 // Avoid flicker-like gutter resizes when the line number gains another digit by
22618 // only resizing the gutter on files with > 10**min_line_number_digits lines.
22619 let min_width_for_number_on_gutter =
22620 ch_advance * gutter_settings.min_line_number_digits as f32;
22621 max_line_number_width.max(min_width_for_number_on_gutter)
22622 } else {
22623 0.0.into()
22624 };
22625
22626 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
22627 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
22628
22629 let git_blame_entries_width =
22630 self.git_blame_gutter_max_author_length
22631 .map(|max_author_length| {
22632 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
22633 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
22634
22635 /// The number of characters to dedicate to gaps and margins.
22636 const SPACING_WIDTH: usize = 4;
22637
22638 let max_char_count = max_author_length.min(renderer.max_author_length())
22639 + ::git::SHORT_SHA_LENGTH
22640 + MAX_RELATIVE_TIMESTAMP.len()
22641 + SPACING_WIDTH;
22642
22643 ch_advance * max_char_count
22644 });
22645
22646 let is_singleton = self.buffer_snapshot.is_singleton();
22647
22648 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
22649 left_padding += if !is_singleton {
22650 ch_width * 4.0
22651 } else if show_runnables || show_breakpoints {
22652 ch_width * 3.0
22653 } else if show_git_gutter && show_line_numbers {
22654 ch_width * 2.0
22655 } else if show_git_gutter || show_line_numbers {
22656 ch_width
22657 } else {
22658 px(0.)
22659 };
22660
22661 let shows_folds = is_singleton && gutter_settings.folds;
22662
22663 let right_padding = if shows_folds && show_line_numbers {
22664 ch_width * 4.0
22665 } else if shows_folds || (!is_singleton && show_line_numbers) {
22666 ch_width * 3.0
22667 } else if show_line_numbers {
22668 ch_width
22669 } else {
22670 px(0.)
22671 };
22672
22673 Some(GutterDimensions {
22674 left_padding,
22675 right_padding,
22676 width: line_gutter_width + left_padding + right_padding,
22677 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
22678 git_blame_entries_width,
22679 })
22680 }
22681
22682 pub fn render_crease_toggle(
22683 &self,
22684 buffer_row: MultiBufferRow,
22685 row_contains_cursor: bool,
22686 editor: Entity<Editor>,
22687 window: &mut Window,
22688 cx: &mut App,
22689 ) -> Option<AnyElement> {
22690 let folded = self.is_line_folded(buffer_row);
22691 let mut is_foldable = false;
22692
22693 if let Some(crease) = self
22694 .crease_snapshot
22695 .query_row(buffer_row, &self.buffer_snapshot)
22696 {
22697 is_foldable = true;
22698 match crease {
22699 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
22700 if let Some(render_toggle) = render_toggle {
22701 let toggle_callback =
22702 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
22703 if folded {
22704 editor.update(cx, |editor, cx| {
22705 editor.fold_at(buffer_row, window, cx)
22706 });
22707 } else {
22708 editor.update(cx, |editor, cx| {
22709 editor.unfold_at(buffer_row, window, cx)
22710 });
22711 }
22712 });
22713 return Some((render_toggle)(
22714 buffer_row,
22715 folded,
22716 toggle_callback,
22717 window,
22718 cx,
22719 ));
22720 }
22721 }
22722 }
22723 }
22724
22725 is_foldable |= self.starts_indent(buffer_row);
22726
22727 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
22728 Some(
22729 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
22730 .toggle_state(folded)
22731 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
22732 if folded {
22733 this.unfold_at(buffer_row, window, cx);
22734 } else {
22735 this.fold_at(buffer_row, window, cx);
22736 }
22737 }))
22738 .into_any_element(),
22739 )
22740 } else {
22741 None
22742 }
22743 }
22744
22745 pub fn render_crease_trailer(
22746 &self,
22747 buffer_row: MultiBufferRow,
22748 window: &mut Window,
22749 cx: &mut App,
22750 ) -> Option<AnyElement> {
22751 let folded = self.is_line_folded(buffer_row);
22752 if let Crease::Inline { render_trailer, .. } = self
22753 .crease_snapshot
22754 .query_row(buffer_row, &self.buffer_snapshot)?
22755 {
22756 let render_trailer = render_trailer.as_ref()?;
22757 Some(render_trailer(buffer_row, folded, window, cx))
22758 } else {
22759 None
22760 }
22761 }
22762}
22763
22764impl Deref for EditorSnapshot {
22765 type Target = DisplaySnapshot;
22766
22767 fn deref(&self) -> &Self::Target {
22768 &self.display_snapshot
22769 }
22770}
22771
22772#[derive(Clone, Debug, PartialEq, Eq)]
22773pub enum EditorEvent {
22774 InputIgnored {
22775 text: Arc<str>,
22776 },
22777 InputHandled {
22778 utf16_range_to_replace: Option<Range<isize>>,
22779 text: Arc<str>,
22780 },
22781 ExcerptsAdded {
22782 buffer: Entity<Buffer>,
22783 predecessor: ExcerptId,
22784 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
22785 },
22786 ExcerptsRemoved {
22787 ids: Vec<ExcerptId>,
22788 removed_buffer_ids: Vec<BufferId>,
22789 },
22790 BufferFoldToggled {
22791 ids: Vec<ExcerptId>,
22792 folded: bool,
22793 },
22794 ExcerptsEdited {
22795 ids: Vec<ExcerptId>,
22796 },
22797 ExcerptsExpanded {
22798 ids: Vec<ExcerptId>,
22799 },
22800 BufferEdited,
22801 Edited {
22802 transaction_id: clock::Lamport,
22803 },
22804 Reparsed(BufferId),
22805 Focused,
22806 FocusedIn,
22807 Blurred,
22808 DirtyChanged,
22809 Saved,
22810 TitleChanged,
22811 DiffBaseChanged,
22812 SelectionsChanged {
22813 local: bool,
22814 },
22815 ScrollPositionChanged {
22816 local: bool,
22817 autoscroll: bool,
22818 },
22819 Closed,
22820 TransactionUndone {
22821 transaction_id: clock::Lamport,
22822 },
22823 TransactionBegun {
22824 transaction_id: clock::Lamport,
22825 },
22826 Reloaded,
22827 CursorShapeChanged,
22828 PushedToNavHistory {
22829 anchor: Anchor,
22830 is_deactivate: bool,
22831 },
22832}
22833
22834impl EventEmitter<EditorEvent> for Editor {}
22835
22836impl Focusable for Editor {
22837 fn focus_handle(&self, _cx: &App) -> FocusHandle {
22838 self.focus_handle.clone()
22839 }
22840}
22841
22842impl Render for Editor {
22843 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22844 let settings = ThemeSettings::get_global(cx);
22845
22846 let mut text_style = match self.mode {
22847 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
22848 color: cx.theme().colors().editor_foreground,
22849 font_family: settings.ui_font.family.clone(),
22850 font_features: settings.ui_font.features.clone(),
22851 font_fallbacks: settings.ui_font.fallbacks.clone(),
22852 font_size: rems(0.875).into(),
22853 font_weight: settings.ui_font.weight,
22854 line_height: relative(settings.buffer_line_height.value()),
22855 ..Default::default()
22856 },
22857 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
22858 color: cx.theme().colors().editor_foreground,
22859 font_family: settings.buffer_font.family.clone(),
22860 font_features: settings.buffer_font.features.clone(),
22861 font_fallbacks: settings.buffer_font.fallbacks.clone(),
22862 font_size: settings.buffer_font_size(cx).into(),
22863 font_weight: settings.buffer_font.weight,
22864 line_height: relative(settings.buffer_line_height.value()),
22865 ..Default::default()
22866 },
22867 };
22868 if let Some(text_style_refinement) = &self.text_style_refinement {
22869 text_style.refine(text_style_refinement)
22870 }
22871
22872 let background = match self.mode {
22873 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
22874 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
22875 EditorMode::Full { .. } => cx.theme().colors().editor_background,
22876 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
22877 };
22878
22879 EditorElement::new(
22880 &cx.entity(),
22881 EditorStyle {
22882 background,
22883 border: cx.theme().colors().border,
22884 local_player: cx.theme().players().local(),
22885 text: text_style,
22886 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
22887 syntax: cx.theme().syntax().clone(),
22888 status: cx.theme().status().clone(),
22889 inlay_hints_style: make_inlay_hints_style(cx),
22890 edit_prediction_styles: make_suggestion_styles(cx),
22891 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
22892 show_underlines: self.diagnostics_enabled(),
22893 },
22894 )
22895 }
22896}
22897
22898impl EntityInputHandler for Editor {
22899 fn text_for_range(
22900 &mut self,
22901 range_utf16: Range<usize>,
22902 adjusted_range: &mut Option<Range<usize>>,
22903 _: &mut Window,
22904 cx: &mut Context<Self>,
22905 ) -> Option<String> {
22906 let snapshot = self.buffer.read(cx).read(cx);
22907 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
22908 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
22909 if (start.0..end.0) != range_utf16 {
22910 adjusted_range.replace(start.0..end.0);
22911 }
22912 Some(snapshot.text_for_range(start..end).collect())
22913 }
22914
22915 fn selected_text_range(
22916 &mut self,
22917 ignore_disabled_input: bool,
22918 _: &mut Window,
22919 cx: &mut Context<Self>,
22920 ) -> Option<UTF16Selection> {
22921 // Prevent the IME menu from appearing when holding down an alphabetic key
22922 // while input is disabled.
22923 if !ignore_disabled_input && !self.input_enabled {
22924 return None;
22925 }
22926
22927 let selection = self.selections.newest::<OffsetUtf16>(cx);
22928 let range = selection.range();
22929
22930 Some(UTF16Selection {
22931 range: range.start.0..range.end.0,
22932 reversed: selection.reversed,
22933 })
22934 }
22935
22936 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
22937 let snapshot = self.buffer.read(cx).read(cx);
22938 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
22939 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
22940 }
22941
22942 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
22943 self.clear_highlights::<InputComposition>(cx);
22944 self.ime_transaction.take();
22945 }
22946
22947 fn replace_text_in_range(
22948 &mut self,
22949 range_utf16: Option<Range<usize>>,
22950 text: &str,
22951 window: &mut Window,
22952 cx: &mut Context<Self>,
22953 ) {
22954 if !self.input_enabled {
22955 cx.emit(EditorEvent::InputIgnored { text: text.into() });
22956 return;
22957 }
22958
22959 self.transact(window, cx, |this, window, cx| {
22960 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
22961 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
22962 Some(this.selection_replacement_ranges(range_utf16, cx))
22963 } else {
22964 this.marked_text_ranges(cx)
22965 };
22966
22967 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
22968 let newest_selection_id = this.selections.newest_anchor().id;
22969 this.selections
22970 .all::<OffsetUtf16>(cx)
22971 .iter()
22972 .zip(ranges_to_replace.iter())
22973 .find_map(|(selection, range)| {
22974 if selection.id == newest_selection_id {
22975 Some(
22976 (range.start.0 as isize - selection.head().0 as isize)
22977 ..(range.end.0 as isize - selection.head().0 as isize),
22978 )
22979 } else {
22980 None
22981 }
22982 })
22983 });
22984
22985 cx.emit(EditorEvent::InputHandled {
22986 utf16_range_to_replace: range_to_replace,
22987 text: text.into(),
22988 });
22989
22990 if let Some(new_selected_ranges) = new_selected_ranges {
22991 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22992 selections.select_ranges(new_selected_ranges)
22993 });
22994 this.backspace(&Default::default(), window, cx);
22995 }
22996
22997 this.handle_input(text, window, cx);
22998 });
22999
23000 if let Some(transaction) = self.ime_transaction {
23001 self.buffer.update(cx, |buffer, cx| {
23002 buffer.group_until_transaction(transaction, cx);
23003 });
23004 }
23005
23006 self.unmark_text(window, cx);
23007 }
23008
23009 fn replace_and_mark_text_in_range(
23010 &mut self,
23011 range_utf16: Option<Range<usize>>,
23012 text: &str,
23013 new_selected_range_utf16: Option<Range<usize>>,
23014 window: &mut Window,
23015 cx: &mut Context<Self>,
23016 ) {
23017 if !self.input_enabled {
23018 return;
23019 }
23020
23021 let transaction = self.transact(window, cx, |this, window, cx| {
23022 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
23023 let snapshot = this.buffer.read(cx).read(cx);
23024 if let Some(relative_range_utf16) = range_utf16.as_ref() {
23025 for marked_range in &mut marked_ranges {
23026 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
23027 marked_range.start.0 += relative_range_utf16.start;
23028 marked_range.start =
23029 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
23030 marked_range.end =
23031 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
23032 }
23033 }
23034 Some(marked_ranges)
23035 } else if let Some(range_utf16) = range_utf16 {
23036 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23037 Some(this.selection_replacement_ranges(range_utf16, cx))
23038 } else {
23039 None
23040 };
23041
23042 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
23043 let newest_selection_id = this.selections.newest_anchor().id;
23044 this.selections
23045 .all::<OffsetUtf16>(cx)
23046 .iter()
23047 .zip(ranges_to_replace.iter())
23048 .find_map(|(selection, range)| {
23049 if selection.id == newest_selection_id {
23050 Some(
23051 (range.start.0 as isize - selection.head().0 as isize)
23052 ..(range.end.0 as isize - selection.head().0 as isize),
23053 )
23054 } else {
23055 None
23056 }
23057 })
23058 });
23059
23060 cx.emit(EditorEvent::InputHandled {
23061 utf16_range_to_replace: range_to_replace,
23062 text: text.into(),
23063 });
23064
23065 if let Some(ranges) = ranges_to_replace {
23066 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23067 s.select_ranges(ranges)
23068 });
23069 }
23070
23071 let marked_ranges = {
23072 let snapshot = this.buffer.read(cx).read(cx);
23073 this.selections
23074 .disjoint_anchors()
23075 .iter()
23076 .map(|selection| {
23077 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
23078 })
23079 .collect::<Vec<_>>()
23080 };
23081
23082 if text.is_empty() {
23083 this.unmark_text(window, cx);
23084 } else {
23085 this.highlight_text::<InputComposition>(
23086 marked_ranges.clone(),
23087 HighlightStyle {
23088 underline: Some(UnderlineStyle {
23089 thickness: px(1.),
23090 color: None,
23091 wavy: false,
23092 }),
23093 ..Default::default()
23094 },
23095 cx,
23096 );
23097 }
23098
23099 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
23100 let use_autoclose = this.use_autoclose;
23101 let use_auto_surround = this.use_auto_surround;
23102 this.set_use_autoclose(false);
23103 this.set_use_auto_surround(false);
23104 this.handle_input(text, window, cx);
23105 this.set_use_autoclose(use_autoclose);
23106 this.set_use_auto_surround(use_auto_surround);
23107
23108 if let Some(new_selected_range) = new_selected_range_utf16 {
23109 let snapshot = this.buffer.read(cx).read(cx);
23110 let new_selected_ranges = marked_ranges
23111 .into_iter()
23112 .map(|marked_range| {
23113 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
23114 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
23115 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
23116 snapshot.clip_offset_utf16(new_start, Bias::Left)
23117 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
23118 })
23119 .collect::<Vec<_>>();
23120
23121 drop(snapshot);
23122 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23123 selections.select_ranges(new_selected_ranges)
23124 });
23125 }
23126 });
23127
23128 self.ime_transaction = self.ime_transaction.or(transaction);
23129 if let Some(transaction) = self.ime_transaction {
23130 self.buffer.update(cx, |buffer, cx| {
23131 buffer.group_until_transaction(transaction, cx);
23132 });
23133 }
23134
23135 if self.text_highlights::<InputComposition>(cx).is_none() {
23136 self.ime_transaction.take();
23137 }
23138 }
23139
23140 fn bounds_for_range(
23141 &mut self,
23142 range_utf16: Range<usize>,
23143 element_bounds: gpui::Bounds<Pixels>,
23144 window: &mut Window,
23145 cx: &mut Context<Self>,
23146 ) -> Option<gpui::Bounds<Pixels>> {
23147 let text_layout_details = self.text_layout_details(window);
23148 let CharacterDimensions {
23149 em_width,
23150 em_advance,
23151 line_height,
23152 } = self.character_dimensions(window);
23153
23154 let snapshot = self.snapshot(window, cx);
23155 let scroll_position = snapshot.scroll_position();
23156 let scroll_left = scroll_position.x * em_advance;
23157
23158 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
23159 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
23160 + self.gutter_dimensions.full_width();
23161 let y = line_height * (start.row().as_f32() - scroll_position.y);
23162
23163 Some(Bounds {
23164 origin: element_bounds.origin + point(x, y),
23165 size: size(em_width, line_height),
23166 })
23167 }
23168
23169 fn character_index_for_point(
23170 &mut self,
23171 point: gpui::Point<Pixels>,
23172 _window: &mut Window,
23173 _cx: &mut Context<Self>,
23174 ) -> Option<usize> {
23175 let position_map = self.last_position_map.as_ref()?;
23176 if !position_map.text_hitbox.contains(&point) {
23177 return None;
23178 }
23179 let display_point = position_map.point_for_position(point).previous_valid;
23180 let anchor = position_map
23181 .snapshot
23182 .display_point_to_anchor(display_point, Bias::Left);
23183 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
23184 Some(utf16_offset.0)
23185 }
23186}
23187
23188trait SelectionExt {
23189 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
23190 fn spanned_rows(
23191 &self,
23192 include_end_if_at_line_start: bool,
23193 map: &DisplaySnapshot,
23194 ) -> Range<MultiBufferRow>;
23195}
23196
23197impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
23198 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
23199 let start = self
23200 .start
23201 .to_point(&map.buffer_snapshot)
23202 .to_display_point(map);
23203 let end = self
23204 .end
23205 .to_point(&map.buffer_snapshot)
23206 .to_display_point(map);
23207 if self.reversed {
23208 end..start
23209 } else {
23210 start..end
23211 }
23212 }
23213
23214 fn spanned_rows(
23215 &self,
23216 include_end_if_at_line_start: bool,
23217 map: &DisplaySnapshot,
23218 ) -> Range<MultiBufferRow> {
23219 let start = self.start.to_point(&map.buffer_snapshot);
23220 let mut end = self.end.to_point(&map.buffer_snapshot);
23221 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
23222 end.row -= 1;
23223 }
23224
23225 let buffer_start = map.prev_line_boundary(start).0;
23226 let buffer_end = map.next_line_boundary(end).0;
23227 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
23228 }
23229}
23230
23231impl<T: InvalidationRegion> InvalidationStack<T> {
23232 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
23233 where
23234 S: Clone + ToOffset,
23235 {
23236 while let Some(region) = self.last() {
23237 let all_selections_inside_invalidation_ranges =
23238 if selections.len() == region.ranges().len() {
23239 selections
23240 .iter()
23241 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
23242 .all(|(selection, invalidation_range)| {
23243 let head = selection.head().to_offset(buffer);
23244 invalidation_range.start <= head && invalidation_range.end >= head
23245 })
23246 } else {
23247 false
23248 };
23249
23250 if all_selections_inside_invalidation_ranges {
23251 break;
23252 } else {
23253 self.pop();
23254 }
23255 }
23256 }
23257}
23258
23259impl<T> Default for InvalidationStack<T> {
23260 fn default() -> Self {
23261 Self(Default::default())
23262 }
23263}
23264
23265impl<T> Deref for InvalidationStack<T> {
23266 type Target = Vec<T>;
23267
23268 fn deref(&self) -> &Self::Target {
23269 &self.0
23270 }
23271}
23272
23273impl<T> DerefMut for InvalidationStack<T> {
23274 fn deref_mut(&mut self) -> &mut Self::Target {
23275 &mut self.0
23276 }
23277}
23278
23279impl InvalidationRegion for SnippetState {
23280 fn ranges(&self) -> &[Range<Anchor>] {
23281 &self.ranges[self.active_index]
23282 }
23283}
23284
23285fn edit_prediction_edit_text(
23286 current_snapshot: &BufferSnapshot,
23287 edits: &[(Range<Anchor>, String)],
23288 edit_preview: &EditPreview,
23289 include_deletions: bool,
23290 cx: &App,
23291) -> HighlightedText {
23292 let edits = edits
23293 .iter()
23294 .map(|(anchor, text)| {
23295 (
23296 anchor.start.text_anchor..anchor.end.text_anchor,
23297 text.clone(),
23298 )
23299 })
23300 .collect::<Vec<_>>();
23301
23302 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
23303}
23304
23305fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, String)], cx: &App) -> HighlightedText {
23306 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
23307 // Just show the raw edit text with basic styling
23308 let mut text = String::new();
23309 let mut highlights = Vec::new();
23310
23311 let insertion_highlight_style = HighlightStyle {
23312 color: Some(cx.theme().colors().text),
23313 ..Default::default()
23314 };
23315
23316 for (_, edit_text) in edits {
23317 let start_offset = text.len();
23318 text.push_str(edit_text);
23319 let end_offset = text.len();
23320
23321 if start_offset < end_offset {
23322 highlights.push((start_offset..end_offset, insertion_highlight_style));
23323 }
23324 }
23325
23326 HighlightedText {
23327 text: text.into(),
23328 highlights,
23329 }
23330}
23331
23332pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
23333 match severity {
23334 lsp::DiagnosticSeverity::ERROR => colors.error,
23335 lsp::DiagnosticSeverity::WARNING => colors.warning,
23336 lsp::DiagnosticSeverity::INFORMATION => colors.info,
23337 lsp::DiagnosticSeverity::HINT => colors.info,
23338 _ => colors.ignored,
23339 }
23340}
23341
23342pub fn styled_runs_for_code_label<'a>(
23343 label: &'a CodeLabel,
23344 syntax_theme: &'a theme::SyntaxTheme,
23345) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
23346 let fade_out = HighlightStyle {
23347 fade_out: Some(0.35),
23348 ..Default::default()
23349 };
23350
23351 let mut prev_end = label.filter_range.end;
23352 label
23353 .runs
23354 .iter()
23355 .enumerate()
23356 .flat_map(move |(ix, (range, highlight_id))| {
23357 let style = if let Some(style) = highlight_id.style(syntax_theme) {
23358 style
23359 } else {
23360 return Default::default();
23361 };
23362 let mut muted_style = style;
23363 muted_style.highlight(fade_out);
23364
23365 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
23366 if range.start >= label.filter_range.end {
23367 if range.start > prev_end {
23368 runs.push((prev_end..range.start, fade_out));
23369 }
23370 runs.push((range.clone(), muted_style));
23371 } else if range.end <= label.filter_range.end {
23372 runs.push((range.clone(), style));
23373 } else {
23374 runs.push((range.start..label.filter_range.end, style));
23375 runs.push((label.filter_range.end..range.end, muted_style));
23376 }
23377 prev_end = cmp::max(prev_end, range.end);
23378
23379 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
23380 runs.push((prev_end..label.text.len(), fade_out));
23381 }
23382
23383 runs
23384 })
23385}
23386
23387pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
23388 let mut prev_index = 0;
23389 let mut prev_codepoint: Option<char> = None;
23390 text.char_indices()
23391 .chain([(text.len(), '\0')])
23392 .filter_map(move |(index, codepoint)| {
23393 let prev_codepoint = prev_codepoint.replace(codepoint)?;
23394 let is_boundary = index == text.len()
23395 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
23396 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
23397 if is_boundary {
23398 let chunk = &text[prev_index..index];
23399 prev_index = index;
23400 Some(chunk)
23401 } else {
23402 None
23403 }
23404 })
23405}
23406
23407pub trait RangeToAnchorExt: Sized {
23408 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
23409
23410 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
23411 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
23412 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
23413 }
23414}
23415
23416impl<T: ToOffset> RangeToAnchorExt for Range<T> {
23417 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
23418 let start_offset = self.start.to_offset(snapshot);
23419 let end_offset = self.end.to_offset(snapshot);
23420 if start_offset == end_offset {
23421 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
23422 } else {
23423 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
23424 }
23425 }
23426}
23427
23428pub trait RowExt {
23429 fn as_f32(&self) -> f32;
23430
23431 fn next_row(&self) -> Self;
23432
23433 fn previous_row(&self) -> Self;
23434
23435 fn minus(&self, other: Self) -> u32;
23436}
23437
23438impl RowExt for DisplayRow {
23439 fn as_f32(&self) -> f32 {
23440 self.0 as f32
23441 }
23442
23443 fn next_row(&self) -> Self {
23444 Self(self.0 + 1)
23445 }
23446
23447 fn previous_row(&self) -> Self {
23448 Self(self.0.saturating_sub(1))
23449 }
23450
23451 fn minus(&self, other: Self) -> u32 {
23452 self.0 - other.0
23453 }
23454}
23455
23456impl RowExt for MultiBufferRow {
23457 fn as_f32(&self) -> f32 {
23458 self.0 as f32
23459 }
23460
23461 fn next_row(&self) -> Self {
23462 Self(self.0 + 1)
23463 }
23464
23465 fn previous_row(&self) -> Self {
23466 Self(self.0.saturating_sub(1))
23467 }
23468
23469 fn minus(&self, other: Self) -> u32 {
23470 self.0 - other.0
23471 }
23472}
23473
23474trait RowRangeExt {
23475 type Row;
23476
23477 fn len(&self) -> usize;
23478
23479 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
23480}
23481
23482impl RowRangeExt for Range<MultiBufferRow> {
23483 type Row = MultiBufferRow;
23484
23485 fn len(&self) -> usize {
23486 (self.end.0 - self.start.0) as usize
23487 }
23488
23489 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
23490 (self.start.0..self.end.0).map(MultiBufferRow)
23491 }
23492}
23493
23494impl RowRangeExt for Range<DisplayRow> {
23495 type Row = DisplayRow;
23496
23497 fn len(&self) -> usize {
23498 (self.end.0 - self.start.0) as usize
23499 }
23500
23501 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
23502 (self.start.0..self.end.0).map(DisplayRow)
23503 }
23504}
23505
23506/// If select range has more than one line, we
23507/// just point the cursor to range.start.
23508fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
23509 if range.start.row == range.end.row {
23510 range
23511 } else {
23512 range.start..range.start
23513 }
23514}
23515pub struct KillRing(ClipboardItem);
23516impl Global for KillRing {}
23517
23518const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
23519
23520enum BreakpointPromptEditAction {
23521 Log,
23522 Condition,
23523 HitCondition,
23524}
23525
23526struct BreakpointPromptEditor {
23527 pub(crate) prompt: Entity<Editor>,
23528 editor: WeakEntity<Editor>,
23529 breakpoint_anchor: Anchor,
23530 breakpoint: Breakpoint,
23531 edit_action: BreakpointPromptEditAction,
23532 block_ids: HashSet<CustomBlockId>,
23533 editor_margins: Arc<Mutex<EditorMargins>>,
23534 _subscriptions: Vec<Subscription>,
23535}
23536
23537impl BreakpointPromptEditor {
23538 const MAX_LINES: u8 = 4;
23539
23540 fn new(
23541 editor: WeakEntity<Editor>,
23542 breakpoint_anchor: Anchor,
23543 breakpoint: Breakpoint,
23544 edit_action: BreakpointPromptEditAction,
23545 window: &mut Window,
23546 cx: &mut Context<Self>,
23547 ) -> Self {
23548 let base_text = match edit_action {
23549 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
23550 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
23551 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
23552 }
23553 .map(|msg| msg.to_string())
23554 .unwrap_or_default();
23555
23556 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
23557 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
23558
23559 let prompt = cx.new(|cx| {
23560 let mut prompt = Editor::new(
23561 EditorMode::AutoHeight {
23562 min_lines: 1,
23563 max_lines: Some(Self::MAX_LINES as usize),
23564 },
23565 buffer,
23566 None,
23567 window,
23568 cx,
23569 );
23570 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
23571 prompt.set_show_cursor_when_unfocused(false, cx);
23572 prompt.set_placeholder_text(
23573 match edit_action {
23574 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
23575 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
23576 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
23577 },
23578 cx,
23579 );
23580
23581 prompt
23582 });
23583
23584 Self {
23585 prompt,
23586 editor,
23587 breakpoint_anchor,
23588 breakpoint,
23589 edit_action,
23590 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
23591 block_ids: Default::default(),
23592 _subscriptions: vec![],
23593 }
23594 }
23595
23596 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
23597 self.block_ids.extend(block_ids)
23598 }
23599
23600 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
23601 if let Some(editor) = self.editor.upgrade() {
23602 let message = self
23603 .prompt
23604 .read(cx)
23605 .buffer
23606 .read(cx)
23607 .as_singleton()
23608 .expect("A multi buffer in breakpoint prompt isn't possible")
23609 .read(cx)
23610 .as_rope()
23611 .to_string();
23612
23613 editor.update(cx, |editor, cx| {
23614 editor.edit_breakpoint_at_anchor(
23615 self.breakpoint_anchor,
23616 self.breakpoint.clone(),
23617 match self.edit_action {
23618 BreakpointPromptEditAction::Log => {
23619 BreakpointEditAction::EditLogMessage(message.into())
23620 }
23621 BreakpointPromptEditAction::Condition => {
23622 BreakpointEditAction::EditCondition(message.into())
23623 }
23624 BreakpointPromptEditAction::HitCondition => {
23625 BreakpointEditAction::EditHitCondition(message.into())
23626 }
23627 },
23628 cx,
23629 );
23630
23631 editor.remove_blocks(self.block_ids.clone(), None, cx);
23632 cx.focus_self(window);
23633 });
23634 }
23635 }
23636
23637 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
23638 self.editor
23639 .update(cx, |editor, cx| {
23640 editor.remove_blocks(self.block_ids.clone(), None, cx);
23641 window.focus(&editor.focus_handle);
23642 })
23643 .log_err();
23644 }
23645
23646 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
23647 let settings = ThemeSettings::get_global(cx);
23648 let text_style = TextStyle {
23649 color: if self.prompt.read(cx).read_only(cx) {
23650 cx.theme().colors().text_disabled
23651 } else {
23652 cx.theme().colors().text
23653 },
23654 font_family: settings.buffer_font.family.clone(),
23655 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23656 font_size: settings.buffer_font_size(cx).into(),
23657 font_weight: settings.buffer_font.weight,
23658 line_height: relative(settings.buffer_line_height.value()),
23659 ..Default::default()
23660 };
23661 EditorElement::new(
23662 &self.prompt,
23663 EditorStyle {
23664 background: cx.theme().colors().editor_background,
23665 local_player: cx.theme().players().local(),
23666 text: text_style,
23667 ..Default::default()
23668 },
23669 )
23670 }
23671}
23672
23673impl Render for BreakpointPromptEditor {
23674 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23675 let editor_margins = *self.editor_margins.lock();
23676 let gutter_dimensions = editor_margins.gutter;
23677 h_flex()
23678 .key_context("Editor")
23679 .bg(cx.theme().colors().editor_background)
23680 .border_y_1()
23681 .border_color(cx.theme().status().info_border)
23682 .size_full()
23683 .py(window.line_height() / 2.5)
23684 .on_action(cx.listener(Self::confirm))
23685 .on_action(cx.listener(Self::cancel))
23686 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
23687 .child(div().flex_1().child(self.render_prompt_editor(cx)))
23688 }
23689}
23690
23691impl Focusable for BreakpointPromptEditor {
23692 fn focus_handle(&self, cx: &App) -> FocusHandle {
23693 self.prompt.focus_handle(cx)
23694 }
23695}
23696
23697fn all_edits_insertions_or_deletions(
23698 edits: &Vec<(Range<Anchor>, String)>,
23699 snapshot: &MultiBufferSnapshot,
23700) -> bool {
23701 let mut all_insertions = true;
23702 let mut all_deletions = true;
23703
23704 for (range, new_text) in edits.iter() {
23705 let range_is_empty = range.to_offset(&snapshot).is_empty();
23706 let text_is_empty = new_text.is_empty();
23707
23708 if range_is_empty != text_is_empty {
23709 if range_is_empty {
23710 all_deletions = false;
23711 } else {
23712 all_insertions = false;
23713 }
23714 } else {
23715 return false;
23716 }
23717
23718 if !all_insertions && !all_deletions {
23719 return false;
23720 }
23721 }
23722 all_insertions || all_deletions
23723}
23724
23725struct MissingEditPredictionKeybindingTooltip;
23726
23727impl Render for MissingEditPredictionKeybindingTooltip {
23728 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23729 ui::tooltip_container(window, cx, |container, _, cx| {
23730 container
23731 .flex_shrink_0()
23732 .max_w_80()
23733 .min_h(rems_from_px(124.))
23734 .justify_between()
23735 .child(
23736 v_flex()
23737 .flex_1()
23738 .text_ui_sm(cx)
23739 .child(Label::new("Conflict with Accept Keybinding"))
23740 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
23741 )
23742 .child(
23743 h_flex()
23744 .pb_1()
23745 .gap_1()
23746 .items_end()
23747 .w_full()
23748 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
23749 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
23750 }))
23751 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
23752 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
23753 })),
23754 )
23755 })
23756 }
23757}
23758
23759#[derive(Debug, Clone, Copy, PartialEq)]
23760pub struct LineHighlight {
23761 pub background: Background,
23762 pub border: Option<gpui::Hsla>,
23763 pub include_gutter: bool,
23764 pub type_id: Option<TypeId>,
23765}
23766
23767struct LineManipulationResult {
23768 pub new_text: String,
23769 pub line_count_before: usize,
23770 pub line_count_after: usize,
23771}
23772
23773fn render_diff_hunk_controls(
23774 row: u32,
23775 status: &DiffHunkStatus,
23776 hunk_range: Range<Anchor>,
23777 is_created_file: bool,
23778 line_height: Pixels,
23779 editor: &Entity<Editor>,
23780 _window: &mut Window,
23781 cx: &mut App,
23782) -> AnyElement {
23783 h_flex()
23784 .h(line_height)
23785 .mr_1()
23786 .gap_1()
23787 .px_0p5()
23788 .pb_1()
23789 .border_x_1()
23790 .border_b_1()
23791 .border_color(cx.theme().colors().border_variant)
23792 .rounded_b_lg()
23793 .bg(cx.theme().colors().editor_background)
23794 .gap_1()
23795 .block_mouse_except_scroll()
23796 .shadow_md()
23797 .child(if status.has_secondary_hunk() {
23798 Button::new(("stage", row as u64), "Stage")
23799 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
23800 .tooltip({
23801 let focus_handle = editor.focus_handle(cx);
23802 move |window, cx| {
23803 Tooltip::for_action_in(
23804 "Stage Hunk",
23805 &::git::ToggleStaged,
23806 &focus_handle,
23807 window,
23808 cx,
23809 )
23810 }
23811 })
23812 .on_click({
23813 let editor = editor.clone();
23814 move |_event, _window, cx| {
23815 editor.update(cx, |editor, cx| {
23816 editor.stage_or_unstage_diff_hunks(
23817 true,
23818 vec![hunk_range.start..hunk_range.start],
23819 cx,
23820 );
23821 });
23822 }
23823 })
23824 } else {
23825 Button::new(("unstage", row as u64), "Unstage")
23826 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
23827 .tooltip({
23828 let focus_handle = editor.focus_handle(cx);
23829 move |window, cx| {
23830 Tooltip::for_action_in(
23831 "Unstage Hunk",
23832 &::git::ToggleStaged,
23833 &focus_handle,
23834 window,
23835 cx,
23836 )
23837 }
23838 })
23839 .on_click({
23840 let editor = editor.clone();
23841 move |_event, _window, cx| {
23842 editor.update(cx, |editor, cx| {
23843 editor.stage_or_unstage_diff_hunks(
23844 false,
23845 vec![hunk_range.start..hunk_range.start],
23846 cx,
23847 );
23848 });
23849 }
23850 })
23851 })
23852 .child(
23853 Button::new(("restore", row as u64), "Restore")
23854 .tooltip({
23855 let focus_handle = editor.focus_handle(cx);
23856 move |window, cx| {
23857 Tooltip::for_action_in(
23858 "Restore Hunk",
23859 &::git::Restore,
23860 &focus_handle,
23861 window,
23862 cx,
23863 )
23864 }
23865 })
23866 .on_click({
23867 let editor = editor.clone();
23868 move |_event, window, cx| {
23869 editor.update(cx, |editor, cx| {
23870 let snapshot = editor.snapshot(window, cx);
23871 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
23872 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
23873 });
23874 }
23875 })
23876 .disabled(is_created_file),
23877 )
23878 .when(
23879 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
23880 |el| {
23881 el.child(
23882 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
23883 .shape(IconButtonShape::Square)
23884 .icon_size(IconSize::Small)
23885 // .disabled(!has_multiple_hunks)
23886 .tooltip({
23887 let focus_handle = editor.focus_handle(cx);
23888 move |window, cx| {
23889 Tooltip::for_action_in(
23890 "Next Hunk",
23891 &GoToHunk,
23892 &focus_handle,
23893 window,
23894 cx,
23895 )
23896 }
23897 })
23898 .on_click({
23899 let editor = editor.clone();
23900 move |_event, window, cx| {
23901 editor.update(cx, |editor, cx| {
23902 let snapshot = editor.snapshot(window, cx);
23903 let position =
23904 hunk_range.end.to_point(&snapshot.buffer_snapshot);
23905 editor.go_to_hunk_before_or_after_position(
23906 &snapshot,
23907 position,
23908 Direction::Next,
23909 window,
23910 cx,
23911 );
23912 editor.expand_selected_diff_hunks(cx);
23913 });
23914 }
23915 }),
23916 )
23917 .child(
23918 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
23919 .shape(IconButtonShape::Square)
23920 .icon_size(IconSize::Small)
23921 // .disabled(!has_multiple_hunks)
23922 .tooltip({
23923 let focus_handle = editor.focus_handle(cx);
23924 move |window, cx| {
23925 Tooltip::for_action_in(
23926 "Previous Hunk",
23927 &GoToPreviousHunk,
23928 &focus_handle,
23929 window,
23930 cx,
23931 )
23932 }
23933 })
23934 .on_click({
23935 let editor = editor.clone();
23936 move |_event, window, cx| {
23937 editor.update(cx, |editor, cx| {
23938 let snapshot = editor.snapshot(window, cx);
23939 let point =
23940 hunk_range.start.to_point(&snapshot.buffer_snapshot);
23941 editor.go_to_hunk_before_or_after_position(
23942 &snapshot,
23943 point,
23944 Direction::Prev,
23945 window,
23946 cx,
23947 );
23948 editor.expand_selected_diff_hunks(cx);
23949 });
23950 }
23951 }),
23952 )
23953 },
23954 )
23955 .into_any_element()
23956}