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_ext;
33mod mouse_context_menu;
34pub mod movement;
35mod persistence;
36mod proposed_changes_editor;
37mod rust_analyzer_ext;
38pub mod scroll;
39mod selections_collection;
40pub mod tasks;
41
42#[cfg(test)]
43mod code_completion_tests;
44#[cfg(test)]
45mod editor_tests;
46#[cfg(test)]
47mod inline_completion_tests;
48mod signature_help;
49#[cfg(any(test, feature = "test-support"))]
50pub mod test;
51
52pub(crate) use actions::*;
53pub use actions::{AcceptEditPrediction, OpenExcerpts, OpenExcerptsSplit};
54use aho_corasick::AhoCorasick;
55use anyhow::{Context as _, Result, anyhow};
56use blink_manager::BlinkManager;
57use buffer_diff::DiffHunkStatus;
58use client::{Collaborator, ParticipantIndex};
59use clock::{AGENT_REPLICA_ID, ReplicaId};
60use collections::{BTreeMap, HashMap, HashSet, VecDeque};
61use convert_case::{Case, Casing};
62use dap::TelemetrySpawnLocation;
63use display_map::*;
64pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
65pub use editor_settings::{
66 CurrentLineHighlight, EditorSettings, HideMouseMode, ScrollBeyondLastLine, ScrollbarAxes,
67 SearchSettings, ShowScrollbar,
68};
69use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
70pub use editor_settings_controls::*;
71use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
72pub use element::{
73 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
74};
75use feature_flags::{DebuggerFeatureFlag, FeatureFlagAppExt};
76use futures::{
77 FutureExt,
78 future::{self, Shared, join},
79};
80use fuzzy::{StringMatch, StringMatchCandidate};
81
82use ::git::blame::BlameEntry;
83use ::git::{Restore, blame::ParsedCommitMessage};
84use code_context_menus::{
85 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
86 CompletionsMenu, ContextMenuOrigin,
87};
88use git::blame::{GitBlame, GlobalBlameRenderer};
89use gpui::{
90 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
91 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
92 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
93 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
94 MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render, ScrollHandle,
95 SharedString, Size, Stateful, Styled, Subscription, Task, TextStyle, TextStyleRefinement,
96 UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
97 div, impl_actions, point, prelude::*, pulsating_between, px, relative, size,
98};
99use highlight_matching_bracket::refresh_matching_bracket_highlights;
100use hover_links::{HoverLink, HoveredLinkState, InlayHighlight, find_file};
101pub use hover_popover::hover_markdown_style;
102use hover_popover::{HoverState, hide_hover};
103use indent_guides::ActiveIndentGuidesState;
104use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
105pub use inline_completion::Direction;
106use inline_completion::{EditPredictionProvider, InlineCompletionProviderHandle};
107pub use items::MAX_TAB_TITLE_LEN;
108use itertools::Itertools;
109use language::{
110 AutoindentMode, BracketMatch, BracketPair, Buffer, Capability, CharKind, CodeLabel,
111 CursorShape, DiagnosticEntry, DiffOptions, DocumentationConfig, EditPredictionsMode,
112 EditPreview, HighlightedText, IndentKind, IndentSize, Language, OffsetRangeExt, Point,
113 Selection, SelectionGoal, TextObject, TransactionId, TreeSitterOptions, WordsQuery,
114 language_settings::{
115 self, InlayHintSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
116 all_language_settings, language_settings,
117 },
118 point_from_lsp, text_diff_with_options,
119};
120use language::{BufferRow, CharClassifier, Runnable, RunnableRange, point_to_lsp};
121use linked_editing_ranges::refresh_linked_ranges;
122use markdown::Markdown;
123use mouse_context_menu::MouseContextMenu;
124use persistence::DB;
125use project::{
126 BreakpointWithPosition, CompletionResponse, ProjectPath,
127 debugger::{
128 breakpoint_store::{
129 BreakpointEditAction, BreakpointSessionState, BreakpointState, BreakpointStore,
130 BreakpointStoreEvent,
131 },
132 session::{Session, SessionEvent},
133 },
134 project_settings::DiagnosticSeverity,
135};
136
137pub use git::blame::BlameRenderer;
138pub use proposed_changes_editor::{
139 ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
140};
141use std::{cell::OnceCell, iter::Peekable, ops::Not};
142use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
143
144pub use lsp::CompletionContext;
145use lsp::{
146 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
147 LanguageServerId, LanguageServerName,
148};
149
150use language::BufferSnapshot;
151pub use lsp_ext::lsp_tasks;
152use movement::TextLayoutDetails;
153pub use multi_buffer::{
154 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, PathKey,
155 RowInfo, ToOffset, ToPoint,
156};
157use multi_buffer::{
158 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
159 MultiOrSingleBufferOffsetRange, ToOffsetUtf16,
160};
161use parking_lot::Mutex;
162use project::{
163 CodeAction, Completion, CompletionIntent, CompletionSource, DocumentHighlight, InlayHint,
164 Location, LocationLink, PrepareRenameResponse, Project, ProjectItem, ProjectTransaction,
165 TaskSourceKind,
166 debugger::breakpoint_store::Breakpoint,
167 lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
168 project_settings::{GitGutterSetting, ProjectSettings},
169};
170use rand::prelude::*;
171use rpc::{ErrorExt, proto::*};
172use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide};
173use selections_collection::{
174 MutableSelectionsCollection, SelectionsCollection, resolve_selections,
175};
176use serde::{Deserialize, Serialize};
177use settings::{Settings, SettingsLocation, SettingsStore, update_settings_file};
178use smallvec::{SmallVec, smallvec};
179use snippet::Snippet;
180use std::sync::Arc;
181use std::{
182 any::TypeId,
183 borrow::Cow,
184 cell::RefCell,
185 cmp::{self, Ordering, Reverse},
186 mem,
187 num::NonZeroU32,
188 ops::{ControlFlow, Deref, DerefMut, Range, RangeInclusive},
189 path::{Path, PathBuf},
190 rc::Rc,
191 time::{Duration, Instant},
192};
193pub use sum_tree::Bias;
194use sum_tree::TreeMap;
195use text::{BufferId, FromAnchor, OffsetUtf16, Rope};
196use theme::{
197 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, ThemeColors, ThemeSettings,
198 observe_buffer_font_size_adjustment,
199};
200use ui::{
201 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
202 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*,
203};
204use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
205use workspace::{
206 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
207 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
208 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
209 item::{ItemHandle, PreviewTabsSettings},
210 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
211 searchable::SearchEvent,
212};
213
214use crate::signature_help::{SignatureHelpHiddenBy, SignatureHelpState};
215use crate::{
216 code_context_menus::CompletionsMenuSource,
217 hover_links::{find_url, find_url_from_range},
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 MIN_LINE_NUMBER_DIGITS: u32 = 4;
239pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
240
241pub type RenderDiffHunkControlsFn = Arc<
242 dyn Fn(
243 u32,
244 &DiffHunkStatus,
245 Range<Anchor>,
246 bool,
247 Pixels,
248 &Entity<Editor>,
249 &mut Window,
250 &mut App,
251 ) -> AnyElement,
252>;
253
254const COLUMNAR_SELECTION_MODIFIERS: Modifiers = Modifiers {
255 alt: true,
256 shift: true,
257 control: false,
258 platform: false,
259 function: false,
260};
261
262struct InlineValueCache {
263 enabled: bool,
264 inlays: Vec<InlayId>,
265 refresh_task: Task<Option<()>>,
266}
267
268impl InlineValueCache {
269 fn new(enabled: bool) -> Self {
270 Self {
271 enabled,
272 inlays: Vec::new(),
273 refresh_task: Task::ready(None),
274 }
275 }
276}
277
278#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
279pub enum InlayId {
280 InlineCompletion(usize),
281 Hint(usize),
282 DebuggerValue(usize),
283}
284
285impl InlayId {
286 fn id(&self) -> usize {
287 match self {
288 Self::InlineCompletion(id) => *id,
289 Self::Hint(id) => *id,
290 Self::DebuggerValue(id) => *id,
291 }
292 }
293}
294
295pub enum ActiveDebugLine {}
296pub enum DebugStackFrameLine {}
297enum DocumentHighlightRead {}
298enum DocumentHighlightWrite {}
299enum InputComposition {}
300enum SelectedTextHighlight {}
301
302pub enum ConflictsOuter {}
303pub enum ConflictsOurs {}
304pub enum ConflictsTheirs {}
305pub enum ConflictsOursMarker {}
306pub enum ConflictsTheirsMarker {}
307
308#[derive(Debug, Copy, Clone, PartialEq, Eq)]
309pub enum Navigated {
310 Yes,
311 No,
312}
313
314impl Navigated {
315 pub fn from_bool(yes: bool) -> Navigated {
316 if yes { Navigated::Yes } else { Navigated::No }
317 }
318}
319
320#[derive(Debug, Clone, PartialEq, Eq)]
321enum DisplayDiffHunk {
322 Folded {
323 display_row: DisplayRow,
324 },
325 Unfolded {
326 is_created_file: bool,
327 diff_base_byte_range: Range<usize>,
328 display_row_range: Range<DisplayRow>,
329 multi_buffer_range: Range<Anchor>,
330 status: DiffHunkStatus,
331 },
332}
333
334pub enum HideMouseCursorOrigin {
335 TypingAction,
336 MovementAction,
337}
338
339pub fn init_settings(cx: &mut App) {
340 EditorSettings::register(cx);
341}
342
343pub fn init(cx: &mut App) {
344 init_settings(cx);
345
346 cx.set_global(GlobalBlameRenderer(Arc::new(())));
347
348 workspace::register_project_item::<Editor>(cx);
349 workspace::FollowableViewRegistry::register::<Editor>(cx);
350 workspace::register_serializable_item::<Editor>(cx);
351
352 cx.observe_new(
353 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
354 workspace.register_action(Editor::new_file);
355 workspace.register_action(Editor::new_file_vertical);
356 workspace.register_action(Editor::new_file_horizontal);
357 workspace.register_action(Editor::cancel_language_server_work);
358 },
359 )
360 .detach();
361
362 cx.on_action(move |_: &workspace::NewFile, cx| {
363 let app_state = workspace::AppState::global(cx);
364 if let Some(app_state) = app_state.upgrade() {
365 workspace::open_new(
366 Default::default(),
367 app_state,
368 cx,
369 |workspace, window, cx| {
370 Editor::new_file(workspace, &Default::default(), window, cx)
371 },
372 )
373 .detach();
374 }
375 });
376 cx.on_action(move |_: &workspace::NewWindow, cx| {
377 let app_state = workspace::AppState::global(cx);
378 if let Some(app_state) = app_state.upgrade() {
379 workspace::open_new(
380 Default::default(),
381 app_state,
382 cx,
383 |workspace, window, cx| {
384 cx.activate(true);
385 Editor::new_file(workspace, &Default::default(), window, cx)
386 },
387 )
388 .detach();
389 }
390 });
391}
392
393pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
394 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
395}
396
397pub trait DiagnosticRenderer {
398 fn render_group(
399 &self,
400 diagnostic_group: Vec<DiagnosticEntry<Point>>,
401 buffer_id: BufferId,
402 snapshot: EditorSnapshot,
403 editor: WeakEntity<Editor>,
404 cx: &mut App,
405 ) -> Vec<BlockProperties<Anchor>>;
406
407 fn render_hover(
408 &self,
409 diagnostic_group: Vec<DiagnosticEntry<Point>>,
410 range: Range<Point>,
411 buffer_id: BufferId,
412 cx: &mut App,
413 ) -> Option<Entity<markdown::Markdown>>;
414
415 fn open_link(
416 &self,
417 editor: &mut Editor,
418 link: SharedString,
419 window: &mut Window,
420 cx: &mut Context<Editor>,
421 );
422}
423
424pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
425
426impl GlobalDiagnosticRenderer {
427 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
428 cx.try_global::<Self>().map(|g| g.0.clone())
429 }
430}
431
432impl gpui::Global for GlobalDiagnosticRenderer {}
433pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
434 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
435}
436
437pub struct SearchWithinRange;
438
439trait InvalidationRegion {
440 fn ranges(&self) -> &[Range<Anchor>];
441}
442
443#[derive(Clone, Debug, PartialEq)]
444pub enum SelectPhase {
445 Begin {
446 position: DisplayPoint,
447 add: bool,
448 click_count: usize,
449 },
450 BeginColumnar {
451 position: DisplayPoint,
452 reset: bool,
453 goal_column: u32,
454 },
455 Extend {
456 position: DisplayPoint,
457 click_count: usize,
458 },
459 Update {
460 position: DisplayPoint,
461 goal_column: u32,
462 scroll_delta: gpui::Point<f32>,
463 },
464 End,
465}
466
467#[derive(Clone, Debug)]
468pub enum SelectMode {
469 Character,
470 Word(Range<Anchor>),
471 Line(Range<Anchor>),
472 All,
473}
474
475#[derive(Clone, PartialEq, Eq, Debug)]
476pub enum EditorMode {
477 SingleLine {
478 auto_width: bool,
479 },
480 AutoHeight {
481 max_lines: usize,
482 },
483 Full {
484 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
485 scale_ui_elements_with_buffer_font_size: bool,
486 /// When set to `true`, the editor will render a background for the active line.
487 show_active_line_background: bool,
488 /// When set to `true`, the editor's height will be determined by its content.
489 sized_by_content: bool,
490 },
491 Minimap {
492 parent: WeakEntity<Editor>,
493 },
494}
495
496impl EditorMode {
497 pub fn full() -> Self {
498 Self::Full {
499 scale_ui_elements_with_buffer_font_size: true,
500 show_active_line_background: true,
501 sized_by_content: false,
502 }
503 }
504
505 pub fn is_full(&self) -> bool {
506 matches!(self, Self::Full { .. })
507 }
508
509 fn is_minimap(&self) -> bool {
510 matches!(self, Self::Minimap { .. })
511 }
512}
513
514#[derive(Copy, Clone, Debug)]
515pub enum SoftWrap {
516 /// Prefer not to wrap at all.
517 ///
518 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
519 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
520 GitDiff,
521 /// Prefer a single line generally, unless an overly long line is encountered.
522 None,
523 /// Soft wrap lines that exceed the editor width.
524 EditorWidth,
525 /// Soft wrap lines at the preferred line length.
526 Column(u32),
527 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
528 Bounded(u32),
529}
530
531#[derive(Clone)]
532pub struct EditorStyle {
533 pub background: Hsla,
534 pub local_player: PlayerColor,
535 pub text: TextStyle,
536 pub scrollbar_width: Pixels,
537 pub syntax: Arc<SyntaxTheme>,
538 pub status: StatusColors,
539 pub inlay_hints_style: HighlightStyle,
540 pub inline_completion_styles: InlineCompletionStyles,
541 pub unnecessary_code_fade: f32,
542 pub show_underlines: bool,
543}
544
545impl Default for EditorStyle {
546 fn default() -> Self {
547 Self {
548 background: Hsla::default(),
549 local_player: PlayerColor::default(),
550 text: TextStyle::default(),
551 scrollbar_width: Pixels::default(),
552 syntax: Default::default(),
553 // HACK: Status colors don't have a real default.
554 // We should look into removing the status colors from the editor
555 // style and retrieve them directly from the theme.
556 status: StatusColors::dark(),
557 inlay_hints_style: HighlightStyle::default(),
558 inline_completion_styles: InlineCompletionStyles {
559 insertion: HighlightStyle::default(),
560 whitespace: HighlightStyle::default(),
561 },
562 unnecessary_code_fade: Default::default(),
563 show_underlines: true,
564 }
565 }
566}
567
568pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
569 let show_background = language_settings::language_settings(None, None, cx)
570 .inlay_hints
571 .show_background;
572
573 HighlightStyle {
574 color: Some(cx.theme().status().hint),
575 background_color: show_background.then(|| cx.theme().status().hint_background),
576 ..HighlightStyle::default()
577 }
578}
579
580pub fn make_suggestion_styles(cx: &mut App) -> InlineCompletionStyles {
581 InlineCompletionStyles {
582 insertion: HighlightStyle {
583 color: Some(cx.theme().status().predictive),
584 ..HighlightStyle::default()
585 },
586 whitespace: HighlightStyle {
587 background_color: Some(cx.theme().status().created_background),
588 ..HighlightStyle::default()
589 },
590 }
591}
592
593type CompletionId = usize;
594
595pub(crate) enum EditDisplayMode {
596 TabAccept,
597 DiffPopover,
598 Inline,
599}
600
601enum InlineCompletion {
602 Edit {
603 edits: Vec<(Range<Anchor>, String)>,
604 edit_preview: Option<EditPreview>,
605 display_mode: EditDisplayMode,
606 snapshot: BufferSnapshot,
607 },
608 Move {
609 target: Anchor,
610 snapshot: BufferSnapshot,
611 },
612}
613
614struct InlineCompletionState {
615 inlay_ids: Vec<InlayId>,
616 completion: InlineCompletion,
617 completion_id: Option<SharedString>,
618 invalidation_range: Range<Anchor>,
619}
620
621enum EditPredictionSettings {
622 Disabled,
623 Enabled {
624 show_in_menu: bool,
625 preview_requires_modifier: bool,
626 },
627}
628
629enum InlineCompletionHighlight {}
630
631#[derive(Debug, Clone)]
632struct InlineDiagnostic {
633 message: SharedString,
634 group_id: usize,
635 is_primary: bool,
636 start: Point,
637 severity: lsp::DiagnosticSeverity,
638}
639
640pub enum MenuInlineCompletionsPolicy {
641 Never,
642 ByProvider,
643}
644
645pub enum EditPredictionPreview {
646 /// Modifier is not pressed
647 Inactive { released_too_fast: bool },
648 /// Modifier pressed
649 Active {
650 since: Instant,
651 previous_scroll_position: Option<ScrollAnchor>,
652 },
653}
654
655impl EditPredictionPreview {
656 pub fn released_too_fast(&self) -> bool {
657 match self {
658 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
659 EditPredictionPreview::Active { .. } => false,
660 }
661 }
662
663 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
664 if let EditPredictionPreview::Active {
665 previous_scroll_position,
666 ..
667 } = self
668 {
669 *previous_scroll_position = scroll_position;
670 }
671 }
672}
673
674pub struct ContextMenuOptions {
675 pub min_entries_visible: usize,
676 pub max_entries_visible: usize,
677 pub placement: Option<ContextMenuPlacement>,
678}
679
680#[derive(Debug, Clone, PartialEq, Eq)]
681pub enum ContextMenuPlacement {
682 Above,
683 Below,
684}
685
686#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
687struct EditorActionId(usize);
688
689impl EditorActionId {
690 pub fn post_inc(&mut self) -> Self {
691 let answer = self.0;
692
693 *self = Self(answer + 1);
694
695 Self(answer)
696 }
697}
698
699// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
700// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
701
702type BackgroundHighlight = (fn(&ThemeColors) -> Hsla, Arc<[Range<Anchor>]>);
703type GutterHighlight = (fn(&App) -> Hsla, Arc<[Range<Anchor>]>);
704
705#[derive(Default)]
706struct ScrollbarMarkerState {
707 scrollbar_size: Size<Pixels>,
708 dirty: bool,
709 markers: Arc<[PaintQuad]>,
710 pending_refresh: Option<Task<Result<()>>>,
711}
712
713impl ScrollbarMarkerState {
714 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
715 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
716 }
717}
718
719#[derive(Clone, Copy, PartialEq, Eq)]
720pub enum MinimapVisibility {
721 Disabled,
722 Enabled {
723 /// The configuration currently present in the users settings.
724 setting_configuration: bool,
725 /// Whether to override the currently set visibility from the users setting.
726 toggle_override: bool,
727 },
728}
729
730impl MinimapVisibility {
731 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
732 if mode.is_full() {
733 Self::Enabled {
734 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
735 toggle_override: false,
736 }
737 } else {
738 Self::Disabled
739 }
740 }
741
742 fn hidden(&self) -> Self {
743 match *self {
744 Self::Enabled {
745 setting_configuration,
746 ..
747 } => Self::Enabled {
748 setting_configuration,
749 toggle_override: setting_configuration,
750 },
751 Self::Disabled => Self::Disabled,
752 }
753 }
754
755 fn disabled(&self) -> bool {
756 match *self {
757 Self::Disabled => true,
758 _ => false,
759 }
760 }
761
762 fn settings_visibility(&self) -> bool {
763 match *self {
764 Self::Enabled {
765 setting_configuration,
766 ..
767 } => setting_configuration,
768 _ => false,
769 }
770 }
771
772 fn visible(&self) -> bool {
773 match *self {
774 Self::Enabled {
775 setting_configuration,
776 toggle_override,
777 } => setting_configuration ^ toggle_override,
778 _ => false,
779 }
780 }
781
782 fn toggle_visibility(&self) -> Self {
783 match *self {
784 Self::Enabled {
785 toggle_override,
786 setting_configuration,
787 } => Self::Enabled {
788 setting_configuration,
789 toggle_override: !toggle_override,
790 },
791 Self::Disabled => Self::Disabled,
792 }
793 }
794}
795
796#[derive(Clone, Debug)]
797struct RunnableTasks {
798 templates: Vec<(TaskSourceKind, TaskTemplate)>,
799 offset: multi_buffer::Anchor,
800 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
801 column: u32,
802 // Values of all named captures, including those starting with '_'
803 extra_variables: HashMap<String, String>,
804 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
805 context_range: Range<BufferOffset>,
806}
807
808impl RunnableTasks {
809 fn resolve<'a>(
810 &'a self,
811 cx: &'a task::TaskContext,
812 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
813 self.templates.iter().filter_map(|(kind, template)| {
814 template
815 .resolve_task(&kind.to_id_base(), cx)
816 .map(|task| (kind.clone(), task))
817 })
818 }
819}
820
821#[derive(Clone)]
822pub struct ResolvedTasks {
823 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
824 position: Anchor,
825}
826
827#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
828struct BufferOffset(usize);
829
830// Addons allow storing per-editor state in other crates (e.g. Vim)
831pub trait Addon: 'static {
832 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
833
834 fn render_buffer_header_controls(
835 &self,
836 _: &ExcerptInfo,
837 _: &Window,
838 _: &App,
839 ) -> Option<AnyElement> {
840 None
841 }
842
843 fn to_any(&self) -> &dyn std::any::Any;
844
845 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
846 None
847 }
848}
849
850/// A set of caret positions, registered when the editor was edited.
851pub struct ChangeList {
852 changes: Vec<Vec<Anchor>>,
853 /// Currently "selected" change.
854 position: Option<usize>,
855}
856
857impl ChangeList {
858 pub fn new() -> Self {
859 Self {
860 changes: Vec::new(),
861 position: None,
862 }
863 }
864
865 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
866 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
867 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
868 if self.changes.is_empty() {
869 return None;
870 }
871
872 let prev = self.position.unwrap_or(self.changes.len());
873 let next = if direction == Direction::Prev {
874 prev.saturating_sub(count)
875 } else {
876 (prev + count).min(self.changes.len() - 1)
877 };
878 self.position = Some(next);
879 self.changes.get(next).map(|anchors| anchors.as_slice())
880 }
881
882 /// Adds a new change to the list, resetting the change list position.
883 pub fn push_to_change_list(&mut self, pop_state: bool, new_positions: Vec<Anchor>) {
884 self.position.take();
885 if pop_state {
886 self.changes.pop();
887 }
888 self.changes.push(new_positions.clone());
889 }
890
891 pub fn last(&self) -> Option<&[Anchor]> {
892 self.changes.last().map(|anchors| anchors.as_slice())
893 }
894}
895
896#[derive(Clone)]
897struct InlineBlamePopoverState {
898 scroll_handle: ScrollHandle,
899 commit_message: Option<ParsedCommitMessage>,
900 markdown: Entity<Markdown>,
901}
902
903struct InlineBlamePopover {
904 position: gpui::Point<Pixels>,
905 show_task: Option<Task<()>>,
906 hide_task: Option<Task<()>>,
907 popover_bounds: Option<Bounds<Pixels>>,
908 popover_state: InlineBlamePopoverState,
909}
910
911/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
912/// a breakpoint on them.
913#[derive(Clone, Copy, Debug, PartialEq, Eq)]
914struct PhantomBreakpointIndicator {
915 display_row: DisplayRow,
916 /// There's a small debounce between hovering over the line and showing the indicator.
917 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
918 is_active: bool,
919 collides_with_existing_breakpoint: bool,
920}
921
922/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
923///
924/// See the [module level documentation](self) for more information.
925pub struct Editor {
926 focus_handle: FocusHandle,
927 last_focused_descendant: Option<WeakFocusHandle>,
928 /// The text buffer being edited
929 buffer: Entity<MultiBuffer>,
930 /// Map of how text in the buffer should be displayed.
931 /// Handles soft wraps, folds, fake inlay text insertions, etc.
932 pub display_map: Entity<DisplayMap>,
933 pub selections: SelectionsCollection,
934 pub scroll_manager: ScrollManager,
935 /// When inline assist editors are linked, they all render cursors because
936 /// typing enters text into each of them, even the ones that aren't focused.
937 pub(crate) show_cursor_when_unfocused: bool,
938 columnar_selection_tail: Option<Anchor>,
939 columnar_display_point: Option<DisplayPoint>,
940 add_selections_state: Option<AddSelectionsState>,
941 select_next_state: Option<SelectNextState>,
942 select_prev_state: Option<SelectNextState>,
943 selection_history: SelectionHistory,
944 defer_selection_effects: bool,
945 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
946 autoclose_regions: Vec<AutocloseRegion>,
947 snippet_stack: InvalidationStack<SnippetState>,
948 select_syntax_node_history: SelectSyntaxNodeHistory,
949 ime_transaction: Option<TransactionId>,
950 pub diagnostics_max_severity: DiagnosticSeverity,
951 active_diagnostics: ActiveDiagnostic,
952 show_inline_diagnostics: bool,
953 inline_diagnostics_update: Task<()>,
954 inline_diagnostics_enabled: bool,
955 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
956 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
957 hard_wrap: Option<usize>,
958
959 // TODO: make this a access method
960 pub project: Option<Entity<Project>>,
961 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
962 completion_provider: Option<Rc<dyn CompletionProvider>>,
963 collaboration_hub: Option<Box<dyn CollaborationHub>>,
964 blink_manager: Entity<BlinkManager>,
965 show_cursor_names: bool,
966 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
967 pub show_local_selections: bool,
968 mode: EditorMode,
969 show_breadcrumbs: bool,
970 show_gutter: bool,
971 show_scrollbars: ScrollbarAxes,
972 minimap_visibility: MinimapVisibility,
973 offset_content: bool,
974 disable_expand_excerpt_buttons: bool,
975 show_line_numbers: Option<bool>,
976 use_relative_line_numbers: Option<bool>,
977 show_git_diff_gutter: Option<bool>,
978 show_code_actions: Option<bool>,
979 show_runnables: Option<bool>,
980 show_breakpoints: Option<bool>,
981 show_wrap_guides: Option<bool>,
982 show_indent_guides: Option<bool>,
983 placeholder_text: Option<Arc<str>>,
984 highlight_order: usize,
985 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
986 background_highlights: TreeMap<TypeId, BackgroundHighlight>,
987 gutter_highlights: TreeMap<TypeId, GutterHighlight>,
988 scrollbar_marker_state: ScrollbarMarkerState,
989 active_indent_guides_state: ActiveIndentGuidesState,
990 nav_history: Option<ItemNavHistory>,
991 context_menu: RefCell<Option<CodeContextMenu>>,
992 context_menu_options: Option<ContextMenuOptions>,
993 mouse_context_menu: Option<MouseContextMenu>,
994 completion_tasks: Vec<(CompletionId, Task<()>)>,
995 inline_blame_popover: Option<InlineBlamePopover>,
996 signature_help_state: SignatureHelpState,
997 auto_signature_help: Option<bool>,
998 find_all_references_task_sources: Vec<Anchor>,
999 next_completion_id: CompletionId,
1000 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1001 code_actions_task: Option<Task<Result<()>>>,
1002 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1003 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1004 document_highlights_task: Option<Task<()>>,
1005 linked_editing_range_task: Option<Task<Option<()>>>,
1006 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1007 pending_rename: Option<RenameState>,
1008 searchable: bool,
1009 cursor_shape: CursorShape,
1010 current_line_highlight: Option<CurrentLineHighlight>,
1011 collapse_matches: bool,
1012 autoindent_mode: Option<AutoindentMode>,
1013 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1014 input_enabled: bool,
1015 use_modal_editing: bool,
1016 read_only: bool,
1017 leader_id: Option<CollaboratorId>,
1018 remote_id: Option<ViewId>,
1019 pub hover_state: HoverState,
1020 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1021 gutter_hovered: bool,
1022 hovered_link_state: Option<HoveredLinkState>,
1023 edit_prediction_provider: Option<RegisteredInlineCompletionProvider>,
1024 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1025 active_inline_completion: Option<InlineCompletionState>,
1026 /// Used to prevent flickering as the user types while the menu is open
1027 stale_inline_completion_in_menu: Option<InlineCompletionState>,
1028 edit_prediction_settings: EditPredictionSettings,
1029 inline_completions_hidden_for_vim_mode: bool,
1030 show_inline_completions_override: Option<bool>,
1031 menu_inline_completions_policy: MenuInlineCompletionsPolicy,
1032 edit_prediction_preview: EditPredictionPreview,
1033 edit_prediction_indent_conflict: bool,
1034 edit_prediction_requires_modifier_in_indent_conflict: bool,
1035 inlay_hint_cache: InlayHintCache,
1036 next_inlay_id: usize,
1037 _subscriptions: Vec<Subscription>,
1038 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1039 gutter_dimensions: GutterDimensions,
1040 style: Option<EditorStyle>,
1041 text_style_refinement: Option<TextStyleRefinement>,
1042 next_editor_action_id: EditorActionId,
1043 editor_actions:
1044 Rc<RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&mut Window, &mut Context<Self>)>>>>,
1045 use_autoclose: bool,
1046 use_auto_surround: bool,
1047 auto_replace_emoji_shortcode: bool,
1048 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1049 show_git_blame_gutter: bool,
1050 show_git_blame_inline: bool,
1051 show_git_blame_inline_delay_task: Option<Task<()>>,
1052 git_blame_inline_enabled: bool,
1053 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1054 serialize_dirty_buffers: bool,
1055 show_selection_menu: Option<bool>,
1056 blame: Option<Entity<GitBlame>>,
1057 blame_subscription: Option<Subscription>,
1058 custom_context_menu: Option<
1059 Box<
1060 dyn 'static
1061 + Fn(
1062 &mut Self,
1063 DisplayPoint,
1064 &mut Window,
1065 &mut Context<Self>,
1066 ) -> Option<Entity<ui::ContextMenu>>,
1067 >,
1068 >,
1069 last_bounds: Option<Bounds<Pixels>>,
1070 last_position_map: Option<Rc<PositionMap>>,
1071 expect_bounds_change: Option<Bounds<Pixels>>,
1072 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1073 tasks_update_task: Option<Task<()>>,
1074 breakpoint_store: Option<Entity<BreakpointStore>>,
1075 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1076 in_project_search: bool,
1077 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1078 breadcrumb_header: Option<String>,
1079 focused_block: Option<FocusedBlock>,
1080 next_scroll_position: NextScrollCursorCenterTopBottom,
1081 addons: HashMap<TypeId, Box<dyn Addon>>,
1082 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1083 load_diff_task: Option<Shared<Task<()>>>,
1084 /// Whether we are temporarily displaying a diff other than git's
1085 temporary_diff_override: bool,
1086 selection_mark_mode: bool,
1087 toggle_fold_multiple_buffers: Task<()>,
1088 _scroll_cursor_center_top_bottom_task: Task<()>,
1089 serialize_selections: Task<()>,
1090 serialize_folds: Task<()>,
1091 mouse_cursor_hidden: bool,
1092 minimap: Option<Entity<Self>>,
1093 hide_mouse_mode: HideMouseMode,
1094 pub change_list: ChangeList,
1095 inline_value_cache: InlineValueCache,
1096}
1097
1098#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1099enum NextScrollCursorCenterTopBottom {
1100 #[default]
1101 Center,
1102 Top,
1103 Bottom,
1104}
1105
1106impl NextScrollCursorCenterTopBottom {
1107 fn next(&self) -> Self {
1108 match self {
1109 Self::Center => Self::Top,
1110 Self::Top => Self::Bottom,
1111 Self::Bottom => Self::Center,
1112 }
1113 }
1114}
1115
1116#[derive(Clone)]
1117pub struct EditorSnapshot {
1118 pub mode: EditorMode,
1119 show_gutter: bool,
1120 show_line_numbers: Option<bool>,
1121 show_git_diff_gutter: Option<bool>,
1122 show_code_actions: Option<bool>,
1123 show_runnables: Option<bool>,
1124 show_breakpoints: Option<bool>,
1125 git_blame_gutter_max_author_length: Option<usize>,
1126 pub display_snapshot: DisplaySnapshot,
1127 pub placeholder_text: Option<Arc<str>>,
1128 is_focused: bool,
1129 scroll_anchor: ScrollAnchor,
1130 ongoing_scroll: OngoingScroll,
1131 current_line_highlight: CurrentLineHighlight,
1132 gutter_hovered: bool,
1133}
1134
1135#[derive(Default, Debug, Clone, Copy)]
1136pub struct GutterDimensions {
1137 pub left_padding: Pixels,
1138 pub right_padding: Pixels,
1139 pub width: Pixels,
1140 pub margin: Pixels,
1141 pub git_blame_entries_width: Option<Pixels>,
1142}
1143
1144impl GutterDimensions {
1145 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1146 Self {
1147 margin: Self::default_gutter_margin(font_id, font_size, cx),
1148 ..Default::default()
1149 }
1150 }
1151
1152 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1153 -cx.text_system().descent(font_id, font_size)
1154 }
1155 /// The full width of the space taken up by the gutter.
1156 pub fn full_width(&self) -> Pixels {
1157 self.margin + self.width
1158 }
1159
1160 /// The width of the space reserved for the fold indicators,
1161 /// use alongside 'justify_end' and `gutter_width` to
1162 /// right align content with the line numbers
1163 pub fn fold_area_width(&self) -> Pixels {
1164 self.margin + self.right_padding
1165 }
1166}
1167
1168#[derive(Debug)]
1169pub struct RemoteSelection {
1170 pub replica_id: ReplicaId,
1171 pub selection: Selection<Anchor>,
1172 pub cursor_shape: CursorShape,
1173 pub collaborator_id: CollaboratorId,
1174 pub line_mode: bool,
1175 pub user_name: Option<SharedString>,
1176 pub color: PlayerColor,
1177}
1178
1179#[derive(Clone, Debug)]
1180struct SelectionHistoryEntry {
1181 selections: Arc<[Selection<Anchor>]>,
1182 select_next_state: Option<SelectNextState>,
1183 select_prev_state: Option<SelectNextState>,
1184 add_selections_state: Option<AddSelectionsState>,
1185}
1186
1187enum SelectionHistoryMode {
1188 Normal,
1189 Undoing,
1190 Redoing,
1191}
1192
1193#[derive(Clone, PartialEq, Eq, Hash)]
1194struct HoveredCursor {
1195 replica_id: u16,
1196 selection_id: usize,
1197}
1198
1199impl Default for SelectionHistoryMode {
1200 fn default() -> Self {
1201 Self::Normal
1202 }
1203}
1204
1205struct DeferredSelectionEffectsState {
1206 changed: bool,
1207 should_update_completions: bool,
1208 autoscroll: Option<Autoscroll>,
1209 old_cursor_position: Anchor,
1210 history_entry: SelectionHistoryEntry,
1211}
1212
1213#[derive(Default)]
1214struct SelectionHistory {
1215 #[allow(clippy::type_complexity)]
1216 selections_by_transaction:
1217 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1218 mode: SelectionHistoryMode,
1219 undo_stack: VecDeque<SelectionHistoryEntry>,
1220 redo_stack: VecDeque<SelectionHistoryEntry>,
1221}
1222
1223impl SelectionHistory {
1224 fn insert_transaction(
1225 &mut self,
1226 transaction_id: TransactionId,
1227 selections: Arc<[Selection<Anchor>]>,
1228 ) {
1229 self.selections_by_transaction
1230 .insert(transaction_id, (selections, None));
1231 }
1232
1233 #[allow(clippy::type_complexity)]
1234 fn transaction(
1235 &self,
1236 transaction_id: TransactionId,
1237 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1238 self.selections_by_transaction.get(&transaction_id)
1239 }
1240
1241 #[allow(clippy::type_complexity)]
1242 fn transaction_mut(
1243 &mut self,
1244 transaction_id: TransactionId,
1245 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1246 self.selections_by_transaction.get_mut(&transaction_id)
1247 }
1248
1249 fn push(&mut self, entry: SelectionHistoryEntry) {
1250 if !entry.selections.is_empty() {
1251 match self.mode {
1252 SelectionHistoryMode::Normal => {
1253 self.push_undo(entry);
1254 self.redo_stack.clear();
1255 }
1256 SelectionHistoryMode::Undoing => self.push_redo(entry),
1257 SelectionHistoryMode::Redoing => self.push_undo(entry),
1258 }
1259 }
1260 }
1261
1262 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1263 if self
1264 .undo_stack
1265 .back()
1266 .map_or(true, |e| e.selections != entry.selections)
1267 {
1268 self.undo_stack.push_back(entry);
1269 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1270 self.undo_stack.pop_front();
1271 }
1272 }
1273 }
1274
1275 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1276 if self
1277 .redo_stack
1278 .back()
1279 .map_or(true, |e| e.selections != entry.selections)
1280 {
1281 self.redo_stack.push_back(entry);
1282 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1283 self.redo_stack.pop_front();
1284 }
1285 }
1286 }
1287}
1288
1289#[derive(Clone, Copy)]
1290pub struct RowHighlightOptions {
1291 pub autoscroll: bool,
1292 pub include_gutter: bool,
1293}
1294
1295impl Default for RowHighlightOptions {
1296 fn default() -> Self {
1297 Self {
1298 autoscroll: Default::default(),
1299 include_gutter: true,
1300 }
1301 }
1302}
1303
1304struct RowHighlight {
1305 index: usize,
1306 range: Range<Anchor>,
1307 color: Hsla,
1308 options: RowHighlightOptions,
1309 type_id: TypeId,
1310}
1311
1312#[derive(Clone, Debug)]
1313struct AddSelectionsState {
1314 above: bool,
1315 stack: Vec<usize>,
1316}
1317
1318#[derive(Clone)]
1319struct SelectNextState {
1320 query: AhoCorasick,
1321 wordwise: bool,
1322 done: bool,
1323}
1324
1325impl std::fmt::Debug for SelectNextState {
1326 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1327 f.debug_struct(std::any::type_name::<Self>())
1328 .field("wordwise", &self.wordwise)
1329 .field("done", &self.done)
1330 .finish()
1331 }
1332}
1333
1334#[derive(Debug)]
1335struct AutocloseRegion {
1336 selection_id: usize,
1337 range: Range<Anchor>,
1338 pair: BracketPair,
1339}
1340
1341#[derive(Debug)]
1342struct SnippetState {
1343 ranges: Vec<Vec<Range<Anchor>>>,
1344 active_index: usize,
1345 choices: Vec<Option<Vec<String>>>,
1346}
1347
1348#[doc(hidden)]
1349pub struct RenameState {
1350 pub range: Range<Anchor>,
1351 pub old_name: Arc<str>,
1352 pub editor: Entity<Editor>,
1353 block_id: CustomBlockId,
1354}
1355
1356struct InvalidationStack<T>(Vec<T>);
1357
1358struct RegisteredInlineCompletionProvider {
1359 provider: Arc<dyn InlineCompletionProviderHandle>,
1360 _subscription: Subscription,
1361}
1362
1363#[derive(Debug, PartialEq, Eq)]
1364pub struct ActiveDiagnosticGroup {
1365 pub active_range: Range<Anchor>,
1366 pub active_message: String,
1367 pub group_id: usize,
1368 pub blocks: HashSet<CustomBlockId>,
1369}
1370
1371#[derive(Debug, PartialEq, Eq)]
1372
1373pub(crate) enum ActiveDiagnostic {
1374 None,
1375 All,
1376 Group(ActiveDiagnosticGroup),
1377}
1378
1379#[derive(Serialize, Deserialize, Clone, Debug)]
1380pub struct ClipboardSelection {
1381 /// The number of bytes in this selection.
1382 pub len: usize,
1383 /// Whether this was a full-line selection.
1384 pub is_entire_line: bool,
1385 /// The indentation of the first line when this content was originally copied.
1386 pub first_line_indent: u32,
1387}
1388
1389// selections, scroll behavior, was newest selection reversed
1390type SelectSyntaxNodeHistoryState = (
1391 Box<[Selection<usize>]>,
1392 SelectSyntaxNodeScrollBehavior,
1393 bool,
1394);
1395
1396#[derive(Default)]
1397struct SelectSyntaxNodeHistory {
1398 stack: Vec<SelectSyntaxNodeHistoryState>,
1399 // disable temporarily to allow changing selections without losing the stack
1400 pub disable_clearing: bool,
1401}
1402
1403impl SelectSyntaxNodeHistory {
1404 pub fn try_clear(&mut self) {
1405 if !self.disable_clearing {
1406 self.stack.clear();
1407 }
1408 }
1409
1410 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1411 self.stack.push(selection);
1412 }
1413
1414 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1415 self.stack.pop()
1416 }
1417}
1418
1419enum SelectSyntaxNodeScrollBehavior {
1420 CursorTop,
1421 FitSelection,
1422 CursorBottom,
1423}
1424
1425#[derive(Debug)]
1426pub(crate) struct NavigationData {
1427 cursor_anchor: Anchor,
1428 cursor_position: Point,
1429 scroll_anchor: ScrollAnchor,
1430 scroll_top_row: u32,
1431}
1432
1433#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1434pub enum GotoDefinitionKind {
1435 Symbol,
1436 Declaration,
1437 Type,
1438 Implementation,
1439}
1440
1441#[derive(Debug, Clone)]
1442enum InlayHintRefreshReason {
1443 ModifiersChanged(bool),
1444 Toggle(bool),
1445 SettingsChange(InlayHintSettings),
1446 NewLinesShown,
1447 BufferEdited(HashSet<Arc<Language>>),
1448 RefreshRequested,
1449 ExcerptsRemoved(Vec<ExcerptId>),
1450}
1451
1452impl InlayHintRefreshReason {
1453 fn description(&self) -> &'static str {
1454 match self {
1455 Self::ModifiersChanged(_) => "modifiers changed",
1456 Self::Toggle(_) => "toggle",
1457 Self::SettingsChange(_) => "settings change",
1458 Self::NewLinesShown => "new lines shown",
1459 Self::BufferEdited(_) => "buffer edited",
1460 Self::RefreshRequested => "refresh requested",
1461 Self::ExcerptsRemoved(_) => "excerpts removed",
1462 }
1463 }
1464}
1465
1466pub enum FormatTarget {
1467 Buffers,
1468 Ranges(Vec<Range<MultiBufferPoint>>),
1469}
1470
1471pub(crate) struct FocusedBlock {
1472 id: BlockId,
1473 focus_handle: WeakFocusHandle,
1474}
1475
1476#[derive(Clone)]
1477enum JumpData {
1478 MultiBufferRow {
1479 row: MultiBufferRow,
1480 line_offset_from_top: u32,
1481 },
1482 MultiBufferPoint {
1483 excerpt_id: ExcerptId,
1484 position: Point,
1485 anchor: text::Anchor,
1486 line_offset_from_top: u32,
1487 },
1488}
1489
1490pub enum MultibufferSelectionMode {
1491 First,
1492 All,
1493}
1494
1495#[derive(Clone, Copy, Debug, Default)]
1496pub struct RewrapOptions {
1497 pub override_language_settings: bool,
1498 pub preserve_existing_whitespace: bool,
1499}
1500
1501impl Editor {
1502 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1503 let buffer = cx.new(|cx| Buffer::local("", cx));
1504 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1505 Self::new(
1506 EditorMode::SingleLine { auto_width: false },
1507 buffer,
1508 None,
1509 window,
1510 cx,
1511 )
1512 }
1513
1514 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1515 let buffer = cx.new(|cx| Buffer::local("", cx));
1516 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1517 Self::new(EditorMode::full(), buffer, None, window, cx)
1518 }
1519
1520 pub fn auto_width(window: &mut Window, cx: &mut Context<Self>) -> Self {
1521 let buffer = cx.new(|cx| Buffer::local("", cx));
1522 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1523 Self::new(
1524 EditorMode::SingleLine { auto_width: true },
1525 buffer,
1526 None,
1527 window,
1528 cx,
1529 )
1530 }
1531
1532 pub fn auto_height(max_lines: usize, window: &mut Window, cx: &mut Context<Self>) -> Self {
1533 let buffer = cx.new(|cx| Buffer::local("", cx));
1534 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1535 Self::new(
1536 EditorMode::AutoHeight { max_lines },
1537 buffer,
1538 None,
1539 window,
1540 cx,
1541 )
1542 }
1543
1544 pub fn for_buffer(
1545 buffer: Entity<Buffer>,
1546 project: Option<Entity<Project>>,
1547 window: &mut Window,
1548 cx: &mut Context<Self>,
1549 ) -> Self {
1550 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1551 Self::new(EditorMode::full(), buffer, project, window, cx)
1552 }
1553
1554 pub fn for_multibuffer(
1555 buffer: Entity<MultiBuffer>,
1556 project: Option<Entity<Project>>,
1557 window: &mut Window,
1558 cx: &mut Context<Self>,
1559 ) -> Self {
1560 Self::new(EditorMode::full(), buffer, project, window, cx)
1561 }
1562
1563 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1564 let mut clone = Self::new(
1565 self.mode.clone(),
1566 self.buffer.clone(),
1567 self.project.clone(),
1568 window,
1569 cx,
1570 );
1571 self.display_map.update(cx, |display_map, cx| {
1572 let snapshot = display_map.snapshot(cx);
1573 clone.display_map.update(cx, |display_map, cx| {
1574 display_map.set_state(&snapshot, cx);
1575 });
1576 });
1577 clone.folds_did_change(cx);
1578 clone.selections.clone_state(&self.selections);
1579 clone.scroll_manager.clone_state(&self.scroll_manager);
1580 clone.searchable = self.searchable;
1581 clone.read_only = self.read_only;
1582 clone
1583 }
1584
1585 pub fn new(
1586 mode: EditorMode,
1587 buffer: Entity<MultiBuffer>,
1588 project: Option<Entity<Project>>,
1589 window: &mut Window,
1590 cx: &mut Context<Self>,
1591 ) -> Self {
1592 Editor::new_internal(mode, buffer, project, None, window, cx)
1593 }
1594
1595 fn new_internal(
1596 mode: EditorMode,
1597 buffer: Entity<MultiBuffer>,
1598 project: Option<Entity<Project>>,
1599 display_map: Option<Entity<DisplayMap>>,
1600 window: &mut Window,
1601 cx: &mut Context<Self>,
1602 ) -> Self {
1603 debug_assert!(
1604 display_map.is_none() || mode.is_minimap(),
1605 "Providing a display map for a new editor is only intended for the minimap and might have unindended side effects otherwise!"
1606 );
1607
1608 let full_mode = mode.is_full();
1609 let diagnostics_max_severity = if full_mode {
1610 EditorSettings::get_global(cx)
1611 .diagnostics_max_severity
1612 .unwrap_or(DiagnosticSeverity::Hint)
1613 } else {
1614 DiagnosticSeverity::Off
1615 };
1616 let style = window.text_style();
1617 let font_size = style.font_size.to_pixels(window.rem_size());
1618 let editor = cx.entity().downgrade();
1619 let fold_placeholder = FoldPlaceholder {
1620 constrain_width: true,
1621 render: Arc::new(move |fold_id, fold_range, cx| {
1622 let editor = editor.clone();
1623 div()
1624 .id(fold_id)
1625 .bg(cx.theme().colors().ghost_element_background)
1626 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1627 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1628 .rounded_xs()
1629 .size_full()
1630 .cursor_pointer()
1631 .child("⋯")
1632 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1633 .on_click(move |_, _window, cx| {
1634 editor
1635 .update(cx, |editor, cx| {
1636 editor.unfold_ranges(
1637 &[fold_range.start..fold_range.end],
1638 true,
1639 false,
1640 cx,
1641 );
1642 cx.stop_propagation();
1643 })
1644 .ok();
1645 })
1646 .into_any()
1647 }),
1648 merge_adjacent: true,
1649 ..FoldPlaceholder::default()
1650 };
1651 let display_map = display_map.unwrap_or_else(|| {
1652 cx.new(|cx| {
1653 DisplayMap::new(
1654 buffer.clone(),
1655 style.font(),
1656 font_size,
1657 None,
1658 FILE_HEADER_HEIGHT,
1659 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1660 fold_placeholder,
1661 diagnostics_max_severity,
1662 cx,
1663 )
1664 })
1665 });
1666
1667 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1668
1669 let blink_manager = cx.new(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx));
1670
1671 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1672 .then(|| language_settings::SoftWrap::None);
1673
1674 let mut project_subscriptions = Vec::new();
1675 if mode.is_full() {
1676 if let Some(project) = project.as_ref() {
1677 project_subscriptions.push(cx.subscribe_in(
1678 project,
1679 window,
1680 |editor, _, event, window, cx| match event {
1681 project::Event::RefreshCodeLens => {
1682 // we always query lens with actions, without storing them, always refreshing them
1683 }
1684 project::Event::RefreshInlayHints => {
1685 editor
1686 .refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1687 }
1688 project::Event::LanguageServerAdded(..)
1689 | project::Event::LanguageServerRemoved(..) => {
1690 if editor.tasks_update_task.is_none() {
1691 editor.tasks_update_task =
1692 Some(editor.refresh_runnables(window, cx));
1693 }
1694 }
1695 project::Event::SnippetEdit(id, snippet_edits) => {
1696 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1697 let focus_handle = editor.focus_handle(cx);
1698 if focus_handle.is_focused(window) {
1699 let snapshot = buffer.read(cx).snapshot();
1700 for (range, snippet) in snippet_edits {
1701 let editor_range =
1702 language::range_from_lsp(*range).to_offset(&snapshot);
1703 editor
1704 .insert_snippet(
1705 &[editor_range],
1706 snippet.clone(),
1707 window,
1708 cx,
1709 )
1710 .ok();
1711 }
1712 }
1713 }
1714 }
1715 _ => {}
1716 },
1717 ));
1718 if let Some(task_inventory) = project
1719 .read(cx)
1720 .task_store()
1721 .read(cx)
1722 .task_inventory()
1723 .cloned()
1724 {
1725 project_subscriptions.push(cx.observe_in(
1726 &task_inventory,
1727 window,
1728 |editor, _, window, cx| {
1729 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1730 },
1731 ));
1732 };
1733
1734 project_subscriptions.push(cx.subscribe_in(
1735 &project.read(cx).breakpoint_store(),
1736 window,
1737 |editor, _, event, window, cx| match event {
1738 BreakpointStoreEvent::ClearDebugLines => {
1739 editor.clear_row_highlights::<ActiveDebugLine>();
1740 editor.refresh_inline_values(cx);
1741 }
1742 BreakpointStoreEvent::SetDebugLine => {
1743 if editor.go_to_active_debug_line(window, cx) {
1744 cx.stop_propagation();
1745 }
1746
1747 editor.refresh_inline_values(cx);
1748 }
1749 _ => {}
1750 },
1751 ));
1752 }
1753 }
1754
1755 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1756
1757 let inlay_hint_settings =
1758 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1759 let focus_handle = cx.focus_handle();
1760 cx.on_focus(&focus_handle, window, Self::handle_focus)
1761 .detach();
1762 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1763 .detach();
1764 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1765 .detach();
1766 cx.on_blur(&focus_handle, window, Self::handle_blur)
1767 .detach();
1768
1769 let show_indent_guides = if matches!(mode, EditorMode::SingleLine { .. }) {
1770 Some(false)
1771 } else {
1772 None
1773 };
1774
1775 let breakpoint_store = match (&mode, project.as_ref()) {
1776 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
1777 _ => None,
1778 };
1779
1780 let mut code_action_providers = Vec::new();
1781 let mut load_uncommitted_diff = None;
1782 if let Some(project) = project.clone() {
1783 load_uncommitted_diff = Some(
1784 update_uncommitted_diff_for_buffer(
1785 cx.entity(),
1786 &project,
1787 buffer.read(cx).all_buffers(),
1788 buffer.clone(),
1789 cx,
1790 )
1791 .shared(),
1792 );
1793 code_action_providers.push(Rc::new(project) as Rc<_>);
1794 }
1795
1796 let mut this = Self {
1797 focus_handle,
1798 show_cursor_when_unfocused: false,
1799 last_focused_descendant: None,
1800 buffer: buffer.clone(),
1801 display_map: display_map.clone(),
1802 selections,
1803 scroll_manager: ScrollManager::new(cx),
1804 columnar_selection_tail: None,
1805 columnar_display_point: None,
1806 add_selections_state: None,
1807 select_next_state: None,
1808 select_prev_state: None,
1809 selection_history: SelectionHistory::default(),
1810 defer_selection_effects: false,
1811 deferred_selection_effects_state: None,
1812 autoclose_regions: Vec::new(),
1813 snippet_stack: InvalidationStack::default(),
1814 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
1815 ime_transaction: None,
1816 active_diagnostics: ActiveDiagnostic::None,
1817 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
1818 inline_diagnostics_update: Task::ready(()),
1819 inline_diagnostics: Vec::new(),
1820 soft_wrap_mode_override,
1821 diagnostics_max_severity,
1822 hard_wrap: None,
1823 completion_provider: project.clone().map(|project| Rc::new(project) as _),
1824 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
1825 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
1826 project,
1827 blink_manager: blink_manager.clone(),
1828 show_local_selections: true,
1829 show_scrollbars: ScrollbarAxes {
1830 horizontal: full_mode,
1831 vertical: full_mode,
1832 },
1833 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
1834 offset_content: !matches!(mode, EditorMode::SingleLine { .. }),
1835 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
1836 show_gutter: mode.is_full(),
1837 show_line_numbers: None,
1838 use_relative_line_numbers: None,
1839 disable_expand_excerpt_buttons: false,
1840 show_git_diff_gutter: None,
1841 show_code_actions: None,
1842 show_runnables: None,
1843 show_breakpoints: None,
1844 show_wrap_guides: None,
1845 show_indent_guides,
1846 placeholder_text: None,
1847 highlight_order: 0,
1848 highlighted_rows: HashMap::default(),
1849 background_highlights: TreeMap::default(),
1850 gutter_highlights: TreeMap::default(),
1851 scrollbar_marker_state: ScrollbarMarkerState::default(),
1852 active_indent_guides_state: ActiveIndentGuidesState::default(),
1853 nav_history: None,
1854 context_menu: RefCell::new(None),
1855 context_menu_options: None,
1856 mouse_context_menu: None,
1857 completion_tasks: Vec::new(),
1858 inline_blame_popover: None,
1859 signature_help_state: SignatureHelpState::default(),
1860 auto_signature_help: None,
1861 find_all_references_task_sources: Vec::new(),
1862 next_completion_id: 0,
1863 next_inlay_id: 0,
1864 code_action_providers,
1865 available_code_actions: None,
1866 code_actions_task: None,
1867 quick_selection_highlight_task: None,
1868 debounced_selection_highlight_task: None,
1869 document_highlights_task: None,
1870 linked_editing_range_task: None,
1871 pending_rename: None,
1872 searchable: true,
1873 cursor_shape: EditorSettings::get_global(cx)
1874 .cursor_shape
1875 .unwrap_or_default(),
1876 current_line_highlight: None,
1877 autoindent_mode: Some(AutoindentMode::EachLine),
1878 collapse_matches: false,
1879 workspace: None,
1880 input_enabled: true,
1881 use_modal_editing: mode.is_full(),
1882 read_only: mode.is_minimap(),
1883 use_autoclose: true,
1884 use_auto_surround: true,
1885 auto_replace_emoji_shortcode: false,
1886 jsx_tag_auto_close_enabled_in_any_buffer: false,
1887 leader_id: None,
1888 remote_id: None,
1889 hover_state: HoverState::default(),
1890 pending_mouse_down: None,
1891 hovered_link_state: None,
1892 edit_prediction_provider: None,
1893 active_inline_completion: None,
1894 stale_inline_completion_in_menu: None,
1895 edit_prediction_preview: EditPredictionPreview::Inactive {
1896 released_too_fast: false,
1897 },
1898 inline_diagnostics_enabled: mode.is_full(),
1899 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
1900 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
1901
1902 gutter_hovered: false,
1903 pixel_position_of_newest_cursor: None,
1904 last_bounds: None,
1905 last_position_map: None,
1906 expect_bounds_change: None,
1907 gutter_dimensions: GutterDimensions::default(),
1908 style: None,
1909 show_cursor_names: false,
1910 hovered_cursors: HashMap::default(),
1911 next_editor_action_id: EditorActionId::default(),
1912 editor_actions: Rc::default(),
1913 inline_completions_hidden_for_vim_mode: false,
1914 show_inline_completions_override: None,
1915 menu_inline_completions_policy: MenuInlineCompletionsPolicy::ByProvider,
1916 edit_prediction_settings: EditPredictionSettings::Disabled,
1917 edit_prediction_indent_conflict: false,
1918 edit_prediction_requires_modifier_in_indent_conflict: true,
1919 custom_context_menu: None,
1920 show_git_blame_gutter: false,
1921 show_git_blame_inline: false,
1922 show_selection_menu: None,
1923 show_git_blame_inline_delay_task: None,
1924 git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(),
1925 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
1926 serialize_dirty_buffers: !mode.is_minimap()
1927 && ProjectSettings::get_global(cx)
1928 .session
1929 .restore_unsaved_buffers,
1930 blame: None,
1931 blame_subscription: None,
1932 tasks: BTreeMap::default(),
1933
1934 breakpoint_store,
1935 gutter_breakpoint_indicator: (None, None),
1936 _subscriptions: vec![
1937 cx.observe(&buffer, Self::on_buffer_changed),
1938 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
1939 cx.observe_in(&display_map, window, Self::on_display_map_changed),
1940 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
1941 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
1942 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
1943 cx.observe_window_activation(window, |editor, window, cx| {
1944 let active = window.is_window_active();
1945 editor.blink_manager.update(cx, |blink_manager, cx| {
1946 if active {
1947 blink_manager.enable(cx);
1948 } else {
1949 blink_manager.disable(cx);
1950 }
1951 });
1952 if active {
1953 editor.show_mouse_cursor();
1954 }
1955 }),
1956 ],
1957 tasks_update_task: None,
1958 linked_edit_ranges: Default::default(),
1959 in_project_search: false,
1960 previous_search_ranges: None,
1961 breadcrumb_header: None,
1962 focused_block: None,
1963 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
1964 addons: HashMap::default(),
1965 registered_buffers: HashMap::default(),
1966 _scroll_cursor_center_top_bottom_task: Task::ready(()),
1967 selection_mark_mode: false,
1968 toggle_fold_multiple_buffers: Task::ready(()),
1969 serialize_selections: Task::ready(()),
1970 serialize_folds: Task::ready(()),
1971 text_style_refinement: None,
1972 load_diff_task: load_uncommitted_diff,
1973 temporary_diff_override: false,
1974 mouse_cursor_hidden: false,
1975 minimap: None,
1976 hide_mouse_mode: EditorSettings::get_global(cx)
1977 .hide_mouse
1978 .unwrap_or_default(),
1979 change_list: ChangeList::new(),
1980 mode,
1981 };
1982 if let Some(breakpoints) = this.breakpoint_store.as_ref() {
1983 this._subscriptions
1984 .push(cx.observe(breakpoints, |_, _, cx| {
1985 cx.notify();
1986 }));
1987 }
1988 this.tasks_update_task = Some(this.refresh_runnables(window, cx));
1989 this._subscriptions.extend(project_subscriptions);
1990
1991 this._subscriptions.push(cx.subscribe_in(
1992 &cx.entity(),
1993 window,
1994 |editor, _, e: &EditorEvent, window, cx| match e {
1995 EditorEvent::ScrollPositionChanged { local, .. } => {
1996 if *local {
1997 let new_anchor = editor.scroll_manager.anchor();
1998 let snapshot = editor.snapshot(window, cx);
1999 editor.update_restoration_data(cx, move |data| {
2000 data.scroll_position = (
2001 new_anchor.top_row(&snapshot.buffer_snapshot),
2002 new_anchor.offset,
2003 );
2004 });
2005 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2006 editor.inline_blame_popover.take();
2007 }
2008 }
2009 EditorEvent::Edited { .. } => {
2010 if !vim_enabled(cx) {
2011 let (map, selections) = editor.selections.all_adjusted_display(cx);
2012 let pop_state = editor
2013 .change_list
2014 .last()
2015 .map(|previous| {
2016 previous.len() == selections.len()
2017 && previous.iter().enumerate().all(|(ix, p)| {
2018 p.to_display_point(&map).row()
2019 == selections[ix].head().row()
2020 })
2021 })
2022 .unwrap_or(false);
2023 let new_positions = selections
2024 .into_iter()
2025 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
2026 .collect();
2027 editor
2028 .change_list
2029 .push_to_change_list(pop_state, new_positions);
2030 }
2031 }
2032 _ => (),
2033 },
2034 ));
2035
2036 if let Some(dap_store) = this
2037 .project
2038 .as_ref()
2039 .map(|project| project.read(cx).dap_store())
2040 {
2041 let weak_editor = cx.weak_entity();
2042
2043 this._subscriptions
2044 .push(
2045 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2046 let session_entity = cx.entity();
2047 weak_editor
2048 .update(cx, |editor, cx| {
2049 editor._subscriptions.push(
2050 cx.subscribe(&session_entity, Self::on_debug_session_event),
2051 );
2052 })
2053 .ok();
2054 }),
2055 );
2056
2057 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2058 this._subscriptions
2059 .push(cx.subscribe(&session, Self::on_debug_session_event));
2060 }
2061 }
2062
2063 this.end_selection(window, cx);
2064 this.scroll_manager.show_scrollbars(window, cx);
2065 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut this, &buffer, cx);
2066
2067 if full_mode {
2068 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2069 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2070
2071 if this.git_blame_inline_enabled {
2072 this.start_git_blame_inline(false, window, cx);
2073 }
2074
2075 this.go_to_active_debug_line(window, cx);
2076
2077 if let Some(buffer) = buffer.read(cx).as_singleton() {
2078 if let Some(project) = this.project.as_ref() {
2079 let handle = project.update(cx, |project, cx| {
2080 project.register_buffer_with_language_servers(&buffer, cx)
2081 });
2082 this.registered_buffers
2083 .insert(buffer.read(cx).remote_id(), handle);
2084 }
2085 }
2086
2087 this.minimap = this.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2088 }
2089
2090 this.report_editor_event("Editor Opened", None, cx);
2091 this
2092 }
2093
2094 pub fn deploy_mouse_context_menu(
2095 &mut self,
2096 position: gpui::Point<Pixels>,
2097 context_menu: Entity<ContextMenu>,
2098 window: &mut Window,
2099 cx: &mut Context<Self>,
2100 ) {
2101 self.mouse_context_menu = Some(MouseContextMenu::new(
2102 self,
2103 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2104 context_menu,
2105 window,
2106 cx,
2107 ));
2108 }
2109
2110 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2111 self.mouse_context_menu
2112 .as_ref()
2113 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2114 }
2115
2116 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2117 self.key_context_internal(self.has_active_inline_completion(), window, cx)
2118 }
2119
2120 fn key_context_internal(
2121 &self,
2122 has_active_edit_prediction: bool,
2123 window: &Window,
2124 cx: &App,
2125 ) -> KeyContext {
2126 let mut key_context = KeyContext::new_with_defaults();
2127 key_context.add("Editor");
2128 let mode = match self.mode {
2129 EditorMode::SingleLine { .. } => "single_line",
2130 EditorMode::AutoHeight { .. } => "auto_height",
2131 EditorMode::Minimap { .. } => "minimap",
2132 EditorMode::Full { .. } => "full",
2133 };
2134
2135 if EditorSettings::jupyter_enabled(cx) {
2136 key_context.add("jupyter");
2137 }
2138
2139 key_context.set("mode", mode);
2140 if self.pending_rename.is_some() {
2141 key_context.add("renaming");
2142 }
2143
2144 match self.context_menu.borrow().as_ref() {
2145 Some(CodeContextMenu::Completions(_)) => {
2146 key_context.add("menu");
2147 key_context.add("showing_completions");
2148 }
2149 Some(CodeContextMenu::CodeActions(_)) => {
2150 key_context.add("menu");
2151 key_context.add("showing_code_actions")
2152 }
2153 None => {}
2154 }
2155
2156 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2157 if !self.focus_handle(cx).contains_focused(window, cx)
2158 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2159 {
2160 for addon in self.addons.values() {
2161 addon.extend_key_context(&mut key_context, cx)
2162 }
2163 }
2164
2165 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2166 if let Some(extension) = singleton_buffer
2167 .read(cx)
2168 .file()
2169 .and_then(|file| file.path().extension()?.to_str())
2170 {
2171 key_context.set("extension", extension.to_string());
2172 }
2173 } else {
2174 key_context.add("multibuffer");
2175 }
2176
2177 if has_active_edit_prediction {
2178 if self.edit_prediction_in_conflict() {
2179 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2180 } else {
2181 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2182 key_context.add("copilot_suggestion");
2183 }
2184 }
2185
2186 if self.selection_mark_mode {
2187 key_context.add("selection_mode");
2188 }
2189
2190 key_context
2191 }
2192
2193 fn show_mouse_cursor(&mut self) {
2194 self.mouse_cursor_hidden = false;
2195 }
2196
2197 pub fn hide_mouse_cursor(&mut self, origin: &HideMouseCursorOrigin) {
2198 self.mouse_cursor_hidden = match origin {
2199 HideMouseCursorOrigin::TypingAction => {
2200 matches!(
2201 self.hide_mouse_mode,
2202 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2203 )
2204 }
2205 HideMouseCursorOrigin::MovementAction => {
2206 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2207 }
2208 };
2209 }
2210
2211 pub fn edit_prediction_in_conflict(&self) -> bool {
2212 if !self.show_edit_predictions_in_menu() {
2213 return false;
2214 }
2215
2216 let showing_completions = self
2217 .context_menu
2218 .borrow()
2219 .as_ref()
2220 .map_or(false, |context| {
2221 matches!(context, CodeContextMenu::Completions(_))
2222 });
2223
2224 showing_completions
2225 || self.edit_prediction_requires_modifier()
2226 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2227 // bindings to insert tab characters.
2228 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2229 }
2230
2231 pub fn accept_edit_prediction_keybind(
2232 &self,
2233 window: &Window,
2234 cx: &App,
2235 ) -> AcceptEditPredictionBinding {
2236 let key_context = self.key_context_internal(true, window, cx);
2237 let in_conflict = self.edit_prediction_in_conflict();
2238
2239 AcceptEditPredictionBinding(
2240 window
2241 .bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2242 .into_iter()
2243 .filter(|binding| {
2244 !in_conflict
2245 || binding
2246 .keystrokes()
2247 .first()
2248 .map_or(false, |keystroke| keystroke.modifiers.modified())
2249 })
2250 .rev()
2251 .min_by_key(|binding| {
2252 binding
2253 .keystrokes()
2254 .first()
2255 .map_or(u8::MAX, |k| k.modifiers.number_of_modifiers())
2256 }),
2257 )
2258 }
2259
2260 pub fn new_file(
2261 workspace: &mut Workspace,
2262 _: &workspace::NewFile,
2263 window: &mut Window,
2264 cx: &mut Context<Workspace>,
2265 ) {
2266 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2267 "Failed to create buffer",
2268 window,
2269 cx,
2270 |e, _, _| match e.error_code() {
2271 ErrorCode::RemoteUpgradeRequired => Some(format!(
2272 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2273 e.error_tag("required").unwrap_or("the latest version")
2274 )),
2275 _ => None,
2276 },
2277 );
2278 }
2279
2280 pub fn new_in_workspace(
2281 workspace: &mut Workspace,
2282 window: &mut Window,
2283 cx: &mut Context<Workspace>,
2284 ) -> Task<Result<Entity<Editor>>> {
2285 let project = workspace.project().clone();
2286 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2287
2288 cx.spawn_in(window, async move |workspace, cx| {
2289 let buffer = create.await?;
2290 workspace.update_in(cx, |workspace, window, cx| {
2291 let editor =
2292 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2293 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2294 editor
2295 })
2296 })
2297 }
2298
2299 fn new_file_vertical(
2300 workspace: &mut Workspace,
2301 _: &workspace::NewFileSplitVertical,
2302 window: &mut Window,
2303 cx: &mut Context<Workspace>,
2304 ) {
2305 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2306 }
2307
2308 fn new_file_horizontal(
2309 workspace: &mut Workspace,
2310 _: &workspace::NewFileSplitHorizontal,
2311 window: &mut Window,
2312 cx: &mut Context<Workspace>,
2313 ) {
2314 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2315 }
2316
2317 fn new_file_in_direction(
2318 workspace: &mut Workspace,
2319 direction: SplitDirection,
2320 window: &mut Window,
2321 cx: &mut Context<Workspace>,
2322 ) {
2323 let project = workspace.project().clone();
2324 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2325
2326 cx.spawn_in(window, async move |workspace, cx| {
2327 let buffer = create.await?;
2328 workspace.update_in(cx, move |workspace, window, cx| {
2329 workspace.split_item(
2330 direction,
2331 Box::new(
2332 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2333 ),
2334 window,
2335 cx,
2336 )
2337 })?;
2338 anyhow::Ok(())
2339 })
2340 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2341 match e.error_code() {
2342 ErrorCode::RemoteUpgradeRequired => Some(format!(
2343 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2344 e.error_tag("required").unwrap_or("the latest version")
2345 )),
2346 _ => None,
2347 }
2348 });
2349 }
2350
2351 pub fn leader_id(&self) -> Option<CollaboratorId> {
2352 self.leader_id
2353 }
2354
2355 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2356 &self.buffer
2357 }
2358
2359 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2360 self.workspace.as_ref()?.0.upgrade()
2361 }
2362
2363 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2364 self.buffer().read(cx).title(cx)
2365 }
2366
2367 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2368 let git_blame_gutter_max_author_length = self
2369 .render_git_blame_gutter(cx)
2370 .then(|| {
2371 if let Some(blame) = self.blame.as_ref() {
2372 let max_author_length =
2373 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2374 Some(max_author_length)
2375 } else {
2376 None
2377 }
2378 })
2379 .flatten();
2380
2381 EditorSnapshot {
2382 mode: self.mode.clone(),
2383 show_gutter: self.show_gutter,
2384 show_line_numbers: self.show_line_numbers,
2385 show_git_diff_gutter: self.show_git_diff_gutter,
2386 show_code_actions: self.show_code_actions,
2387 show_runnables: self.show_runnables,
2388 show_breakpoints: self.show_breakpoints,
2389 git_blame_gutter_max_author_length,
2390 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2391 scroll_anchor: self.scroll_manager.anchor(),
2392 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2393 placeholder_text: self.placeholder_text.clone(),
2394 is_focused: self.focus_handle.is_focused(window),
2395 current_line_highlight: self
2396 .current_line_highlight
2397 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2398 gutter_hovered: self.gutter_hovered,
2399 }
2400 }
2401
2402 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2403 self.buffer.read(cx).language_at(point, cx)
2404 }
2405
2406 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2407 self.buffer.read(cx).read(cx).file_at(point).cloned()
2408 }
2409
2410 pub fn active_excerpt(
2411 &self,
2412 cx: &App,
2413 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2414 self.buffer
2415 .read(cx)
2416 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2417 }
2418
2419 pub fn mode(&self) -> &EditorMode {
2420 &self.mode
2421 }
2422
2423 pub fn set_mode(&mut self, mode: EditorMode) {
2424 self.mode = mode;
2425 }
2426
2427 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2428 self.collaboration_hub.as_deref()
2429 }
2430
2431 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2432 self.collaboration_hub = Some(hub);
2433 }
2434
2435 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2436 self.in_project_search = in_project_search;
2437 }
2438
2439 pub fn set_custom_context_menu(
2440 &mut self,
2441 f: impl 'static
2442 + Fn(
2443 &mut Self,
2444 DisplayPoint,
2445 &mut Window,
2446 &mut Context<Self>,
2447 ) -> Option<Entity<ui::ContextMenu>>,
2448 ) {
2449 self.custom_context_menu = Some(Box::new(f))
2450 }
2451
2452 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2453 self.completion_provider = provider;
2454 }
2455
2456 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2457 self.semantics_provider.clone()
2458 }
2459
2460 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2461 self.semantics_provider = provider;
2462 }
2463
2464 pub fn set_edit_prediction_provider<T>(
2465 &mut self,
2466 provider: Option<Entity<T>>,
2467 window: &mut Window,
2468 cx: &mut Context<Self>,
2469 ) where
2470 T: EditPredictionProvider,
2471 {
2472 self.edit_prediction_provider =
2473 provider.map(|provider| RegisteredInlineCompletionProvider {
2474 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2475 if this.focus_handle.is_focused(window) {
2476 this.update_visible_inline_completion(window, cx);
2477 }
2478 }),
2479 provider: Arc::new(provider),
2480 });
2481 self.update_edit_prediction_settings(cx);
2482 self.refresh_inline_completion(false, false, window, cx);
2483 }
2484
2485 pub fn placeholder_text(&self) -> Option<&str> {
2486 self.placeholder_text.as_deref()
2487 }
2488
2489 pub fn set_placeholder_text(
2490 &mut self,
2491 placeholder_text: impl Into<Arc<str>>,
2492 cx: &mut Context<Self>,
2493 ) {
2494 let placeholder_text = Some(placeholder_text.into());
2495 if self.placeholder_text != placeholder_text {
2496 self.placeholder_text = placeholder_text;
2497 cx.notify();
2498 }
2499 }
2500
2501 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2502 self.cursor_shape = cursor_shape;
2503
2504 // Disrupt blink for immediate user feedback that the cursor shape has changed
2505 self.blink_manager.update(cx, BlinkManager::show_cursor);
2506
2507 cx.notify();
2508 }
2509
2510 pub fn set_current_line_highlight(
2511 &mut self,
2512 current_line_highlight: Option<CurrentLineHighlight>,
2513 ) {
2514 self.current_line_highlight = current_line_highlight;
2515 }
2516
2517 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2518 self.collapse_matches = collapse_matches;
2519 }
2520
2521 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2522 let buffers = self.buffer.read(cx).all_buffers();
2523 let Some(project) = self.project.as_ref() else {
2524 return;
2525 };
2526 project.update(cx, |project, cx| {
2527 for buffer in buffers {
2528 self.registered_buffers
2529 .entry(buffer.read(cx).remote_id())
2530 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2531 }
2532 })
2533 }
2534
2535 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2536 if self.collapse_matches {
2537 return range.start..range.start;
2538 }
2539 range.clone()
2540 }
2541
2542 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2543 if self.display_map.read(cx).clip_at_line_ends != clip {
2544 self.display_map
2545 .update(cx, |map, _| map.clip_at_line_ends = clip);
2546 }
2547 }
2548
2549 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2550 self.input_enabled = input_enabled;
2551 }
2552
2553 pub fn set_inline_completions_hidden_for_vim_mode(
2554 &mut self,
2555 hidden: bool,
2556 window: &mut Window,
2557 cx: &mut Context<Self>,
2558 ) {
2559 if hidden != self.inline_completions_hidden_for_vim_mode {
2560 self.inline_completions_hidden_for_vim_mode = hidden;
2561 if hidden {
2562 self.update_visible_inline_completion(window, cx);
2563 } else {
2564 self.refresh_inline_completion(true, false, window, cx);
2565 }
2566 }
2567 }
2568
2569 pub fn set_menu_inline_completions_policy(&mut self, value: MenuInlineCompletionsPolicy) {
2570 self.menu_inline_completions_policy = value;
2571 }
2572
2573 pub fn set_autoindent(&mut self, autoindent: bool) {
2574 if autoindent {
2575 self.autoindent_mode = Some(AutoindentMode::EachLine);
2576 } else {
2577 self.autoindent_mode = None;
2578 }
2579 }
2580
2581 pub fn read_only(&self, cx: &App) -> bool {
2582 self.read_only || self.buffer.read(cx).read_only()
2583 }
2584
2585 pub fn set_read_only(&mut self, read_only: bool) {
2586 self.read_only = read_only;
2587 }
2588
2589 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2590 self.use_autoclose = autoclose;
2591 }
2592
2593 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2594 self.use_auto_surround = auto_surround;
2595 }
2596
2597 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2598 self.auto_replace_emoji_shortcode = auto_replace;
2599 }
2600
2601 pub fn toggle_edit_predictions(
2602 &mut self,
2603 _: &ToggleEditPrediction,
2604 window: &mut Window,
2605 cx: &mut Context<Self>,
2606 ) {
2607 if self.show_inline_completions_override.is_some() {
2608 self.set_show_edit_predictions(None, window, cx);
2609 } else {
2610 let show_edit_predictions = !self.edit_predictions_enabled();
2611 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2612 }
2613 }
2614
2615 pub fn set_show_edit_predictions(
2616 &mut self,
2617 show_edit_predictions: Option<bool>,
2618 window: &mut Window,
2619 cx: &mut Context<Self>,
2620 ) {
2621 self.show_inline_completions_override = show_edit_predictions;
2622 self.update_edit_prediction_settings(cx);
2623
2624 if let Some(false) = show_edit_predictions {
2625 self.discard_inline_completion(false, cx);
2626 } else {
2627 self.refresh_inline_completion(false, true, window, cx);
2628 }
2629 }
2630
2631 fn inline_completions_disabled_in_scope(
2632 &self,
2633 buffer: &Entity<Buffer>,
2634 buffer_position: language::Anchor,
2635 cx: &App,
2636 ) -> bool {
2637 let snapshot = buffer.read(cx).snapshot();
2638 let settings = snapshot.settings_at(buffer_position, cx);
2639
2640 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2641 return false;
2642 };
2643
2644 scope.override_name().map_or(false, |scope_name| {
2645 settings
2646 .edit_predictions_disabled_in
2647 .iter()
2648 .any(|s| s == scope_name)
2649 })
2650 }
2651
2652 pub fn set_use_modal_editing(&mut self, to: bool) {
2653 self.use_modal_editing = to;
2654 }
2655
2656 pub fn use_modal_editing(&self) -> bool {
2657 self.use_modal_editing
2658 }
2659
2660 fn selections_did_change(
2661 &mut self,
2662 local: bool,
2663 old_cursor_position: &Anchor,
2664 should_update_completions: bool,
2665 window: &mut Window,
2666 cx: &mut Context<Self>,
2667 ) {
2668 window.invalidate_character_coordinates();
2669
2670 // Copy selections to primary selection buffer
2671 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2672 if local {
2673 let selections = self.selections.all::<usize>(cx);
2674 let buffer_handle = self.buffer.read(cx).read(cx);
2675
2676 let mut text = String::new();
2677 for (index, selection) in selections.iter().enumerate() {
2678 let text_for_selection = buffer_handle
2679 .text_for_range(selection.start..selection.end)
2680 .collect::<String>();
2681
2682 text.push_str(&text_for_selection);
2683 if index != selections.len() - 1 {
2684 text.push('\n');
2685 }
2686 }
2687
2688 if !text.is_empty() {
2689 cx.write_to_primary(ClipboardItem::new_string(text));
2690 }
2691 }
2692
2693 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
2694 self.buffer.update(cx, |buffer, cx| {
2695 buffer.set_active_selections(
2696 &self.selections.disjoint_anchors(),
2697 self.selections.line_mode,
2698 self.cursor_shape,
2699 cx,
2700 )
2701 });
2702 }
2703 let display_map = self
2704 .display_map
2705 .update(cx, |display_map, cx| display_map.snapshot(cx));
2706 let buffer = &display_map.buffer_snapshot;
2707 self.add_selections_state = None;
2708 self.select_next_state = None;
2709 self.select_prev_state = None;
2710 self.select_syntax_node_history.try_clear();
2711 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), buffer);
2712 self.snippet_stack
2713 .invalidate(&self.selections.disjoint_anchors(), buffer);
2714 self.take_rename(false, window, cx);
2715
2716 let new_cursor_position = self.selections.newest_anchor().head();
2717
2718 self.push_to_nav_history(
2719 *old_cursor_position,
2720 Some(new_cursor_position.to_point(buffer)),
2721 false,
2722 cx,
2723 );
2724
2725 if local {
2726 let new_cursor_position = self.selections.newest_anchor().head();
2727
2728 if let Some(buffer_id) = new_cursor_position.buffer_id {
2729 if !self.registered_buffers.contains_key(&buffer_id) {
2730 if let Some(project) = self.project.as_ref() {
2731 project.update(cx, |project, cx| {
2732 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
2733 return;
2734 };
2735 self.registered_buffers.insert(
2736 buffer_id,
2737 project.register_buffer_with_language_servers(&buffer, cx),
2738 );
2739 })
2740 }
2741 }
2742 }
2743
2744 let mut context_menu = self.context_menu.borrow_mut();
2745 let completion_menu = match context_menu.as_ref() {
2746 Some(CodeContextMenu::Completions(menu)) => Some(menu),
2747 Some(CodeContextMenu::CodeActions(_)) => {
2748 *context_menu = None;
2749 None
2750 }
2751 None => None,
2752 };
2753 let completion_position = completion_menu.map(|menu| menu.initial_position);
2754 drop(context_menu);
2755
2756 if should_update_completions {
2757 if let Some(completion_position) = completion_position {
2758 let new_cursor_offset = new_cursor_position.to_offset(buffer);
2759 let position_matches =
2760 new_cursor_offset == completion_position.to_offset(buffer);
2761 let continue_showing = if position_matches {
2762 let (word_range, kind) = buffer.surrounding_word(new_cursor_offset, true);
2763 if let Some(CharKind::Word) = kind {
2764 word_range.start < new_cursor_offset
2765 } else {
2766 false
2767 }
2768 } else {
2769 false
2770 };
2771
2772 if continue_showing {
2773 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
2774 } else {
2775 self.hide_context_menu(window, cx);
2776 }
2777 }
2778 }
2779
2780 hide_hover(self, cx);
2781
2782 if old_cursor_position.to_display_point(&display_map).row()
2783 != new_cursor_position.to_display_point(&display_map).row()
2784 {
2785 self.available_code_actions.take();
2786 }
2787 self.refresh_code_actions(window, cx);
2788 self.refresh_document_highlights(cx);
2789 self.refresh_selected_text_highlights(false, window, cx);
2790 refresh_matching_bracket_highlights(self, window, cx);
2791 self.update_visible_inline_completion(window, cx);
2792 self.edit_prediction_requires_modifier_in_indent_conflict = true;
2793 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
2794 self.inline_blame_popover.take();
2795 if self.git_blame_inline_enabled {
2796 self.start_inline_blame_timer(window, cx);
2797 }
2798 }
2799
2800 self.blink_manager.update(cx, BlinkManager::pause_blinking);
2801 cx.emit(EditorEvent::SelectionsChanged { local });
2802
2803 let selections = &self.selections.disjoint;
2804 if selections.len() == 1 {
2805 cx.emit(SearchEvent::ActiveMatchChanged)
2806 }
2807 if local {
2808 if let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
2809 let inmemory_selections = selections
2810 .iter()
2811 .map(|s| {
2812 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
2813 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
2814 })
2815 .collect();
2816 self.update_restoration_data(cx, |data| {
2817 data.selections = inmemory_selections;
2818 });
2819
2820 if WorkspaceSettings::get(None, cx).restore_on_startup
2821 != RestoreOnStartupBehavior::None
2822 {
2823 if let Some(workspace_id) =
2824 self.workspace.as_ref().and_then(|workspace| workspace.1)
2825 {
2826 let snapshot = self.buffer().read(cx).snapshot(cx);
2827 let selections = selections.clone();
2828 let background_executor = cx.background_executor().clone();
2829 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2830 self.serialize_selections = cx.background_spawn(async move {
2831 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2832 let db_selections = selections
2833 .iter()
2834 .map(|selection| {
2835 (
2836 selection.start.to_offset(&snapshot),
2837 selection.end.to_offset(&snapshot),
2838 )
2839 })
2840 .collect();
2841
2842 DB.save_editor_selections(editor_id, workspace_id, db_selections)
2843 .await
2844 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
2845 .log_err();
2846 });
2847 }
2848 }
2849 }
2850 }
2851
2852 cx.notify();
2853 }
2854
2855 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
2856 use text::ToOffset as _;
2857 use text::ToPoint as _;
2858
2859 if self.mode.is_minimap()
2860 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
2861 {
2862 return;
2863 }
2864
2865 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
2866 return;
2867 };
2868
2869 let snapshot = singleton.read(cx).snapshot();
2870 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
2871 let display_snapshot = display_map.snapshot(cx);
2872
2873 display_snapshot
2874 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
2875 .map(|fold| {
2876 fold.range.start.text_anchor.to_point(&snapshot)
2877 ..fold.range.end.text_anchor.to_point(&snapshot)
2878 })
2879 .collect()
2880 });
2881 self.update_restoration_data(cx, |data| {
2882 data.folds = inmemory_folds;
2883 });
2884
2885 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
2886 return;
2887 };
2888 let background_executor = cx.background_executor().clone();
2889 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2890 let db_folds = self.display_map.update(cx, |display_map, cx| {
2891 display_map
2892 .snapshot(cx)
2893 .folds_in_range(0..snapshot.len())
2894 .map(|fold| {
2895 (
2896 fold.range.start.text_anchor.to_offset(&snapshot),
2897 fold.range.end.text_anchor.to_offset(&snapshot),
2898 )
2899 })
2900 .collect()
2901 });
2902 self.serialize_folds = cx.background_spawn(async move {
2903 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2904 DB.save_editor_folds(editor_id, workspace_id, db_folds)
2905 .await
2906 .with_context(|| {
2907 format!(
2908 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
2909 )
2910 })
2911 .log_err();
2912 });
2913 }
2914
2915 pub fn sync_selections(
2916 &mut self,
2917 other: Entity<Editor>,
2918 cx: &mut Context<Self>,
2919 ) -> gpui::Subscription {
2920 let other_selections = other.read(cx).selections.disjoint.to_vec();
2921 self.selections.change_with(cx, |selections| {
2922 selections.select_anchors(other_selections);
2923 });
2924
2925 let other_subscription =
2926 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
2927 EditorEvent::SelectionsChanged { local: true } => {
2928 let other_selections = other.read(cx).selections.disjoint.to_vec();
2929 if other_selections.is_empty() {
2930 return;
2931 }
2932 this.selections.change_with(cx, |selections| {
2933 selections.select_anchors(other_selections);
2934 });
2935 }
2936 _ => {}
2937 });
2938
2939 let this_subscription =
2940 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
2941 EditorEvent::SelectionsChanged { local: true } => {
2942 let these_selections = this.selections.disjoint.to_vec();
2943 if these_selections.is_empty() {
2944 return;
2945 }
2946 other.update(cx, |other_editor, cx| {
2947 other_editor.selections.change_with(cx, |selections| {
2948 selections.select_anchors(these_selections);
2949 })
2950 });
2951 }
2952 _ => {}
2953 });
2954
2955 Subscription::join(other_subscription, this_subscription)
2956 }
2957
2958 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
2959 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
2960 /// effects of selection change occur at the end of the transaction.
2961 pub fn change_selections<R>(
2962 &mut self,
2963 autoscroll: Option<Autoscroll>,
2964 window: &mut Window,
2965 cx: &mut Context<Self>,
2966 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2967 ) -> R {
2968 self.change_selections_inner(true, autoscroll, window, cx, change)
2969 }
2970
2971 pub(crate) fn change_selections_without_updating_completions<R>(
2972 &mut self,
2973 autoscroll: Option<Autoscroll>,
2974 window: &mut Window,
2975 cx: &mut Context<Self>,
2976 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2977 ) -> R {
2978 self.change_selections_inner(false, autoscroll, window, cx, change)
2979 }
2980
2981 fn change_selections_inner<R>(
2982 &mut self,
2983 should_update_completions: bool,
2984 autoscroll: Option<Autoscroll>,
2985 window: &mut Window,
2986 cx: &mut Context<Self>,
2987 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2988 ) -> R {
2989 if let Some(state) = &mut self.deferred_selection_effects_state {
2990 state.autoscroll = autoscroll.or(state.autoscroll);
2991 state.should_update_completions = should_update_completions;
2992 let (changed, result) = self.selections.change_with(cx, change);
2993 state.changed |= changed;
2994 return result;
2995 }
2996 let mut state = DeferredSelectionEffectsState {
2997 changed: false,
2998 should_update_completions,
2999 autoscroll,
3000 old_cursor_position: self.selections.newest_anchor().head(),
3001 history_entry: SelectionHistoryEntry {
3002 selections: self.selections.disjoint_anchors(),
3003 select_next_state: self.select_next_state.clone(),
3004 select_prev_state: self.select_prev_state.clone(),
3005 add_selections_state: self.add_selections_state.clone(),
3006 },
3007 };
3008 let (changed, result) = self.selections.change_with(cx, change);
3009 state.changed = state.changed || changed;
3010 if self.defer_selection_effects {
3011 self.deferred_selection_effects_state = Some(state);
3012 } else {
3013 self.apply_selection_effects(state, window, cx);
3014 }
3015 result
3016 }
3017
3018 /// Defers the effects of selection change, so that the effects of multiple calls to
3019 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3020 /// to selection history and the state of popovers based on selection position aren't
3021 /// erroneously updated.
3022 pub fn with_selection_effects_deferred<R>(
3023 &mut self,
3024 window: &mut Window,
3025 cx: &mut Context<Self>,
3026 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3027 ) -> R {
3028 let already_deferred = self.defer_selection_effects;
3029 self.defer_selection_effects = true;
3030 let result = update(self, window, cx);
3031 if !already_deferred {
3032 self.defer_selection_effects = false;
3033 if let Some(state) = self.deferred_selection_effects_state.take() {
3034 self.apply_selection_effects(state, window, cx);
3035 }
3036 }
3037 result
3038 }
3039
3040 fn apply_selection_effects(
3041 &mut self,
3042 state: DeferredSelectionEffectsState,
3043 window: &mut Window,
3044 cx: &mut Context<Self>,
3045 ) {
3046 if state.changed {
3047 self.selection_history.push(state.history_entry);
3048
3049 if let Some(autoscroll) = state.autoscroll {
3050 self.request_autoscroll(autoscroll, cx);
3051 }
3052
3053 let old_cursor_position = &state.old_cursor_position;
3054
3055 self.selections_did_change(
3056 true,
3057 &old_cursor_position,
3058 state.should_update_completions,
3059 window,
3060 cx,
3061 );
3062
3063 if self.should_open_signature_help_automatically(&old_cursor_position, cx) {
3064 self.show_signature_help(&ShowSignatureHelp, window, cx);
3065 }
3066 }
3067 }
3068
3069 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3070 where
3071 I: IntoIterator<Item = (Range<S>, T)>,
3072 S: ToOffset,
3073 T: Into<Arc<str>>,
3074 {
3075 if self.read_only(cx) {
3076 return;
3077 }
3078
3079 self.buffer
3080 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3081 }
3082
3083 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3084 where
3085 I: IntoIterator<Item = (Range<S>, T)>,
3086 S: ToOffset,
3087 T: Into<Arc<str>>,
3088 {
3089 if self.read_only(cx) {
3090 return;
3091 }
3092
3093 self.buffer.update(cx, |buffer, cx| {
3094 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3095 });
3096 }
3097
3098 pub fn edit_with_block_indent<I, S, T>(
3099 &mut self,
3100 edits: I,
3101 original_indent_columns: Vec<Option<u32>>,
3102 cx: &mut Context<Self>,
3103 ) where
3104 I: IntoIterator<Item = (Range<S>, T)>,
3105 S: ToOffset,
3106 T: Into<Arc<str>>,
3107 {
3108 if self.read_only(cx) {
3109 return;
3110 }
3111
3112 self.buffer.update(cx, |buffer, cx| {
3113 buffer.edit(
3114 edits,
3115 Some(AutoindentMode::Block {
3116 original_indent_columns,
3117 }),
3118 cx,
3119 )
3120 });
3121 }
3122
3123 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3124 self.hide_context_menu(window, cx);
3125
3126 match phase {
3127 SelectPhase::Begin {
3128 position,
3129 add,
3130 click_count,
3131 } => self.begin_selection(position, add, click_count, window, cx),
3132 SelectPhase::BeginColumnar {
3133 position,
3134 goal_column,
3135 reset,
3136 } => self.begin_columnar_selection(position, goal_column, reset, window, cx),
3137 SelectPhase::Extend {
3138 position,
3139 click_count,
3140 } => self.extend_selection(position, click_count, window, cx),
3141 SelectPhase::Update {
3142 position,
3143 goal_column,
3144 scroll_delta,
3145 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3146 SelectPhase::End => self.end_selection(window, cx),
3147 }
3148 }
3149
3150 fn extend_selection(
3151 &mut self,
3152 position: DisplayPoint,
3153 click_count: usize,
3154 window: &mut Window,
3155 cx: &mut Context<Self>,
3156 ) {
3157 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3158 let tail = self.selections.newest::<usize>(cx).tail();
3159 self.begin_selection(position, false, click_count, window, cx);
3160
3161 let position = position.to_offset(&display_map, Bias::Left);
3162 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3163
3164 let mut pending_selection = self
3165 .selections
3166 .pending_anchor()
3167 .expect("extend_selection not called with pending selection");
3168 if position >= tail {
3169 pending_selection.start = tail_anchor;
3170 } else {
3171 pending_selection.end = tail_anchor;
3172 pending_selection.reversed = true;
3173 }
3174
3175 let mut pending_mode = self.selections.pending_mode().unwrap();
3176 match &mut pending_mode {
3177 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3178 _ => {}
3179 }
3180
3181 let auto_scroll = EditorSettings::get_global(cx).autoscroll_on_clicks;
3182
3183 self.change_selections(auto_scroll.then(Autoscroll::fit), window, cx, |s| {
3184 s.set_pending(pending_selection, pending_mode)
3185 });
3186 }
3187
3188 fn begin_selection(
3189 &mut self,
3190 position: DisplayPoint,
3191 add: bool,
3192 click_count: usize,
3193 window: &mut Window,
3194 cx: &mut Context<Self>,
3195 ) {
3196 if !self.focus_handle.is_focused(window) {
3197 self.last_focused_descendant = None;
3198 window.focus(&self.focus_handle);
3199 }
3200
3201 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3202 let buffer = &display_map.buffer_snapshot;
3203 let position = display_map.clip_point(position, Bias::Left);
3204
3205 let start;
3206 let end;
3207 let mode;
3208 let mut auto_scroll;
3209 match click_count {
3210 1 => {
3211 start = buffer.anchor_before(position.to_point(&display_map));
3212 end = start;
3213 mode = SelectMode::Character;
3214 auto_scroll = true;
3215 }
3216 2 => {
3217 let range = movement::surrounding_word(&display_map, position);
3218 start = buffer.anchor_before(range.start.to_point(&display_map));
3219 end = buffer.anchor_before(range.end.to_point(&display_map));
3220 mode = SelectMode::Word(start..end);
3221 auto_scroll = true;
3222 }
3223 3 => {
3224 let position = display_map
3225 .clip_point(position, Bias::Left)
3226 .to_point(&display_map);
3227 let line_start = display_map.prev_line_boundary(position).0;
3228 let next_line_start = buffer.clip_point(
3229 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3230 Bias::Left,
3231 );
3232 start = buffer.anchor_before(line_start);
3233 end = buffer.anchor_before(next_line_start);
3234 mode = SelectMode::Line(start..end);
3235 auto_scroll = true;
3236 }
3237 _ => {
3238 start = buffer.anchor_before(0);
3239 end = buffer.anchor_before(buffer.len());
3240 mode = SelectMode::All;
3241 auto_scroll = false;
3242 }
3243 }
3244 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3245
3246 let point_to_delete: Option<usize> = {
3247 let selected_points: Vec<Selection<Point>> =
3248 self.selections.disjoint_in_range(start..end, cx);
3249
3250 if !add || click_count > 1 {
3251 None
3252 } else if !selected_points.is_empty() {
3253 Some(selected_points[0].id)
3254 } else {
3255 let clicked_point_already_selected =
3256 self.selections.disjoint.iter().find(|selection| {
3257 selection.start.to_point(buffer) == start.to_point(buffer)
3258 || selection.end.to_point(buffer) == end.to_point(buffer)
3259 });
3260
3261 clicked_point_already_selected.map(|selection| selection.id)
3262 }
3263 };
3264
3265 let selections_count = self.selections.count();
3266
3267 self.change_selections(auto_scroll.then(Autoscroll::newest), window, cx, |s| {
3268 if let Some(point_to_delete) = point_to_delete {
3269 s.delete(point_to_delete);
3270
3271 if selections_count == 1 {
3272 s.set_pending_anchor_range(start..end, mode);
3273 }
3274 } else {
3275 if !add {
3276 s.clear_disjoint();
3277 }
3278
3279 s.set_pending_anchor_range(start..end, mode);
3280 }
3281 });
3282 }
3283
3284 fn begin_columnar_selection(
3285 &mut self,
3286 position: DisplayPoint,
3287 goal_column: u32,
3288 reset: bool,
3289 window: &mut Window,
3290 cx: &mut Context<Self>,
3291 ) {
3292 if !self.focus_handle.is_focused(window) {
3293 self.last_focused_descendant = None;
3294 window.focus(&self.focus_handle);
3295 }
3296
3297 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3298
3299 if reset {
3300 let pointer_position = display_map
3301 .buffer_snapshot
3302 .anchor_before(position.to_point(&display_map));
3303
3304 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
3305 s.clear_disjoint();
3306 s.set_pending_anchor_range(
3307 pointer_position..pointer_position,
3308 SelectMode::Character,
3309 );
3310 });
3311 if position.column() != goal_column {
3312 self.columnar_display_point = Some(DisplayPoint::new(position.row(), goal_column));
3313 } else {
3314 self.columnar_display_point = None;
3315 }
3316 }
3317
3318 let tail = self.selections.newest::<Point>(cx).tail();
3319 self.columnar_selection_tail = Some(display_map.buffer_snapshot.anchor_before(tail));
3320
3321 if !reset {
3322 self.columnar_display_point = None;
3323 self.select_columns(
3324 tail.to_display_point(&display_map),
3325 position,
3326 goal_column,
3327 &display_map,
3328 window,
3329 cx,
3330 );
3331 }
3332 }
3333
3334 fn update_selection(
3335 &mut self,
3336 position: DisplayPoint,
3337 goal_column: u32,
3338 scroll_delta: gpui::Point<f32>,
3339 window: &mut Window,
3340 cx: &mut Context<Self>,
3341 ) {
3342 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3343
3344 if let Some(tail) = self.columnar_selection_tail.as_ref() {
3345 let tail = self
3346 .columnar_display_point
3347 .unwrap_or_else(|| tail.to_display_point(&display_map));
3348 self.select_columns(tail, position, goal_column, &display_map, window, cx);
3349 } else if let Some(mut pending) = self.selections.pending_anchor() {
3350 let buffer = self.buffer.read(cx).snapshot(cx);
3351 let head;
3352 let tail;
3353 let mode = self.selections.pending_mode().unwrap();
3354 match &mode {
3355 SelectMode::Character => {
3356 head = position.to_point(&display_map);
3357 tail = pending.tail().to_point(&buffer);
3358 }
3359 SelectMode::Word(original_range) => {
3360 let original_display_range = original_range.start.to_display_point(&display_map)
3361 ..original_range.end.to_display_point(&display_map);
3362 let original_buffer_range = original_display_range.start.to_point(&display_map)
3363 ..original_display_range.end.to_point(&display_map);
3364 if movement::is_inside_word(&display_map, position)
3365 || original_display_range.contains(&position)
3366 {
3367 let word_range = movement::surrounding_word(&display_map, position);
3368 if word_range.start < original_display_range.start {
3369 head = word_range.start.to_point(&display_map);
3370 } else {
3371 head = word_range.end.to_point(&display_map);
3372 }
3373 } else {
3374 head = position.to_point(&display_map);
3375 }
3376
3377 if head <= original_buffer_range.start {
3378 tail = original_buffer_range.end;
3379 } else {
3380 tail = original_buffer_range.start;
3381 }
3382 }
3383 SelectMode::Line(original_range) => {
3384 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3385
3386 let position = display_map
3387 .clip_point(position, Bias::Left)
3388 .to_point(&display_map);
3389 let line_start = display_map.prev_line_boundary(position).0;
3390 let next_line_start = buffer.clip_point(
3391 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3392 Bias::Left,
3393 );
3394
3395 if line_start < original_range.start {
3396 head = line_start
3397 } else {
3398 head = next_line_start
3399 }
3400
3401 if head <= original_range.start {
3402 tail = original_range.end;
3403 } else {
3404 tail = original_range.start;
3405 }
3406 }
3407 SelectMode::All => {
3408 return;
3409 }
3410 };
3411
3412 if head < tail {
3413 pending.start = buffer.anchor_before(head);
3414 pending.end = buffer.anchor_before(tail);
3415 pending.reversed = true;
3416 } else {
3417 pending.start = buffer.anchor_before(tail);
3418 pending.end = buffer.anchor_before(head);
3419 pending.reversed = false;
3420 }
3421
3422 self.change_selections(None, window, cx, |s| {
3423 s.set_pending(pending, mode);
3424 });
3425 } else {
3426 log::error!("update_selection dispatched with no pending selection");
3427 return;
3428 }
3429
3430 self.apply_scroll_delta(scroll_delta, window, cx);
3431 cx.notify();
3432 }
3433
3434 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3435 self.columnar_selection_tail.take();
3436 if self.selections.pending_anchor().is_some() {
3437 let selections = self.selections.all::<usize>(cx);
3438 self.change_selections(None, window, cx, |s| {
3439 s.select(selections);
3440 s.clear_pending();
3441 });
3442 }
3443 }
3444
3445 fn select_columns(
3446 &mut self,
3447 tail: DisplayPoint,
3448 head: DisplayPoint,
3449 goal_column: u32,
3450 display_map: &DisplaySnapshot,
3451 window: &mut Window,
3452 cx: &mut Context<Self>,
3453 ) {
3454 let start_row = cmp::min(tail.row(), head.row());
3455 let end_row = cmp::max(tail.row(), head.row());
3456 let start_column = cmp::min(tail.column(), goal_column);
3457 let end_column = cmp::max(tail.column(), goal_column);
3458 let reversed = start_column < tail.column();
3459
3460 let selection_ranges = (start_row.0..=end_row.0)
3461 .map(DisplayRow)
3462 .filter_map(|row| {
3463 if !display_map.is_block_line(row) {
3464 let start = display_map
3465 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3466 .to_point(display_map);
3467 let end = display_map
3468 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3469 .to_point(display_map);
3470 if reversed {
3471 Some(end..start)
3472 } else {
3473 Some(start..end)
3474 }
3475 } else {
3476 None
3477 }
3478 })
3479 .collect::<Vec<_>>();
3480
3481 let mut non_empty_ranges = selection_ranges
3482 .iter()
3483 .filter(|selection_range| selection_range.start != selection_range.end)
3484 .peekable();
3485
3486 let ranges = if non_empty_ranges.peek().is_some() {
3487 non_empty_ranges.cloned().collect()
3488 } else {
3489 selection_ranges
3490 };
3491
3492 self.change_selections(None, window, cx, |s| {
3493 s.select_ranges(ranges);
3494 });
3495 cx.notify();
3496 }
3497
3498 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3499 self.selections
3500 .all_adjusted(cx)
3501 .iter()
3502 .any(|selection| !selection.is_empty())
3503 }
3504
3505 pub fn has_pending_nonempty_selection(&self) -> bool {
3506 let pending_nonempty_selection = match self.selections.pending_anchor() {
3507 Some(Selection { start, end, .. }) => start != end,
3508 None => false,
3509 };
3510
3511 pending_nonempty_selection
3512 || (self.columnar_selection_tail.is_some() && self.selections.disjoint.len() > 1)
3513 }
3514
3515 pub fn has_pending_selection(&self) -> bool {
3516 self.selections.pending_anchor().is_some() || self.columnar_selection_tail.is_some()
3517 }
3518
3519 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3520 self.selection_mark_mode = false;
3521
3522 if self.clear_expanded_diff_hunks(cx) {
3523 cx.notify();
3524 return;
3525 }
3526 if self.dismiss_menus_and_popups(true, window, cx) {
3527 return;
3528 }
3529
3530 if self.mode.is_full()
3531 && self.change_selections(Some(Autoscroll::fit()), window, cx, |s| s.try_cancel())
3532 {
3533 return;
3534 }
3535
3536 cx.propagate();
3537 }
3538
3539 pub fn dismiss_menus_and_popups(
3540 &mut self,
3541 is_user_requested: bool,
3542 window: &mut Window,
3543 cx: &mut Context<Self>,
3544 ) -> bool {
3545 if self.take_rename(false, window, cx).is_some() {
3546 return true;
3547 }
3548
3549 if hide_hover(self, cx) {
3550 return true;
3551 }
3552
3553 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3554 return true;
3555 }
3556
3557 if self.hide_context_menu(window, cx).is_some() {
3558 return true;
3559 }
3560
3561 if self.mouse_context_menu.take().is_some() {
3562 return true;
3563 }
3564
3565 if is_user_requested && self.discard_inline_completion(true, cx) {
3566 return true;
3567 }
3568
3569 if self.snippet_stack.pop().is_some() {
3570 return true;
3571 }
3572
3573 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3574 self.dismiss_diagnostics(cx);
3575 return true;
3576 }
3577
3578 false
3579 }
3580
3581 fn linked_editing_ranges_for(
3582 &self,
3583 selection: Range<text::Anchor>,
3584 cx: &App,
3585 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3586 if self.linked_edit_ranges.is_empty() {
3587 return None;
3588 }
3589 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3590 selection.end.buffer_id.and_then(|end_buffer_id| {
3591 if selection.start.buffer_id != Some(end_buffer_id) {
3592 return None;
3593 }
3594 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3595 let snapshot = buffer.read(cx).snapshot();
3596 self.linked_edit_ranges
3597 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3598 .map(|ranges| (ranges, snapshot, buffer))
3599 })?;
3600 use text::ToOffset as TO;
3601 // find offset from the start of current range to current cursor position
3602 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3603
3604 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3605 let start_difference = start_offset - start_byte_offset;
3606 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3607 let end_difference = end_offset - start_byte_offset;
3608 // Current range has associated linked ranges.
3609 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3610 for range in linked_ranges.iter() {
3611 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3612 let end_offset = start_offset + end_difference;
3613 let start_offset = start_offset + start_difference;
3614 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3615 continue;
3616 }
3617 if self.selections.disjoint_anchor_ranges().any(|s| {
3618 if s.start.buffer_id != selection.start.buffer_id
3619 || s.end.buffer_id != selection.end.buffer_id
3620 {
3621 return false;
3622 }
3623 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3624 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3625 }) {
3626 continue;
3627 }
3628 let start = buffer_snapshot.anchor_after(start_offset);
3629 let end = buffer_snapshot.anchor_after(end_offset);
3630 linked_edits
3631 .entry(buffer.clone())
3632 .or_default()
3633 .push(start..end);
3634 }
3635 Some(linked_edits)
3636 }
3637
3638 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3639 let text: Arc<str> = text.into();
3640
3641 if self.read_only(cx) {
3642 return;
3643 }
3644
3645 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3646
3647 let selections = self.selections.all_adjusted(cx);
3648 let mut bracket_inserted = false;
3649 let mut edits = Vec::new();
3650 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3651 let mut new_selections = Vec::with_capacity(selections.len());
3652 let mut new_autoclose_regions = Vec::new();
3653 let snapshot = self.buffer.read(cx).read(cx);
3654 let mut clear_linked_edit_ranges = false;
3655
3656 for (selection, autoclose_region) in
3657 self.selections_with_autoclose_regions(selections, &snapshot)
3658 {
3659 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3660 // Determine if the inserted text matches the opening or closing
3661 // bracket of any of this language's bracket pairs.
3662 let mut bracket_pair = None;
3663 let mut is_bracket_pair_start = false;
3664 let mut is_bracket_pair_end = false;
3665 if !text.is_empty() {
3666 let mut bracket_pair_matching_end = None;
3667 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3668 // and they are removing the character that triggered IME popup.
3669 for (pair, enabled) in scope.brackets() {
3670 if !pair.close && !pair.surround {
3671 continue;
3672 }
3673
3674 if enabled && pair.start.ends_with(text.as_ref()) {
3675 let prefix_len = pair.start.len() - text.len();
3676 let preceding_text_matches_prefix = prefix_len == 0
3677 || (selection.start.column >= (prefix_len as u32)
3678 && snapshot.contains_str_at(
3679 Point::new(
3680 selection.start.row,
3681 selection.start.column - (prefix_len as u32),
3682 ),
3683 &pair.start[..prefix_len],
3684 ));
3685 if preceding_text_matches_prefix {
3686 bracket_pair = Some(pair.clone());
3687 is_bracket_pair_start = true;
3688 break;
3689 }
3690 }
3691 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
3692 {
3693 // take first bracket pair matching end, but don't break in case a later bracket
3694 // pair matches start
3695 bracket_pair_matching_end = Some(pair.clone());
3696 }
3697 }
3698 if bracket_pair.is_none() && bracket_pair_matching_end.is_some() {
3699 bracket_pair = Some(bracket_pair_matching_end.unwrap());
3700 is_bracket_pair_end = true;
3701 }
3702 }
3703
3704 if let Some(bracket_pair) = bracket_pair {
3705 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
3706 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
3707 let auto_surround =
3708 self.use_auto_surround && snapshot_settings.use_auto_surround;
3709 if selection.is_empty() {
3710 if is_bracket_pair_start {
3711 // If the inserted text is a suffix of an opening bracket and the
3712 // selection is preceded by the rest of the opening bracket, then
3713 // insert the closing bracket.
3714 let following_text_allows_autoclose = snapshot
3715 .chars_at(selection.start)
3716 .next()
3717 .map_or(true, |c| scope.should_autoclose_before(c));
3718
3719 let preceding_text_allows_autoclose = selection.start.column == 0
3720 || snapshot.reversed_chars_at(selection.start).next().map_or(
3721 true,
3722 |c| {
3723 bracket_pair.start != bracket_pair.end
3724 || !snapshot
3725 .char_classifier_at(selection.start)
3726 .is_word(c)
3727 },
3728 );
3729
3730 let is_closing_quote = if bracket_pair.end == bracket_pair.start
3731 && bracket_pair.start.len() == 1
3732 {
3733 let target = bracket_pair.start.chars().next().unwrap();
3734 let current_line_count = snapshot
3735 .reversed_chars_at(selection.start)
3736 .take_while(|&c| c != '\n')
3737 .filter(|&c| c == target)
3738 .count();
3739 current_line_count % 2 == 1
3740 } else {
3741 false
3742 };
3743
3744 if autoclose
3745 && bracket_pair.close
3746 && following_text_allows_autoclose
3747 && preceding_text_allows_autoclose
3748 && !is_closing_quote
3749 {
3750 let anchor = snapshot.anchor_before(selection.end);
3751 new_selections.push((selection.map(|_| anchor), text.len()));
3752 new_autoclose_regions.push((
3753 anchor,
3754 text.len(),
3755 selection.id,
3756 bracket_pair.clone(),
3757 ));
3758 edits.push((
3759 selection.range(),
3760 format!("{}{}", text, bracket_pair.end).into(),
3761 ));
3762 bracket_inserted = true;
3763 continue;
3764 }
3765 }
3766
3767 if let Some(region) = autoclose_region {
3768 // If the selection is followed by an auto-inserted closing bracket,
3769 // then don't insert that closing bracket again; just move the selection
3770 // past the closing bracket.
3771 let should_skip = selection.end == region.range.end.to_point(&snapshot)
3772 && text.as_ref() == region.pair.end.as_str();
3773 if should_skip {
3774 let anchor = snapshot.anchor_after(selection.end);
3775 new_selections
3776 .push((selection.map(|_| anchor), region.pair.end.len()));
3777 continue;
3778 }
3779 }
3780
3781 let always_treat_brackets_as_autoclosed = snapshot
3782 .language_settings_at(selection.start, cx)
3783 .always_treat_brackets_as_autoclosed;
3784 if always_treat_brackets_as_autoclosed
3785 && is_bracket_pair_end
3786 && snapshot.contains_str_at(selection.end, text.as_ref())
3787 {
3788 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
3789 // and the inserted text is a closing bracket and the selection is followed
3790 // by the closing bracket then move the selection past the closing bracket.
3791 let anchor = snapshot.anchor_after(selection.end);
3792 new_selections.push((selection.map(|_| anchor), text.len()));
3793 continue;
3794 }
3795 }
3796 // If an opening bracket is 1 character long and is typed while
3797 // text is selected, then surround that text with the bracket pair.
3798 else if auto_surround
3799 && bracket_pair.surround
3800 && is_bracket_pair_start
3801 && bracket_pair.start.chars().count() == 1
3802 {
3803 edits.push((selection.start..selection.start, text.clone()));
3804 edits.push((
3805 selection.end..selection.end,
3806 bracket_pair.end.as_str().into(),
3807 ));
3808 bracket_inserted = true;
3809 new_selections.push((
3810 Selection {
3811 id: selection.id,
3812 start: snapshot.anchor_after(selection.start),
3813 end: snapshot.anchor_before(selection.end),
3814 reversed: selection.reversed,
3815 goal: selection.goal,
3816 },
3817 0,
3818 ));
3819 continue;
3820 }
3821 }
3822 }
3823
3824 if self.auto_replace_emoji_shortcode
3825 && selection.is_empty()
3826 && text.as_ref().ends_with(':')
3827 {
3828 if let Some(possible_emoji_short_code) =
3829 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
3830 {
3831 if !possible_emoji_short_code.is_empty() {
3832 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
3833 let emoji_shortcode_start = Point::new(
3834 selection.start.row,
3835 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
3836 );
3837
3838 // Remove shortcode from buffer
3839 edits.push((
3840 emoji_shortcode_start..selection.start,
3841 "".to_string().into(),
3842 ));
3843 new_selections.push((
3844 Selection {
3845 id: selection.id,
3846 start: snapshot.anchor_after(emoji_shortcode_start),
3847 end: snapshot.anchor_before(selection.start),
3848 reversed: selection.reversed,
3849 goal: selection.goal,
3850 },
3851 0,
3852 ));
3853
3854 // Insert emoji
3855 let selection_start_anchor = snapshot.anchor_after(selection.start);
3856 new_selections.push((selection.map(|_| selection_start_anchor), 0));
3857 edits.push((selection.start..selection.end, emoji.to_string().into()));
3858
3859 continue;
3860 }
3861 }
3862 }
3863 }
3864
3865 // If not handling any auto-close operation, then just replace the selected
3866 // text with the given input and move the selection to the end of the
3867 // newly inserted text.
3868 let anchor = snapshot.anchor_after(selection.end);
3869 if !self.linked_edit_ranges.is_empty() {
3870 let start_anchor = snapshot.anchor_before(selection.start);
3871
3872 let is_word_char = text.chars().next().map_or(true, |char| {
3873 let classifier = snapshot
3874 .char_classifier_at(start_anchor.to_offset(&snapshot))
3875 .ignore_punctuation(true);
3876 classifier.is_word(char)
3877 });
3878
3879 if is_word_char {
3880 if let Some(ranges) = self
3881 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
3882 {
3883 for (buffer, edits) in ranges {
3884 linked_edits
3885 .entry(buffer.clone())
3886 .or_default()
3887 .extend(edits.into_iter().map(|range| (range, text.clone())));
3888 }
3889 }
3890 } else {
3891 clear_linked_edit_ranges = true;
3892 }
3893 }
3894
3895 new_selections.push((selection.map(|_| anchor), 0));
3896 edits.push((selection.start..selection.end, text.clone()));
3897 }
3898
3899 drop(snapshot);
3900
3901 self.transact(window, cx, |this, window, cx| {
3902 if clear_linked_edit_ranges {
3903 this.linked_edit_ranges.clear();
3904 }
3905 let initial_buffer_versions =
3906 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
3907
3908 this.buffer.update(cx, |buffer, cx| {
3909 buffer.edit(edits, this.autoindent_mode.clone(), cx);
3910 });
3911 for (buffer, edits) in linked_edits {
3912 buffer.update(cx, |buffer, cx| {
3913 let snapshot = buffer.snapshot();
3914 let edits = edits
3915 .into_iter()
3916 .map(|(range, text)| {
3917 use text::ToPoint as TP;
3918 let end_point = TP::to_point(&range.end, &snapshot);
3919 let start_point = TP::to_point(&range.start, &snapshot);
3920 (start_point..end_point, text)
3921 })
3922 .sorted_by_key(|(range, _)| range.start);
3923 buffer.edit(edits, None, cx);
3924 })
3925 }
3926 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
3927 let new_selection_deltas = new_selections.iter().map(|e| e.1);
3928 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
3929 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
3930 .zip(new_selection_deltas)
3931 .map(|(selection, delta)| Selection {
3932 id: selection.id,
3933 start: selection.start + delta,
3934 end: selection.end + delta,
3935 reversed: selection.reversed,
3936 goal: SelectionGoal::None,
3937 })
3938 .collect::<Vec<_>>();
3939
3940 let mut i = 0;
3941 for (position, delta, selection_id, pair) in new_autoclose_regions {
3942 let position = position.to_offset(&map.buffer_snapshot) + delta;
3943 let start = map.buffer_snapshot.anchor_before(position);
3944 let end = map.buffer_snapshot.anchor_after(position);
3945 while let Some(existing_state) = this.autoclose_regions.get(i) {
3946 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
3947 Ordering::Less => i += 1,
3948 Ordering::Greater => break,
3949 Ordering::Equal => {
3950 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
3951 Ordering::Less => i += 1,
3952 Ordering::Equal => break,
3953 Ordering::Greater => break,
3954 }
3955 }
3956 }
3957 }
3958 this.autoclose_regions.insert(
3959 i,
3960 AutocloseRegion {
3961 selection_id,
3962 range: start..end,
3963 pair,
3964 },
3965 );
3966 }
3967
3968 let had_active_inline_completion = this.has_active_inline_completion();
3969 this.change_selections_without_updating_completions(
3970 Some(Autoscroll::fit()),
3971 window,
3972 cx,
3973 |s| s.select(new_selections),
3974 );
3975
3976 if !bracket_inserted {
3977 if let Some(on_type_format_task) =
3978 this.trigger_on_type_formatting(text.to_string(), window, cx)
3979 {
3980 on_type_format_task.detach_and_log_err(cx);
3981 }
3982 }
3983
3984 let editor_settings = EditorSettings::get_global(cx);
3985 if bracket_inserted
3986 && (editor_settings.auto_signature_help
3987 || editor_settings.show_signature_help_after_edits)
3988 {
3989 this.show_signature_help(&ShowSignatureHelp, window, cx);
3990 }
3991
3992 let trigger_in_words =
3993 this.show_edit_predictions_in_menu() || !had_active_inline_completion;
3994 if this.hard_wrap.is_some() {
3995 let latest: Range<Point> = this.selections.newest(cx).range();
3996 if latest.is_empty()
3997 && this
3998 .buffer()
3999 .read(cx)
4000 .snapshot(cx)
4001 .line_len(MultiBufferRow(latest.start.row))
4002 == latest.start.column
4003 {
4004 this.rewrap_impl(
4005 RewrapOptions {
4006 override_language_settings: true,
4007 preserve_existing_whitespace: true,
4008 },
4009 cx,
4010 )
4011 }
4012 }
4013 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4014 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
4015 this.refresh_inline_completion(true, false, window, cx);
4016 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4017 });
4018 }
4019
4020 fn find_possible_emoji_shortcode_at_position(
4021 snapshot: &MultiBufferSnapshot,
4022 position: Point,
4023 ) -> Option<String> {
4024 let mut chars = Vec::new();
4025 let mut found_colon = false;
4026 for char in snapshot.reversed_chars_at(position).take(100) {
4027 // Found a possible emoji shortcode in the middle of the buffer
4028 if found_colon {
4029 if char.is_whitespace() {
4030 chars.reverse();
4031 return Some(chars.iter().collect());
4032 }
4033 // If the previous character is not a whitespace, we are in the middle of a word
4034 // and we only want to complete the shortcode if the word is made up of other emojis
4035 let mut containing_word = String::new();
4036 for ch in snapshot
4037 .reversed_chars_at(position)
4038 .skip(chars.len() + 1)
4039 .take(100)
4040 {
4041 if ch.is_whitespace() {
4042 break;
4043 }
4044 containing_word.push(ch);
4045 }
4046 let containing_word = containing_word.chars().rev().collect::<String>();
4047 if util::word_consists_of_emojis(containing_word.as_str()) {
4048 chars.reverse();
4049 return Some(chars.iter().collect());
4050 }
4051 }
4052
4053 if char.is_whitespace() || !char.is_ascii() {
4054 return None;
4055 }
4056 if char == ':' {
4057 found_colon = true;
4058 } else {
4059 chars.push(char);
4060 }
4061 }
4062 // Found a possible emoji shortcode at the beginning of the buffer
4063 chars.reverse();
4064 Some(chars.iter().collect())
4065 }
4066
4067 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4068 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4069 self.transact(window, cx, |this, window, cx| {
4070 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4071 let selections = this.selections.all::<usize>(cx);
4072 let multi_buffer = this.buffer.read(cx);
4073 let buffer = multi_buffer.snapshot(cx);
4074 selections
4075 .iter()
4076 .map(|selection| {
4077 let start_point = selection.start.to_point(&buffer);
4078 let mut existing_indent =
4079 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4080 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4081 let start = selection.start;
4082 let end = selection.end;
4083 let selection_is_empty = start == end;
4084 let language_scope = buffer.language_scope_at(start);
4085 let (
4086 comment_delimiter,
4087 doc_delimiter,
4088 insert_extra_newline,
4089 indent_on_newline,
4090 indent_on_extra_newline,
4091 ) = if let Some(language) = &language_scope {
4092 let mut insert_extra_newline =
4093 insert_extra_newline_brackets(&buffer, start..end, language)
4094 || insert_extra_newline_tree_sitter(&buffer, start..end);
4095
4096 // Comment extension on newline is allowed only for cursor selections
4097 let comment_delimiter = maybe!({
4098 if !selection_is_empty {
4099 return None;
4100 }
4101
4102 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4103 return None;
4104 }
4105
4106 let delimiters = language.line_comment_prefixes();
4107 let max_len_of_delimiter =
4108 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4109 let (snapshot, range) =
4110 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4111
4112 let num_of_whitespaces = snapshot
4113 .chars_for_range(range.clone())
4114 .take_while(|c| c.is_whitespace())
4115 .count();
4116 let comment_candidate = snapshot
4117 .chars_for_range(range)
4118 .skip(num_of_whitespaces)
4119 .take(max_len_of_delimiter)
4120 .collect::<String>();
4121 let (delimiter, trimmed_len) = delimiters
4122 .iter()
4123 .filter_map(|delimiter| {
4124 let prefix = delimiter.trim_end();
4125 if comment_candidate.starts_with(prefix) {
4126 Some((delimiter, prefix.len()))
4127 } else {
4128 None
4129 }
4130 })
4131 .max_by_key(|(_, len)| *len)?;
4132
4133 let cursor_is_placed_after_comment_marker =
4134 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4135 if cursor_is_placed_after_comment_marker {
4136 Some(delimiter.clone())
4137 } else {
4138 None
4139 }
4140 });
4141
4142 let mut indent_on_newline = IndentSize::spaces(0);
4143 let mut indent_on_extra_newline = IndentSize::spaces(0);
4144
4145 let doc_delimiter = maybe!({
4146 if !selection_is_empty {
4147 return None;
4148 }
4149
4150 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4151 return None;
4152 }
4153
4154 let DocumentationConfig {
4155 start: start_tag,
4156 end: end_tag,
4157 prefix: delimiter,
4158 tab_size: len,
4159 } = language.documentation()?;
4160
4161 let is_within_block_comment = buffer
4162 .language_scope_at(start_point)
4163 .is_some_and(|scope| scope.override_name() == Some("comment"));
4164 if !is_within_block_comment {
4165 return None;
4166 }
4167
4168 let (snapshot, range) =
4169 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4170
4171 let num_of_whitespaces = snapshot
4172 .chars_for_range(range.clone())
4173 .take_while(|c| c.is_whitespace())
4174 .count();
4175
4176 // 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.
4177 let column = start_point.column;
4178 let cursor_is_after_start_tag = {
4179 let start_tag_len = start_tag.len();
4180 let start_tag_line = snapshot
4181 .chars_for_range(range.clone())
4182 .skip(num_of_whitespaces)
4183 .take(start_tag_len)
4184 .collect::<String>();
4185 if start_tag_line.starts_with(start_tag.as_ref()) {
4186 num_of_whitespaces + start_tag_len <= column as usize
4187 } else {
4188 false
4189 }
4190 };
4191
4192 let cursor_is_after_delimiter = {
4193 let delimiter_trim = delimiter.trim_end();
4194 let delimiter_line = snapshot
4195 .chars_for_range(range.clone())
4196 .skip(num_of_whitespaces)
4197 .take(delimiter_trim.len())
4198 .collect::<String>();
4199 if delimiter_line.starts_with(delimiter_trim) {
4200 num_of_whitespaces + delimiter_trim.len() <= column as usize
4201 } else {
4202 false
4203 }
4204 };
4205
4206 let cursor_is_before_end_tag_if_exists = {
4207 let mut char_position = 0u32;
4208 let mut end_tag_offset = None;
4209
4210 'outer: for chunk in snapshot.text_for_range(range.clone()) {
4211 if let Some(byte_pos) = chunk.find(&**end_tag) {
4212 let chars_before_match =
4213 chunk[..byte_pos].chars().count() as u32;
4214 end_tag_offset =
4215 Some(char_position + chars_before_match);
4216 break 'outer;
4217 }
4218 char_position += chunk.chars().count() as u32;
4219 }
4220
4221 if let Some(end_tag_offset) = end_tag_offset {
4222 let cursor_is_before_end_tag = column <= end_tag_offset;
4223 if cursor_is_after_start_tag {
4224 if cursor_is_before_end_tag {
4225 insert_extra_newline = true;
4226 }
4227 let cursor_is_at_start_of_end_tag =
4228 column == end_tag_offset;
4229 if cursor_is_at_start_of_end_tag {
4230 indent_on_extra_newline.len = (*len).into();
4231 }
4232 }
4233 cursor_is_before_end_tag
4234 } else {
4235 true
4236 }
4237 };
4238
4239 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4240 && cursor_is_before_end_tag_if_exists
4241 {
4242 if cursor_is_after_start_tag {
4243 indent_on_newline.len = (*len).into();
4244 }
4245 Some(delimiter.clone())
4246 } else {
4247 None
4248 }
4249 });
4250
4251 (
4252 comment_delimiter,
4253 doc_delimiter,
4254 insert_extra_newline,
4255 indent_on_newline,
4256 indent_on_extra_newline,
4257 )
4258 } else {
4259 (
4260 None,
4261 None,
4262 false,
4263 IndentSize::default(),
4264 IndentSize::default(),
4265 )
4266 };
4267
4268 let prevent_auto_indent = doc_delimiter.is_some();
4269 let delimiter = comment_delimiter.or(doc_delimiter);
4270
4271 let capacity_for_delimiter =
4272 delimiter.as_deref().map(str::len).unwrap_or_default();
4273 let mut new_text = String::with_capacity(
4274 1 + capacity_for_delimiter
4275 + existing_indent.len as usize
4276 + indent_on_newline.len as usize
4277 + indent_on_extra_newline.len as usize,
4278 );
4279 new_text.push('\n');
4280 new_text.extend(existing_indent.chars());
4281 new_text.extend(indent_on_newline.chars());
4282
4283 if let Some(delimiter) = &delimiter {
4284 new_text.push_str(delimiter);
4285 }
4286
4287 if insert_extra_newline {
4288 new_text.push('\n');
4289 new_text.extend(existing_indent.chars());
4290 new_text.extend(indent_on_extra_newline.chars());
4291 }
4292
4293 let anchor = buffer.anchor_after(end);
4294 let new_selection = selection.map(|_| anchor);
4295 (
4296 ((start..end, new_text), prevent_auto_indent),
4297 (insert_extra_newline, new_selection),
4298 )
4299 })
4300 .unzip()
4301 };
4302
4303 let mut auto_indent_edits = Vec::new();
4304 let mut edits = Vec::new();
4305 for (edit, prevent_auto_indent) in edits_with_flags {
4306 if prevent_auto_indent {
4307 edits.push(edit);
4308 } else {
4309 auto_indent_edits.push(edit);
4310 }
4311 }
4312 if !edits.is_empty() {
4313 this.edit(edits, cx);
4314 }
4315 if !auto_indent_edits.is_empty() {
4316 this.edit_with_autoindent(auto_indent_edits, cx);
4317 }
4318
4319 let buffer = this.buffer.read(cx).snapshot(cx);
4320 let new_selections = selection_info
4321 .into_iter()
4322 .map(|(extra_newline_inserted, new_selection)| {
4323 let mut cursor = new_selection.end.to_point(&buffer);
4324 if extra_newline_inserted {
4325 cursor.row -= 1;
4326 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4327 }
4328 new_selection.map(|_| cursor)
4329 })
4330 .collect();
4331
4332 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4333 s.select(new_selections)
4334 });
4335 this.refresh_inline_completion(true, false, window, cx);
4336 });
4337 }
4338
4339 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4340 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4341
4342 let buffer = self.buffer.read(cx);
4343 let snapshot = buffer.snapshot(cx);
4344
4345 let mut edits = Vec::new();
4346 let mut rows = Vec::new();
4347
4348 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4349 let cursor = selection.head();
4350 let row = cursor.row;
4351
4352 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4353
4354 let newline = "\n".to_string();
4355 edits.push((start_of_line..start_of_line, newline));
4356
4357 rows.push(row + rows_inserted as u32);
4358 }
4359
4360 self.transact(window, cx, |editor, window, cx| {
4361 editor.edit(edits, cx);
4362
4363 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4364 let mut index = 0;
4365 s.move_cursors_with(|map, _, _| {
4366 let row = rows[index];
4367 index += 1;
4368
4369 let point = Point::new(row, 0);
4370 let boundary = map.next_line_boundary(point).1;
4371 let clipped = map.clip_point(boundary, Bias::Left);
4372
4373 (clipped, SelectionGoal::None)
4374 });
4375 });
4376
4377 let mut indent_edits = Vec::new();
4378 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4379 for row in rows {
4380 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4381 for (row, indent) in indents {
4382 if indent.len == 0 {
4383 continue;
4384 }
4385
4386 let text = match indent.kind {
4387 IndentKind::Space => " ".repeat(indent.len as usize),
4388 IndentKind::Tab => "\t".repeat(indent.len as usize),
4389 };
4390 let point = Point::new(row.0, 0);
4391 indent_edits.push((point..point, text));
4392 }
4393 }
4394 editor.edit(indent_edits, cx);
4395 });
4396 }
4397
4398 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4399 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4400
4401 let buffer = self.buffer.read(cx);
4402 let snapshot = buffer.snapshot(cx);
4403
4404 let mut edits = Vec::new();
4405 let mut rows = Vec::new();
4406 let mut rows_inserted = 0;
4407
4408 for selection in self.selections.all_adjusted(cx) {
4409 let cursor = selection.head();
4410 let row = cursor.row;
4411
4412 let point = Point::new(row + 1, 0);
4413 let start_of_line = snapshot.clip_point(point, Bias::Left);
4414
4415 let newline = "\n".to_string();
4416 edits.push((start_of_line..start_of_line, newline));
4417
4418 rows_inserted += 1;
4419 rows.push(row + rows_inserted);
4420 }
4421
4422 self.transact(window, cx, |editor, window, cx| {
4423 editor.edit(edits, cx);
4424
4425 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4426 let mut index = 0;
4427 s.move_cursors_with(|map, _, _| {
4428 let row = rows[index];
4429 index += 1;
4430
4431 let point = Point::new(row, 0);
4432 let boundary = map.next_line_boundary(point).1;
4433 let clipped = map.clip_point(boundary, Bias::Left);
4434
4435 (clipped, SelectionGoal::None)
4436 });
4437 });
4438
4439 let mut indent_edits = Vec::new();
4440 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4441 for row in rows {
4442 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4443 for (row, indent) in indents {
4444 if indent.len == 0 {
4445 continue;
4446 }
4447
4448 let text = match indent.kind {
4449 IndentKind::Space => " ".repeat(indent.len as usize),
4450 IndentKind::Tab => "\t".repeat(indent.len as usize),
4451 };
4452 let point = Point::new(row.0, 0);
4453 indent_edits.push((point..point, text));
4454 }
4455 }
4456 editor.edit(indent_edits, cx);
4457 });
4458 }
4459
4460 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4461 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4462 original_indent_columns: Vec::new(),
4463 });
4464 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4465 }
4466
4467 fn insert_with_autoindent_mode(
4468 &mut self,
4469 text: &str,
4470 autoindent_mode: Option<AutoindentMode>,
4471 window: &mut Window,
4472 cx: &mut Context<Self>,
4473 ) {
4474 if self.read_only(cx) {
4475 return;
4476 }
4477
4478 let text: Arc<str> = text.into();
4479 self.transact(window, cx, |this, window, cx| {
4480 let old_selections = this.selections.all_adjusted(cx);
4481 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4482 let anchors = {
4483 let snapshot = buffer.read(cx);
4484 old_selections
4485 .iter()
4486 .map(|s| {
4487 let anchor = snapshot.anchor_after(s.head());
4488 s.map(|_| anchor)
4489 })
4490 .collect::<Vec<_>>()
4491 };
4492 buffer.edit(
4493 old_selections
4494 .iter()
4495 .map(|s| (s.start..s.end, text.clone())),
4496 autoindent_mode,
4497 cx,
4498 );
4499 anchors
4500 });
4501
4502 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4503 s.select_anchors(selection_anchors);
4504 });
4505
4506 cx.notify();
4507 });
4508 }
4509
4510 fn trigger_completion_on_input(
4511 &mut self,
4512 text: &str,
4513 trigger_in_words: bool,
4514 window: &mut Window,
4515 cx: &mut Context<Self>,
4516 ) {
4517 let completions_source = self
4518 .context_menu
4519 .borrow()
4520 .as_ref()
4521 .and_then(|menu| match menu {
4522 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4523 CodeContextMenu::CodeActions(_) => None,
4524 });
4525
4526 match completions_source {
4527 Some(CompletionsMenuSource::Words) => {
4528 self.show_word_completions(&ShowWordCompletions, window, cx)
4529 }
4530 Some(CompletionsMenuSource::Normal)
4531 | Some(CompletionsMenuSource::SnippetChoices)
4532 | None
4533 if self.is_completion_trigger(
4534 text,
4535 trigger_in_words,
4536 completions_source.is_some(),
4537 cx,
4538 ) =>
4539 {
4540 self.show_completions(
4541 &ShowCompletions {
4542 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4543 },
4544 window,
4545 cx,
4546 )
4547 }
4548 _ => {
4549 self.hide_context_menu(window, cx);
4550 }
4551 }
4552 }
4553
4554 fn is_completion_trigger(
4555 &self,
4556 text: &str,
4557 trigger_in_words: bool,
4558 menu_is_open: bool,
4559 cx: &mut Context<Self>,
4560 ) -> bool {
4561 let position = self.selections.newest_anchor().head();
4562 let multibuffer = self.buffer.read(cx);
4563 let Some(buffer) = position
4564 .buffer_id
4565 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
4566 else {
4567 return false;
4568 };
4569
4570 if let Some(completion_provider) = &self.completion_provider {
4571 completion_provider.is_completion_trigger(
4572 &buffer,
4573 position.text_anchor,
4574 text,
4575 trigger_in_words,
4576 menu_is_open,
4577 cx,
4578 )
4579 } else {
4580 false
4581 }
4582 }
4583
4584 /// If any empty selections is touching the start of its innermost containing autoclose
4585 /// region, expand it to select the brackets.
4586 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4587 let selections = self.selections.all::<usize>(cx);
4588 let buffer = self.buffer.read(cx).read(cx);
4589 let new_selections = self
4590 .selections_with_autoclose_regions(selections, &buffer)
4591 .map(|(mut selection, region)| {
4592 if !selection.is_empty() {
4593 return selection;
4594 }
4595
4596 if let Some(region) = region {
4597 let mut range = region.range.to_offset(&buffer);
4598 if selection.start == range.start && range.start >= region.pair.start.len() {
4599 range.start -= region.pair.start.len();
4600 if buffer.contains_str_at(range.start, ®ion.pair.start)
4601 && buffer.contains_str_at(range.end, ®ion.pair.end)
4602 {
4603 range.end += region.pair.end.len();
4604 selection.start = range.start;
4605 selection.end = range.end;
4606
4607 return selection;
4608 }
4609 }
4610 }
4611
4612 let always_treat_brackets_as_autoclosed = buffer
4613 .language_settings_at(selection.start, cx)
4614 .always_treat_brackets_as_autoclosed;
4615
4616 if !always_treat_brackets_as_autoclosed {
4617 return selection;
4618 }
4619
4620 if let Some(scope) = buffer.language_scope_at(selection.start) {
4621 for (pair, enabled) in scope.brackets() {
4622 if !enabled || !pair.close {
4623 continue;
4624 }
4625
4626 if buffer.contains_str_at(selection.start, &pair.end) {
4627 let pair_start_len = pair.start.len();
4628 if buffer.contains_str_at(
4629 selection.start.saturating_sub(pair_start_len),
4630 &pair.start,
4631 ) {
4632 selection.start -= pair_start_len;
4633 selection.end += pair.end.len();
4634
4635 return selection;
4636 }
4637 }
4638 }
4639 }
4640
4641 selection
4642 })
4643 .collect();
4644
4645 drop(buffer);
4646 self.change_selections(None, window, cx, |selections| {
4647 selections.select(new_selections)
4648 });
4649 }
4650
4651 /// Iterate the given selections, and for each one, find the smallest surrounding
4652 /// autoclose region. This uses the ordering of the selections and the autoclose
4653 /// regions to avoid repeated comparisons.
4654 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
4655 &'a self,
4656 selections: impl IntoIterator<Item = Selection<D>>,
4657 buffer: &'a MultiBufferSnapshot,
4658 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
4659 let mut i = 0;
4660 let mut regions = self.autoclose_regions.as_slice();
4661 selections.into_iter().map(move |selection| {
4662 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
4663
4664 let mut enclosing = None;
4665 while let Some(pair_state) = regions.get(i) {
4666 if pair_state.range.end.to_offset(buffer) < range.start {
4667 regions = ®ions[i + 1..];
4668 i = 0;
4669 } else if pair_state.range.start.to_offset(buffer) > range.end {
4670 break;
4671 } else {
4672 if pair_state.selection_id == selection.id {
4673 enclosing = Some(pair_state);
4674 }
4675 i += 1;
4676 }
4677 }
4678
4679 (selection, enclosing)
4680 })
4681 }
4682
4683 /// Remove any autoclose regions that no longer contain their selection.
4684 fn invalidate_autoclose_regions(
4685 &mut self,
4686 mut selections: &[Selection<Anchor>],
4687 buffer: &MultiBufferSnapshot,
4688 ) {
4689 self.autoclose_regions.retain(|state| {
4690 let mut i = 0;
4691 while let Some(selection) = selections.get(i) {
4692 if selection.end.cmp(&state.range.start, buffer).is_lt() {
4693 selections = &selections[1..];
4694 continue;
4695 }
4696 if selection.start.cmp(&state.range.end, buffer).is_gt() {
4697 break;
4698 }
4699 if selection.id == state.selection_id {
4700 return true;
4701 } else {
4702 i += 1;
4703 }
4704 }
4705 false
4706 });
4707 }
4708
4709 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
4710 let offset = position.to_offset(buffer);
4711 let (word_range, kind) = buffer.surrounding_word(offset, true);
4712 if offset > word_range.start && kind == Some(CharKind::Word) {
4713 Some(
4714 buffer
4715 .text_for_range(word_range.start..offset)
4716 .collect::<String>(),
4717 )
4718 } else {
4719 None
4720 }
4721 }
4722
4723 pub fn toggle_inline_values(
4724 &mut self,
4725 _: &ToggleInlineValues,
4726 _: &mut Window,
4727 cx: &mut Context<Self>,
4728 ) {
4729 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
4730
4731 self.refresh_inline_values(cx);
4732 }
4733
4734 pub fn toggle_inlay_hints(
4735 &mut self,
4736 _: &ToggleInlayHints,
4737 _: &mut Window,
4738 cx: &mut Context<Self>,
4739 ) {
4740 self.refresh_inlay_hints(
4741 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
4742 cx,
4743 );
4744 }
4745
4746 pub fn inlay_hints_enabled(&self) -> bool {
4747 self.inlay_hint_cache.enabled
4748 }
4749
4750 pub fn inline_values_enabled(&self) -> bool {
4751 self.inline_value_cache.enabled
4752 }
4753
4754 #[cfg(any(test, feature = "test-support"))]
4755 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
4756 self.display_map
4757 .read(cx)
4758 .current_inlays()
4759 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
4760 .cloned()
4761 .collect()
4762 }
4763
4764 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
4765 if self.semantics_provider.is_none() || !self.mode.is_full() {
4766 return;
4767 }
4768
4769 let reason_description = reason.description();
4770 let ignore_debounce = matches!(
4771 reason,
4772 InlayHintRefreshReason::SettingsChange(_)
4773 | InlayHintRefreshReason::Toggle(_)
4774 | InlayHintRefreshReason::ExcerptsRemoved(_)
4775 | InlayHintRefreshReason::ModifiersChanged(_)
4776 );
4777 let (invalidate_cache, required_languages) = match reason {
4778 InlayHintRefreshReason::ModifiersChanged(enabled) => {
4779 match self.inlay_hint_cache.modifiers_override(enabled) {
4780 Some(enabled) => {
4781 if enabled {
4782 (InvalidationStrategy::RefreshRequested, None)
4783 } else {
4784 self.splice_inlays(
4785 &self
4786 .visible_inlay_hints(cx)
4787 .iter()
4788 .map(|inlay| inlay.id)
4789 .collect::<Vec<InlayId>>(),
4790 Vec::new(),
4791 cx,
4792 );
4793 return;
4794 }
4795 }
4796 None => return,
4797 }
4798 }
4799 InlayHintRefreshReason::Toggle(enabled) => {
4800 if self.inlay_hint_cache.toggle(enabled) {
4801 if enabled {
4802 (InvalidationStrategy::RefreshRequested, None)
4803 } else {
4804 self.splice_inlays(
4805 &self
4806 .visible_inlay_hints(cx)
4807 .iter()
4808 .map(|inlay| inlay.id)
4809 .collect::<Vec<InlayId>>(),
4810 Vec::new(),
4811 cx,
4812 );
4813 return;
4814 }
4815 } else {
4816 return;
4817 }
4818 }
4819 InlayHintRefreshReason::SettingsChange(new_settings) => {
4820 match self.inlay_hint_cache.update_settings(
4821 &self.buffer,
4822 new_settings,
4823 self.visible_inlay_hints(cx),
4824 cx,
4825 ) {
4826 ControlFlow::Break(Some(InlaySplice {
4827 to_remove,
4828 to_insert,
4829 })) => {
4830 self.splice_inlays(&to_remove, to_insert, cx);
4831 return;
4832 }
4833 ControlFlow::Break(None) => return,
4834 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
4835 }
4836 }
4837 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
4838 if let Some(InlaySplice {
4839 to_remove,
4840 to_insert,
4841 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
4842 {
4843 self.splice_inlays(&to_remove, to_insert, cx);
4844 }
4845 self.display_map.update(cx, |display_map, _| {
4846 display_map.remove_inlays_for_excerpts(&excerpts_removed)
4847 });
4848 return;
4849 }
4850 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
4851 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
4852 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
4853 }
4854 InlayHintRefreshReason::RefreshRequested => {
4855 (InvalidationStrategy::RefreshRequested, None)
4856 }
4857 };
4858
4859 if let Some(InlaySplice {
4860 to_remove,
4861 to_insert,
4862 }) = self.inlay_hint_cache.spawn_hint_refresh(
4863 reason_description,
4864 self.excerpts_for_inlay_hints_query(required_languages.as_ref(), cx),
4865 invalidate_cache,
4866 ignore_debounce,
4867 cx,
4868 ) {
4869 self.splice_inlays(&to_remove, to_insert, cx);
4870 }
4871 }
4872
4873 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
4874 self.display_map
4875 .read(cx)
4876 .current_inlays()
4877 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
4878 .cloned()
4879 .collect()
4880 }
4881
4882 pub fn excerpts_for_inlay_hints_query(
4883 &self,
4884 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
4885 cx: &mut Context<Editor>,
4886 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
4887 let Some(project) = self.project.as_ref() else {
4888 return HashMap::default();
4889 };
4890 let project = project.read(cx);
4891 let multi_buffer = self.buffer().read(cx);
4892 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
4893 let multi_buffer_visible_start = self
4894 .scroll_manager
4895 .anchor()
4896 .anchor
4897 .to_point(&multi_buffer_snapshot);
4898 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
4899 multi_buffer_visible_start
4900 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
4901 Bias::Left,
4902 );
4903 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
4904 multi_buffer_snapshot
4905 .range_to_buffer_ranges(multi_buffer_visible_range)
4906 .into_iter()
4907 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
4908 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
4909 let buffer_file = project::File::from_dyn(buffer.file())?;
4910 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
4911 let worktree_entry = buffer_worktree
4912 .read(cx)
4913 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
4914 if worktree_entry.is_ignored {
4915 return None;
4916 }
4917
4918 let language = buffer.language()?;
4919 if let Some(restrict_to_languages) = restrict_to_languages {
4920 if !restrict_to_languages.contains(language) {
4921 return None;
4922 }
4923 }
4924 Some((
4925 excerpt_id,
4926 (
4927 multi_buffer.buffer(buffer.remote_id()).unwrap(),
4928 buffer.version().clone(),
4929 excerpt_visible_range,
4930 ),
4931 ))
4932 })
4933 .collect()
4934 }
4935
4936 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
4937 TextLayoutDetails {
4938 text_system: window.text_system().clone(),
4939 editor_style: self.style.clone().unwrap(),
4940 rem_size: window.rem_size(),
4941 scroll_anchor: self.scroll_manager.anchor(),
4942 visible_rows: self.visible_line_count(),
4943 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
4944 }
4945 }
4946
4947 pub fn splice_inlays(
4948 &self,
4949 to_remove: &[InlayId],
4950 to_insert: Vec<Inlay>,
4951 cx: &mut Context<Self>,
4952 ) {
4953 self.display_map.update(cx, |display_map, cx| {
4954 display_map.splice_inlays(to_remove, to_insert, cx)
4955 });
4956 cx.notify();
4957 }
4958
4959 fn trigger_on_type_formatting(
4960 &self,
4961 input: String,
4962 window: &mut Window,
4963 cx: &mut Context<Self>,
4964 ) -> Option<Task<Result<()>>> {
4965 if input.len() != 1 {
4966 return None;
4967 }
4968
4969 let project = self.project.as_ref()?;
4970 let position = self.selections.newest_anchor().head();
4971 let (buffer, buffer_position) = self
4972 .buffer
4973 .read(cx)
4974 .text_anchor_for_position(position, cx)?;
4975
4976 let settings = language_settings::language_settings(
4977 buffer
4978 .read(cx)
4979 .language_at(buffer_position)
4980 .map(|l| l.name()),
4981 buffer.read(cx).file(),
4982 cx,
4983 );
4984 if !settings.use_on_type_format {
4985 return None;
4986 }
4987
4988 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
4989 // hence we do LSP request & edit on host side only — add formats to host's history.
4990 let push_to_lsp_host_history = true;
4991 // If this is not the host, append its history with new edits.
4992 let push_to_client_history = project.read(cx).is_via_collab();
4993
4994 let on_type_formatting = project.update(cx, |project, cx| {
4995 project.on_type_format(
4996 buffer.clone(),
4997 buffer_position,
4998 input,
4999 push_to_lsp_host_history,
5000 cx,
5001 )
5002 });
5003 Some(cx.spawn_in(window, async move |editor, cx| {
5004 if let Some(transaction) = on_type_formatting.await? {
5005 if push_to_client_history {
5006 buffer
5007 .update(cx, |buffer, _| {
5008 buffer.push_transaction(transaction, Instant::now());
5009 buffer.finalize_last_transaction();
5010 })
5011 .ok();
5012 }
5013 editor.update(cx, |editor, cx| {
5014 editor.refresh_document_highlights(cx);
5015 })?;
5016 }
5017 Ok(())
5018 }))
5019 }
5020
5021 pub fn show_word_completions(
5022 &mut self,
5023 _: &ShowWordCompletions,
5024 window: &mut Window,
5025 cx: &mut Context<Self>,
5026 ) {
5027 self.open_or_update_completions_menu(Some(CompletionsMenuSource::Words), None, window, cx);
5028 }
5029
5030 pub fn show_completions(
5031 &mut self,
5032 options: &ShowCompletions,
5033 window: &mut Window,
5034 cx: &mut Context<Self>,
5035 ) {
5036 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5037 }
5038
5039 fn open_or_update_completions_menu(
5040 &mut self,
5041 requested_source: Option<CompletionsMenuSource>,
5042 trigger: Option<&str>,
5043 window: &mut Window,
5044 cx: &mut Context<Self>,
5045 ) {
5046 if self.pending_rename.is_some() {
5047 return;
5048 }
5049
5050 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5051
5052 let position = self
5053 .selections
5054 .newest_anchor()
5055 .head()
5056 .bias_right(&multibuffer_snapshot);
5057 if position.diff_base_anchor.is_some() {
5058 return;
5059 }
5060 let (buffer, buffer_position) =
5061 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
5062 output
5063 } else {
5064 return;
5065 };
5066 let buffer_snapshot = buffer.read(cx).snapshot();
5067
5068 let query: Option<Arc<String>> =
5069 Self::completion_query(&multibuffer_snapshot, position).map(|query| query.into());
5070
5071 drop(multibuffer_snapshot);
5072
5073 let provider = match requested_source {
5074 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5075 Some(CompletionsMenuSource::Words) => None,
5076 Some(CompletionsMenuSource::SnippetChoices) => {
5077 log::error!("bug: SnippetChoices requested_source is not handled");
5078 None
5079 }
5080 };
5081
5082 let sort_completions = provider
5083 .as_ref()
5084 .map_or(false, |provider| provider.sort_completions());
5085
5086 let filter_completions = provider
5087 .as_ref()
5088 .map_or(true, |provider| provider.filter_completions());
5089
5090 // When `is_incomplete` is false, can filter completions instead of re-querying when the
5091 // current query is a suffix of the initial query.
5092 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5093 if !menu.is_incomplete && filter_completions {
5094 // If the new query is a suffix of the old query (typing more characters) and
5095 // the previous result was complete, the existing completions can be filtered.
5096 //
5097 // Note that this is always true for snippet completions.
5098 let query_matches = match (&menu.initial_query, &query) {
5099 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5100 (None, _) => true,
5101 _ => false,
5102 };
5103 if query_matches {
5104 let position_matches = if menu.initial_position == position {
5105 true
5106 } else {
5107 let snapshot = self.buffer.read(cx).read(cx);
5108 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5109 };
5110 if position_matches {
5111 menu.filter(query.clone(), provider.clone(), window, cx);
5112 return;
5113 }
5114 }
5115 }
5116 };
5117
5118 let trigger_kind = match trigger {
5119 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5120 CompletionTriggerKind::TRIGGER_CHARACTER
5121 }
5122 _ => CompletionTriggerKind::INVOKED,
5123 };
5124 let completion_context = CompletionContext {
5125 trigger_character: trigger.and_then(|trigger| {
5126 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5127 Some(String::from(trigger))
5128 } else {
5129 None
5130 }
5131 }),
5132 trigger_kind,
5133 };
5134
5135 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5136 buffer_snapshot.surrounding_word(buffer_position)
5137 {
5138 let word_to_exclude = buffer_snapshot
5139 .text_for_range(word_range.clone())
5140 .collect::<String>();
5141 (
5142 buffer_snapshot.anchor_before(word_range.start)
5143 ..buffer_snapshot.anchor_after(buffer_position),
5144 Some(word_to_exclude),
5145 )
5146 } else {
5147 (buffer_position..buffer_position, None)
5148 };
5149
5150 let language = buffer_snapshot
5151 .language_at(buffer_position)
5152 .map(|language| language.name());
5153
5154 let completion_settings =
5155 language_settings(language.clone(), buffer_snapshot.file(), cx).completions;
5156
5157 let show_completion_documentation = buffer_snapshot
5158 .settings_at(buffer_position, cx)
5159 .show_completion_documentation;
5160
5161 // The document can be large, so stay in reasonable bounds when searching for words,
5162 // otherwise completion pop-up might be slow to appear.
5163 const WORD_LOOKUP_ROWS: u32 = 5_000;
5164 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5165 let min_word_search = buffer_snapshot.clip_point(
5166 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5167 Bias::Left,
5168 );
5169 let max_word_search = buffer_snapshot.clip_point(
5170 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5171 Bias::Right,
5172 );
5173 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5174 ..buffer_snapshot.point_to_offset(max_word_search);
5175
5176 let skip_digits = query
5177 .as_ref()
5178 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
5179
5180 let (mut words, provider_responses) = match &provider {
5181 Some(provider) => {
5182 let provider_responses = provider.completions(
5183 position.excerpt_id,
5184 &buffer,
5185 buffer_position,
5186 completion_context,
5187 window,
5188 cx,
5189 );
5190
5191 let words = match completion_settings.words {
5192 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
5193 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
5194 .background_spawn(async move {
5195 buffer_snapshot.words_in_range(WordsQuery {
5196 fuzzy_contents: None,
5197 range: word_search_range,
5198 skip_digits,
5199 })
5200 }),
5201 };
5202
5203 (words, provider_responses)
5204 }
5205 None => (
5206 cx.background_spawn(async move {
5207 buffer_snapshot.words_in_range(WordsQuery {
5208 fuzzy_contents: None,
5209 range: word_search_range,
5210 skip_digits,
5211 })
5212 }),
5213 Task::ready(Ok(Vec::new())),
5214 ),
5215 };
5216
5217 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5218
5219 let id = post_inc(&mut self.next_completion_id);
5220 let task = cx.spawn_in(window, async move |editor, cx| {
5221 let Ok(()) = editor.update(cx, |this, _| {
5222 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5223 }) else {
5224 return;
5225 };
5226
5227 // TODO: Ideally completions from different sources would be selectively re-queried, so
5228 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5229 let mut completions = Vec::new();
5230 let mut is_incomplete = false;
5231 if let Some(provider_responses) = provider_responses.await.log_err() {
5232 if !provider_responses.is_empty() {
5233 for response in provider_responses {
5234 completions.extend(response.completions);
5235 is_incomplete = is_incomplete || response.is_incomplete;
5236 }
5237 if completion_settings.words == WordsCompletionMode::Fallback {
5238 words = Task::ready(BTreeMap::default());
5239 }
5240 }
5241 }
5242
5243 let mut words = words.await;
5244 if let Some(word_to_exclude) = &word_to_exclude {
5245 words.remove(word_to_exclude);
5246 }
5247 for lsp_completion in &completions {
5248 words.remove(&lsp_completion.new_text);
5249 }
5250 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5251 replace_range: word_replace_range.clone(),
5252 new_text: word.clone(),
5253 label: CodeLabel::plain(word, None),
5254 icon_path: None,
5255 documentation: None,
5256 source: CompletionSource::BufferWord {
5257 word_range,
5258 resolved: false,
5259 },
5260 insert_text_mode: Some(InsertTextMode::AS_IS),
5261 confirm: None,
5262 }));
5263
5264 let menu = if completions.is_empty() {
5265 None
5266 } else {
5267 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5268 let languages = editor
5269 .workspace
5270 .as_ref()
5271 .and_then(|(workspace, _)| workspace.upgrade())
5272 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5273 let menu = CompletionsMenu::new(
5274 id,
5275 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5276 sort_completions,
5277 show_completion_documentation,
5278 position,
5279 query.clone(),
5280 is_incomplete,
5281 buffer.clone(),
5282 completions.into(),
5283 snippet_sort_order,
5284 languages,
5285 language,
5286 cx,
5287 );
5288
5289 let query = if filter_completions { query } else { None };
5290 let matches_task = if let Some(query) = query {
5291 menu.do_async_filtering(query, cx)
5292 } else {
5293 Task::ready(menu.unfiltered_matches())
5294 };
5295 (menu, matches_task)
5296 }) else {
5297 return;
5298 };
5299
5300 let matches = matches_task.await;
5301
5302 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5303 // Newer menu already set, so exit.
5304 match editor.context_menu.borrow().as_ref() {
5305 Some(CodeContextMenu::Completions(prev_menu)) => {
5306 if prev_menu.id > id {
5307 return;
5308 }
5309 }
5310 _ => {}
5311 };
5312
5313 // Only valid to take prev_menu because it the new menu is immediately set
5314 // below, or the menu is hidden.
5315 match editor.context_menu.borrow_mut().take() {
5316 Some(CodeContextMenu::Completions(prev_menu)) => {
5317 let position_matches =
5318 if prev_menu.initial_position == menu.initial_position {
5319 true
5320 } else {
5321 let snapshot = editor.buffer.read(cx).read(cx);
5322 prev_menu.initial_position.to_offset(&snapshot)
5323 == menu.initial_position.to_offset(&snapshot)
5324 };
5325 if position_matches {
5326 // Preserve markdown cache before `set_filter_results` because it will
5327 // try to populate the documentation cache.
5328 menu.preserve_markdown_cache(prev_menu);
5329 }
5330 }
5331 _ => {}
5332 };
5333
5334 menu.set_filter_results(matches, provider, window, cx);
5335 }) else {
5336 return;
5337 };
5338
5339 menu.visible().then_some(menu)
5340 };
5341
5342 editor
5343 .update_in(cx, |editor, window, cx| {
5344 if editor.focus_handle.is_focused(window) {
5345 if let Some(menu) = menu {
5346 *editor.context_menu.borrow_mut() =
5347 Some(CodeContextMenu::Completions(menu));
5348
5349 crate::hover_popover::hide_hover(editor, cx);
5350 if editor.show_edit_predictions_in_menu() {
5351 editor.update_visible_inline_completion(window, cx);
5352 } else {
5353 editor.discard_inline_completion(false, cx);
5354 }
5355
5356 cx.notify();
5357 return;
5358 }
5359 }
5360
5361 if editor.completion_tasks.len() <= 1 {
5362 // If there are no more completion tasks and the last menu was empty, we should hide it.
5363 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5364 // If it was already hidden and we don't show inline completions in the menu, we should
5365 // also show the inline-completion when available.
5366 if was_hidden && editor.show_edit_predictions_in_menu() {
5367 editor.update_visible_inline_completion(window, cx);
5368 }
5369 }
5370 })
5371 .ok();
5372 });
5373
5374 self.completion_tasks.push((id, task));
5375 }
5376
5377 #[cfg(feature = "test-support")]
5378 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5379 let menu = self.context_menu.borrow();
5380 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5381 let completions = menu.completions.borrow();
5382 Some(completions.to_vec())
5383 } else {
5384 None
5385 }
5386 }
5387
5388 pub fn with_completions_menu_matching_id<R>(
5389 &self,
5390 id: CompletionId,
5391 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5392 ) -> R {
5393 let mut context_menu = self.context_menu.borrow_mut();
5394 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5395 return f(None);
5396 };
5397 if completions_menu.id != id {
5398 return f(None);
5399 }
5400 f(Some(completions_menu))
5401 }
5402
5403 pub fn confirm_completion(
5404 &mut self,
5405 action: &ConfirmCompletion,
5406 window: &mut Window,
5407 cx: &mut Context<Self>,
5408 ) -> Option<Task<Result<()>>> {
5409 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5410 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5411 }
5412
5413 pub fn confirm_completion_insert(
5414 &mut self,
5415 _: &ConfirmCompletionInsert,
5416 window: &mut Window,
5417 cx: &mut Context<Self>,
5418 ) -> Option<Task<Result<()>>> {
5419 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5420 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5421 }
5422
5423 pub fn confirm_completion_replace(
5424 &mut self,
5425 _: &ConfirmCompletionReplace,
5426 window: &mut Window,
5427 cx: &mut Context<Self>,
5428 ) -> Option<Task<Result<()>>> {
5429 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5430 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5431 }
5432
5433 pub fn compose_completion(
5434 &mut self,
5435 action: &ComposeCompletion,
5436 window: &mut Window,
5437 cx: &mut Context<Self>,
5438 ) -> Option<Task<Result<()>>> {
5439 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5440 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5441 }
5442
5443 fn do_completion(
5444 &mut self,
5445 item_ix: Option<usize>,
5446 intent: CompletionIntent,
5447 window: &mut Window,
5448 cx: &mut Context<Editor>,
5449 ) -> Option<Task<Result<()>>> {
5450 use language::ToOffset as _;
5451
5452 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5453 else {
5454 return None;
5455 };
5456
5457 let candidate_id = {
5458 let entries = completions_menu.entries.borrow();
5459 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5460 if self.show_edit_predictions_in_menu() {
5461 self.discard_inline_completion(true, cx);
5462 }
5463 mat.candidate_id
5464 };
5465
5466 let completion = completions_menu
5467 .completions
5468 .borrow()
5469 .get(candidate_id)?
5470 .clone();
5471 cx.stop_propagation();
5472
5473 let buffer_handle = completions_menu.buffer.clone();
5474
5475 let CompletionEdit {
5476 new_text,
5477 snippet,
5478 replace_range,
5479 } = process_completion_for_edit(
5480 &completion,
5481 intent,
5482 &buffer_handle,
5483 &completions_menu.initial_position.text_anchor,
5484 cx,
5485 );
5486
5487 let buffer = buffer_handle.read(cx);
5488 let snapshot = self.buffer.read(cx).snapshot(cx);
5489 let newest_anchor = self.selections.newest_anchor();
5490 let replace_range_multibuffer = {
5491 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5492 let multibuffer_anchor = snapshot
5493 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5494 .unwrap()
5495 ..snapshot
5496 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5497 .unwrap();
5498 multibuffer_anchor.start.to_offset(&snapshot)
5499 ..multibuffer_anchor.end.to_offset(&snapshot)
5500 };
5501 if newest_anchor.head().buffer_id != Some(buffer.remote_id()) {
5502 return None;
5503 }
5504
5505 let old_text = buffer
5506 .text_for_range(replace_range.clone())
5507 .collect::<String>();
5508 let lookbehind = newest_anchor
5509 .start
5510 .text_anchor
5511 .to_offset(buffer)
5512 .saturating_sub(replace_range.start);
5513 let lookahead = replace_range
5514 .end
5515 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5516 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5517 let suffix = &old_text[lookbehind.min(old_text.len())..];
5518
5519 let selections = self.selections.all::<usize>(cx);
5520 let mut ranges = Vec::new();
5521 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5522
5523 for selection in &selections {
5524 let range = if selection.id == newest_anchor.id {
5525 replace_range_multibuffer.clone()
5526 } else {
5527 let mut range = selection.range();
5528
5529 // if prefix is present, don't duplicate it
5530 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5531 range.start = range.start.saturating_sub(lookbehind);
5532
5533 // if suffix is also present, mimic the newest cursor and replace it
5534 if selection.id != newest_anchor.id
5535 && snapshot.contains_str_at(range.end, suffix)
5536 {
5537 range.end += lookahead;
5538 }
5539 }
5540 range
5541 };
5542
5543 ranges.push(range.clone());
5544
5545 if !self.linked_edit_ranges.is_empty() {
5546 let start_anchor = snapshot.anchor_before(range.start);
5547 let end_anchor = snapshot.anchor_after(range.end);
5548 if let Some(ranges) = self
5549 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5550 {
5551 for (buffer, edits) in ranges {
5552 linked_edits
5553 .entry(buffer.clone())
5554 .or_default()
5555 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5556 }
5557 }
5558 }
5559 }
5560
5561 let common_prefix_len = old_text
5562 .chars()
5563 .zip(new_text.chars())
5564 .take_while(|(a, b)| a == b)
5565 .map(|(a, _)| a.len_utf8())
5566 .sum::<usize>();
5567
5568 cx.emit(EditorEvent::InputHandled {
5569 utf16_range_to_replace: None,
5570 text: new_text[common_prefix_len..].into(),
5571 });
5572
5573 self.transact(window, cx, |this, window, cx| {
5574 if let Some(mut snippet) = snippet {
5575 snippet.text = new_text.to_string();
5576 this.insert_snippet(&ranges, snippet, window, cx).log_err();
5577 } else {
5578 this.buffer.update(cx, |buffer, cx| {
5579 let auto_indent = match completion.insert_text_mode {
5580 Some(InsertTextMode::AS_IS) => None,
5581 _ => this.autoindent_mode.clone(),
5582 };
5583 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5584 buffer.edit(edits, auto_indent, cx);
5585 });
5586 }
5587 for (buffer, edits) in linked_edits {
5588 buffer.update(cx, |buffer, cx| {
5589 let snapshot = buffer.snapshot();
5590 let edits = edits
5591 .into_iter()
5592 .map(|(range, text)| {
5593 use text::ToPoint as TP;
5594 let end_point = TP::to_point(&range.end, &snapshot);
5595 let start_point = TP::to_point(&range.start, &snapshot);
5596 (start_point..end_point, text)
5597 })
5598 .sorted_by_key(|(range, _)| range.start);
5599 buffer.edit(edits, None, cx);
5600 })
5601 }
5602
5603 this.refresh_inline_completion(true, false, window, cx);
5604 });
5605
5606 let show_new_completions_on_confirm = completion
5607 .confirm
5608 .as_ref()
5609 .map_or(false, |confirm| confirm(intent, window, cx));
5610 if show_new_completions_on_confirm {
5611 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5612 }
5613
5614 let provider = self.completion_provider.as_ref()?;
5615 drop(completion);
5616 let apply_edits = provider.apply_additional_edits_for_completion(
5617 buffer_handle,
5618 completions_menu.completions.clone(),
5619 candidate_id,
5620 true,
5621 cx,
5622 );
5623
5624 let editor_settings = EditorSettings::get_global(cx);
5625 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5626 // After the code completion is finished, users often want to know what signatures are needed.
5627 // so we should automatically call signature_help
5628 self.show_signature_help(&ShowSignatureHelp, window, cx);
5629 }
5630
5631 Some(cx.foreground_executor().spawn(async move {
5632 apply_edits.await?;
5633 Ok(())
5634 }))
5635 }
5636
5637 pub fn toggle_code_actions(
5638 &mut self,
5639 action: &ToggleCodeActions,
5640 window: &mut Window,
5641 cx: &mut Context<Self>,
5642 ) {
5643 let quick_launch = action.quick_launch;
5644 let mut context_menu = self.context_menu.borrow_mut();
5645 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5646 if code_actions.deployed_from == action.deployed_from {
5647 // Toggle if we're selecting the same one
5648 *context_menu = None;
5649 cx.notify();
5650 return;
5651 } else {
5652 // Otherwise, clear it and start a new one
5653 *context_menu = None;
5654 cx.notify();
5655 }
5656 }
5657 drop(context_menu);
5658 let snapshot = self.snapshot(window, cx);
5659 let deployed_from = action.deployed_from.clone();
5660 let mut task = self.code_actions_task.take();
5661 let action = action.clone();
5662 cx.spawn_in(window, async move |editor, cx| {
5663 while let Some(prev_task) = task {
5664 prev_task.await.log_err();
5665 task = editor.update(cx, |this, _| this.code_actions_task.take())?;
5666 }
5667
5668 let spawned_test_task = editor.update_in(cx, |editor, window, cx| {
5669 if editor.focus_handle.is_focused(window) {
5670 let multibuffer_point = match &action.deployed_from {
5671 Some(CodeActionSource::Indicator(row)) => {
5672 DisplayPoint::new(*row, 0).to_point(&snapshot)
5673 }
5674 _ => editor.selections.newest::<Point>(cx).head(),
5675 };
5676 let (buffer, buffer_row) = snapshot
5677 .buffer_snapshot
5678 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
5679 .and_then(|(buffer_snapshot, range)| {
5680 editor
5681 .buffer
5682 .read(cx)
5683 .buffer(buffer_snapshot.remote_id())
5684 .map(|buffer| (buffer, range.start.row))
5685 })?;
5686 let (_, code_actions) = editor
5687 .available_code_actions
5688 .clone()
5689 .and_then(|(location, code_actions)| {
5690 let snapshot = location.buffer.read(cx).snapshot();
5691 let point_range = location.range.to_point(&snapshot);
5692 let point_range = point_range.start.row..=point_range.end.row;
5693 if point_range.contains(&buffer_row) {
5694 Some((location, code_actions))
5695 } else {
5696 None
5697 }
5698 })
5699 .unzip();
5700 let buffer_id = buffer.read(cx).remote_id();
5701 let tasks = editor
5702 .tasks
5703 .get(&(buffer_id, buffer_row))
5704 .map(|t| Arc::new(t.to_owned()));
5705 if tasks.is_none() && code_actions.is_none() {
5706 return None;
5707 }
5708
5709 editor.completion_tasks.clear();
5710 editor.discard_inline_completion(false, cx);
5711 let task_context =
5712 tasks
5713 .as_ref()
5714 .zip(editor.project.clone())
5715 .map(|(tasks, project)| {
5716 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx)
5717 });
5718
5719 Some(cx.spawn_in(window, async move |editor, cx| {
5720 let task_context = match task_context {
5721 Some(task_context) => task_context.await,
5722 None => None,
5723 };
5724 let resolved_tasks =
5725 tasks
5726 .zip(task_context.clone())
5727 .map(|(tasks, task_context)| ResolvedTasks {
5728 templates: tasks.resolve(&task_context).collect(),
5729 position: snapshot.buffer_snapshot.anchor_before(Point::new(
5730 multibuffer_point.row,
5731 tasks.column,
5732 )),
5733 });
5734 let debug_scenarios = editor.update(cx, |editor, cx| {
5735 if cx.has_flag::<DebuggerFeatureFlag>() {
5736 maybe!({
5737 let project = editor.project.as_ref()?;
5738 let dap_store = project.read(cx).dap_store();
5739 let mut scenarios = vec![];
5740 let resolved_tasks = resolved_tasks.as_ref()?;
5741 let buffer = buffer.read(cx);
5742 let language = buffer.language()?;
5743 let file = buffer.file();
5744 let debug_adapter =
5745 language_settings(language.name().into(), file, cx)
5746 .debuggers
5747 .first()
5748 .map(SharedString::from)
5749 .or_else(|| {
5750 language
5751 .config()
5752 .debuggers
5753 .first()
5754 .map(SharedString::from)
5755 })?;
5756
5757 dap_store.update(cx, |dap_store, cx| {
5758 for (_, task) in &resolved_tasks.templates {
5759 if let Some(scenario) = dap_store
5760 .debug_scenario_for_build_task(
5761 task.original_task().clone(),
5762 debug_adapter.clone().into(),
5763 task.display_label().to_owned().into(),
5764 cx,
5765 )
5766 {
5767 scenarios.push(scenario);
5768 }
5769 }
5770 });
5771 Some(scenarios)
5772 })
5773 .unwrap_or_default()
5774 } else {
5775 vec![]
5776 }
5777 })?;
5778 let spawn_straight_away = quick_launch
5779 && resolved_tasks
5780 .as_ref()
5781 .map_or(false, |tasks| tasks.templates.len() == 1)
5782 && code_actions
5783 .as_ref()
5784 .map_or(true, |actions| actions.is_empty())
5785 && debug_scenarios.is_empty();
5786 if let Ok(task) = editor.update_in(cx, |editor, window, cx| {
5787 crate::hover_popover::hide_hover(editor, cx);
5788 *editor.context_menu.borrow_mut() =
5789 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
5790 buffer,
5791 actions: CodeActionContents::new(
5792 resolved_tasks,
5793 code_actions,
5794 debug_scenarios,
5795 task_context.unwrap_or_default(),
5796 ),
5797 selected_item: Default::default(),
5798 scroll_handle: UniformListScrollHandle::default(),
5799 deployed_from,
5800 }));
5801 if spawn_straight_away {
5802 if let Some(task) = editor.confirm_code_action(
5803 &ConfirmCodeAction { item_ix: Some(0) },
5804 window,
5805 cx,
5806 ) {
5807 cx.notify();
5808 return task;
5809 }
5810 }
5811 cx.notify();
5812 Task::ready(Ok(()))
5813 }) {
5814 task.await
5815 } else {
5816 Ok(())
5817 }
5818 }))
5819 } else {
5820 Some(Task::ready(Ok(())))
5821 }
5822 })?;
5823 if let Some(task) = spawned_test_task {
5824 task.await?;
5825 }
5826
5827 anyhow::Ok(())
5828 })
5829 .detach_and_log_err(cx);
5830 }
5831
5832 pub fn confirm_code_action(
5833 &mut self,
5834 action: &ConfirmCodeAction,
5835 window: &mut Window,
5836 cx: &mut Context<Self>,
5837 ) -> Option<Task<Result<()>>> {
5838 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5839
5840 let actions_menu =
5841 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
5842 menu
5843 } else {
5844 return None;
5845 };
5846
5847 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
5848 let action = actions_menu.actions.get(action_ix)?;
5849 let title = action.label();
5850 let buffer = actions_menu.buffer;
5851 let workspace = self.workspace()?;
5852
5853 match action {
5854 CodeActionsItem::Task(task_source_kind, resolved_task) => {
5855 workspace.update(cx, |workspace, cx| {
5856 workspace.schedule_resolved_task(
5857 task_source_kind,
5858 resolved_task,
5859 false,
5860 window,
5861 cx,
5862 );
5863
5864 Some(Task::ready(Ok(())))
5865 })
5866 }
5867 CodeActionsItem::CodeAction {
5868 excerpt_id,
5869 action,
5870 provider,
5871 } => {
5872 let apply_code_action =
5873 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
5874 let workspace = workspace.downgrade();
5875 Some(cx.spawn_in(window, async move |editor, cx| {
5876 let project_transaction = apply_code_action.await?;
5877 Self::open_project_transaction(
5878 &editor,
5879 workspace,
5880 project_transaction,
5881 title,
5882 cx,
5883 )
5884 .await
5885 }))
5886 }
5887 CodeActionsItem::DebugScenario(scenario) => {
5888 let context = actions_menu.actions.context.clone();
5889
5890 workspace.update(cx, |workspace, cx| {
5891 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
5892 workspace.start_debug_session(scenario, context, Some(buffer), window, cx);
5893 });
5894 Some(Task::ready(Ok(())))
5895 }
5896 }
5897 }
5898
5899 pub async fn open_project_transaction(
5900 this: &WeakEntity<Editor>,
5901 workspace: WeakEntity<Workspace>,
5902 transaction: ProjectTransaction,
5903 title: String,
5904 cx: &mut AsyncWindowContext,
5905 ) -> Result<()> {
5906 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
5907 cx.update(|_, cx| {
5908 entries.sort_unstable_by_key(|(buffer, _)| {
5909 buffer.read(cx).file().map(|f| f.path().clone())
5910 });
5911 })?;
5912
5913 // If the project transaction's edits are all contained within this editor, then
5914 // avoid opening a new editor to display them.
5915
5916 if let Some((buffer, transaction)) = entries.first() {
5917 if entries.len() == 1 {
5918 let excerpt = this.update(cx, |editor, cx| {
5919 editor
5920 .buffer()
5921 .read(cx)
5922 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
5923 })?;
5924 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
5925 if excerpted_buffer == *buffer {
5926 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
5927 let excerpt_range = excerpt_range.to_offset(buffer);
5928 buffer
5929 .edited_ranges_for_transaction::<usize>(transaction)
5930 .all(|range| {
5931 excerpt_range.start <= range.start
5932 && excerpt_range.end >= range.end
5933 })
5934 })?;
5935
5936 if all_edits_within_excerpt {
5937 return Ok(());
5938 }
5939 }
5940 }
5941 }
5942 } else {
5943 return Ok(());
5944 }
5945
5946 let mut ranges_to_highlight = Vec::new();
5947 let excerpt_buffer = cx.new(|cx| {
5948 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
5949 for (buffer_handle, transaction) in &entries {
5950 let edited_ranges = buffer_handle
5951 .read(cx)
5952 .edited_ranges_for_transaction::<Point>(transaction)
5953 .collect::<Vec<_>>();
5954 let (ranges, _) = multibuffer.set_excerpts_for_path(
5955 PathKey::for_buffer(buffer_handle, cx),
5956 buffer_handle.clone(),
5957 edited_ranges,
5958 DEFAULT_MULTIBUFFER_CONTEXT,
5959 cx,
5960 );
5961
5962 ranges_to_highlight.extend(ranges);
5963 }
5964 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
5965 multibuffer
5966 })?;
5967
5968 workspace.update_in(cx, |workspace, window, cx| {
5969 let project = workspace.project().clone();
5970 let editor =
5971 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
5972 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
5973 editor.update(cx, |editor, cx| {
5974 editor.highlight_background::<Self>(
5975 &ranges_to_highlight,
5976 |theme| theme.editor_highlighted_line_background,
5977 cx,
5978 );
5979 });
5980 })?;
5981
5982 Ok(())
5983 }
5984
5985 pub fn clear_code_action_providers(&mut self) {
5986 self.code_action_providers.clear();
5987 self.available_code_actions.take();
5988 }
5989
5990 pub fn add_code_action_provider(
5991 &mut self,
5992 provider: Rc<dyn CodeActionProvider>,
5993 window: &mut Window,
5994 cx: &mut Context<Self>,
5995 ) {
5996 if self
5997 .code_action_providers
5998 .iter()
5999 .any(|existing_provider| existing_provider.id() == provider.id())
6000 {
6001 return;
6002 }
6003
6004 self.code_action_providers.push(provider);
6005 self.refresh_code_actions(window, cx);
6006 }
6007
6008 pub fn remove_code_action_provider(
6009 &mut self,
6010 id: Arc<str>,
6011 window: &mut Window,
6012 cx: &mut Context<Self>,
6013 ) {
6014 self.code_action_providers
6015 .retain(|provider| provider.id() != id);
6016 self.refresh_code_actions(window, cx);
6017 }
6018
6019 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6020 !self.code_action_providers.is_empty()
6021 && EditorSettings::get_global(cx).toolbar.code_actions
6022 }
6023
6024 pub fn has_available_code_actions(&self) -> bool {
6025 self.available_code_actions
6026 .as_ref()
6027 .is_some_and(|(_, actions)| !actions.is_empty())
6028 }
6029
6030 fn render_inline_code_actions(
6031 &self,
6032 icon_size: ui::IconSize,
6033 display_row: DisplayRow,
6034 is_active: bool,
6035 cx: &mut Context<Self>,
6036 ) -> AnyElement {
6037 let show_tooltip = !self.context_menu_visible();
6038 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6039 .icon_size(icon_size)
6040 .shape(ui::IconButtonShape::Square)
6041 .style(ButtonStyle::Transparent)
6042 .icon_color(ui::Color::Hidden)
6043 .toggle_state(is_active)
6044 .when(show_tooltip, |this| {
6045 this.tooltip({
6046 let focus_handle = self.focus_handle.clone();
6047 move |window, cx| {
6048 Tooltip::for_action_in(
6049 "Toggle Code Actions",
6050 &ToggleCodeActions {
6051 deployed_from: None,
6052 quick_launch: false,
6053 },
6054 &focus_handle,
6055 window,
6056 cx,
6057 )
6058 }
6059 })
6060 })
6061 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6062 window.focus(&editor.focus_handle(cx));
6063 editor.toggle_code_actions(
6064 &crate::actions::ToggleCodeActions {
6065 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6066 display_row,
6067 )),
6068 quick_launch: false,
6069 },
6070 window,
6071 cx,
6072 );
6073 }))
6074 .into_any_element()
6075 }
6076
6077 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6078 &self.context_menu
6079 }
6080
6081 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
6082 let newest_selection = self.selections.newest_anchor().clone();
6083 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
6084 let buffer = self.buffer.read(cx);
6085 if newest_selection.head().diff_base_anchor.is_some() {
6086 return None;
6087 }
6088 let (start_buffer, start) =
6089 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6090 let (end_buffer, end) =
6091 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6092 if start_buffer != end_buffer {
6093 return None;
6094 }
6095
6096 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6097 cx.background_executor()
6098 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6099 .await;
6100
6101 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6102 let providers = this.code_action_providers.clone();
6103 let tasks = this
6104 .code_action_providers
6105 .iter()
6106 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6107 .collect::<Vec<_>>();
6108 (providers, tasks)
6109 })?;
6110
6111 let mut actions = Vec::new();
6112 for (provider, provider_actions) in
6113 providers.into_iter().zip(future::join_all(tasks).await)
6114 {
6115 if let Some(provider_actions) = provider_actions.log_err() {
6116 actions.extend(provider_actions.into_iter().map(|action| {
6117 AvailableCodeAction {
6118 excerpt_id: newest_selection.start.excerpt_id,
6119 action,
6120 provider: provider.clone(),
6121 }
6122 }));
6123 }
6124 }
6125
6126 this.update(cx, |this, cx| {
6127 this.available_code_actions = if actions.is_empty() {
6128 None
6129 } else {
6130 Some((
6131 Location {
6132 buffer: start_buffer,
6133 range: start..end,
6134 },
6135 actions.into(),
6136 ))
6137 };
6138 cx.notify();
6139 })
6140 }));
6141 None
6142 }
6143
6144 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6145 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6146 self.show_git_blame_inline = false;
6147
6148 self.show_git_blame_inline_delay_task =
6149 Some(cx.spawn_in(window, async move |this, cx| {
6150 cx.background_executor().timer(delay).await;
6151
6152 this.update(cx, |this, cx| {
6153 this.show_git_blame_inline = true;
6154 cx.notify();
6155 })
6156 .log_err();
6157 }));
6158 }
6159 }
6160
6161 fn show_blame_popover(
6162 &mut self,
6163 blame_entry: &BlameEntry,
6164 position: gpui::Point<Pixels>,
6165 cx: &mut Context<Self>,
6166 ) {
6167 if let Some(state) = &mut self.inline_blame_popover {
6168 state.hide_task.take();
6169 cx.notify();
6170 } else {
6171 let delay = EditorSettings::get_global(cx).hover_popover_delay;
6172 let show_task = cx.spawn(async move |editor, cx| {
6173 cx.background_executor()
6174 .timer(std::time::Duration::from_millis(delay))
6175 .await;
6176 editor
6177 .update(cx, |editor, cx| {
6178 if let Some(state) = &mut editor.inline_blame_popover {
6179 state.show_task = None;
6180 cx.notify();
6181 }
6182 })
6183 .ok();
6184 });
6185 let Some(blame) = self.blame.as_ref() else {
6186 return;
6187 };
6188 let blame = blame.read(cx);
6189 let details = blame.details_for_entry(&blame_entry);
6190 let markdown = cx.new(|cx| {
6191 Markdown::new(
6192 details
6193 .as_ref()
6194 .map(|message| message.message.clone())
6195 .unwrap_or_default(),
6196 None,
6197 None,
6198 cx,
6199 )
6200 });
6201 self.inline_blame_popover = Some(InlineBlamePopover {
6202 position,
6203 show_task: Some(show_task),
6204 hide_task: None,
6205 popover_bounds: None,
6206 popover_state: InlineBlamePopoverState {
6207 scroll_handle: ScrollHandle::new(),
6208 commit_message: details,
6209 markdown,
6210 },
6211 });
6212 }
6213 }
6214
6215 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6216 if let Some(state) = &mut self.inline_blame_popover {
6217 if state.show_task.is_some() {
6218 self.inline_blame_popover.take();
6219 cx.notify();
6220 } else {
6221 let hide_task = cx.spawn(async move |editor, cx| {
6222 cx.background_executor()
6223 .timer(std::time::Duration::from_millis(100))
6224 .await;
6225 editor
6226 .update(cx, |editor, cx| {
6227 editor.inline_blame_popover.take();
6228 cx.notify();
6229 })
6230 .ok();
6231 });
6232 state.hide_task = Some(hide_task);
6233 }
6234 }
6235 }
6236
6237 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6238 if self.pending_rename.is_some() {
6239 return None;
6240 }
6241
6242 let provider = self.semantics_provider.clone()?;
6243 let buffer = self.buffer.read(cx);
6244 let newest_selection = self.selections.newest_anchor().clone();
6245 let cursor_position = newest_selection.head();
6246 let (cursor_buffer, cursor_buffer_position) =
6247 buffer.text_anchor_for_position(cursor_position, cx)?;
6248 let (tail_buffer, tail_buffer_position) =
6249 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6250 if cursor_buffer != tail_buffer {
6251 return None;
6252 }
6253
6254 let snapshot = cursor_buffer.read(cx).snapshot();
6255 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position);
6256 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position);
6257 if start_word_range != end_word_range {
6258 self.document_highlights_task.take();
6259 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6260 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6261 return None;
6262 }
6263
6264 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6265 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6266 cx.background_executor()
6267 .timer(Duration::from_millis(debounce))
6268 .await;
6269
6270 let highlights = if let Some(highlights) = cx
6271 .update(|cx| {
6272 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6273 })
6274 .ok()
6275 .flatten()
6276 {
6277 highlights.await.log_err()
6278 } else {
6279 None
6280 };
6281
6282 if let Some(highlights) = highlights {
6283 this.update(cx, |this, cx| {
6284 if this.pending_rename.is_some() {
6285 return;
6286 }
6287
6288 let buffer_id = cursor_position.buffer_id;
6289 let buffer = this.buffer.read(cx);
6290 if !buffer
6291 .text_anchor_for_position(cursor_position, cx)
6292 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
6293 {
6294 return;
6295 }
6296
6297 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6298 let mut write_ranges = Vec::new();
6299 let mut read_ranges = Vec::new();
6300 for highlight in highlights {
6301 for (excerpt_id, excerpt_range) in
6302 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
6303 {
6304 let start = highlight
6305 .range
6306 .start
6307 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6308 let end = highlight
6309 .range
6310 .end
6311 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6312 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6313 continue;
6314 }
6315
6316 let range = Anchor {
6317 buffer_id,
6318 excerpt_id,
6319 text_anchor: start,
6320 diff_base_anchor: None,
6321 }..Anchor {
6322 buffer_id,
6323 excerpt_id,
6324 text_anchor: end,
6325 diff_base_anchor: None,
6326 };
6327 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6328 write_ranges.push(range);
6329 } else {
6330 read_ranges.push(range);
6331 }
6332 }
6333 }
6334
6335 this.highlight_background::<DocumentHighlightRead>(
6336 &read_ranges,
6337 |theme| theme.editor_document_highlight_read_background,
6338 cx,
6339 );
6340 this.highlight_background::<DocumentHighlightWrite>(
6341 &write_ranges,
6342 |theme| theme.editor_document_highlight_write_background,
6343 cx,
6344 );
6345 cx.notify();
6346 })
6347 .log_err();
6348 }
6349 }));
6350 None
6351 }
6352
6353 fn prepare_highlight_query_from_selection(
6354 &mut self,
6355 cx: &mut Context<Editor>,
6356 ) -> Option<(String, Range<Anchor>)> {
6357 if matches!(self.mode, EditorMode::SingleLine { .. }) {
6358 return None;
6359 }
6360 if !EditorSettings::get_global(cx).selection_highlight {
6361 return None;
6362 }
6363 if self.selections.count() != 1 || self.selections.line_mode {
6364 return None;
6365 }
6366 let selection = self.selections.newest::<Point>(cx);
6367 if selection.is_empty() || selection.start.row != selection.end.row {
6368 return None;
6369 }
6370 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6371 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6372 let query = multi_buffer_snapshot
6373 .text_for_range(selection_anchor_range.clone())
6374 .collect::<String>();
6375 if query.trim().is_empty() {
6376 return None;
6377 }
6378 Some((query, selection_anchor_range))
6379 }
6380
6381 fn update_selection_occurrence_highlights(
6382 &mut self,
6383 query_text: String,
6384 query_range: Range<Anchor>,
6385 multi_buffer_range_to_query: Range<Point>,
6386 use_debounce: bool,
6387 window: &mut Window,
6388 cx: &mut Context<Editor>,
6389 ) -> Task<()> {
6390 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6391 cx.spawn_in(window, async move |editor, cx| {
6392 if use_debounce {
6393 cx.background_executor()
6394 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6395 .await;
6396 }
6397 let match_task = cx.background_spawn(async move {
6398 let buffer_ranges = multi_buffer_snapshot
6399 .range_to_buffer_ranges(multi_buffer_range_to_query)
6400 .into_iter()
6401 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6402 let mut match_ranges = Vec::new();
6403 let Ok(regex) = project::search::SearchQuery::text(
6404 query_text.clone(),
6405 false,
6406 false,
6407 false,
6408 Default::default(),
6409 Default::default(),
6410 false,
6411 None,
6412 ) else {
6413 return Vec::default();
6414 };
6415 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6416 match_ranges.extend(
6417 regex
6418 .search(&buffer_snapshot, Some(search_range.clone()))
6419 .await
6420 .into_iter()
6421 .filter_map(|match_range| {
6422 let match_start = buffer_snapshot
6423 .anchor_after(search_range.start + match_range.start);
6424 let match_end = buffer_snapshot
6425 .anchor_before(search_range.start + match_range.end);
6426 let match_anchor_range = Anchor::range_in_buffer(
6427 excerpt_id,
6428 buffer_snapshot.remote_id(),
6429 match_start..match_end,
6430 );
6431 (match_anchor_range != query_range).then_some(match_anchor_range)
6432 }),
6433 );
6434 }
6435 match_ranges
6436 });
6437 let match_ranges = match_task.await;
6438 editor
6439 .update_in(cx, |editor, _, cx| {
6440 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6441 if !match_ranges.is_empty() {
6442 editor.highlight_background::<SelectedTextHighlight>(
6443 &match_ranges,
6444 |theme| theme.editor_document_highlight_bracket_background,
6445 cx,
6446 )
6447 }
6448 })
6449 .log_err();
6450 })
6451 }
6452
6453 fn refresh_selected_text_highlights(
6454 &mut self,
6455 on_buffer_edit: bool,
6456 window: &mut Window,
6457 cx: &mut Context<Editor>,
6458 ) {
6459 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
6460 else {
6461 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6462 self.quick_selection_highlight_task.take();
6463 self.debounced_selection_highlight_task.take();
6464 return;
6465 };
6466 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6467 if on_buffer_edit
6468 || self
6469 .quick_selection_highlight_task
6470 .as_ref()
6471 .map_or(true, |(prev_anchor_range, _)| {
6472 prev_anchor_range != &query_range
6473 })
6474 {
6475 let multi_buffer_visible_start = self
6476 .scroll_manager
6477 .anchor()
6478 .anchor
6479 .to_point(&multi_buffer_snapshot);
6480 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
6481 multi_buffer_visible_start
6482 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
6483 Bias::Left,
6484 );
6485 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
6486 self.quick_selection_highlight_task = Some((
6487 query_range.clone(),
6488 self.update_selection_occurrence_highlights(
6489 query_text.clone(),
6490 query_range.clone(),
6491 multi_buffer_visible_range,
6492 false,
6493 window,
6494 cx,
6495 ),
6496 ));
6497 }
6498 if on_buffer_edit
6499 || self
6500 .debounced_selection_highlight_task
6501 .as_ref()
6502 .map_or(true, |(prev_anchor_range, _)| {
6503 prev_anchor_range != &query_range
6504 })
6505 {
6506 let multi_buffer_start = multi_buffer_snapshot
6507 .anchor_before(0)
6508 .to_point(&multi_buffer_snapshot);
6509 let multi_buffer_end = multi_buffer_snapshot
6510 .anchor_after(multi_buffer_snapshot.len())
6511 .to_point(&multi_buffer_snapshot);
6512 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
6513 self.debounced_selection_highlight_task = Some((
6514 query_range.clone(),
6515 self.update_selection_occurrence_highlights(
6516 query_text,
6517 query_range,
6518 multi_buffer_full_range,
6519 true,
6520 window,
6521 cx,
6522 ),
6523 ));
6524 }
6525 }
6526
6527 pub fn refresh_inline_completion(
6528 &mut self,
6529 debounce: bool,
6530 user_requested: bool,
6531 window: &mut Window,
6532 cx: &mut Context<Self>,
6533 ) -> Option<()> {
6534 let provider = self.edit_prediction_provider()?;
6535 let cursor = self.selections.newest_anchor().head();
6536 let (buffer, cursor_buffer_position) =
6537 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6538
6539 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
6540 self.discard_inline_completion(false, cx);
6541 return None;
6542 }
6543
6544 if !user_requested
6545 && (!self.should_show_edit_predictions()
6546 || !self.is_focused(window)
6547 || buffer.read(cx).is_empty())
6548 {
6549 self.discard_inline_completion(false, cx);
6550 return None;
6551 }
6552
6553 self.update_visible_inline_completion(window, cx);
6554 provider.refresh(
6555 self.project.clone(),
6556 buffer,
6557 cursor_buffer_position,
6558 debounce,
6559 cx,
6560 );
6561 Some(())
6562 }
6563
6564 fn show_edit_predictions_in_menu(&self) -> bool {
6565 match self.edit_prediction_settings {
6566 EditPredictionSettings::Disabled => false,
6567 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
6568 }
6569 }
6570
6571 pub fn edit_predictions_enabled(&self) -> bool {
6572 match self.edit_prediction_settings {
6573 EditPredictionSettings::Disabled => false,
6574 EditPredictionSettings::Enabled { .. } => true,
6575 }
6576 }
6577
6578 fn edit_prediction_requires_modifier(&self) -> bool {
6579 match self.edit_prediction_settings {
6580 EditPredictionSettings::Disabled => false,
6581 EditPredictionSettings::Enabled {
6582 preview_requires_modifier,
6583 ..
6584 } => preview_requires_modifier,
6585 }
6586 }
6587
6588 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
6589 if self.edit_prediction_provider.is_none() {
6590 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6591 } else {
6592 let selection = self.selections.newest_anchor();
6593 let cursor = selection.head();
6594
6595 if let Some((buffer, cursor_buffer_position)) =
6596 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6597 {
6598 self.edit_prediction_settings =
6599 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6600 }
6601 }
6602 }
6603
6604 fn edit_prediction_settings_at_position(
6605 &self,
6606 buffer: &Entity<Buffer>,
6607 buffer_position: language::Anchor,
6608 cx: &App,
6609 ) -> EditPredictionSettings {
6610 if !self.mode.is_full()
6611 || !self.show_inline_completions_override.unwrap_or(true)
6612 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
6613 {
6614 return EditPredictionSettings::Disabled;
6615 }
6616
6617 let buffer = buffer.read(cx);
6618
6619 let file = buffer.file();
6620
6621 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
6622 return EditPredictionSettings::Disabled;
6623 };
6624
6625 let by_provider = matches!(
6626 self.menu_inline_completions_policy,
6627 MenuInlineCompletionsPolicy::ByProvider
6628 );
6629
6630 let show_in_menu = by_provider
6631 && self
6632 .edit_prediction_provider
6633 .as_ref()
6634 .map_or(false, |provider| {
6635 provider.provider.show_completions_in_menu()
6636 });
6637
6638 let preview_requires_modifier =
6639 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
6640
6641 EditPredictionSettings::Enabled {
6642 show_in_menu,
6643 preview_requires_modifier,
6644 }
6645 }
6646
6647 fn should_show_edit_predictions(&self) -> bool {
6648 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
6649 }
6650
6651 pub fn edit_prediction_preview_is_active(&self) -> bool {
6652 matches!(
6653 self.edit_prediction_preview,
6654 EditPredictionPreview::Active { .. }
6655 )
6656 }
6657
6658 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
6659 let cursor = self.selections.newest_anchor().head();
6660 if let Some((buffer, cursor_position)) =
6661 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6662 {
6663 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
6664 } else {
6665 false
6666 }
6667 }
6668
6669 pub fn supports_minimap(&self, cx: &App) -> bool {
6670 !self.minimap_visibility.disabled() && self.is_singleton(cx)
6671 }
6672
6673 fn edit_predictions_enabled_in_buffer(
6674 &self,
6675 buffer: &Entity<Buffer>,
6676 buffer_position: language::Anchor,
6677 cx: &App,
6678 ) -> bool {
6679 maybe!({
6680 if self.read_only(cx) {
6681 return Some(false);
6682 }
6683 let provider = self.edit_prediction_provider()?;
6684 if !provider.is_enabled(&buffer, buffer_position, cx) {
6685 return Some(false);
6686 }
6687 let buffer = buffer.read(cx);
6688 let Some(file) = buffer.file() else {
6689 return Some(true);
6690 };
6691 let settings = all_language_settings(Some(file), cx);
6692 Some(settings.edit_predictions_enabled_for_file(file, cx))
6693 })
6694 .unwrap_or(false)
6695 }
6696
6697 fn cycle_inline_completion(
6698 &mut self,
6699 direction: Direction,
6700 window: &mut Window,
6701 cx: &mut Context<Self>,
6702 ) -> Option<()> {
6703 let provider = self.edit_prediction_provider()?;
6704 let cursor = self.selections.newest_anchor().head();
6705 let (buffer, cursor_buffer_position) =
6706 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6707 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
6708 return None;
6709 }
6710
6711 provider.cycle(buffer, cursor_buffer_position, direction, cx);
6712 self.update_visible_inline_completion(window, cx);
6713
6714 Some(())
6715 }
6716
6717 pub fn show_inline_completion(
6718 &mut self,
6719 _: &ShowEditPrediction,
6720 window: &mut Window,
6721 cx: &mut Context<Self>,
6722 ) {
6723 if !self.has_active_inline_completion() {
6724 self.refresh_inline_completion(false, true, window, cx);
6725 return;
6726 }
6727
6728 self.update_visible_inline_completion(window, cx);
6729 }
6730
6731 pub fn display_cursor_names(
6732 &mut self,
6733 _: &DisplayCursorNames,
6734 window: &mut Window,
6735 cx: &mut Context<Self>,
6736 ) {
6737 self.show_cursor_names(window, cx);
6738 }
6739
6740 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6741 self.show_cursor_names = true;
6742 cx.notify();
6743 cx.spawn_in(window, async move |this, cx| {
6744 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
6745 this.update(cx, |this, cx| {
6746 this.show_cursor_names = false;
6747 cx.notify()
6748 })
6749 .ok()
6750 })
6751 .detach();
6752 }
6753
6754 pub fn next_edit_prediction(
6755 &mut self,
6756 _: &NextEditPrediction,
6757 window: &mut Window,
6758 cx: &mut Context<Self>,
6759 ) {
6760 if self.has_active_inline_completion() {
6761 self.cycle_inline_completion(Direction::Next, window, cx);
6762 } else {
6763 let is_copilot_disabled = self
6764 .refresh_inline_completion(false, true, window, cx)
6765 .is_none();
6766 if is_copilot_disabled {
6767 cx.propagate();
6768 }
6769 }
6770 }
6771
6772 pub fn previous_edit_prediction(
6773 &mut self,
6774 _: &PreviousEditPrediction,
6775 window: &mut Window,
6776 cx: &mut Context<Self>,
6777 ) {
6778 if self.has_active_inline_completion() {
6779 self.cycle_inline_completion(Direction::Prev, window, cx);
6780 } else {
6781 let is_copilot_disabled = self
6782 .refresh_inline_completion(false, true, window, cx)
6783 .is_none();
6784 if is_copilot_disabled {
6785 cx.propagate();
6786 }
6787 }
6788 }
6789
6790 pub fn accept_edit_prediction(
6791 &mut self,
6792 _: &AcceptEditPrediction,
6793 window: &mut Window,
6794 cx: &mut Context<Self>,
6795 ) {
6796 if self.show_edit_predictions_in_menu() {
6797 self.hide_context_menu(window, cx);
6798 }
6799
6800 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6801 return;
6802 };
6803
6804 self.report_inline_completion_event(
6805 active_inline_completion.completion_id.clone(),
6806 true,
6807 cx,
6808 );
6809
6810 match &active_inline_completion.completion {
6811 InlineCompletion::Move { target, .. } => {
6812 let target = *target;
6813
6814 if let Some(position_map) = &self.last_position_map {
6815 if position_map
6816 .visible_row_range
6817 .contains(&target.to_display_point(&position_map.snapshot).row())
6818 || !self.edit_prediction_requires_modifier()
6819 {
6820 self.unfold_ranges(&[target..target], true, false, cx);
6821 // Note that this is also done in vim's handler of the Tab action.
6822 self.change_selections(
6823 Some(Autoscroll::newest()),
6824 window,
6825 cx,
6826 |selections| {
6827 selections.select_anchor_ranges([target..target]);
6828 },
6829 );
6830 self.clear_row_highlights::<EditPredictionPreview>();
6831
6832 self.edit_prediction_preview
6833 .set_previous_scroll_position(None);
6834 } else {
6835 self.edit_prediction_preview
6836 .set_previous_scroll_position(Some(
6837 position_map.snapshot.scroll_anchor,
6838 ));
6839
6840 self.highlight_rows::<EditPredictionPreview>(
6841 target..target,
6842 cx.theme().colors().editor_highlighted_line_background,
6843 RowHighlightOptions {
6844 autoscroll: true,
6845 ..Default::default()
6846 },
6847 cx,
6848 );
6849 self.request_autoscroll(Autoscroll::fit(), cx);
6850 }
6851 }
6852 }
6853 InlineCompletion::Edit { edits, .. } => {
6854 if let Some(provider) = self.edit_prediction_provider() {
6855 provider.accept(cx);
6856 }
6857
6858 // Store the transaction ID and selections before applying the edit
6859 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
6860
6861 let snapshot = self.buffer.read(cx).snapshot(cx);
6862 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
6863
6864 self.buffer.update(cx, |buffer, cx| {
6865 buffer.edit(edits.iter().cloned(), None, cx)
6866 });
6867
6868 self.change_selections(None, window, cx, |s| {
6869 s.select_anchor_ranges([last_edit_end..last_edit_end]);
6870 });
6871
6872 let selections = self.selections.disjoint_anchors();
6873 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
6874 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
6875 if has_new_transaction {
6876 self.selection_history
6877 .insert_transaction(transaction_id_now, selections);
6878 }
6879 }
6880
6881 self.update_visible_inline_completion(window, cx);
6882 if self.active_inline_completion.is_none() {
6883 self.refresh_inline_completion(true, true, window, cx);
6884 }
6885
6886 cx.notify();
6887 }
6888 }
6889
6890 self.edit_prediction_requires_modifier_in_indent_conflict = false;
6891 }
6892
6893 pub fn accept_partial_inline_completion(
6894 &mut self,
6895 _: &AcceptPartialEditPrediction,
6896 window: &mut Window,
6897 cx: &mut Context<Self>,
6898 ) {
6899 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6900 return;
6901 };
6902 if self.selections.count() != 1 {
6903 return;
6904 }
6905
6906 self.report_inline_completion_event(
6907 active_inline_completion.completion_id.clone(),
6908 true,
6909 cx,
6910 );
6911
6912 match &active_inline_completion.completion {
6913 InlineCompletion::Move { target, .. } => {
6914 let target = *target;
6915 self.change_selections(Some(Autoscroll::newest()), window, cx, |selections| {
6916 selections.select_anchor_ranges([target..target]);
6917 });
6918 }
6919 InlineCompletion::Edit { edits, .. } => {
6920 // Find an insertion that starts at the cursor position.
6921 let snapshot = self.buffer.read(cx).snapshot(cx);
6922 let cursor_offset = self.selections.newest::<usize>(cx).head();
6923 let insertion = edits.iter().find_map(|(range, text)| {
6924 let range = range.to_offset(&snapshot);
6925 if range.is_empty() && range.start == cursor_offset {
6926 Some(text)
6927 } else {
6928 None
6929 }
6930 });
6931
6932 if let Some(text) = insertion {
6933 let mut partial_completion = text
6934 .chars()
6935 .by_ref()
6936 .take_while(|c| c.is_alphabetic())
6937 .collect::<String>();
6938 if partial_completion.is_empty() {
6939 partial_completion = text
6940 .chars()
6941 .by_ref()
6942 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
6943 .collect::<String>();
6944 }
6945
6946 cx.emit(EditorEvent::InputHandled {
6947 utf16_range_to_replace: None,
6948 text: partial_completion.clone().into(),
6949 });
6950
6951 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
6952
6953 self.refresh_inline_completion(true, true, window, cx);
6954 cx.notify();
6955 } else {
6956 self.accept_edit_prediction(&Default::default(), window, cx);
6957 }
6958 }
6959 }
6960 }
6961
6962 fn discard_inline_completion(
6963 &mut self,
6964 should_report_inline_completion_event: bool,
6965 cx: &mut Context<Self>,
6966 ) -> bool {
6967 if should_report_inline_completion_event {
6968 let completion_id = self
6969 .active_inline_completion
6970 .as_ref()
6971 .and_then(|active_completion| active_completion.completion_id.clone());
6972
6973 self.report_inline_completion_event(completion_id, false, cx);
6974 }
6975
6976 if let Some(provider) = self.edit_prediction_provider() {
6977 provider.discard(cx);
6978 }
6979
6980 self.take_active_inline_completion(cx)
6981 }
6982
6983 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
6984 let Some(provider) = self.edit_prediction_provider() else {
6985 return;
6986 };
6987
6988 let Some((_, buffer, _)) = self
6989 .buffer
6990 .read(cx)
6991 .excerpt_containing(self.selections.newest_anchor().head(), cx)
6992 else {
6993 return;
6994 };
6995
6996 let extension = buffer
6997 .read(cx)
6998 .file()
6999 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
7000
7001 let event_type = match accepted {
7002 true => "Edit Prediction Accepted",
7003 false => "Edit Prediction Discarded",
7004 };
7005 telemetry::event!(
7006 event_type,
7007 provider = provider.name(),
7008 prediction_id = id,
7009 suggestion_accepted = accepted,
7010 file_extension = extension,
7011 );
7012 }
7013
7014 pub fn has_active_inline_completion(&self) -> bool {
7015 self.active_inline_completion.is_some()
7016 }
7017
7018 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
7019 let Some(active_inline_completion) = self.active_inline_completion.take() else {
7020 return false;
7021 };
7022
7023 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
7024 self.clear_highlights::<InlineCompletionHighlight>(cx);
7025 self.stale_inline_completion_in_menu = Some(active_inline_completion);
7026 true
7027 }
7028
7029 /// Returns true when we're displaying the edit prediction popover below the cursor
7030 /// like we are not previewing and the LSP autocomplete menu is visible
7031 /// or we are in `when_holding_modifier` mode.
7032 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7033 if self.edit_prediction_preview_is_active()
7034 || !self.show_edit_predictions_in_menu()
7035 || !self.edit_predictions_enabled()
7036 {
7037 return false;
7038 }
7039
7040 if self.has_visible_completions_menu() {
7041 return true;
7042 }
7043
7044 has_completion && self.edit_prediction_requires_modifier()
7045 }
7046
7047 fn handle_modifiers_changed(
7048 &mut self,
7049 modifiers: Modifiers,
7050 position_map: &PositionMap,
7051 window: &mut Window,
7052 cx: &mut Context<Self>,
7053 ) {
7054 if self.show_edit_predictions_in_menu() {
7055 self.update_edit_prediction_preview(&modifiers, window, cx);
7056 }
7057
7058 self.update_selection_mode(&modifiers, position_map, window, cx);
7059
7060 let mouse_position = window.mouse_position();
7061 if !position_map.text_hitbox.is_hovered(window) {
7062 return;
7063 }
7064
7065 self.update_hovered_link(
7066 position_map.point_for_position(mouse_position),
7067 &position_map.snapshot,
7068 modifiers,
7069 window,
7070 cx,
7071 )
7072 }
7073
7074 fn update_selection_mode(
7075 &mut self,
7076 modifiers: &Modifiers,
7077 position_map: &PositionMap,
7078 window: &mut Window,
7079 cx: &mut Context<Self>,
7080 ) {
7081 if modifiers != &COLUMNAR_SELECTION_MODIFIERS || self.selections.pending.is_none() {
7082 return;
7083 }
7084
7085 let mouse_position = window.mouse_position();
7086 let point_for_position = position_map.point_for_position(mouse_position);
7087 let position = point_for_position.previous_valid;
7088
7089 self.select(
7090 SelectPhase::BeginColumnar {
7091 position,
7092 reset: false,
7093 goal_column: point_for_position.exact_unclipped.column(),
7094 },
7095 window,
7096 cx,
7097 );
7098 }
7099
7100 fn update_edit_prediction_preview(
7101 &mut self,
7102 modifiers: &Modifiers,
7103 window: &mut Window,
7104 cx: &mut Context<Self>,
7105 ) {
7106 let accept_keybind = self.accept_edit_prediction_keybind(window, cx);
7107 let Some(accept_keystroke) = accept_keybind.keystroke() else {
7108 return;
7109 };
7110
7111 if &accept_keystroke.modifiers == modifiers && accept_keystroke.modifiers.modified() {
7112 if matches!(
7113 self.edit_prediction_preview,
7114 EditPredictionPreview::Inactive { .. }
7115 ) {
7116 self.edit_prediction_preview = EditPredictionPreview::Active {
7117 previous_scroll_position: None,
7118 since: Instant::now(),
7119 };
7120
7121 self.update_visible_inline_completion(window, cx);
7122 cx.notify();
7123 }
7124 } else if let EditPredictionPreview::Active {
7125 previous_scroll_position,
7126 since,
7127 } = self.edit_prediction_preview
7128 {
7129 if let (Some(previous_scroll_position), Some(position_map)) =
7130 (previous_scroll_position, self.last_position_map.as_ref())
7131 {
7132 self.set_scroll_position(
7133 previous_scroll_position
7134 .scroll_position(&position_map.snapshot.display_snapshot),
7135 window,
7136 cx,
7137 );
7138 }
7139
7140 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7141 released_too_fast: since.elapsed() < Duration::from_millis(200),
7142 };
7143 self.clear_row_highlights::<EditPredictionPreview>();
7144 self.update_visible_inline_completion(window, cx);
7145 cx.notify();
7146 }
7147 }
7148
7149 fn update_visible_inline_completion(
7150 &mut self,
7151 _window: &mut Window,
7152 cx: &mut Context<Self>,
7153 ) -> Option<()> {
7154 let selection = self.selections.newest_anchor();
7155 let cursor = selection.head();
7156 let multibuffer = self.buffer.read(cx).snapshot(cx);
7157 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7158 let excerpt_id = cursor.excerpt_id;
7159
7160 let show_in_menu = self.show_edit_predictions_in_menu();
7161 let completions_menu_has_precedence = !show_in_menu
7162 && (self.context_menu.borrow().is_some()
7163 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
7164
7165 if completions_menu_has_precedence
7166 || !offset_selection.is_empty()
7167 || self
7168 .active_inline_completion
7169 .as_ref()
7170 .map_or(false, |completion| {
7171 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
7172 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7173 !invalidation_range.contains(&offset_selection.head())
7174 })
7175 {
7176 self.discard_inline_completion(false, cx);
7177 return None;
7178 }
7179
7180 self.take_active_inline_completion(cx);
7181 let Some(provider) = self.edit_prediction_provider() else {
7182 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7183 return None;
7184 };
7185
7186 let (buffer, cursor_buffer_position) =
7187 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7188
7189 self.edit_prediction_settings =
7190 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7191
7192 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7193
7194 if self.edit_prediction_indent_conflict {
7195 let cursor_point = cursor.to_point(&multibuffer);
7196
7197 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7198
7199 if let Some((_, indent)) = indents.iter().next() {
7200 if indent.len == cursor_point.column {
7201 self.edit_prediction_indent_conflict = false;
7202 }
7203 }
7204 }
7205
7206 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7207 let edits = inline_completion
7208 .edits
7209 .into_iter()
7210 .flat_map(|(range, new_text)| {
7211 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7212 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7213 Some((start..end, new_text))
7214 })
7215 .collect::<Vec<_>>();
7216 if edits.is_empty() {
7217 return None;
7218 }
7219
7220 let first_edit_start = edits.first().unwrap().0.start;
7221 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7222 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7223
7224 let last_edit_end = edits.last().unwrap().0.end;
7225 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7226 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7227
7228 let cursor_row = cursor.to_point(&multibuffer).row;
7229
7230 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7231
7232 let mut inlay_ids = Vec::new();
7233 let invalidation_row_range;
7234 let move_invalidation_row_range = if cursor_row < edit_start_row {
7235 Some(cursor_row..edit_end_row)
7236 } else if cursor_row > edit_end_row {
7237 Some(edit_start_row..cursor_row)
7238 } else {
7239 None
7240 };
7241 let is_move =
7242 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
7243 let completion = if is_move {
7244 invalidation_row_range =
7245 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7246 let target = first_edit_start;
7247 InlineCompletion::Move { target, snapshot }
7248 } else {
7249 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7250 && !self.inline_completions_hidden_for_vim_mode;
7251
7252 if show_completions_in_buffer {
7253 if edits
7254 .iter()
7255 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7256 {
7257 let mut inlays = Vec::new();
7258 for (range, new_text) in &edits {
7259 let inlay = Inlay::inline_completion(
7260 post_inc(&mut self.next_inlay_id),
7261 range.start,
7262 new_text.as_str(),
7263 );
7264 inlay_ids.push(inlay.id);
7265 inlays.push(inlay);
7266 }
7267
7268 self.splice_inlays(&[], inlays, cx);
7269 } else {
7270 let background_color = cx.theme().status().deleted_background;
7271 self.highlight_text::<InlineCompletionHighlight>(
7272 edits.iter().map(|(range, _)| range.clone()).collect(),
7273 HighlightStyle {
7274 background_color: Some(background_color),
7275 ..Default::default()
7276 },
7277 cx,
7278 );
7279 }
7280 }
7281
7282 invalidation_row_range = edit_start_row..edit_end_row;
7283
7284 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7285 if provider.show_tab_accept_marker() {
7286 EditDisplayMode::TabAccept
7287 } else {
7288 EditDisplayMode::Inline
7289 }
7290 } else {
7291 EditDisplayMode::DiffPopover
7292 };
7293
7294 InlineCompletion::Edit {
7295 edits,
7296 edit_preview: inline_completion.edit_preview,
7297 display_mode,
7298 snapshot,
7299 }
7300 };
7301
7302 let invalidation_range = multibuffer
7303 .anchor_before(Point::new(invalidation_row_range.start, 0))
7304 ..multibuffer.anchor_after(Point::new(
7305 invalidation_row_range.end,
7306 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7307 ));
7308
7309 self.stale_inline_completion_in_menu = None;
7310 self.active_inline_completion = Some(InlineCompletionState {
7311 inlay_ids,
7312 completion,
7313 completion_id: inline_completion.id,
7314 invalidation_range,
7315 });
7316
7317 cx.notify();
7318
7319 Some(())
7320 }
7321
7322 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
7323 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7324 }
7325
7326 fn clear_tasks(&mut self) {
7327 self.tasks.clear()
7328 }
7329
7330 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7331 if self.tasks.insert(key, value).is_some() {
7332 // This case should hopefully be rare, but just in case...
7333 log::error!(
7334 "multiple different run targets found on a single line, only the last target will be rendered"
7335 )
7336 }
7337 }
7338
7339 /// Get all display points of breakpoints that will be rendered within editor
7340 ///
7341 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7342 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7343 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7344 fn active_breakpoints(
7345 &self,
7346 range: Range<DisplayRow>,
7347 window: &mut Window,
7348 cx: &mut Context<Self>,
7349 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7350 let mut breakpoint_display_points = HashMap::default();
7351
7352 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7353 return breakpoint_display_points;
7354 };
7355
7356 let snapshot = self.snapshot(window, cx);
7357
7358 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
7359 let Some(project) = self.project.as_ref() else {
7360 return breakpoint_display_points;
7361 };
7362
7363 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7364 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7365
7366 for (buffer_snapshot, range, excerpt_id) in
7367 multi_buffer_snapshot.range_to_buffer_ranges(range)
7368 {
7369 let Some(buffer) = project
7370 .read(cx)
7371 .buffer_for_id(buffer_snapshot.remote_id(), cx)
7372 else {
7373 continue;
7374 };
7375 let breakpoints = breakpoint_store.read(cx).breakpoints(
7376 &buffer,
7377 Some(
7378 buffer_snapshot.anchor_before(range.start)
7379 ..buffer_snapshot.anchor_after(range.end),
7380 ),
7381 buffer_snapshot,
7382 cx,
7383 );
7384 for (breakpoint, state) in breakpoints {
7385 let multi_buffer_anchor =
7386 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
7387 let position = multi_buffer_anchor
7388 .to_point(&multi_buffer_snapshot)
7389 .to_display_point(&snapshot);
7390
7391 breakpoint_display_points.insert(
7392 position.row(),
7393 (multi_buffer_anchor, breakpoint.bp.clone(), state),
7394 );
7395 }
7396 }
7397
7398 breakpoint_display_points
7399 }
7400
7401 fn breakpoint_context_menu(
7402 &self,
7403 anchor: Anchor,
7404 window: &mut Window,
7405 cx: &mut Context<Self>,
7406 ) -> Entity<ui::ContextMenu> {
7407 let weak_editor = cx.weak_entity();
7408 let focus_handle = self.focus_handle(cx);
7409
7410 let row = self
7411 .buffer
7412 .read(cx)
7413 .snapshot(cx)
7414 .summary_for_anchor::<Point>(&anchor)
7415 .row;
7416
7417 let breakpoint = self
7418 .breakpoint_at_row(row, window, cx)
7419 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
7420
7421 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
7422 "Edit Log Breakpoint"
7423 } else {
7424 "Set Log Breakpoint"
7425 };
7426
7427 let condition_breakpoint_msg = if breakpoint
7428 .as_ref()
7429 .is_some_and(|bp| bp.1.condition.is_some())
7430 {
7431 "Edit Condition Breakpoint"
7432 } else {
7433 "Set Condition Breakpoint"
7434 };
7435
7436 let hit_condition_breakpoint_msg = if breakpoint
7437 .as_ref()
7438 .is_some_and(|bp| bp.1.hit_condition.is_some())
7439 {
7440 "Edit Hit Condition Breakpoint"
7441 } else {
7442 "Set Hit Condition Breakpoint"
7443 };
7444
7445 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
7446 "Unset Breakpoint"
7447 } else {
7448 "Set Breakpoint"
7449 };
7450
7451 let run_to_cursor = command_palette_hooks::CommandPaletteFilter::try_global(cx)
7452 .map_or(false, |filter| !filter.is_hidden(&DebuggerRunToCursor));
7453
7454 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
7455 BreakpointState::Enabled => Some("Disable"),
7456 BreakpointState::Disabled => Some("Enable"),
7457 });
7458
7459 let (anchor, breakpoint) =
7460 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
7461
7462 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
7463 menu.on_blur_subscription(Subscription::new(|| {}))
7464 .context(focus_handle)
7465 .when(run_to_cursor, |this| {
7466 let weak_editor = weak_editor.clone();
7467 this.entry("Run to cursor", None, move |window, cx| {
7468 weak_editor
7469 .update(cx, |editor, cx| {
7470 editor.change_selections(None, window, cx, |s| {
7471 s.select_ranges([Point::new(row, 0)..Point::new(row, 0)])
7472 });
7473 })
7474 .ok();
7475
7476 window.dispatch_action(Box::new(DebuggerRunToCursor), cx);
7477 })
7478 .separator()
7479 })
7480 .when_some(toggle_state_msg, |this, msg| {
7481 this.entry(msg, None, {
7482 let weak_editor = weak_editor.clone();
7483 let breakpoint = breakpoint.clone();
7484 move |_window, cx| {
7485 weak_editor
7486 .update(cx, |this, cx| {
7487 this.edit_breakpoint_at_anchor(
7488 anchor,
7489 breakpoint.as_ref().clone(),
7490 BreakpointEditAction::InvertState,
7491 cx,
7492 );
7493 })
7494 .log_err();
7495 }
7496 })
7497 })
7498 .entry(set_breakpoint_msg, None, {
7499 let weak_editor = weak_editor.clone();
7500 let breakpoint = breakpoint.clone();
7501 move |_window, cx| {
7502 weak_editor
7503 .update(cx, |this, cx| {
7504 this.edit_breakpoint_at_anchor(
7505 anchor,
7506 breakpoint.as_ref().clone(),
7507 BreakpointEditAction::Toggle,
7508 cx,
7509 );
7510 })
7511 .log_err();
7512 }
7513 })
7514 .entry(log_breakpoint_msg, None, {
7515 let breakpoint = breakpoint.clone();
7516 let weak_editor = weak_editor.clone();
7517 move |window, cx| {
7518 weak_editor
7519 .update(cx, |this, cx| {
7520 this.add_edit_breakpoint_block(
7521 anchor,
7522 breakpoint.as_ref(),
7523 BreakpointPromptEditAction::Log,
7524 window,
7525 cx,
7526 );
7527 })
7528 .log_err();
7529 }
7530 })
7531 .entry(condition_breakpoint_msg, None, {
7532 let breakpoint = breakpoint.clone();
7533 let weak_editor = weak_editor.clone();
7534 move |window, cx| {
7535 weak_editor
7536 .update(cx, |this, cx| {
7537 this.add_edit_breakpoint_block(
7538 anchor,
7539 breakpoint.as_ref(),
7540 BreakpointPromptEditAction::Condition,
7541 window,
7542 cx,
7543 );
7544 })
7545 .log_err();
7546 }
7547 })
7548 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
7549 weak_editor
7550 .update(cx, |this, cx| {
7551 this.add_edit_breakpoint_block(
7552 anchor,
7553 breakpoint.as_ref(),
7554 BreakpointPromptEditAction::HitCondition,
7555 window,
7556 cx,
7557 );
7558 })
7559 .log_err();
7560 })
7561 })
7562 }
7563
7564 fn render_breakpoint(
7565 &self,
7566 position: Anchor,
7567 row: DisplayRow,
7568 breakpoint: &Breakpoint,
7569 state: Option<BreakpointSessionState>,
7570 cx: &mut Context<Self>,
7571 ) -> IconButton {
7572 let is_rejected = state.is_some_and(|s| !s.verified);
7573 // Is it a breakpoint that shows up when hovering over gutter?
7574 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
7575 (false, false),
7576 |PhantomBreakpointIndicator {
7577 is_active,
7578 display_row,
7579 collides_with_existing_breakpoint,
7580 }| {
7581 (
7582 is_active && display_row == row,
7583 collides_with_existing_breakpoint,
7584 )
7585 },
7586 );
7587
7588 let (color, icon) = {
7589 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
7590 (false, false) => ui::IconName::DebugBreakpoint,
7591 (true, false) => ui::IconName::DebugLogBreakpoint,
7592 (false, true) => ui::IconName::DebugDisabledBreakpoint,
7593 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
7594 };
7595
7596 let color = if is_phantom {
7597 Color::Hint
7598 } else if is_rejected {
7599 Color::Disabled
7600 } else {
7601 Color::Debugger
7602 };
7603
7604 (color, icon)
7605 };
7606
7607 let breakpoint = Arc::from(breakpoint.clone());
7608
7609 let alt_as_text = gpui::Keystroke {
7610 modifiers: Modifiers::secondary_key(),
7611 ..Default::default()
7612 };
7613 let primary_action_text = if breakpoint.is_disabled() {
7614 "Enable breakpoint"
7615 } else if is_phantom && !collides_with_existing {
7616 "Set breakpoint"
7617 } else {
7618 "Unset breakpoint"
7619 };
7620 let focus_handle = self.focus_handle.clone();
7621
7622 let meta = if is_rejected {
7623 SharedString::from("No executable code is associated with this line.")
7624 } else if collides_with_existing && !breakpoint.is_disabled() {
7625 SharedString::from(format!(
7626 "{alt_as_text}-click to disable,\nright-click for more options."
7627 ))
7628 } else {
7629 SharedString::from("Right-click for more options.")
7630 };
7631 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
7632 .icon_size(IconSize::XSmall)
7633 .size(ui::ButtonSize::None)
7634 .when(is_rejected, |this| {
7635 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
7636 })
7637 .icon_color(color)
7638 .style(ButtonStyle::Transparent)
7639 .on_click(cx.listener({
7640 let breakpoint = breakpoint.clone();
7641
7642 move |editor, event: &ClickEvent, window, cx| {
7643 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
7644 BreakpointEditAction::InvertState
7645 } else {
7646 BreakpointEditAction::Toggle
7647 };
7648
7649 window.focus(&editor.focus_handle(cx));
7650 editor.edit_breakpoint_at_anchor(
7651 position,
7652 breakpoint.as_ref().clone(),
7653 edit_action,
7654 cx,
7655 );
7656 }
7657 }))
7658 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7659 editor.set_breakpoint_context_menu(
7660 row,
7661 Some(position),
7662 event.down.position,
7663 window,
7664 cx,
7665 );
7666 }))
7667 .tooltip(move |window, cx| {
7668 Tooltip::with_meta_in(
7669 primary_action_text,
7670 Some(&ToggleBreakpoint),
7671 meta.clone(),
7672 &focus_handle,
7673 window,
7674 cx,
7675 )
7676 })
7677 }
7678
7679 fn build_tasks_context(
7680 project: &Entity<Project>,
7681 buffer: &Entity<Buffer>,
7682 buffer_row: u32,
7683 tasks: &Arc<RunnableTasks>,
7684 cx: &mut Context<Self>,
7685 ) -> Task<Option<task::TaskContext>> {
7686 let position = Point::new(buffer_row, tasks.column);
7687 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
7688 let location = Location {
7689 buffer: buffer.clone(),
7690 range: range_start..range_start,
7691 };
7692 // Fill in the environmental variables from the tree-sitter captures
7693 let mut captured_task_variables = TaskVariables::default();
7694 for (capture_name, value) in tasks.extra_variables.clone() {
7695 captured_task_variables.insert(
7696 task::VariableName::Custom(capture_name.into()),
7697 value.clone(),
7698 );
7699 }
7700 project.update(cx, |project, cx| {
7701 project.task_store().update(cx, |task_store, cx| {
7702 task_store.task_context_for_location(captured_task_variables, location, cx)
7703 })
7704 })
7705 }
7706
7707 pub fn spawn_nearest_task(
7708 &mut self,
7709 action: &SpawnNearestTask,
7710 window: &mut Window,
7711 cx: &mut Context<Self>,
7712 ) {
7713 let Some((workspace, _)) = self.workspace.clone() else {
7714 return;
7715 };
7716 let Some(project) = self.project.clone() else {
7717 return;
7718 };
7719
7720 // Try to find a closest, enclosing node using tree-sitter that has a
7721 // task
7722 let Some((buffer, buffer_row, tasks)) = self
7723 .find_enclosing_node_task(cx)
7724 // Or find the task that's closest in row-distance.
7725 .or_else(|| self.find_closest_task(cx))
7726 else {
7727 return;
7728 };
7729
7730 let reveal_strategy = action.reveal;
7731 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
7732 cx.spawn_in(window, async move |_, cx| {
7733 let context = task_context.await?;
7734 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
7735
7736 let resolved = &mut resolved_task.resolved;
7737 resolved.reveal = reveal_strategy;
7738
7739 workspace
7740 .update_in(cx, |workspace, window, cx| {
7741 workspace.schedule_resolved_task(
7742 task_source_kind,
7743 resolved_task,
7744 false,
7745 window,
7746 cx,
7747 );
7748 })
7749 .ok()
7750 })
7751 .detach();
7752 }
7753
7754 fn find_closest_task(
7755 &mut self,
7756 cx: &mut Context<Self>,
7757 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7758 let cursor_row = self.selections.newest_adjusted(cx).head().row;
7759
7760 let ((buffer_id, row), tasks) = self
7761 .tasks
7762 .iter()
7763 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
7764
7765 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
7766 let tasks = Arc::new(tasks.to_owned());
7767 Some((buffer, *row, tasks))
7768 }
7769
7770 fn find_enclosing_node_task(
7771 &mut self,
7772 cx: &mut Context<Self>,
7773 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7774 let snapshot = self.buffer.read(cx).snapshot(cx);
7775 let offset = self.selections.newest::<usize>(cx).head();
7776 let excerpt = snapshot.excerpt_containing(offset..offset)?;
7777 let buffer_id = excerpt.buffer().remote_id();
7778
7779 let layer = excerpt.buffer().syntax_layer_at(offset)?;
7780 let mut cursor = layer.node().walk();
7781
7782 while cursor.goto_first_child_for_byte(offset).is_some() {
7783 if cursor.node().end_byte() == offset {
7784 cursor.goto_next_sibling();
7785 }
7786 }
7787
7788 // Ascend to the smallest ancestor that contains the range and has a task.
7789 loop {
7790 let node = cursor.node();
7791 let node_range = node.byte_range();
7792 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
7793
7794 // Check if this node contains our offset
7795 if node_range.start <= offset && node_range.end >= offset {
7796 // If it contains offset, check for task
7797 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
7798 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
7799 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
7800 }
7801 }
7802
7803 if !cursor.goto_parent() {
7804 break;
7805 }
7806 }
7807 None
7808 }
7809
7810 fn render_run_indicator(
7811 &self,
7812 _style: &EditorStyle,
7813 is_active: bool,
7814 row: DisplayRow,
7815 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
7816 cx: &mut Context<Self>,
7817 ) -> IconButton {
7818 let color = Color::Muted;
7819 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
7820
7821 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
7822 .shape(ui::IconButtonShape::Square)
7823 .icon_size(IconSize::XSmall)
7824 .icon_color(color)
7825 .toggle_state(is_active)
7826 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
7827 let quick_launch = e.down.button == MouseButton::Left;
7828 window.focus(&editor.focus_handle(cx));
7829 editor.toggle_code_actions(
7830 &ToggleCodeActions {
7831 deployed_from: Some(CodeActionSource::Indicator(row)),
7832 quick_launch,
7833 },
7834 window,
7835 cx,
7836 );
7837 }))
7838 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7839 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
7840 }))
7841 }
7842
7843 pub fn context_menu_visible(&self) -> bool {
7844 !self.edit_prediction_preview_is_active()
7845 && self
7846 .context_menu
7847 .borrow()
7848 .as_ref()
7849 .map_or(false, |menu| menu.visible())
7850 }
7851
7852 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
7853 self.context_menu
7854 .borrow()
7855 .as_ref()
7856 .map(|menu| menu.origin())
7857 }
7858
7859 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
7860 self.context_menu_options = Some(options);
7861 }
7862
7863 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
7864 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
7865
7866 fn render_edit_prediction_popover(
7867 &mut self,
7868 text_bounds: &Bounds<Pixels>,
7869 content_origin: gpui::Point<Pixels>,
7870 right_margin: Pixels,
7871 editor_snapshot: &EditorSnapshot,
7872 visible_row_range: Range<DisplayRow>,
7873 scroll_top: f32,
7874 scroll_bottom: f32,
7875 line_layouts: &[LineWithInvisibles],
7876 line_height: Pixels,
7877 scroll_pixel_position: gpui::Point<Pixels>,
7878 newest_selection_head: Option<DisplayPoint>,
7879 editor_width: Pixels,
7880 style: &EditorStyle,
7881 window: &mut Window,
7882 cx: &mut App,
7883 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7884 if self.mode().is_minimap() {
7885 return None;
7886 }
7887 let active_inline_completion = self.active_inline_completion.as_ref()?;
7888
7889 if self.edit_prediction_visible_in_cursor_popover(true) {
7890 return None;
7891 }
7892
7893 match &active_inline_completion.completion {
7894 InlineCompletion::Move { target, .. } => {
7895 let target_display_point = target.to_display_point(editor_snapshot);
7896
7897 if self.edit_prediction_requires_modifier() {
7898 if !self.edit_prediction_preview_is_active() {
7899 return None;
7900 }
7901
7902 self.render_edit_prediction_modifier_jump_popover(
7903 text_bounds,
7904 content_origin,
7905 visible_row_range,
7906 line_layouts,
7907 line_height,
7908 scroll_pixel_position,
7909 newest_selection_head,
7910 target_display_point,
7911 window,
7912 cx,
7913 )
7914 } else {
7915 self.render_edit_prediction_eager_jump_popover(
7916 text_bounds,
7917 content_origin,
7918 editor_snapshot,
7919 visible_row_range,
7920 scroll_top,
7921 scroll_bottom,
7922 line_height,
7923 scroll_pixel_position,
7924 target_display_point,
7925 editor_width,
7926 window,
7927 cx,
7928 )
7929 }
7930 }
7931 InlineCompletion::Edit {
7932 display_mode: EditDisplayMode::Inline,
7933 ..
7934 } => None,
7935 InlineCompletion::Edit {
7936 display_mode: EditDisplayMode::TabAccept,
7937 edits,
7938 ..
7939 } => {
7940 let range = &edits.first()?.0;
7941 let target_display_point = range.end.to_display_point(editor_snapshot);
7942
7943 self.render_edit_prediction_end_of_line_popover(
7944 "Accept",
7945 editor_snapshot,
7946 visible_row_range,
7947 target_display_point,
7948 line_height,
7949 scroll_pixel_position,
7950 content_origin,
7951 editor_width,
7952 window,
7953 cx,
7954 )
7955 }
7956 InlineCompletion::Edit {
7957 edits,
7958 edit_preview,
7959 display_mode: EditDisplayMode::DiffPopover,
7960 snapshot,
7961 } => self.render_edit_prediction_diff_popover(
7962 text_bounds,
7963 content_origin,
7964 right_margin,
7965 editor_snapshot,
7966 visible_row_range,
7967 line_layouts,
7968 line_height,
7969 scroll_pixel_position,
7970 newest_selection_head,
7971 editor_width,
7972 style,
7973 edits,
7974 edit_preview,
7975 snapshot,
7976 window,
7977 cx,
7978 ),
7979 }
7980 }
7981
7982 fn render_edit_prediction_modifier_jump_popover(
7983 &mut self,
7984 text_bounds: &Bounds<Pixels>,
7985 content_origin: gpui::Point<Pixels>,
7986 visible_row_range: Range<DisplayRow>,
7987 line_layouts: &[LineWithInvisibles],
7988 line_height: Pixels,
7989 scroll_pixel_position: gpui::Point<Pixels>,
7990 newest_selection_head: Option<DisplayPoint>,
7991 target_display_point: DisplayPoint,
7992 window: &mut Window,
7993 cx: &mut App,
7994 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7995 let scrolled_content_origin =
7996 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
7997
7998 const SCROLL_PADDING_Y: Pixels = px(12.);
7999
8000 if target_display_point.row() < visible_row_range.start {
8001 return self.render_edit_prediction_scroll_popover(
8002 |_| SCROLL_PADDING_Y,
8003 IconName::ArrowUp,
8004 visible_row_range,
8005 line_layouts,
8006 newest_selection_head,
8007 scrolled_content_origin,
8008 window,
8009 cx,
8010 );
8011 } else if target_display_point.row() >= visible_row_range.end {
8012 return self.render_edit_prediction_scroll_popover(
8013 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8014 IconName::ArrowDown,
8015 visible_row_range,
8016 line_layouts,
8017 newest_selection_head,
8018 scrolled_content_origin,
8019 window,
8020 cx,
8021 );
8022 }
8023
8024 const POLE_WIDTH: Pixels = px(2.);
8025
8026 let line_layout =
8027 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8028 let target_column = target_display_point.column() as usize;
8029
8030 let target_x = line_layout.x_for_index(target_column);
8031 let target_y =
8032 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
8033
8034 let flag_on_right = target_x < text_bounds.size.width / 2.;
8035
8036 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8037 border_color.l += 0.001;
8038
8039 let mut element = v_flex()
8040 .items_end()
8041 .when(flag_on_right, |el| el.items_start())
8042 .child(if flag_on_right {
8043 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8044 .rounded_bl(px(0.))
8045 .rounded_tl(px(0.))
8046 .border_l_2()
8047 .border_color(border_color)
8048 } else {
8049 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8050 .rounded_br(px(0.))
8051 .rounded_tr(px(0.))
8052 .border_r_2()
8053 .border_color(border_color)
8054 })
8055 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8056 .into_any();
8057
8058 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8059
8060 let mut origin = scrolled_content_origin + point(target_x, target_y)
8061 - point(
8062 if flag_on_right {
8063 POLE_WIDTH
8064 } else {
8065 size.width - POLE_WIDTH
8066 },
8067 size.height - line_height,
8068 );
8069
8070 origin.x = origin.x.max(content_origin.x);
8071
8072 element.prepaint_at(origin, window, cx);
8073
8074 Some((element, origin))
8075 }
8076
8077 fn render_edit_prediction_scroll_popover(
8078 &mut self,
8079 to_y: impl Fn(Size<Pixels>) -> Pixels,
8080 scroll_icon: IconName,
8081 visible_row_range: Range<DisplayRow>,
8082 line_layouts: &[LineWithInvisibles],
8083 newest_selection_head: Option<DisplayPoint>,
8084 scrolled_content_origin: gpui::Point<Pixels>,
8085 window: &mut Window,
8086 cx: &mut App,
8087 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8088 let mut element = self
8089 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
8090 .into_any();
8091
8092 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8093
8094 let cursor = newest_selection_head?;
8095 let cursor_row_layout =
8096 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8097 let cursor_column = cursor.column() as usize;
8098
8099 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8100
8101 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8102
8103 element.prepaint_at(origin, window, cx);
8104 Some((element, origin))
8105 }
8106
8107 fn render_edit_prediction_eager_jump_popover(
8108 &mut self,
8109 text_bounds: &Bounds<Pixels>,
8110 content_origin: gpui::Point<Pixels>,
8111 editor_snapshot: &EditorSnapshot,
8112 visible_row_range: Range<DisplayRow>,
8113 scroll_top: f32,
8114 scroll_bottom: f32,
8115 line_height: Pixels,
8116 scroll_pixel_position: gpui::Point<Pixels>,
8117 target_display_point: DisplayPoint,
8118 editor_width: Pixels,
8119 window: &mut Window,
8120 cx: &mut App,
8121 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8122 if target_display_point.row().as_f32() < scroll_top {
8123 let mut element = self
8124 .render_edit_prediction_line_popover(
8125 "Jump to Edit",
8126 Some(IconName::ArrowUp),
8127 window,
8128 cx,
8129 )?
8130 .into_any();
8131
8132 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8133 let offset = point(
8134 (text_bounds.size.width - size.width) / 2.,
8135 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8136 );
8137
8138 let origin = text_bounds.origin + offset;
8139 element.prepaint_at(origin, window, cx);
8140 Some((element, origin))
8141 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
8142 let mut element = self
8143 .render_edit_prediction_line_popover(
8144 "Jump to Edit",
8145 Some(IconName::ArrowDown),
8146 window,
8147 cx,
8148 )?
8149 .into_any();
8150
8151 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8152 let offset = point(
8153 (text_bounds.size.width - size.width) / 2.,
8154 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8155 );
8156
8157 let origin = text_bounds.origin + offset;
8158 element.prepaint_at(origin, window, cx);
8159 Some((element, origin))
8160 } else {
8161 self.render_edit_prediction_end_of_line_popover(
8162 "Jump to Edit",
8163 editor_snapshot,
8164 visible_row_range,
8165 target_display_point,
8166 line_height,
8167 scroll_pixel_position,
8168 content_origin,
8169 editor_width,
8170 window,
8171 cx,
8172 )
8173 }
8174 }
8175
8176 fn render_edit_prediction_end_of_line_popover(
8177 self: &mut Editor,
8178 label: &'static str,
8179 editor_snapshot: &EditorSnapshot,
8180 visible_row_range: Range<DisplayRow>,
8181 target_display_point: DisplayPoint,
8182 line_height: Pixels,
8183 scroll_pixel_position: gpui::Point<Pixels>,
8184 content_origin: gpui::Point<Pixels>,
8185 editor_width: Pixels,
8186 window: &mut Window,
8187 cx: &mut App,
8188 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8189 let target_line_end = DisplayPoint::new(
8190 target_display_point.row(),
8191 editor_snapshot.line_len(target_display_point.row()),
8192 );
8193
8194 let mut element = self
8195 .render_edit_prediction_line_popover(label, None, window, cx)?
8196 .into_any();
8197
8198 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8199
8200 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8201
8202 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
8203 let mut origin = start_point
8204 + line_origin
8205 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8206 origin.x = origin.x.max(content_origin.x);
8207
8208 let max_x = content_origin.x + editor_width - size.width;
8209
8210 if origin.x > max_x {
8211 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8212
8213 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8214 origin.y += offset;
8215 IconName::ArrowUp
8216 } else {
8217 origin.y -= offset;
8218 IconName::ArrowDown
8219 };
8220
8221 element = self
8222 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
8223 .into_any();
8224
8225 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8226
8227 origin.x = content_origin.x + editor_width - size.width - px(2.);
8228 }
8229
8230 element.prepaint_at(origin, window, cx);
8231 Some((element, origin))
8232 }
8233
8234 fn render_edit_prediction_diff_popover(
8235 self: &Editor,
8236 text_bounds: &Bounds<Pixels>,
8237 content_origin: gpui::Point<Pixels>,
8238 right_margin: Pixels,
8239 editor_snapshot: &EditorSnapshot,
8240 visible_row_range: Range<DisplayRow>,
8241 line_layouts: &[LineWithInvisibles],
8242 line_height: Pixels,
8243 scroll_pixel_position: gpui::Point<Pixels>,
8244 newest_selection_head: Option<DisplayPoint>,
8245 editor_width: Pixels,
8246 style: &EditorStyle,
8247 edits: &Vec<(Range<Anchor>, String)>,
8248 edit_preview: &Option<language::EditPreview>,
8249 snapshot: &language::BufferSnapshot,
8250 window: &mut Window,
8251 cx: &mut App,
8252 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8253 let edit_start = edits
8254 .first()
8255 .unwrap()
8256 .0
8257 .start
8258 .to_display_point(editor_snapshot);
8259 let edit_end = edits
8260 .last()
8261 .unwrap()
8262 .0
8263 .end
8264 .to_display_point(editor_snapshot);
8265
8266 let is_visible = visible_row_range.contains(&edit_start.row())
8267 || visible_row_range.contains(&edit_end.row());
8268 if !is_visible {
8269 return None;
8270 }
8271
8272 let highlighted_edits =
8273 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
8274
8275 let styled_text = highlighted_edits.to_styled_text(&style.text);
8276 let line_count = highlighted_edits.text.lines().count();
8277
8278 const BORDER_WIDTH: Pixels = px(1.);
8279
8280 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8281 let has_keybind = keybind.is_some();
8282
8283 let mut element = h_flex()
8284 .items_start()
8285 .child(
8286 h_flex()
8287 .bg(cx.theme().colors().editor_background)
8288 .border(BORDER_WIDTH)
8289 .shadow_sm()
8290 .border_color(cx.theme().colors().border)
8291 .rounded_l_lg()
8292 .when(line_count > 1, |el| el.rounded_br_lg())
8293 .pr_1()
8294 .child(styled_text),
8295 )
8296 .child(
8297 h_flex()
8298 .h(line_height + BORDER_WIDTH * 2.)
8299 .px_1p5()
8300 .gap_1()
8301 // Workaround: For some reason, there's a gap if we don't do this
8302 .ml(-BORDER_WIDTH)
8303 .shadow(vec![gpui::BoxShadow {
8304 color: gpui::black().opacity(0.05),
8305 offset: point(px(1.), px(1.)),
8306 blur_radius: px(2.),
8307 spread_radius: px(0.),
8308 }])
8309 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8310 .border(BORDER_WIDTH)
8311 .border_color(cx.theme().colors().border)
8312 .rounded_r_lg()
8313 .id("edit_prediction_diff_popover_keybind")
8314 .when(!has_keybind, |el| {
8315 let status_colors = cx.theme().status();
8316
8317 el.bg(status_colors.error_background)
8318 .border_color(status_colors.error.opacity(0.6))
8319 .child(Icon::new(IconName::Info).color(Color::Error))
8320 .cursor_default()
8321 .hoverable_tooltip(move |_window, cx| {
8322 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8323 })
8324 })
8325 .children(keybind),
8326 )
8327 .into_any();
8328
8329 let longest_row =
8330 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8331 let longest_line_width = if visible_row_range.contains(&longest_row) {
8332 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8333 } else {
8334 layout_line(
8335 longest_row,
8336 editor_snapshot,
8337 style,
8338 editor_width,
8339 |_| false,
8340 window,
8341 cx,
8342 )
8343 .width
8344 };
8345
8346 let viewport_bounds =
8347 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8348 right: -right_margin,
8349 ..Default::default()
8350 });
8351
8352 let x_after_longest =
8353 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
8354 - scroll_pixel_position.x;
8355
8356 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8357
8358 // Fully visible if it can be displayed within the window (allow overlapping other
8359 // panes). However, this is only allowed if the popover starts within text_bounds.
8360 let can_position_to_the_right = x_after_longest < text_bounds.right()
8361 && x_after_longest + element_bounds.width < viewport_bounds.right();
8362
8363 let mut origin = if can_position_to_the_right {
8364 point(
8365 x_after_longest,
8366 text_bounds.origin.y + edit_start.row().as_f32() * line_height
8367 - scroll_pixel_position.y,
8368 )
8369 } else {
8370 let cursor_row = newest_selection_head.map(|head| head.row());
8371 let above_edit = edit_start
8372 .row()
8373 .0
8374 .checked_sub(line_count as u32)
8375 .map(DisplayRow);
8376 let below_edit = Some(edit_end.row() + 1);
8377 let above_cursor =
8378 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
8379 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
8380
8381 // Place the edit popover adjacent to the edit if there is a location
8382 // available that is onscreen and does not obscure the cursor. Otherwise,
8383 // place it adjacent to the cursor.
8384 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
8385 .into_iter()
8386 .flatten()
8387 .find(|&start_row| {
8388 let end_row = start_row + line_count as u32;
8389 visible_row_range.contains(&start_row)
8390 && visible_row_range.contains(&end_row)
8391 && cursor_row.map_or(true, |cursor_row| {
8392 !((start_row..end_row).contains(&cursor_row))
8393 })
8394 })?;
8395
8396 content_origin
8397 + point(
8398 -scroll_pixel_position.x,
8399 row_target.as_f32() * line_height - scroll_pixel_position.y,
8400 )
8401 };
8402
8403 origin.x -= BORDER_WIDTH;
8404
8405 window.defer_draw(element, origin, 1);
8406
8407 // Do not return an element, since it will already be drawn due to defer_draw.
8408 None
8409 }
8410
8411 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
8412 px(30.)
8413 }
8414
8415 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
8416 if self.read_only(cx) {
8417 cx.theme().players().read_only()
8418 } else {
8419 self.style.as_ref().unwrap().local_player
8420 }
8421 }
8422
8423 fn render_edit_prediction_accept_keybind(
8424 &self,
8425 window: &mut Window,
8426 cx: &App,
8427 ) -> Option<AnyElement> {
8428 let accept_binding = self.accept_edit_prediction_keybind(window, cx);
8429 let accept_keystroke = accept_binding.keystroke()?;
8430
8431 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8432
8433 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
8434 Color::Accent
8435 } else {
8436 Color::Muted
8437 };
8438
8439 h_flex()
8440 .px_0p5()
8441 .when(is_platform_style_mac, |parent| parent.gap_0p5())
8442 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8443 .text_size(TextSize::XSmall.rems(cx))
8444 .child(h_flex().children(ui::render_modifiers(
8445 &accept_keystroke.modifiers,
8446 PlatformStyle::platform(),
8447 Some(modifiers_color),
8448 Some(IconSize::XSmall.rems().into()),
8449 true,
8450 )))
8451 .when(is_platform_style_mac, |parent| {
8452 parent.child(accept_keystroke.key.clone())
8453 })
8454 .when(!is_platform_style_mac, |parent| {
8455 parent.child(
8456 Key::new(
8457 util::capitalize(&accept_keystroke.key),
8458 Some(Color::Default),
8459 )
8460 .size(Some(IconSize::XSmall.rems().into())),
8461 )
8462 })
8463 .into_any()
8464 .into()
8465 }
8466
8467 fn render_edit_prediction_line_popover(
8468 &self,
8469 label: impl Into<SharedString>,
8470 icon: Option<IconName>,
8471 window: &mut Window,
8472 cx: &App,
8473 ) -> Option<Stateful<Div>> {
8474 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
8475
8476 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8477 let has_keybind = keybind.is_some();
8478
8479 let result = h_flex()
8480 .id("ep-line-popover")
8481 .py_0p5()
8482 .pl_1()
8483 .pr(padding_right)
8484 .gap_1()
8485 .rounded_md()
8486 .border_1()
8487 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8488 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
8489 .shadow_sm()
8490 .when(!has_keybind, |el| {
8491 let status_colors = cx.theme().status();
8492
8493 el.bg(status_colors.error_background)
8494 .border_color(status_colors.error.opacity(0.6))
8495 .pl_2()
8496 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
8497 .cursor_default()
8498 .hoverable_tooltip(move |_window, cx| {
8499 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8500 })
8501 })
8502 .children(keybind)
8503 .child(
8504 Label::new(label)
8505 .size(LabelSize::Small)
8506 .when(!has_keybind, |el| {
8507 el.color(cx.theme().status().error.into()).strikethrough()
8508 }),
8509 )
8510 .when(!has_keybind, |el| {
8511 el.child(
8512 h_flex().ml_1().child(
8513 Icon::new(IconName::Info)
8514 .size(IconSize::Small)
8515 .color(cx.theme().status().error.into()),
8516 ),
8517 )
8518 })
8519 .when_some(icon, |element, icon| {
8520 element.child(
8521 div()
8522 .mt(px(1.5))
8523 .child(Icon::new(icon).size(IconSize::Small)),
8524 )
8525 });
8526
8527 Some(result)
8528 }
8529
8530 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
8531 let accent_color = cx.theme().colors().text_accent;
8532 let editor_bg_color = cx.theme().colors().editor_background;
8533 editor_bg_color.blend(accent_color.opacity(0.1))
8534 }
8535
8536 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
8537 let accent_color = cx.theme().colors().text_accent;
8538 let editor_bg_color = cx.theme().colors().editor_background;
8539 editor_bg_color.blend(accent_color.opacity(0.6))
8540 }
8541
8542 fn render_edit_prediction_cursor_popover(
8543 &self,
8544 min_width: Pixels,
8545 max_width: Pixels,
8546 cursor_point: Point,
8547 style: &EditorStyle,
8548 accept_keystroke: Option<&gpui::Keystroke>,
8549 _window: &Window,
8550 cx: &mut Context<Editor>,
8551 ) -> Option<AnyElement> {
8552 let provider = self.edit_prediction_provider.as_ref()?;
8553
8554 if provider.provider.needs_terms_acceptance(cx) {
8555 return Some(
8556 h_flex()
8557 .min_w(min_width)
8558 .flex_1()
8559 .px_2()
8560 .py_1()
8561 .gap_3()
8562 .elevation_2(cx)
8563 .hover(|style| style.bg(cx.theme().colors().element_hover))
8564 .id("accept-terms")
8565 .cursor_pointer()
8566 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
8567 .on_click(cx.listener(|this, _event, window, cx| {
8568 cx.stop_propagation();
8569 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
8570 window.dispatch_action(
8571 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
8572 cx,
8573 );
8574 }))
8575 .child(
8576 h_flex()
8577 .flex_1()
8578 .gap_2()
8579 .child(Icon::new(IconName::ZedPredict))
8580 .child(Label::new("Accept Terms of Service"))
8581 .child(div().w_full())
8582 .child(
8583 Icon::new(IconName::ArrowUpRight)
8584 .color(Color::Muted)
8585 .size(IconSize::Small),
8586 )
8587 .into_any_element(),
8588 )
8589 .into_any(),
8590 );
8591 }
8592
8593 let is_refreshing = provider.provider.is_refreshing(cx);
8594
8595 fn pending_completion_container() -> Div {
8596 h_flex()
8597 .h_full()
8598 .flex_1()
8599 .gap_2()
8600 .child(Icon::new(IconName::ZedPredict))
8601 }
8602
8603 let completion = match &self.active_inline_completion {
8604 Some(prediction) => {
8605 if !self.has_visible_completions_menu() {
8606 const RADIUS: Pixels = px(6.);
8607 const BORDER_WIDTH: Pixels = px(1.);
8608
8609 return Some(
8610 h_flex()
8611 .elevation_2(cx)
8612 .border(BORDER_WIDTH)
8613 .border_color(cx.theme().colors().border)
8614 .when(accept_keystroke.is_none(), |el| {
8615 el.border_color(cx.theme().status().error)
8616 })
8617 .rounded(RADIUS)
8618 .rounded_tl(px(0.))
8619 .overflow_hidden()
8620 .child(div().px_1p5().child(match &prediction.completion {
8621 InlineCompletion::Move { target, snapshot } => {
8622 use text::ToPoint as _;
8623 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
8624 {
8625 Icon::new(IconName::ZedPredictDown)
8626 } else {
8627 Icon::new(IconName::ZedPredictUp)
8628 }
8629 }
8630 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
8631 }))
8632 .child(
8633 h_flex()
8634 .gap_1()
8635 .py_1()
8636 .px_2()
8637 .rounded_r(RADIUS - BORDER_WIDTH)
8638 .border_l_1()
8639 .border_color(cx.theme().colors().border)
8640 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8641 .when(self.edit_prediction_preview.released_too_fast(), |el| {
8642 el.child(
8643 Label::new("Hold")
8644 .size(LabelSize::Small)
8645 .when(accept_keystroke.is_none(), |el| {
8646 el.strikethrough()
8647 })
8648 .line_height_style(LineHeightStyle::UiLabel),
8649 )
8650 })
8651 .id("edit_prediction_cursor_popover_keybind")
8652 .when(accept_keystroke.is_none(), |el| {
8653 let status_colors = cx.theme().status();
8654
8655 el.bg(status_colors.error_background)
8656 .border_color(status_colors.error.opacity(0.6))
8657 .child(Icon::new(IconName::Info).color(Color::Error))
8658 .cursor_default()
8659 .hoverable_tooltip(move |_window, cx| {
8660 cx.new(|_| MissingEditPredictionKeybindingTooltip)
8661 .into()
8662 })
8663 })
8664 .when_some(
8665 accept_keystroke.as_ref(),
8666 |el, accept_keystroke| {
8667 el.child(h_flex().children(ui::render_modifiers(
8668 &accept_keystroke.modifiers,
8669 PlatformStyle::platform(),
8670 Some(Color::Default),
8671 Some(IconSize::XSmall.rems().into()),
8672 false,
8673 )))
8674 },
8675 ),
8676 )
8677 .into_any(),
8678 );
8679 }
8680
8681 self.render_edit_prediction_cursor_popover_preview(
8682 prediction,
8683 cursor_point,
8684 style,
8685 cx,
8686 )?
8687 }
8688
8689 None if is_refreshing => match &self.stale_inline_completion_in_menu {
8690 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
8691 stale_completion,
8692 cursor_point,
8693 style,
8694 cx,
8695 )?,
8696
8697 None => {
8698 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
8699 }
8700 },
8701
8702 None => pending_completion_container().child(Label::new("No Prediction")),
8703 };
8704
8705 let completion = if is_refreshing {
8706 completion
8707 .with_animation(
8708 "loading-completion",
8709 Animation::new(Duration::from_secs(2))
8710 .repeat()
8711 .with_easing(pulsating_between(0.4, 0.8)),
8712 |label, delta| label.opacity(delta),
8713 )
8714 .into_any_element()
8715 } else {
8716 completion.into_any_element()
8717 };
8718
8719 let has_completion = self.active_inline_completion.is_some();
8720
8721 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8722 Some(
8723 h_flex()
8724 .min_w(min_width)
8725 .max_w(max_width)
8726 .flex_1()
8727 .elevation_2(cx)
8728 .border_color(cx.theme().colors().border)
8729 .child(
8730 div()
8731 .flex_1()
8732 .py_1()
8733 .px_2()
8734 .overflow_hidden()
8735 .child(completion),
8736 )
8737 .when_some(accept_keystroke, |el, accept_keystroke| {
8738 if !accept_keystroke.modifiers.modified() {
8739 return el;
8740 }
8741
8742 el.child(
8743 h_flex()
8744 .h_full()
8745 .border_l_1()
8746 .rounded_r_lg()
8747 .border_color(cx.theme().colors().border)
8748 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8749 .gap_1()
8750 .py_1()
8751 .px_2()
8752 .child(
8753 h_flex()
8754 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8755 .when(is_platform_style_mac, |parent| parent.gap_1())
8756 .child(h_flex().children(ui::render_modifiers(
8757 &accept_keystroke.modifiers,
8758 PlatformStyle::platform(),
8759 Some(if !has_completion {
8760 Color::Muted
8761 } else {
8762 Color::Default
8763 }),
8764 None,
8765 false,
8766 ))),
8767 )
8768 .child(Label::new("Preview").into_any_element())
8769 .opacity(if has_completion { 1.0 } else { 0.4 }),
8770 )
8771 })
8772 .into_any(),
8773 )
8774 }
8775
8776 fn render_edit_prediction_cursor_popover_preview(
8777 &self,
8778 completion: &InlineCompletionState,
8779 cursor_point: Point,
8780 style: &EditorStyle,
8781 cx: &mut Context<Editor>,
8782 ) -> Option<Div> {
8783 use text::ToPoint as _;
8784
8785 fn render_relative_row_jump(
8786 prefix: impl Into<String>,
8787 current_row: u32,
8788 target_row: u32,
8789 ) -> Div {
8790 let (row_diff, arrow) = if target_row < current_row {
8791 (current_row - target_row, IconName::ArrowUp)
8792 } else {
8793 (target_row - current_row, IconName::ArrowDown)
8794 };
8795
8796 h_flex()
8797 .child(
8798 Label::new(format!("{}{}", prefix.into(), row_diff))
8799 .color(Color::Muted)
8800 .size(LabelSize::Small),
8801 )
8802 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
8803 }
8804
8805 match &completion.completion {
8806 InlineCompletion::Move {
8807 target, snapshot, ..
8808 } => Some(
8809 h_flex()
8810 .px_2()
8811 .gap_2()
8812 .flex_1()
8813 .child(
8814 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
8815 Icon::new(IconName::ZedPredictDown)
8816 } else {
8817 Icon::new(IconName::ZedPredictUp)
8818 },
8819 )
8820 .child(Label::new("Jump to Edit")),
8821 ),
8822
8823 InlineCompletion::Edit {
8824 edits,
8825 edit_preview,
8826 snapshot,
8827 display_mode: _,
8828 } => {
8829 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
8830
8831 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
8832 &snapshot,
8833 &edits,
8834 edit_preview.as_ref()?,
8835 true,
8836 cx,
8837 )
8838 .first_line_preview();
8839
8840 let styled_text = gpui::StyledText::new(highlighted_edits.text)
8841 .with_default_highlights(&style.text, highlighted_edits.highlights);
8842
8843 let preview = h_flex()
8844 .gap_1()
8845 .min_w_16()
8846 .child(styled_text)
8847 .when(has_more_lines, |parent| parent.child("…"));
8848
8849 let left = if first_edit_row != cursor_point.row {
8850 render_relative_row_jump("", cursor_point.row, first_edit_row)
8851 .into_any_element()
8852 } else {
8853 Icon::new(IconName::ZedPredict).into_any_element()
8854 };
8855
8856 Some(
8857 h_flex()
8858 .h_full()
8859 .flex_1()
8860 .gap_2()
8861 .pr_1()
8862 .overflow_x_hidden()
8863 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8864 .child(left)
8865 .child(preview),
8866 )
8867 }
8868 }
8869 }
8870
8871 pub fn render_context_menu(
8872 &self,
8873 style: &EditorStyle,
8874 max_height_in_lines: u32,
8875 window: &mut Window,
8876 cx: &mut Context<Editor>,
8877 ) -> Option<AnyElement> {
8878 let menu = self.context_menu.borrow();
8879 let menu = menu.as_ref()?;
8880 if !menu.visible() {
8881 return None;
8882 };
8883 Some(menu.render(style, max_height_in_lines, window, cx))
8884 }
8885
8886 fn render_context_menu_aside(
8887 &mut self,
8888 max_size: Size<Pixels>,
8889 window: &mut Window,
8890 cx: &mut Context<Editor>,
8891 ) -> Option<AnyElement> {
8892 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
8893 if menu.visible() {
8894 menu.render_aside(max_size, window, cx)
8895 } else {
8896 None
8897 }
8898 })
8899 }
8900
8901 fn hide_context_menu(
8902 &mut self,
8903 window: &mut Window,
8904 cx: &mut Context<Self>,
8905 ) -> Option<CodeContextMenu> {
8906 cx.notify();
8907 self.completion_tasks.clear();
8908 let context_menu = self.context_menu.borrow_mut().take();
8909 self.stale_inline_completion_in_menu.take();
8910 self.update_visible_inline_completion(window, cx);
8911 if let Some(CodeContextMenu::Completions(_)) = &context_menu {
8912 if let Some(completion_provider) = &self.completion_provider {
8913 completion_provider.selection_changed(None, window, cx);
8914 }
8915 }
8916 context_menu
8917 }
8918
8919 fn show_snippet_choices(
8920 &mut self,
8921 choices: &Vec<String>,
8922 selection: Range<Anchor>,
8923 cx: &mut Context<Self>,
8924 ) {
8925 if selection.start.buffer_id.is_none() {
8926 return;
8927 }
8928 let buffer_id = selection.start.buffer_id.unwrap();
8929 let buffer = self.buffer().read(cx).buffer(buffer_id);
8930 let id = post_inc(&mut self.next_completion_id);
8931 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
8932
8933 if let Some(buffer) = buffer {
8934 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
8935 CompletionsMenu::new_snippet_choices(
8936 id,
8937 true,
8938 choices,
8939 selection,
8940 buffer,
8941 snippet_sort_order,
8942 ),
8943 ));
8944 }
8945 }
8946
8947 pub fn insert_snippet(
8948 &mut self,
8949 insertion_ranges: &[Range<usize>],
8950 snippet: Snippet,
8951 window: &mut Window,
8952 cx: &mut Context<Self>,
8953 ) -> Result<()> {
8954 struct Tabstop<T> {
8955 is_end_tabstop: bool,
8956 ranges: Vec<Range<T>>,
8957 choices: Option<Vec<String>>,
8958 }
8959
8960 let tabstops = self.buffer.update(cx, |buffer, cx| {
8961 let snippet_text: Arc<str> = snippet.text.clone().into();
8962 let edits = insertion_ranges
8963 .iter()
8964 .cloned()
8965 .map(|range| (range, snippet_text.clone()));
8966 let autoindent_mode = AutoindentMode::Block {
8967 original_indent_columns: Vec::new(),
8968 };
8969 buffer.edit(edits, Some(autoindent_mode), cx);
8970
8971 let snapshot = &*buffer.read(cx);
8972 let snippet = &snippet;
8973 snippet
8974 .tabstops
8975 .iter()
8976 .map(|tabstop| {
8977 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
8978 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
8979 });
8980 let mut tabstop_ranges = tabstop
8981 .ranges
8982 .iter()
8983 .flat_map(|tabstop_range| {
8984 let mut delta = 0_isize;
8985 insertion_ranges.iter().map(move |insertion_range| {
8986 let insertion_start = insertion_range.start as isize + delta;
8987 delta +=
8988 snippet.text.len() as isize - insertion_range.len() as isize;
8989
8990 let start = ((insertion_start + tabstop_range.start) as usize)
8991 .min(snapshot.len());
8992 let end = ((insertion_start + tabstop_range.end) as usize)
8993 .min(snapshot.len());
8994 snapshot.anchor_before(start)..snapshot.anchor_after(end)
8995 })
8996 })
8997 .collect::<Vec<_>>();
8998 // Sort in reverse order so that the first range is the newest created
8999 // selection. Completions will use it and autoscroll will prioritize it.
9000 tabstop_ranges.sort_unstable_by(|a, b| b.start.cmp(&a.start, snapshot));
9001
9002 Tabstop {
9003 is_end_tabstop,
9004 ranges: tabstop_ranges,
9005 choices: tabstop.choices.clone(),
9006 }
9007 })
9008 .collect::<Vec<_>>()
9009 });
9010 if let Some(tabstop) = tabstops.first() {
9011 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9012 s.select_ranges(tabstop.ranges.iter().cloned());
9013 });
9014
9015 if let Some(choices) = &tabstop.choices {
9016 if let Some(selection) = tabstop.ranges.first() {
9017 self.show_snippet_choices(choices, selection.clone(), cx)
9018 }
9019 }
9020
9021 // If we're already at the last tabstop and it's at the end of the snippet,
9022 // we're done, we don't need to keep the state around.
9023 if !tabstop.is_end_tabstop {
9024 let choices = tabstops
9025 .iter()
9026 .map(|tabstop| tabstop.choices.clone())
9027 .collect();
9028
9029 let ranges = tabstops
9030 .into_iter()
9031 .map(|tabstop| tabstop.ranges)
9032 .collect::<Vec<_>>();
9033
9034 self.snippet_stack.push(SnippetState {
9035 active_index: 0,
9036 ranges,
9037 choices,
9038 });
9039 }
9040
9041 // Check whether the just-entered snippet ends with an auto-closable bracket.
9042 if self.autoclose_regions.is_empty() {
9043 let snapshot = self.buffer.read(cx).snapshot(cx);
9044 for selection in &mut self.selections.all::<Point>(cx) {
9045 let selection_head = selection.head();
9046 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9047 continue;
9048 };
9049
9050 let mut bracket_pair = None;
9051 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
9052 let prev_chars = snapshot
9053 .reversed_chars_at(selection_head)
9054 .collect::<String>();
9055 for (pair, enabled) in scope.brackets() {
9056 if enabled
9057 && pair.close
9058 && prev_chars.starts_with(pair.start.as_str())
9059 && next_chars.starts_with(pair.end.as_str())
9060 {
9061 bracket_pair = Some(pair.clone());
9062 break;
9063 }
9064 }
9065 if let Some(pair) = bracket_pair {
9066 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9067 let autoclose_enabled =
9068 self.use_autoclose && snapshot_settings.use_autoclose;
9069 if autoclose_enabled {
9070 let start = snapshot.anchor_after(selection_head);
9071 let end = snapshot.anchor_after(selection_head);
9072 self.autoclose_regions.push(AutocloseRegion {
9073 selection_id: selection.id,
9074 range: start..end,
9075 pair,
9076 });
9077 }
9078 }
9079 }
9080 }
9081 }
9082 Ok(())
9083 }
9084
9085 pub fn move_to_next_snippet_tabstop(
9086 &mut self,
9087 window: &mut Window,
9088 cx: &mut Context<Self>,
9089 ) -> bool {
9090 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9091 }
9092
9093 pub fn move_to_prev_snippet_tabstop(
9094 &mut self,
9095 window: &mut Window,
9096 cx: &mut Context<Self>,
9097 ) -> bool {
9098 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9099 }
9100
9101 pub fn move_to_snippet_tabstop(
9102 &mut self,
9103 bias: Bias,
9104 window: &mut Window,
9105 cx: &mut Context<Self>,
9106 ) -> bool {
9107 if let Some(mut snippet) = self.snippet_stack.pop() {
9108 match bias {
9109 Bias::Left => {
9110 if snippet.active_index > 0 {
9111 snippet.active_index -= 1;
9112 } else {
9113 self.snippet_stack.push(snippet);
9114 return false;
9115 }
9116 }
9117 Bias::Right => {
9118 if snippet.active_index + 1 < snippet.ranges.len() {
9119 snippet.active_index += 1;
9120 } else {
9121 self.snippet_stack.push(snippet);
9122 return false;
9123 }
9124 }
9125 }
9126 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9127 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9128 s.select_ranges(current_ranges.iter().cloned())
9129 });
9130
9131 if let Some(choices) = &snippet.choices[snippet.active_index] {
9132 if let Some(selection) = current_ranges.first() {
9133 self.show_snippet_choices(&choices, selection.clone(), cx);
9134 }
9135 }
9136
9137 // If snippet state is not at the last tabstop, push it back on the stack
9138 if snippet.active_index + 1 < snippet.ranges.len() {
9139 self.snippet_stack.push(snippet);
9140 }
9141 return true;
9142 }
9143 }
9144
9145 false
9146 }
9147
9148 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9149 self.transact(window, cx, |this, window, cx| {
9150 this.select_all(&SelectAll, window, cx);
9151 this.insert("", window, cx);
9152 });
9153 }
9154
9155 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9156 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9157 self.transact(window, cx, |this, window, cx| {
9158 this.select_autoclose_pair(window, cx);
9159 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9160 if !this.linked_edit_ranges.is_empty() {
9161 let selections = this.selections.all::<MultiBufferPoint>(cx);
9162 let snapshot = this.buffer.read(cx).snapshot(cx);
9163
9164 for selection in selections.iter() {
9165 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9166 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9167 if selection_start.buffer_id != selection_end.buffer_id {
9168 continue;
9169 }
9170 if let Some(ranges) =
9171 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9172 {
9173 for (buffer, entries) in ranges {
9174 linked_ranges.entry(buffer).or_default().extend(entries);
9175 }
9176 }
9177 }
9178 }
9179
9180 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
9181 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9182 for selection in &mut selections {
9183 if selection.is_empty() {
9184 let old_head = selection.head();
9185 let mut new_head =
9186 movement::left(&display_map, old_head.to_display_point(&display_map))
9187 .to_point(&display_map);
9188 if let Some((buffer, line_buffer_range)) = display_map
9189 .buffer_snapshot
9190 .buffer_line_for_row(MultiBufferRow(old_head.row))
9191 {
9192 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9193 let indent_len = match indent_size.kind {
9194 IndentKind::Space => {
9195 buffer.settings_at(line_buffer_range.start, cx).tab_size
9196 }
9197 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9198 };
9199 if old_head.column <= indent_size.len && old_head.column > 0 {
9200 let indent_len = indent_len.get();
9201 new_head = cmp::min(
9202 new_head,
9203 MultiBufferPoint::new(
9204 old_head.row,
9205 ((old_head.column - 1) / indent_len) * indent_len,
9206 ),
9207 );
9208 }
9209 }
9210
9211 selection.set_head(new_head, SelectionGoal::None);
9212 }
9213 }
9214
9215 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9216 s.select(selections)
9217 });
9218 this.insert("", window, cx);
9219 let empty_str: Arc<str> = Arc::from("");
9220 for (buffer, edits) in linked_ranges {
9221 let snapshot = buffer.read(cx).snapshot();
9222 use text::ToPoint as TP;
9223
9224 let edits = edits
9225 .into_iter()
9226 .map(|range| {
9227 let end_point = TP::to_point(&range.end, &snapshot);
9228 let mut start_point = TP::to_point(&range.start, &snapshot);
9229
9230 if end_point == start_point {
9231 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9232 .saturating_sub(1);
9233 start_point =
9234 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9235 };
9236
9237 (start_point..end_point, empty_str.clone())
9238 })
9239 .sorted_by_key(|(range, _)| range.start)
9240 .collect::<Vec<_>>();
9241 buffer.update(cx, |this, cx| {
9242 this.edit(edits, None, cx);
9243 })
9244 }
9245 this.refresh_inline_completion(true, false, window, cx);
9246 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
9247 });
9248 }
9249
9250 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9251 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9252 self.transact(window, cx, |this, window, cx| {
9253 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9254 s.move_with(|map, selection| {
9255 if selection.is_empty() {
9256 let cursor = movement::right(map, selection.head());
9257 selection.end = cursor;
9258 selection.reversed = true;
9259 selection.goal = SelectionGoal::None;
9260 }
9261 })
9262 });
9263 this.insert("", window, cx);
9264 this.refresh_inline_completion(true, false, window, cx);
9265 });
9266 }
9267
9268 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9269 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9270 if self.move_to_prev_snippet_tabstop(window, cx) {
9271 return;
9272 }
9273 self.outdent(&Outdent, window, cx);
9274 }
9275
9276 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9277 if self.move_to_next_snippet_tabstop(window, cx) {
9278 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9279 return;
9280 }
9281 if self.read_only(cx) {
9282 return;
9283 }
9284 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9285 let mut selections = self.selections.all_adjusted(cx);
9286 let buffer = self.buffer.read(cx);
9287 let snapshot = buffer.snapshot(cx);
9288 let rows_iter = selections.iter().map(|s| s.head().row);
9289 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9290
9291 let has_some_cursor_in_whitespace = selections
9292 .iter()
9293 .filter(|selection| selection.is_empty())
9294 .any(|selection| {
9295 let cursor = selection.head();
9296 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9297 cursor.column < current_indent.len
9298 });
9299
9300 let mut edits = Vec::new();
9301 let mut prev_edited_row = 0;
9302 let mut row_delta = 0;
9303 for selection in &mut selections {
9304 if selection.start.row != prev_edited_row {
9305 row_delta = 0;
9306 }
9307 prev_edited_row = selection.end.row;
9308
9309 // If the selection is non-empty, then increase the indentation of the selected lines.
9310 if !selection.is_empty() {
9311 row_delta =
9312 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9313 continue;
9314 }
9315
9316 let cursor = selection.head();
9317 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9318 if let Some(suggested_indent) =
9319 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
9320 {
9321 // Don't do anything if already at suggested indent
9322 // and there is any other cursor which is not
9323 if has_some_cursor_in_whitespace
9324 && cursor.column == current_indent.len
9325 && current_indent.len == suggested_indent.len
9326 {
9327 continue;
9328 }
9329
9330 // Adjust line and move cursor to suggested indent
9331 // if cursor is not at suggested indent
9332 if cursor.column < suggested_indent.len
9333 && cursor.column <= current_indent.len
9334 && current_indent.len <= suggested_indent.len
9335 {
9336 selection.start = Point::new(cursor.row, suggested_indent.len);
9337 selection.end = selection.start;
9338 if row_delta == 0 {
9339 edits.extend(Buffer::edit_for_indent_size_adjustment(
9340 cursor.row,
9341 current_indent,
9342 suggested_indent,
9343 ));
9344 row_delta = suggested_indent.len - current_indent.len;
9345 }
9346 continue;
9347 }
9348
9349 // If current indent is more than suggested indent
9350 // only move cursor to current indent and skip indent
9351 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
9352 selection.start = Point::new(cursor.row, current_indent.len);
9353 selection.end = selection.start;
9354 continue;
9355 }
9356 }
9357
9358 // Otherwise, insert a hard or soft tab.
9359 let settings = buffer.language_settings_at(cursor, cx);
9360 let tab_size = if settings.hard_tabs {
9361 IndentSize::tab()
9362 } else {
9363 let tab_size = settings.tab_size.get();
9364 let indent_remainder = snapshot
9365 .text_for_range(Point::new(cursor.row, 0)..cursor)
9366 .flat_map(str::chars)
9367 .fold(row_delta % tab_size, |counter: u32, c| {
9368 if c == '\t' {
9369 0
9370 } else {
9371 (counter + 1) % tab_size
9372 }
9373 });
9374
9375 let chars_to_next_tab_stop = tab_size - indent_remainder;
9376 IndentSize::spaces(chars_to_next_tab_stop)
9377 };
9378 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
9379 selection.end = selection.start;
9380 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
9381 row_delta += tab_size.len;
9382 }
9383
9384 self.transact(window, cx, |this, window, cx| {
9385 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9386 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9387 s.select(selections)
9388 });
9389 this.refresh_inline_completion(true, false, window, cx);
9390 });
9391 }
9392
9393 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
9394 if self.read_only(cx) {
9395 return;
9396 }
9397 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9398 let mut selections = self.selections.all::<Point>(cx);
9399 let mut prev_edited_row = 0;
9400 let mut row_delta = 0;
9401 let mut edits = Vec::new();
9402 let buffer = self.buffer.read(cx);
9403 let snapshot = buffer.snapshot(cx);
9404 for selection in &mut selections {
9405 if selection.start.row != prev_edited_row {
9406 row_delta = 0;
9407 }
9408 prev_edited_row = selection.end.row;
9409
9410 row_delta =
9411 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9412 }
9413
9414 self.transact(window, cx, |this, window, cx| {
9415 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9416 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9417 s.select(selections)
9418 });
9419 });
9420 }
9421
9422 fn indent_selection(
9423 buffer: &MultiBuffer,
9424 snapshot: &MultiBufferSnapshot,
9425 selection: &mut Selection<Point>,
9426 edits: &mut Vec<(Range<Point>, String)>,
9427 delta_for_start_row: u32,
9428 cx: &App,
9429 ) -> u32 {
9430 let settings = buffer.language_settings_at(selection.start, cx);
9431 let tab_size = settings.tab_size.get();
9432 let indent_kind = if settings.hard_tabs {
9433 IndentKind::Tab
9434 } else {
9435 IndentKind::Space
9436 };
9437 let mut start_row = selection.start.row;
9438 let mut end_row = selection.end.row + 1;
9439
9440 // If a selection ends at the beginning of a line, don't indent
9441 // that last line.
9442 if selection.end.column == 0 && selection.end.row > selection.start.row {
9443 end_row -= 1;
9444 }
9445
9446 // Avoid re-indenting a row that has already been indented by a
9447 // previous selection, but still update this selection's column
9448 // to reflect that indentation.
9449 if delta_for_start_row > 0 {
9450 start_row += 1;
9451 selection.start.column += delta_for_start_row;
9452 if selection.end.row == selection.start.row {
9453 selection.end.column += delta_for_start_row;
9454 }
9455 }
9456
9457 let mut delta_for_end_row = 0;
9458 let has_multiple_rows = start_row + 1 != end_row;
9459 for row in start_row..end_row {
9460 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
9461 let indent_delta = match (current_indent.kind, indent_kind) {
9462 (IndentKind::Space, IndentKind::Space) => {
9463 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
9464 IndentSize::spaces(columns_to_next_tab_stop)
9465 }
9466 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
9467 (_, IndentKind::Tab) => IndentSize::tab(),
9468 };
9469
9470 let start = if has_multiple_rows || current_indent.len < selection.start.column {
9471 0
9472 } else {
9473 selection.start.column
9474 };
9475 let row_start = Point::new(row, start);
9476 edits.push((
9477 row_start..row_start,
9478 indent_delta.chars().collect::<String>(),
9479 ));
9480
9481 // Update this selection's endpoints to reflect the indentation.
9482 if row == selection.start.row {
9483 selection.start.column += indent_delta.len;
9484 }
9485 if row == selection.end.row {
9486 selection.end.column += indent_delta.len;
9487 delta_for_end_row = indent_delta.len;
9488 }
9489 }
9490
9491 if selection.start.row == selection.end.row {
9492 delta_for_start_row + delta_for_end_row
9493 } else {
9494 delta_for_end_row
9495 }
9496 }
9497
9498 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
9499 if self.read_only(cx) {
9500 return;
9501 }
9502 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9503 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9504 let selections = self.selections.all::<Point>(cx);
9505 let mut deletion_ranges = Vec::new();
9506 let mut last_outdent = None;
9507 {
9508 let buffer = self.buffer.read(cx);
9509 let snapshot = buffer.snapshot(cx);
9510 for selection in &selections {
9511 let settings = buffer.language_settings_at(selection.start, cx);
9512 let tab_size = settings.tab_size.get();
9513 let mut rows = selection.spanned_rows(false, &display_map);
9514
9515 // Avoid re-outdenting a row that has already been outdented by a
9516 // previous selection.
9517 if let Some(last_row) = last_outdent {
9518 if last_row == rows.start {
9519 rows.start = rows.start.next_row();
9520 }
9521 }
9522 let has_multiple_rows = rows.len() > 1;
9523 for row in rows.iter_rows() {
9524 let indent_size = snapshot.indent_size_for_line(row);
9525 if indent_size.len > 0 {
9526 let deletion_len = match indent_size.kind {
9527 IndentKind::Space => {
9528 let columns_to_prev_tab_stop = indent_size.len % tab_size;
9529 if columns_to_prev_tab_stop == 0 {
9530 tab_size
9531 } else {
9532 columns_to_prev_tab_stop
9533 }
9534 }
9535 IndentKind::Tab => 1,
9536 };
9537 let start = if has_multiple_rows
9538 || deletion_len > selection.start.column
9539 || indent_size.len < selection.start.column
9540 {
9541 0
9542 } else {
9543 selection.start.column - deletion_len
9544 };
9545 deletion_ranges.push(
9546 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
9547 );
9548 last_outdent = Some(row);
9549 }
9550 }
9551 }
9552 }
9553
9554 self.transact(window, cx, |this, window, cx| {
9555 this.buffer.update(cx, |buffer, cx| {
9556 let empty_str: Arc<str> = Arc::default();
9557 buffer.edit(
9558 deletion_ranges
9559 .into_iter()
9560 .map(|range| (range, empty_str.clone())),
9561 None,
9562 cx,
9563 );
9564 });
9565 let selections = this.selections.all::<usize>(cx);
9566 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9567 s.select(selections)
9568 });
9569 });
9570 }
9571
9572 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
9573 if self.read_only(cx) {
9574 return;
9575 }
9576 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9577 let selections = self
9578 .selections
9579 .all::<usize>(cx)
9580 .into_iter()
9581 .map(|s| s.range());
9582
9583 self.transact(window, cx, |this, window, cx| {
9584 this.buffer.update(cx, |buffer, cx| {
9585 buffer.autoindent_ranges(selections, cx);
9586 });
9587 let selections = this.selections.all::<usize>(cx);
9588 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9589 s.select(selections)
9590 });
9591 });
9592 }
9593
9594 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
9595 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9596 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9597 let selections = self.selections.all::<Point>(cx);
9598
9599 let mut new_cursors = Vec::new();
9600 let mut edit_ranges = Vec::new();
9601 let mut selections = selections.iter().peekable();
9602 while let Some(selection) = selections.next() {
9603 let mut rows = selection.spanned_rows(false, &display_map);
9604 let goal_display_column = selection.head().to_display_point(&display_map).column();
9605
9606 // Accumulate contiguous regions of rows that we want to delete.
9607 while let Some(next_selection) = selections.peek() {
9608 let next_rows = next_selection.spanned_rows(false, &display_map);
9609 if next_rows.start <= rows.end {
9610 rows.end = next_rows.end;
9611 selections.next().unwrap();
9612 } else {
9613 break;
9614 }
9615 }
9616
9617 let buffer = &display_map.buffer_snapshot;
9618 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
9619 let edit_end;
9620 let cursor_buffer_row;
9621 if buffer.max_point().row >= rows.end.0 {
9622 // If there's a line after the range, delete the \n from the end of the row range
9623 // and position the cursor on the next line.
9624 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
9625 cursor_buffer_row = rows.end;
9626 } else {
9627 // If there isn't a line after the range, delete the \n from the line before the
9628 // start of the row range and position the cursor there.
9629 edit_start = edit_start.saturating_sub(1);
9630 edit_end = buffer.len();
9631 cursor_buffer_row = rows.start.previous_row();
9632 }
9633
9634 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
9635 *cursor.column_mut() =
9636 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
9637
9638 new_cursors.push((
9639 selection.id,
9640 buffer.anchor_after(cursor.to_point(&display_map)),
9641 ));
9642 edit_ranges.push(edit_start..edit_end);
9643 }
9644
9645 self.transact(window, cx, |this, window, cx| {
9646 let buffer = this.buffer.update(cx, |buffer, cx| {
9647 let empty_str: Arc<str> = Arc::default();
9648 buffer.edit(
9649 edit_ranges
9650 .into_iter()
9651 .map(|range| (range, empty_str.clone())),
9652 None,
9653 cx,
9654 );
9655 buffer.snapshot(cx)
9656 });
9657 let new_selections = new_cursors
9658 .into_iter()
9659 .map(|(id, cursor)| {
9660 let cursor = cursor.to_point(&buffer);
9661 Selection {
9662 id,
9663 start: cursor,
9664 end: cursor,
9665 reversed: false,
9666 goal: SelectionGoal::None,
9667 }
9668 })
9669 .collect();
9670
9671 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9672 s.select(new_selections);
9673 });
9674 });
9675 }
9676
9677 pub fn join_lines_impl(
9678 &mut self,
9679 insert_whitespace: bool,
9680 window: &mut Window,
9681 cx: &mut Context<Self>,
9682 ) {
9683 if self.read_only(cx) {
9684 return;
9685 }
9686 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
9687 for selection in self.selections.all::<Point>(cx) {
9688 let start = MultiBufferRow(selection.start.row);
9689 // Treat single line selections as if they include the next line. Otherwise this action
9690 // would do nothing for single line selections individual cursors.
9691 let end = if selection.start.row == selection.end.row {
9692 MultiBufferRow(selection.start.row + 1)
9693 } else {
9694 MultiBufferRow(selection.end.row)
9695 };
9696
9697 if let Some(last_row_range) = row_ranges.last_mut() {
9698 if start <= last_row_range.end {
9699 last_row_range.end = end;
9700 continue;
9701 }
9702 }
9703 row_ranges.push(start..end);
9704 }
9705
9706 let snapshot = self.buffer.read(cx).snapshot(cx);
9707 let mut cursor_positions = Vec::new();
9708 for row_range in &row_ranges {
9709 let anchor = snapshot.anchor_before(Point::new(
9710 row_range.end.previous_row().0,
9711 snapshot.line_len(row_range.end.previous_row()),
9712 ));
9713 cursor_positions.push(anchor..anchor);
9714 }
9715
9716 self.transact(window, cx, |this, window, cx| {
9717 for row_range in row_ranges.into_iter().rev() {
9718 for row in row_range.iter_rows().rev() {
9719 let end_of_line = Point::new(row.0, snapshot.line_len(row));
9720 let next_line_row = row.next_row();
9721 let indent = snapshot.indent_size_for_line(next_line_row);
9722 let start_of_next_line = Point::new(next_line_row.0, indent.len);
9723
9724 let replace =
9725 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
9726 " "
9727 } else {
9728 ""
9729 };
9730
9731 this.buffer.update(cx, |buffer, cx| {
9732 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
9733 });
9734 }
9735 }
9736
9737 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9738 s.select_anchor_ranges(cursor_positions)
9739 });
9740 });
9741 }
9742
9743 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
9744 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9745 self.join_lines_impl(true, window, cx);
9746 }
9747
9748 pub fn sort_lines_case_sensitive(
9749 &mut self,
9750 _: &SortLinesCaseSensitive,
9751 window: &mut Window,
9752 cx: &mut Context<Self>,
9753 ) {
9754 self.manipulate_lines(window, cx, |lines| lines.sort())
9755 }
9756
9757 pub fn sort_lines_case_insensitive(
9758 &mut self,
9759 _: &SortLinesCaseInsensitive,
9760 window: &mut Window,
9761 cx: &mut Context<Self>,
9762 ) {
9763 self.manipulate_lines(window, cx, |lines| {
9764 lines.sort_by_key(|line| line.to_lowercase())
9765 })
9766 }
9767
9768 pub fn unique_lines_case_insensitive(
9769 &mut self,
9770 _: &UniqueLinesCaseInsensitive,
9771 window: &mut Window,
9772 cx: &mut Context<Self>,
9773 ) {
9774 self.manipulate_lines(window, cx, |lines| {
9775 let mut seen = HashSet::default();
9776 lines.retain(|line| seen.insert(line.to_lowercase()));
9777 })
9778 }
9779
9780 pub fn unique_lines_case_sensitive(
9781 &mut self,
9782 _: &UniqueLinesCaseSensitive,
9783 window: &mut Window,
9784 cx: &mut Context<Self>,
9785 ) {
9786 self.manipulate_lines(window, cx, |lines| {
9787 let mut seen = HashSet::default();
9788 lines.retain(|line| seen.insert(*line));
9789 })
9790 }
9791
9792 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
9793 let Some(project) = self.project.clone() else {
9794 return;
9795 };
9796 self.reload(project, window, cx)
9797 .detach_and_notify_err(window, cx);
9798 }
9799
9800 pub fn restore_file(
9801 &mut self,
9802 _: &::git::RestoreFile,
9803 window: &mut Window,
9804 cx: &mut Context<Self>,
9805 ) {
9806 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9807 let mut buffer_ids = HashSet::default();
9808 let snapshot = self.buffer().read(cx).snapshot(cx);
9809 for selection in self.selections.all::<usize>(cx) {
9810 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
9811 }
9812
9813 let buffer = self.buffer().read(cx);
9814 let ranges = buffer_ids
9815 .into_iter()
9816 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
9817 .collect::<Vec<_>>();
9818
9819 self.restore_hunks_in_ranges(ranges, window, cx);
9820 }
9821
9822 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
9823 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9824 let selections = self
9825 .selections
9826 .all(cx)
9827 .into_iter()
9828 .map(|s| s.range())
9829 .collect();
9830 self.restore_hunks_in_ranges(selections, window, cx);
9831 }
9832
9833 pub fn restore_hunks_in_ranges(
9834 &mut self,
9835 ranges: Vec<Range<Point>>,
9836 window: &mut Window,
9837 cx: &mut Context<Editor>,
9838 ) {
9839 let mut revert_changes = HashMap::default();
9840 let chunk_by = self
9841 .snapshot(window, cx)
9842 .hunks_for_ranges(ranges)
9843 .into_iter()
9844 .chunk_by(|hunk| hunk.buffer_id);
9845 for (buffer_id, hunks) in &chunk_by {
9846 let hunks = hunks.collect::<Vec<_>>();
9847 for hunk in &hunks {
9848 self.prepare_restore_change(&mut revert_changes, hunk, cx);
9849 }
9850 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
9851 }
9852 drop(chunk_by);
9853 if !revert_changes.is_empty() {
9854 self.transact(window, cx, |editor, window, cx| {
9855 editor.restore(revert_changes, window, cx);
9856 });
9857 }
9858 }
9859
9860 pub fn open_active_item_in_terminal(
9861 &mut self,
9862 _: &OpenInTerminal,
9863 window: &mut Window,
9864 cx: &mut Context<Self>,
9865 ) {
9866 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
9867 let project_path = buffer.read(cx).project_path(cx)?;
9868 let project = self.project.as_ref()?.read(cx);
9869 let entry = project.entry_for_path(&project_path, cx)?;
9870 let parent = match &entry.canonical_path {
9871 Some(canonical_path) => canonical_path.to_path_buf(),
9872 None => project.absolute_path(&project_path, cx)?,
9873 }
9874 .parent()?
9875 .to_path_buf();
9876 Some(parent)
9877 }) {
9878 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
9879 }
9880 }
9881
9882 fn set_breakpoint_context_menu(
9883 &mut self,
9884 display_row: DisplayRow,
9885 position: Option<Anchor>,
9886 clicked_point: gpui::Point<Pixels>,
9887 window: &mut Window,
9888 cx: &mut Context<Self>,
9889 ) {
9890 if !cx.has_flag::<DebuggerFeatureFlag>() {
9891 return;
9892 }
9893 let source = self
9894 .buffer
9895 .read(cx)
9896 .snapshot(cx)
9897 .anchor_before(Point::new(display_row.0, 0u32));
9898
9899 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
9900
9901 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
9902 self,
9903 source,
9904 clicked_point,
9905 context_menu,
9906 window,
9907 cx,
9908 );
9909 }
9910
9911 fn add_edit_breakpoint_block(
9912 &mut self,
9913 anchor: Anchor,
9914 breakpoint: &Breakpoint,
9915 edit_action: BreakpointPromptEditAction,
9916 window: &mut Window,
9917 cx: &mut Context<Self>,
9918 ) {
9919 let weak_editor = cx.weak_entity();
9920 let bp_prompt = cx.new(|cx| {
9921 BreakpointPromptEditor::new(
9922 weak_editor,
9923 anchor,
9924 breakpoint.clone(),
9925 edit_action,
9926 window,
9927 cx,
9928 )
9929 });
9930
9931 let height = bp_prompt.update(cx, |this, cx| {
9932 this.prompt
9933 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
9934 });
9935 let cloned_prompt = bp_prompt.clone();
9936 let blocks = vec![BlockProperties {
9937 style: BlockStyle::Sticky,
9938 placement: BlockPlacement::Above(anchor),
9939 height: Some(height),
9940 render: Arc::new(move |cx| {
9941 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
9942 cloned_prompt.clone().into_any_element()
9943 }),
9944 priority: 0,
9945 render_in_minimap: true,
9946 }];
9947
9948 let focus_handle = bp_prompt.focus_handle(cx);
9949 window.focus(&focus_handle);
9950
9951 let block_ids = self.insert_blocks(blocks, None, cx);
9952 bp_prompt.update(cx, |prompt, _| {
9953 prompt.add_block_ids(block_ids);
9954 });
9955 }
9956
9957 pub(crate) fn breakpoint_at_row(
9958 &self,
9959 row: u32,
9960 window: &mut Window,
9961 cx: &mut Context<Self>,
9962 ) -> Option<(Anchor, Breakpoint)> {
9963 let snapshot = self.snapshot(window, cx);
9964 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
9965
9966 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
9967 }
9968
9969 pub(crate) fn breakpoint_at_anchor(
9970 &self,
9971 breakpoint_position: Anchor,
9972 snapshot: &EditorSnapshot,
9973 cx: &mut Context<Self>,
9974 ) -> Option<(Anchor, Breakpoint)> {
9975 let project = self.project.clone()?;
9976
9977 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
9978 snapshot
9979 .buffer_snapshot
9980 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
9981 })?;
9982
9983 let enclosing_excerpt = breakpoint_position.excerpt_id;
9984 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
9985 let buffer_snapshot = buffer.read(cx).snapshot();
9986
9987 let row = buffer_snapshot
9988 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
9989 .row;
9990
9991 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
9992 let anchor_end = snapshot
9993 .buffer_snapshot
9994 .anchor_after(Point::new(row, line_len));
9995
9996 let bp = self
9997 .breakpoint_store
9998 .as_ref()?
9999 .read_with(cx, |breakpoint_store, cx| {
10000 breakpoint_store
10001 .breakpoints(
10002 &buffer,
10003 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10004 &buffer_snapshot,
10005 cx,
10006 )
10007 .next()
10008 .and_then(|(bp, _)| {
10009 let breakpoint_row = buffer_snapshot
10010 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10011 .row;
10012
10013 if breakpoint_row == row {
10014 snapshot
10015 .buffer_snapshot
10016 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10017 .map(|position| (position, bp.bp.clone()))
10018 } else {
10019 None
10020 }
10021 })
10022 });
10023 bp
10024 }
10025
10026 pub fn edit_log_breakpoint(
10027 &mut self,
10028 _: &EditLogBreakpoint,
10029 window: &mut Window,
10030 cx: &mut Context<Self>,
10031 ) {
10032 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10033 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10034 message: None,
10035 state: BreakpointState::Enabled,
10036 condition: None,
10037 hit_condition: None,
10038 });
10039
10040 self.add_edit_breakpoint_block(
10041 anchor,
10042 &breakpoint,
10043 BreakpointPromptEditAction::Log,
10044 window,
10045 cx,
10046 );
10047 }
10048 }
10049
10050 fn breakpoints_at_cursors(
10051 &self,
10052 window: &mut Window,
10053 cx: &mut Context<Self>,
10054 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10055 let snapshot = self.snapshot(window, cx);
10056 let cursors = self
10057 .selections
10058 .disjoint_anchors()
10059 .into_iter()
10060 .map(|selection| {
10061 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
10062
10063 let breakpoint_position = self
10064 .breakpoint_at_row(cursor_position.row, window, cx)
10065 .map(|bp| bp.0)
10066 .unwrap_or_else(|| {
10067 snapshot
10068 .display_snapshot
10069 .buffer_snapshot
10070 .anchor_after(Point::new(cursor_position.row, 0))
10071 });
10072
10073 let breakpoint = self
10074 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10075 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10076
10077 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10078 })
10079 // 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.
10080 .collect::<HashMap<Anchor, _>>();
10081
10082 cursors.into_iter().collect()
10083 }
10084
10085 pub fn enable_breakpoint(
10086 &mut self,
10087 _: &crate::actions::EnableBreakpoint,
10088 window: &mut Window,
10089 cx: &mut Context<Self>,
10090 ) {
10091 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10092 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10093 continue;
10094 };
10095 self.edit_breakpoint_at_anchor(
10096 anchor,
10097 breakpoint,
10098 BreakpointEditAction::InvertState,
10099 cx,
10100 );
10101 }
10102 }
10103
10104 pub fn disable_breakpoint(
10105 &mut self,
10106 _: &crate::actions::DisableBreakpoint,
10107 window: &mut Window,
10108 cx: &mut Context<Self>,
10109 ) {
10110 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10111 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10112 continue;
10113 };
10114 self.edit_breakpoint_at_anchor(
10115 anchor,
10116 breakpoint,
10117 BreakpointEditAction::InvertState,
10118 cx,
10119 );
10120 }
10121 }
10122
10123 pub fn toggle_breakpoint(
10124 &mut self,
10125 _: &crate::actions::ToggleBreakpoint,
10126 window: &mut Window,
10127 cx: &mut Context<Self>,
10128 ) {
10129 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10130 if let Some(breakpoint) = breakpoint {
10131 self.edit_breakpoint_at_anchor(
10132 anchor,
10133 breakpoint,
10134 BreakpointEditAction::Toggle,
10135 cx,
10136 );
10137 } else {
10138 self.edit_breakpoint_at_anchor(
10139 anchor,
10140 Breakpoint::new_standard(),
10141 BreakpointEditAction::Toggle,
10142 cx,
10143 );
10144 }
10145 }
10146 }
10147
10148 pub fn edit_breakpoint_at_anchor(
10149 &mut self,
10150 breakpoint_position: Anchor,
10151 breakpoint: Breakpoint,
10152 edit_action: BreakpointEditAction,
10153 cx: &mut Context<Self>,
10154 ) {
10155 let Some(breakpoint_store) = &self.breakpoint_store else {
10156 return;
10157 };
10158
10159 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
10160 if breakpoint_position == Anchor::min() {
10161 self.buffer()
10162 .read(cx)
10163 .excerpt_buffer_ids()
10164 .into_iter()
10165 .next()
10166 } else {
10167 None
10168 }
10169 }) else {
10170 return;
10171 };
10172
10173 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
10174 return;
10175 };
10176
10177 breakpoint_store.update(cx, |breakpoint_store, cx| {
10178 breakpoint_store.toggle_breakpoint(
10179 buffer,
10180 BreakpointWithPosition {
10181 position: breakpoint_position.text_anchor,
10182 bp: breakpoint,
10183 },
10184 edit_action,
10185 cx,
10186 );
10187 });
10188
10189 cx.notify();
10190 }
10191
10192 #[cfg(any(test, feature = "test-support"))]
10193 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10194 self.breakpoint_store.clone()
10195 }
10196
10197 pub fn prepare_restore_change(
10198 &self,
10199 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10200 hunk: &MultiBufferDiffHunk,
10201 cx: &mut App,
10202 ) -> Option<()> {
10203 if hunk.is_created_file() {
10204 return None;
10205 }
10206 let buffer = self.buffer.read(cx);
10207 let diff = buffer.diff_for(hunk.buffer_id)?;
10208 let buffer = buffer.buffer(hunk.buffer_id)?;
10209 let buffer = buffer.read(cx);
10210 let original_text = diff
10211 .read(cx)
10212 .base_text()
10213 .as_rope()
10214 .slice(hunk.diff_base_byte_range.clone());
10215 let buffer_snapshot = buffer.snapshot();
10216 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
10217 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
10218 probe
10219 .0
10220 .start
10221 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
10222 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
10223 }) {
10224 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
10225 Some(())
10226 } else {
10227 None
10228 }
10229 }
10230
10231 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
10232 self.manipulate_lines(window, cx, |lines| lines.reverse())
10233 }
10234
10235 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
10236 self.manipulate_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
10237 }
10238
10239 fn manipulate_lines<Fn>(
10240 &mut self,
10241 window: &mut Window,
10242 cx: &mut Context<Self>,
10243 mut callback: Fn,
10244 ) where
10245 Fn: FnMut(&mut Vec<&str>),
10246 {
10247 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10248
10249 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10250 let buffer = self.buffer.read(cx).snapshot(cx);
10251
10252 let mut edits = Vec::new();
10253
10254 let selections = self.selections.all::<Point>(cx);
10255 let mut selections = selections.iter().peekable();
10256 let mut contiguous_row_selections = Vec::new();
10257 let mut new_selections = Vec::new();
10258 let mut added_lines = 0;
10259 let mut removed_lines = 0;
10260
10261 while let Some(selection) = selections.next() {
10262 let (start_row, end_row) = consume_contiguous_rows(
10263 &mut contiguous_row_selections,
10264 selection,
10265 &display_map,
10266 &mut selections,
10267 );
10268
10269 let start_point = Point::new(start_row.0, 0);
10270 let end_point = Point::new(
10271 end_row.previous_row().0,
10272 buffer.line_len(end_row.previous_row()),
10273 );
10274 let text = buffer
10275 .text_for_range(start_point..end_point)
10276 .collect::<String>();
10277
10278 let mut lines = text.split('\n').collect_vec();
10279
10280 let lines_before = lines.len();
10281 callback(&mut lines);
10282 let lines_after = lines.len();
10283
10284 edits.push((start_point..end_point, lines.join("\n")));
10285
10286 // Selections must change based on added and removed line count
10287 let start_row =
10288 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
10289 let end_row = MultiBufferRow(start_row.0 + lines_after.saturating_sub(1) as u32);
10290 new_selections.push(Selection {
10291 id: selection.id,
10292 start: start_row,
10293 end: end_row,
10294 goal: SelectionGoal::None,
10295 reversed: selection.reversed,
10296 });
10297
10298 if lines_after > lines_before {
10299 added_lines += lines_after - lines_before;
10300 } else if lines_before > lines_after {
10301 removed_lines += lines_before - lines_after;
10302 }
10303 }
10304
10305 self.transact(window, cx, |this, window, cx| {
10306 let buffer = this.buffer.update(cx, |buffer, cx| {
10307 buffer.edit(edits, None, cx);
10308 buffer.snapshot(cx)
10309 });
10310
10311 // Recalculate offsets on newly edited buffer
10312 let new_selections = new_selections
10313 .iter()
10314 .map(|s| {
10315 let start_point = Point::new(s.start.0, 0);
10316 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
10317 Selection {
10318 id: s.id,
10319 start: buffer.point_to_offset(start_point),
10320 end: buffer.point_to_offset(end_point),
10321 goal: s.goal,
10322 reversed: s.reversed,
10323 }
10324 })
10325 .collect();
10326
10327 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10328 s.select(new_selections);
10329 });
10330
10331 this.request_autoscroll(Autoscroll::fit(), cx);
10332 });
10333 }
10334
10335 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
10336 self.manipulate_text(window, cx, |text| {
10337 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
10338 if has_upper_case_characters {
10339 text.to_lowercase()
10340 } else {
10341 text.to_uppercase()
10342 }
10343 })
10344 }
10345
10346 pub fn convert_to_upper_case(
10347 &mut self,
10348 _: &ConvertToUpperCase,
10349 window: &mut Window,
10350 cx: &mut Context<Self>,
10351 ) {
10352 self.manipulate_text(window, cx, |text| text.to_uppercase())
10353 }
10354
10355 pub fn convert_to_lower_case(
10356 &mut self,
10357 _: &ConvertToLowerCase,
10358 window: &mut Window,
10359 cx: &mut Context<Self>,
10360 ) {
10361 self.manipulate_text(window, cx, |text| text.to_lowercase())
10362 }
10363
10364 pub fn convert_to_title_case(
10365 &mut self,
10366 _: &ConvertToTitleCase,
10367 window: &mut Window,
10368 cx: &mut Context<Self>,
10369 ) {
10370 self.manipulate_text(window, cx, |text| {
10371 text.split('\n')
10372 .map(|line| line.to_case(Case::Title))
10373 .join("\n")
10374 })
10375 }
10376
10377 pub fn convert_to_snake_case(
10378 &mut self,
10379 _: &ConvertToSnakeCase,
10380 window: &mut Window,
10381 cx: &mut Context<Self>,
10382 ) {
10383 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
10384 }
10385
10386 pub fn convert_to_kebab_case(
10387 &mut self,
10388 _: &ConvertToKebabCase,
10389 window: &mut Window,
10390 cx: &mut Context<Self>,
10391 ) {
10392 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
10393 }
10394
10395 pub fn convert_to_upper_camel_case(
10396 &mut self,
10397 _: &ConvertToUpperCamelCase,
10398 window: &mut Window,
10399 cx: &mut Context<Self>,
10400 ) {
10401 self.manipulate_text(window, cx, |text| {
10402 text.split('\n')
10403 .map(|line| line.to_case(Case::UpperCamel))
10404 .join("\n")
10405 })
10406 }
10407
10408 pub fn convert_to_lower_camel_case(
10409 &mut self,
10410 _: &ConvertToLowerCamelCase,
10411 window: &mut Window,
10412 cx: &mut Context<Self>,
10413 ) {
10414 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
10415 }
10416
10417 pub fn convert_to_opposite_case(
10418 &mut self,
10419 _: &ConvertToOppositeCase,
10420 window: &mut Window,
10421 cx: &mut Context<Self>,
10422 ) {
10423 self.manipulate_text(window, cx, |text| {
10424 text.chars()
10425 .fold(String::with_capacity(text.len()), |mut t, c| {
10426 if c.is_uppercase() {
10427 t.extend(c.to_lowercase());
10428 } else {
10429 t.extend(c.to_uppercase());
10430 }
10431 t
10432 })
10433 })
10434 }
10435
10436 pub fn convert_to_rot13(
10437 &mut self,
10438 _: &ConvertToRot13,
10439 window: &mut Window,
10440 cx: &mut Context<Self>,
10441 ) {
10442 self.manipulate_text(window, cx, |text| {
10443 text.chars()
10444 .map(|c| match c {
10445 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
10446 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
10447 _ => c,
10448 })
10449 .collect()
10450 })
10451 }
10452
10453 pub fn convert_to_rot47(
10454 &mut self,
10455 _: &ConvertToRot47,
10456 window: &mut Window,
10457 cx: &mut Context<Self>,
10458 ) {
10459 self.manipulate_text(window, cx, |text| {
10460 text.chars()
10461 .map(|c| {
10462 let code_point = c as u32;
10463 if code_point >= 33 && code_point <= 126 {
10464 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
10465 }
10466 c
10467 })
10468 .collect()
10469 })
10470 }
10471
10472 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
10473 where
10474 Fn: FnMut(&str) -> String,
10475 {
10476 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10477 let buffer = self.buffer.read(cx).snapshot(cx);
10478
10479 let mut new_selections = Vec::new();
10480 let mut edits = Vec::new();
10481 let mut selection_adjustment = 0i32;
10482
10483 for selection in self.selections.all::<usize>(cx) {
10484 let selection_is_empty = selection.is_empty();
10485
10486 let (start, end) = if selection_is_empty {
10487 let word_range = movement::surrounding_word(
10488 &display_map,
10489 selection.start.to_display_point(&display_map),
10490 );
10491 let start = word_range.start.to_offset(&display_map, Bias::Left);
10492 let end = word_range.end.to_offset(&display_map, Bias::Left);
10493 (start, end)
10494 } else {
10495 (selection.start, selection.end)
10496 };
10497
10498 let text = buffer.text_for_range(start..end).collect::<String>();
10499 let old_length = text.len() as i32;
10500 let text = callback(&text);
10501
10502 new_selections.push(Selection {
10503 start: (start as i32 - selection_adjustment) as usize,
10504 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
10505 goal: SelectionGoal::None,
10506 ..selection
10507 });
10508
10509 selection_adjustment += old_length - text.len() as i32;
10510
10511 edits.push((start..end, text));
10512 }
10513
10514 self.transact(window, cx, |this, window, cx| {
10515 this.buffer.update(cx, |buffer, cx| {
10516 buffer.edit(edits, None, cx);
10517 });
10518
10519 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10520 s.select(new_selections);
10521 });
10522
10523 this.request_autoscroll(Autoscroll::fit(), cx);
10524 });
10525 }
10526
10527 pub fn duplicate(
10528 &mut self,
10529 upwards: bool,
10530 whole_lines: bool,
10531 window: &mut Window,
10532 cx: &mut Context<Self>,
10533 ) {
10534 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10535
10536 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10537 let buffer = &display_map.buffer_snapshot;
10538 let selections = self.selections.all::<Point>(cx);
10539
10540 let mut edits = Vec::new();
10541 let mut selections_iter = selections.iter().peekable();
10542 while let Some(selection) = selections_iter.next() {
10543 let mut rows = selection.spanned_rows(false, &display_map);
10544 // duplicate line-wise
10545 if whole_lines || selection.start == selection.end {
10546 // Avoid duplicating the same lines twice.
10547 while let Some(next_selection) = selections_iter.peek() {
10548 let next_rows = next_selection.spanned_rows(false, &display_map);
10549 if next_rows.start < rows.end {
10550 rows.end = next_rows.end;
10551 selections_iter.next().unwrap();
10552 } else {
10553 break;
10554 }
10555 }
10556
10557 // Copy the text from the selected row region and splice it either at the start
10558 // or end of the region.
10559 let start = Point::new(rows.start.0, 0);
10560 let end = Point::new(
10561 rows.end.previous_row().0,
10562 buffer.line_len(rows.end.previous_row()),
10563 );
10564 let text = buffer
10565 .text_for_range(start..end)
10566 .chain(Some("\n"))
10567 .collect::<String>();
10568 let insert_location = if upwards {
10569 Point::new(rows.end.0, 0)
10570 } else {
10571 start
10572 };
10573 edits.push((insert_location..insert_location, text));
10574 } else {
10575 // duplicate character-wise
10576 let start = selection.start;
10577 let end = selection.end;
10578 let text = buffer.text_for_range(start..end).collect::<String>();
10579 edits.push((selection.end..selection.end, text));
10580 }
10581 }
10582
10583 self.transact(window, cx, |this, _, cx| {
10584 this.buffer.update(cx, |buffer, cx| {
10585 buffer.edit(edits, None, cx);
10586 });
10587
10588 this.request_autoscroll(Autoscroll::fit(), cx);
10589 });
10590 }
10591
10592 pub fn duplicate_line_up(
10593 &mut self,
10594 _: &DuplicateLineUp,
10595 window: &mut Window,
10596 cx: &mut Context<Self>,
10597 ) {
10598 self.duplicate(true, true, window, cx);
10599 }
10600
10601 pub fn duplicate_line_down(
10602 &mut self,
10603 _: &DuplicateLineDown,
10604 window: &mut Window,
10605 cx: &mut Context<Self>,
10606 ) {
10607 self.duplicate(false, true, window, cx);
10608 }
10609
10610 pub fn duplicate_selection(
10611 &mut self,
10612 _: &DuplicateSelection,
10613 window: &mut Window,
10614 cx: &mut Context<Self>,
10615 ) {
10616 self.duplicate(false, false, window, cx);
10617 }
10618
10619 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
10620 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10621
10622 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10623 let buffer = self.buffer.read(cx).snapshot(cx);
10624
10625 let mut edits = Vec::new();
10626 let mut unfold_ranges = Vec::new();
10627 let mut refold_creases = Vec::new();
10628
10629 let selections = self.selections.all::<Point>(cx);
10630 let mut selections = selections.iter().peekable();
10631 let mut contiguous_row_selections = Vec::new();
10632 let mut new_selections = Vec::new();
10633
10634 while let Some(selection) = selections.next() {
10635 // Find all the selections that span a contiguous row range
10636 let (start_row, end_row) = consume_contiguous_rows(
10637 &mut contiguous_row_selections,
10638 selection,
10639 &display_map,
10640 &mut selections,
10641 );
10642
10643 // Move the text spanned by the row range to be before the line preceding the row range
10644 if start_row.0 > 0 {
10645 let range_to_move = Point::new(
10646 start_row.previous_row().0,
10647 buffer.line_len(start_row.previous_row()),
10648 )
10649 ..Point::new(
10650 end_row.previous_row().0,
10651 buffer.line_len(end_row.previous_row()),
10652 );
10653 let insertion_point = display_map
10654 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
10655 .0;
10656
10657 // Don't move lines across excerpts
10658 if buffer
10659 .excerpt_containing(insertion_point..range_to_move.end)
10660 .is_some()
10661 {
10662 let text = buffer
10663 .text_for_range(range_to_move.clone())
10664 .flat_map(|s| s.chars())
10665 .skip(1)
10666 .chain(['\n'])
10667 .collect::<String>();
10668
10669 edits.push((
10670 buffer.anchor_after(range_to_move.start)
10671 ..buffer.anchor_before(range_to_move.end),
10672 String::new(),
10673 ));
10674 let insertion_anchor = buffer.anchor_after(insertion_point);
10675 edits.push((insertion_anchor..insertion_anchor, text));
10676
10677 let row_delta = range_to_move.start.row - insertion_point.row + 1;
10678
10679 // Move selections up
10680 new_selections.extend(contiguous_row_selections.drain(..).map(
10681 |mut selection| {
10682 selection.start.row -= row_delta;
10683 selection.end.row -= row_delta;
10684 selection
10685 },
10686 ));
10687
10688 // Move folds up
10689 unfold_ranges.push(range_to_move.clone());
10690 for fold in display_map.folds_in_range(
10691 buffer.anchor_before(range_to_move.start)
10692 ..buffer.anchor_after(range_to_move.end),
10693 ) {
10694 let mut start = fold.range.start.to_point(&buffer);
10695 let mut end = fold.range.end.to_point(&buffer);
10696 start.row -= row_delta;
10697 end.row -= row_delta;
10698 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10699 }
10700 }
10701 }
10702
10703 // If we didn't move line(s), preserve the existing selections
10704 new_selections.append(&mut contiguous_row_selections);
10705 }
10706
10707 self.transact(window, cx, |this, window, cx| {
10708 this.unfold_ranges(&unfold_ranges, true, true, cx);
10709 this.buffer.update(cx, |buffer, cx| {
10710 for (range, text) in edits {
10711 buffer.edit([(range, text)], None, cx);
10712 }
10713 });
10714 this.fold_creases(refold_creases, true, window, cx);
10715 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10716 s.select(new_selections);
10717 })
10718 });
10719 }
10720
10721 pub fn move_line_down(
10722 &mut self,
10723 _: &MoveLineDown,
10724 window: &mut Window,
10725 cx: &mut Context<Self>,
10726 ) {
10727 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10728
10729 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10730 let buffer = self.buffer.read(cx).snapshot(cx);
10731
10732 let mut edits = Vec::new();
10733 let mut unfold_ranges = Vec::new();
10734 let mut refold_creases = Vec::new();
10735
10736 let selections = self.selections.all::<Point>(cx);
10737 let mut selections = selections.iter().peekable();
10738 let mut contiguous_row_selections = Vec::new();
10739 let mut new_selections = Vec::new();
10740
10741 while let Some(selection) = selections.next() {
10742 // Find all the selections that span a contiguous row range
10743 let (start_row, end_row) = consume_contiguous_rows(
10744 &mut contiguous_row_selections,
10745 selection,
10746 &display_map,
10747 &mut selections,
10748 );
10749
10750 // Move the text spanned by the row range to be after the last line of the row range
10751 if end_row.0 <= buffer.max_point().row {
10752 let range_to_move =
10753 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
10754 let insertion_point = display_map
10755 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
10756 .0;
10757
10758 // Don't move lines across excerpt boundaries
10759 if buffer
10760 .excerpt_containing(range_to_move.start..insertion_point)
10761 .is_some()
10762 {
10763 let mut text = String::from("\n");
10764 text.extend(buffer.text_for_range(range_to_move.clone()));
10765 text.pop(); // Drop trailing newline
10766 edits.push((
10767 buffer.anchor_after(range_to_move.start)
10768 ..buffer.anchor_before(range_to_move.end),
10769 String::new(),
10770 ));
10771 let insertion_anchor = buffer.anchor_after(insertion_point);
10772 edits.push((insertion_anchor..insertion_anchor, text));
10773
10774 let row_delta = insertion_point.row - range_to_move.end.row + 1;
10775
10776 // Move selections down
10777 new_selections.extend(contiguous_row_selections.drain(..).map(
10778 |mut selection| {
10779 selection.start.row += row_delta;
10780 selection.end.row += row_delta;
10781 selection
10782 },
10783 ));
10784
10785 // Move folds down
10786 unfold_ranges.push(range_to_move.clone());
10787 for fold in display_map.folds_in_range(
10788 buffer.anchor_before(range_to_move.start)
10789 ..buffer.anchor_after(range_to_move.end),
10790 ) {
10791 let mut start = fold.range.start.to_point(&buffer);
10792 let mut end = fold.range.end.to_point(&buffer);
10793 start.row += row_delta;
10794 end.row += row_delta;
10795 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10796 }
10797 }
10798 }
10799
10800 // If we didn't move line(s), preserve the existing selections
10801 new_selections.append(&mut contiguous_row_selections);
10802 }
10803
10804 self.transact(window, cx, |this, window, cx| {
10805 this.unfold_ranges(&unfold_ranges, true, true, cx);
10806 this.buffer.update(cx, |buffer, cx| {
10807 for (range, text) in edits {
10808 buffer.edit([(range, text)], None, cx);
10809 }
10810 });
10811 this.fold_creases(refold_creases, true, window, cx);
10812 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10813 s.select(new_selections)
10814 });
10815 });
10816 }
10817
10818 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
10819 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10820 let text_layout_details = &self.text_layout_details(window);
10821 self.transact(window, cx, |this, window, cx| {
10822 let edits = this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10823 let mut edits: Vec<(Range<usize>, String)> = Default::default();
10824 s.move_with(|display_map, selection| {
10825 if !selection.is_empty() {
10826 return;
10827 }
10828
10829 let mut head = selection.head();
10830 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
10831 if head.column() == display_map.line_len(head.row()) {
10832 transpose_offset = display_map
10833 .buffer_snapshot
10834 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
10835 }
10836
10837 if transpose_offset == 0 {
10838 return;
10839 }
10840
10841 *head.column_mut() += 1;
10842 head = display_map.clip_point(head, Bias::Right);
10843 let goal = SelectionGoal::HorizontalPosition(
10844 display_map
10845 .x_for_display_point(head, text_layout_details)
10846 .into(),
10847 );
10848 selection.collapse_to(head, goal);
10849
10850 let transpose_start = display_map
10851 .buffer_snapshot
10852 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
10853 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
10854 let transpose_end = display_map
10855 .buffer_snapshot
10856 .clip_offset(transpose_offset + 1, Bias::Right);
10857 if let Some(ch) =
10858 display_map.buffer_snapshot.chars_at(transpose_start).next()
10859 {
10860 edits.push((transpose_start..transpose_offset, String::new()));
10861 edits.push((transpose_end..transpose_end, ch.to_string()));
10862 }
10863 }
10864 });
10865 edits
10866 });
10867 this.buffer
10868 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
10869 let selections = this.selections.all::<usize>(cx);
10870 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10871 s.select(selections);
10872 });
10873 });
10874 }
10875
10876 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
10877 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10878 self.rewrap_impl(RewrapOptions::default(), cx)
10879 }
10880
10881 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
10882 let buffer = self.buffer.read(cx).snapshot(cx);
10883 let selections = self.selections.all::<Point>(cx);
10884 let mut selections = selections.iter().peekable();
10885
10886 let mut edits = Vec::new();
10887 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
10888
10889 while let Some(selection) = selections.next() {
10890 let mut start_row = selection.start.row;
10891 let mut end_row = selection.end.row;
10892
10893 // Skip selections that overlap with a range that has already been rewrapped.
10894 let selection_range = start_row..end_row;
10895 if rewrapped_row_ranges
10896 .iter()
10897 .any(|range| range.overlaps(&selection_range))
10898 {
10899 continue;
10900 }
10901
10902 let tab_size = buffer.language_settings_at(selection.head(), cx).tab_size;
10903
10904 // Since not all lines in the selection may be at the same indent
10905 // level, choose the indent size that is the most common between all
10906 // of the lines.
10907 //
10908 // If there is a tie, we use the deepest indent.
10909 let (indent_size, indent_end) = {
10910 let mut indent_size_occurrences = HashMap::default();
10911 let mut rows_by_indent_size = HashMap::<IndentSize, Vec<u32>>::default();
10912
10913 for row in start_row..=end_row {
10914 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
10915 rows_by_indent_size.entry(indent).or_default().push(row);
10916 *indent_size_occurrences.entry(indent).or_insert(0) += 1;
10917 }
10918
10919 let indent_size = indent_size_occurrences
10920 .into_iter()
10921 .max_by_key(|(indent, count)| (*count, indent.len_with_expanded_tabs(tab_size)))
10922 .map(|(indent, _)| indent)
10923 .unwrap_or_default();
10924 let row = rows_by_indent_size[&indent_size][0];
10925 let indent_end = Point::new(row, indent_size.len);
10926
10927 (indent_size, indent_end)
10928 };
10929
10930 let mut line_prefix = indent_size.chars().collect::<String>();
10931
10932 let mut inside_comment = false;
10933 if let Some(comment_prefix) =
10934 buffer
10935 .language_scope_at(selection.head())
10936 .and_then(|language| {
10937 language
10938 .line_comment_prefixes()
10939 .iter()
10940 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
10941 .cloned()
10942 })
10943 {
10944 line_prefix.push_str(&comment_prefix);
10945 inside_comment = true;
10946 }
10947
10948 let language_settings = buffer.language_settings_at(selection.head(), cx);
10949 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
10950 RewrapBehavior::InComments => inside_comment,
10951 RewrapBehavior::InSelections => !selection.is_empty(),
10952 RewrapBehavior::Anywhere => true,
10953 };
10954
10955 let should_rewrap = options.override_language_settings
10956 || allow_rewrap_based_on_language
10957 || self.hard_wrap.is_some();
10958 if !should_rewrap {
10959 continue;
10960 }
10961
10962 if selection.is_empty() {
10963 'expand_upwards: while start_row > 0 {
10964 let prev_row = start_row - 1;
10965 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
10966 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
10967 {
10968 start_row = prev_row;
10969 } else {
10970 break 'expand_upwards;
10971 }
10972 }
10973
10974 'expand_downwards: while end_row < buffer.max_point().row {
10975 let next_row = end_row + 1;
10976 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
10977 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
10978 {
10979 end_row = next_row;
10980 } else {
10981 break 'expand_downwards;
10982 }
10983 }
10984 }
10985
10986 let start = Point::new(start_row, 0);
10987 let start_offset = start.to_offset(&buffer);
10988 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
10989 let selection_text = buffer.text_for_range(start..end).collect::<String>();
10990 let Some(lines_without_prefixes) = selection_text
10991 .lines()
10992 .map(|line| {
10993 line.strip_prefix(&line_prefix)
10994 .or_else(|| line.trim_start().strip_prefix(&line_prefix.trim_start()))
10995 .with_context(|| {
10996 format!("line did not start with prefix {line_prefix:?}: {line:?}")
10997 })
10998 })
10999 .collect::<Result<Vec<_>, _>>()
11000 .log_err()
11001 else {
11002 continue;
11003 };
11004
11005 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
11006 buffer
11007 .language_settings_at(Point::new(start_row, 0), cx)
11008 .preferred_line_length as usize
11009 });
11010 let wrapped_text = wrap_with_prefix(
11011 line_prefix,
11012 lines_without_prefixes.join("\n"),
11013 wrap_column,
11014 tab_size,
11015 options.preserve_existing_whitespace,
11016 );
11017
11018 // TODO: should always use char-based diff while still supporting cursor behavior that
11019 // matches vim.
11020 let mut diff_options = DiffOptions::default();
11021 if options.override_language_settings {
11022 diff_options.max_word_diff_len = 0;
11023 diff_options.max_word_diff_line_count = 0;
11024 } else {
11025 diff_options.max_word_diff_len = usize::MAX;
11026 diff_options.max_word_diff_line_count = usize::MAX;
11027 }
11028
11029 for (old_range, new_text) in
11030 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
11031 {
11032 let edit_start = buffer.anchor_after(start_offset + old_range.start);
11033 let edit_end = buffer.anchor_after(start_offset + old_range.end);
11034 edits.push((edit_start..edit_end, new_text));
11035 }
11036
11037 rewrapped_row_ranges.push(start_row..=end_row);
11038 }
11039
11040 self.buffer
11041 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11042 }
11043
11044 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
11045 let mut text = String::new();
11046 let buffer = self.buffer.read(cx).snapshot(cx);
11047 let mut selections = self.selections.all::<Point>(cx);
11048 let mut clipboard_selections = Vec::with_capacity(selections.len());
11049 {
11050 let max_point = buffer.max_point();
11051 let mut is_first = true;
11052 for selection in &mut selections {
11053 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11054 if is_entire_line {
11055 selection.start = Point::new(selection.start.row, 0);
11056 if !selection.is_empty() && selection.end.column == 0 {
11057 selection.end = cmp::min(max_point, selection.end);
11058 } else {
11059 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
11060 }
11061 selection.goal = SelectionGoal::None;
11062 }
11063 if is_first {
11064 is_first = false;
11065 } else {
11066 text += "\n";
11067 }
11068 let mut len = 0;
11069 for chunk in buffer.text_for_range(selection.start..selection.end) {
11070 text.push_str(chunk);
11071 len += chunk.len();
11072 }
11073 clipboard_selections.push(ClipboardSelection {
11074 len,
11075 is_entire_line,
11076 first_line_indent: buffer
11077 .indent_size_for_line(MultiBufferRow(selection.start.row))
11078 .len,
11079 });
11080 }
11081 }
11082
11083 self.transact(window, cx, |this, window, cx| {
11084 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11085 s.select(selections);
11086 });
11087 this.insert("", window, cx);
11088 });
11089 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
11090 }
11091
11092 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
11093 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11094 let item = self.cut_common(window, cx);
11095 cx.write_to_clipboard(item);
11096 }
11097
11098 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
11099 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11100 self.change_selections(None, window, cx, |s| {
11101 s.move_with(|snapshot, sel| {
11102 if sel.is_empty() {
11103 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
11104 }
11105 });
11106 });
11107 let item = self.cut_common(window, cx);
11108 cx.set_global(KillRing(item))
11109 }
11110
11111 pub fn kill_ring_yank(
11112 &mut self,
11113 _: &KillRingYank,
11114 window: &mut Window,
11115 cx: &mut Context<Self>,
11116 ) {
11117 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11118 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
11119 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
11120 (kill_ring.text().to_string(), kill_ring.metadata_json())
11121 } else {
11122 return;
11123 }
11124 } else {
11125 return;
11126 };
11127 self.do_paste(&text, metadata, false, window, cx);
11128 }
11129
11130 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
11131 self.do_copy(true, cx);
11132 }
11133
11134 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
11135 self.do_copy(false, cx);
11136 }
11137
11138 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
11139 let selections = self.selections.all::<Point>(cx);
11140 let buffer = self.buffer.read(cx).read(cx);
11141 let mut text = String::new();
11142
11143 let mut clipboard_selections = Vec::with_capacity(selections.len());
11144 {
11145 let max_point = buffer.max_point();
11146 let mut is_first = true;
11147 for selection in &selections {
11148 let mut start = selection.start;
11149 let mut end = selection.end;
11150 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11151 if is_entire_line {
11152 start = Point::new(start.row, 0);
11153 end = cmp::min(max_point, Point::new(end.row + 1, 0));
11154 }
11155
11156 let mut trimmed_selections = Vec::new();
11157 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
11158 let row = MultiBufferRow(start.row);
11159 let first_indent = buffer.indent_size_for_line(row);
11160 if first_indent.len == 0 || start.column > first_indent.len {
11161 trimmed_selections.push(start..end);
11162 } else {
11163 trimmed_selections.push(
11164 Point::new(row.0, first_indent.len)
11165 ..Point::new(row.0, buffer.line_len(row)),
11166 );
11167 for row in start.row + 1..=end.row {
11168 let mut line_len = buffer.line_len(MultiBufferRow(row));
11169 if row == end.row {
11170 line_len = end.column;
11171 }
11172 if line_len == 0 {
11173 trimmed_selections
11174 .push(Point::new(row, 0)..Point::new(row, line_len));
11175 continue;
11176 }
11177 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
11178 if row_indent_size.len >= first_indent.len {
11179 trimmed_selections.push(
11180 Point::new(row, first_indent.len)..Point::new(row, line_len),
11181 );
11182 } else {
11183 trimmed_selections.clear();
11184 trimmed_selections.push(start..end);
11185 break;
11186 }
11187 }
11188 }
11189 } else {
11190 trimmed_selections.push(start..end);
11191 }
11192
11193 for trimmed_range in trimmed_selections {
11194 if is_first {
11195 is_first = false;
11196 } else {
11197 text += "\n";
11198 }
11199 let mut len = 0;
11200 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
11201 text.push_str(chunk);
11202 len += chunk.len();
11203 }
11204 clipboard_selections.push(ClipboardSelection {
11205 len,
11206 is_entire_line,
11207 first_line_indent: buffer
11208 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
11209 .len,
11210 });
11211 }
11212 }
11213 }
11214
11215 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
11216 text,
11217 clipboard_selections,
11218 ));
11219 }
11220
11221 pub fn do_paste(
11222 &mut self,
11223 text: &String,
11224 clipboard_selections: Option<Vec<ClipboardSelection>>,
11225 handle_entire_lines: bool,
11226 window: &mut Window,
11227 cx: &mut Context<Self>,
11228 ) {
11229 if self.read_only(cx) {
11230 return;
11231 }
11232
11233 let clipboard_text = Cow::Borrowed(text);
11234
11235 self.transact(window, cx, |this, window, cx| {
11236 if let Some(mut clipboard_selections) = clipboard_selections {
11237 let old_selections = this.selections.all::<usize>(cx);
11238 let all_selections_were_entire_line =
11239 clipboard_selections.iter().all(|s| s.is_entire_line);
11240 let first_selection_indent_column =
11241 clipboard_selections.first().map(|s| s.first_line_indent);
11242 if clipboard_selections.len() != old_selections.len() {
11243 clipboard_selections.drain(..);
11244 }
11245 let cursor_offset = this.selections.last::<usize>(cx).head();
11246 let mut auto_indent_on_paste = true;
11247
11248 this.buffer.update(cx, |buffer, cx| {
11249 let snapshot = buffer.read(cx);
11250 auto_indent_on_paste = snapshot
11251 .language_settings_at(cursor_offset, cx)
11252 .auto_indent_on_paste;
11253
11254 let mut start_offset = 0;
11255 let mut edits = Vec::new();
11256 let mut original_indent_columns = Vec::new();
11257 for (ix, selection) in old_selections.iter().enumerate() {
11258 let to_insert;
11259 let entire_line;
11260 let original_indent_column;
11261 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
11262 let end_offset = start_offset + clipboard_selection.len;
11263 to_insert = &clipboard_text[start_offset..end_offset];
11264 entire_line = clipboard_selection.is_entire_line;
11265 start_offset = end_offset + 1;
11266 original_indent_column = Some(clipboard_selection.first_line_indent);
11267 } else {
11268 to_insert = clipboard_text.as_str();
11269 entire_line = all_selections_were_entire_line;
11270 original_indent_column = first_selection_indent_column
11271 }
11272
11273 // If the corresponding selection was empty when this slice of the
11274 // clipboard text was written, then the entire line containing the
11275 // selection was copied. If this selection is also currently empty,
11276 // then paste the line before the current line of the buffer.
11277 let range = if selection.is_empty() && handle_entire_lines && entire_line {
11278 let column = selection.start.to_point(&snapshot).column as usize;
11279 let line_start = selection.start - column;
11280 line_start..line_start
11281 } else {
11282 selection.range()
11283 };
11284
11285 edits.push((range, to_insert));
11286 original_indent_columns.push(original_indent_column);
11287 }
11288 drop(snapshot);
11289
11290 buffer.edit(
11291 edits,
11292 if auto_indent_on_paste {
11293 Some(AutoindentMode::Block {
11294 original_indent_columns,
11295 })
11296 } else {
11297 None
11298 },
11299 cx,
11300 );
11301 });
11302
11303 let selections = this.selections.all::<usize>(cx);
11304 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11305 s.select(selections)
11306 });
11307 } else {
11308 this.insert(&clipboard_text, window, cx);
11309 }
11310 });
11311 }
11312
11313 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
11314 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11315 if let Some(item) = cx.read_from_clipboard() {
11316 let entries = item.entries();
11317
11318 match entries.first() {
11319 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
11320 // of all the pasted entries.
11321 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
11322 .do_paste(
11323 clipboard_string.text(),
11324 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
11325 true,
11326 window,
11327 cx,
11328 ),
11329 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
11330 }
11331 }
11332 }
11333
11334 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
11335 if self.read_only(cx) {
11336 return;
11337 }
11338
11339 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11340
11341 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
11342 if let Some((selections, _)) =
11343 self.selection_history.transaction(transaction_id).cloned()
11344 {
11345 self.change_selections(None, window, cx, |s| {
11346 s.select_anchors(selections.to_vec());
11347 });
11348 } else {
11349 log::error!(
11350 "No entry in selection_history found for undo. \
11351 This may correspond to a bug where undo does not update the selection. \
11352 If this is occurring, please add details to \
11353 https://github.com/zed-industries/zed/issues/22692"
11354 );
11355 }
11356 self.request_autoscroll(Autoscroll::fit(), cx);
11357 self.unmark_text(window, cx);
11358 self.refresh_inline_completion(true, false, window, cx);
11359 cx.emit(EditorEvent::Edited { transaction_id });
11360 cx.emit(EditorEvent::TransactionUndone { transaction_id });
11361 }
11362 }
11363
11364 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
11365 if self.read_only(cx) {
11366 return;
11367 }
11368
11369 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11370
11371 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
11372 if let Some((_, Some(selections))) =
11373 self.selection_history.transaction(transaction_id).cloned()
11374 {
11375 self.change_selections(None, window, cx, |s| {
11376 s.select_anchors(selections.to_vec());
11377 });
11378 } else {
11379 log::error!(
11380 "No entry in selection_history found for redo. \
11381 This may correspond to a bug where undo does not update the selection. \
11382 If this is occurring, please add details to \
11383 https://github.com/zed-industries/zed/issues/22692"
11384 );
11385 }
11386 self.request_autoscroll(Autoscroll::fit(), cx);
11387 self.unmark_text(window, cx);
11388 self.refresh_inline_completion(true, false, window, cx);
11389 cx.emit(EditorEvent::Edited { transaction_id });
11390 }
11391 }
11392
11393 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
11394 self.buffer
11395 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
11396 }
11397
11398 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
11399 self.buffer
11400 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
11401 }
11402
11403 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
11404 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11405 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11406 s.move_with(|map, selection| {
11407 let cursor = if selection.is_empty() {
11408 movement::left(map, selection.start)
11409 } else {
11410 selection.start
11411 };
11412 selection.collapse_to(cursor, SelectionGoal::None);
11413 });
11414 })
11415 }
11416
11417 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
11418 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11419 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11420 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
11421 })
11422 }
11423
11424 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
11425 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11426 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11427 s.move_with(|map, selection| {
11428 let cursor = if selection.is_empty() {
11429 movement::right(map, selection.end)
11430 } else {
11431 selection.end
11432 };
11433 selection.collapse_to(cursor, SelectionGoal::None)
11434 });
11435 })
11436 }
11437
11438 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
11439 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11440 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11441 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
11442 })
11443 }
11444
11445 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
11446 if self.take_rename(true, window, cx).is_some() {
11447 return;
11448 }
11449
11450 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11451 cx.propagate();
11452 return;
11453 }
11454
11455 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11456
11457 let text_layout_details = &self.text_layout_details(window);
11458 let selection_count = self.selections.count();
11459 let first_selection = self.selections.first_anchor();
11460
11461 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11462 s.move_with(|map, selection| {
11463 if !selection.is_empty() {
11464 selection.goal = SelectionGoal::None;
11465 }
11466 let (cursor, goal) = movement::up(
11467 map,
11468 selection.start,
11469 selection.goal,
11470 false,
11471 text_layout_details,
11472 );
11473 selection.collapse_to(cursor, goal);
11474 });
11475 });
11476
11477 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11478 {
11479 cx.propagate();
11480 }
11481 }
11482
11483 pub fn move_up_by_lines(
11484 &mut self,
11485 action: &MoveUpByLines,
11486 window: &mut Window,
11487 cx: &mut Context<Self>,
11488 ) {
11489 if self.take_rename(true, window, cx).is_some() {
11490 return;
11491 }
11492
11493 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11494 cx.propagate();
11495 return;
11496 }
11497
11498 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11499
11500 let text_layout_details = &self.text_layout_details(window);
11501
11502 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11503 s.move_with(|map, selection| {
11504 if !selection.is_empty() {
11505 selection.goal = SelectionGoal::None;
11506 }
11507 let (cursor, goal) = movement::up_by_rows(
11508 map,
11509 selection.start,
11510 action.lines,
11511 selection.goal,
11512 false,
11513 text_layout_details,
11514 );
11515 selection.collapse_to(cursor, goal);
11516 });
11517 })
11518 }
11519
11520 pub fn move_down_by_lines(
11521 &mut self,
11522 action: &MoveDownByLines,
11523 window: &mut Window,
11524 cx: &mut Context<Self>,
11525 ) {
11526 if self.take_rename(true, window, cx).is_some() {
11527 return;
11528 }
11529
11530 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11531 cx.propagate();
11532 return;
11533 }
11534
11535 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11536
11537 let text_layout_details = &self.text_layout_details(window);
11538
11539 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11540 s.move_with(|map, selection| {
11541 if !selection.is_empty() {
11542 selection.goal = SelectionGoal::None;
11543 }
11544 let (cursor, goal) = movement::down_by_rows(
11545 map,
11546 selection.start,
11547 action.lines,
11548 selection.goal,
11549 false,
11550 text_layout_details,
11551 );
11552 selection.collapse_to(cursor, goal);
11553 });
11554 })
11555 }
11556
11557 pub fn select_down_by_lines(
11558 &mut self,
11559 action: &SelectDownByLines,
11560 window: &mut Window,
11561 cx: &mut Context<Self>,
11562 ) {
11563 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11564 let text_layout_details = &self.text_layout_details(window);
11565 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11566 s.move_heads_with(|map, head, goal| {
11567 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
11568 })
11569 })
11570 }
11571
11572 pub fn select_up_by_lines(
11573 &mut self,
11574 action: &SelectUpByLines,
11575 window: &mut Window,
11576 cx: &mut Context<Self>,
11577 ) {
11578 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11579 let text_layout_details = &self.text_layout_details(window);
11580 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11581 s.move_heads_with(|map, head, goal| {
11582 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
11583 })
11584 })
11585 }
11586
11587 pub fn select_page_up(
11588 &mut self,
11589 _: &SelectPageUp,
11590 window: &mut Window,
11591 cx: &mut Context<Self>,
11592 ) {
11593 let Some(row_count) = self.visible_row_count() else {
11594 return;
11595 };
11596
11597 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11598
11599 let text_layout_details = &self.text_layout_details(window);
11600
11601 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11602 s.move_heads_with(|map, head, goal| {
11603 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
11604 })
11605 })
11606 }
11607
11608 pub fn move_page_up(
11609 &mut self,
11610 action: &MovePageUp,
11611 window: &mut Window,
11612 cx: &mut Context<Self>,
11613 ) {
11614 if self.take_rename(true, window, cx).is_some() {
11615 return;
11616 }
11617
11618 if self
11619 .context_menu
11620 .borrow_mut()
11621 .as_mut()
11622 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
11623 .unwrap_or(false)
11624 {
11625 return;
11626 }
11627
11628 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11629 cx.propagate();
11630 return;
11631 }
11632
11633 let Some(row_count) = self.visible_row_count() else {
11634 return;
11635 };
11636
11637 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11638
11639 let autoscroll = if action.center_cursor {
11640 Autoscroll::center()
11641 } else {
11642 Autoscroll::fit()
11643 };
11644
11645 let text_layout_details = &self.text_layout_details(window);
11646
11647 self.change_selections(Some(autoscroll), window, cx, |s| {
11648 s.move_with(|map, selection| {
11649 if !selection.is_empty() {
11650 selection.goal = SelectionGoal::None;
11651 }
11652 let (cursor, goal) = movement::up_by_rows(
11653 map,
11654 selection.end,
11655 row_count,
11656 selection.goal,
11657 false,
11658 text_layout_details,
11659 );
11660 selection.collapse_to(cursor, goal);
11661 });
11662 });
11663 }
11664
11665 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
11666 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11667 let text_layout_details = &self.text_layout_details(window);
11668 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11669 s.move_heads_with(|map, head, goal| {
11670 movement::up(map, head, goal, false, text_layout_details)
11671 })
11672 })
11673 }
11674
11675 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
11676 self.take_rename(true, window, cx);
11677
11678 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11679 cx.propagate();
11680 return;
11681 }
11682
11683 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11684
11685 let text_layout_details = &self.text_layout_details(window);
11686 let selection_count = self.selections.count();
11687 let first_selection = self.selections.first_anchor();
11688
11689 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11690 s.move_with(|map, selection| {
11691 if !selection.is_empty() {
11692 selection.goal = SelectionGoal::None;
11693 }
11694 let (cursor, goal) = movement::down(
11695 map,
11696 selection.end,
11697 selection.goal,
11698 false,
11699 text_layout_details,
11700 );
11701 selection.collapse_to(cursor, goal);
11702 });
11703 });
11704
11705 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11706 {
11707 cx.propagate();
11708 }
11709 }
11710
11711 pub fn select_page_down(
11712 &mut self,
11713 _: &SelectPageDown,
11714 window: &mut Window,
11715 cx: &mut Context<Self>,
11716 ) {
11717 let Some(row_count) = self.visible_row_count() else {
11718 return;
11719 };
11720
11721 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11722
11723 let text_layout_details = &self.text_layout_details(window);
11724
11725 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11726 s.move_heads_with(|map, head, goal| {
11727 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
11728 })
11729 })
11730 }
11731
11732 pub fn move_page_down(
11733 &mut self,
11734 action: &MovePageDown,
11735 window: &mut Window,
11736 cx: &mut Context<Self>,
11737 ) {
11738 if self.take_rename(true, window, cx).is_some() {
11739 return;
11740 }
11741
11742 if self
11743 .context_menu
11744 .borrow_mut()
11745 .as_mut()
11746 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
11747 .unwrap_or(false)
11748 {
11749 return;
11750 }
11751
11752 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11753 cx.propagate();
11754 return;
11755 }
11756
11757 let Some(row_count) = self.visible_row_count() else {
11758 return;
11759 };
11760
11761 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11762
11763 let autoscroll = if action.center_cursor {
11764 Autoscroll::center()
11765 } else {
11766 Autoscroll::fit()
11767 };
11768
11769 let text_layout_details = &self.text_layout_details(window);
11770 self.change_selections(Some(autoscroll), window, cx, |s| {
11771 s.move_with(|map, selection| {
11772 if !selection.is_empty() {
11773 selection.goal = SelectionGoal::None;
11774 }
11775 let (cursor, goal) = movement::down_by_rows(
11776 map,
11777 selection.end,
11778 row_count,
11779 selection.goal,
11780 false,
11781 text_layout_details,
11782 );
11783 selection.collapse_to(cursor, goal);
11784 });
11785 });
11786 }
11787
11788 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
11789 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11790 let text_layout_details = &self.text_layout_details(window);
11791 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11792 s.move_heads_with(|map, head, goal| {
11793 movement::down(map, head, goal, false, text_layout_details)
11794 })
11795 });
11796 }
11797
11798 pub fn context_menu_first(
11799 &mut self,
11800 _: &ContextMenuFirst,
11801 window: &mut Window,
11802 cx: &mut Context<Self>,
11803 ) {
11804 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11805 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
11806 }
11807 }
11808
11809 pub fn context_menu_prev(
11810 &mut self,
11811 _: &ContextMenuPrevious,
11812 window: &mut Window,
11813 cx: &mut Context<Self>,
11814 ) {
11815 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11816 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
11817 }
11818 }
11819
11820 pub fn context_menu_next(
11821 &mut self,
11822 _: &ContextMenuNext,
11823 window: &mut Window,
11824 cx: &mut Context<Self>,
11825 ) {
11826 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11827 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
11828 }
11829 }
11830
11831 pub fn context_menu_last(
11832 &mut self,
11833 _: &ContextMenuLast,
11834 window: &mut Window,
11835 cx: &mut Context<Self>,
11836 ) {
11837 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11838 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
11839 }
11840 }
11841
11842 pub fn move_to_previous_word_start(
11843 &mut self,
11844 _: &MoveToPreviousWordStart,
11845 window: &mut Window,
11846 cx: &mut Context<Self>,
11847 ) {
11848 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11849 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11850 s.move_cursors_with(|map, head, _| {
11851 (
11852 movement::previous_word_start(map, head),
11853 SelectionGoal::None,
11854 )
11855 });
11856 })
11857 }
11858
11859 pub fn move_to_previous_subword_start(
11860 &mut self,
11861 _: &MoveToPreviousSubwordStart,
11862 window: &mut Window,
11863 cx: &mut Context<Self>,
11864 ) {
11865 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11866 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11867 s.move_cursors_with(|map, head, _| {
11868 (
11869 movement::previous_subword_start(map, head),
11870 SelectionGoal::None,
11871 )
11872 });
11873 })
11874 }
11875
11876 pub fn select_to_previous_word_start(
11877 &mut self,
11878 _: &SelectToPreviousWordStart,
11879 window: &mut Window,
11880 cx: &mut Context<Self>,
11881 ) {
11882 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11883 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11884 s.move_heads_with(|map, head, _| {
11885 (
11886 movement::previous_word_start(map, head),
11887 SelectionGoal::None,
11888 )
11889 });
11890 })
11891 }
11892
11893 pub fn select_to_previous_subword_start(
11894 &mut self,
11895 _: &SelectToPreviousSubwordStart,
11896 window: &mut Window,
11897 cx: &mut Context<Self>,
11898 ) {
11899 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11900 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11901 s.move_heads_with(|map, head, _| {
11902 (
11903 movement::previous_subword_start(map, head),
11904 SelectionGoal::None,
11905 )
11906 });
11907 })
11908 }
11909
11910 pub fn delete_to_previous_word_start(
11911 &mut self,
11912 action: &DeleteToPreviousWordStart,
11913 window: &mut Window,
11914 cx: &mut Context<Self>,
11915 ) {
11916 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11917 self.transact(window, cx, |this, window, cx| {
11918 this.select_autoclose_pair(window, cx);
11919 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11920 s.move_with(|map, selection| {
11921 if selection.is_empty() {
11922 let cursor = if action.ignore_newlines {
11923 movement::previous_word_start(map, selection.head())
11924 } else {
11925 movement::previous_word_start_or_newline(map, selection.head())
11926 };
11927 selection.set_head(cursor, SelectionGoal::None);
11928 }
11929 });
11930 });
11931 this.insert("", window, cx);
11932 });
11933 }
11934
11935 pub fn delete_to_previous_subword_start(
11936 &mut self,
11937 _: &DeleteToPreviousSubwordStart,
11938 window: &mut Window,
11939 cx: &mut Context<Self>,
11940 ) {
11941 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11942 self.transact(window, cx, |this, window, cx| {
11943 this.select_autoclose_pair(window, cx);
11944 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11945 s.move_with(|map, selection| {
11946 if selection.is_empty() {
11947 let cursor = movement::previous_subword_start(map, selection.head());
11948 selection.set_head(cursor, SelectionGoal::None);
11949 }
11950 });
11951 });
11952 this.insert("", window, cx);
11953 });
11954 }
11955
11956 pub fn move_to_next_word_end(
11957 &mut self,
11958 _: &MoveToNextWordEnd,
11959 window: &mut Window,
11960 cx: &mut Context<Self>,
11961 ) {
11962 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11963 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11964 s.move_cursors_with(|map, head, _| {
11965 (movement::next_word_end(map, head), SelectionGoal::None)
11966 });
11967 })
11968 }
11969
11970 pub fn move_to_next_subword_end(
11971 &mut self,
11972 _: &MoveToNextSubwordEnd,
11973 window: &mut Window,
11974 cx: &mut Context<Self>,
11975 ) {
11976 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11977 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11978 s.move_cursors_with(|map, head, _| {
11979 (movement::next_subword_end(map, head), SelectionGoal::None)
11980 });
11981 })
11982 }
11983
11984 pub fn select_to_next_word_end(
11985 &mut self,
11986 _: &SelectToNextWordEnd,
11987 window: &mut Window,
11988 cx: &mut Context<Self>,
11989 ) {
11990 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11991 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11992 s.move_heads_with(|map, head, _| {
11993 (movement::next_word_end(map, head), SelectionGoal::None)
11994 });
11995 })
11996 }
11997
11998 pub fn select_to_next_subword_end(
11999 &mut self,
12000 _: &SelectToNextSubwordEnd,
12001 window: &mut Window,
12002 cx: &mut Context<Self>,
12003 ) {
12004 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12005 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12006 s.move_heads_with(|map, head, _| {
12007 (movement::next_subword_end(map, head), SelectionGoal::None)
12008 });
12009 })
12010 }
12011
12012 pub fn delete_to_next_word_end(
12013 &mut self,
12014 action: &DeleteToNextWordEnd,
12015 window: &mut Window,
12016 cx: &mut Context<Self>,
12017 ) {
12018 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12019 self.transact(window, cx, |this, window, cx| {
12020 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12021 s.move_with(|map, selection| {
12022 if selection.is_empty() {
12023 let cursor = if action.ignore_newlines {
12024 movement::next_word_end(map, selection.head())
12025 } else {
12026 movement::next_word_end_or_newline(map, selection.head())
12027 };
12028 selection.set_head(cursor, SelectionGoal::None);
12029 }
12030 });
12031 });
12032 this.insert("", window, cx);
12033 });
12034 }
12035
12036 pub fn delete_to_next_subword_end(
12037 &mut self,
12038 _: &DeleteToNextSubwordEnd,
12039 window: &mut Window,
12040 cx: &mut Context<Self>,
12041 ) {
12042 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12043 self.transact(window, cx, |this, window, cx| {
12044 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12045 s.move_with(|map, selection| {
12046 if selection.is_empty() {
12047 let cursor = movement::next_subword_end(map, selection.head());
12048 selection.set_head(cursor, SelectionGoal::None);
12049 }
12050 });
12051 });
12052 this.insert("", window, cx);
12053 });
12054 }
12055
12056 pub fn move_to_beginning_of_line(
12057 &mut self,
12058 action: &MoveToBeginningOfLine,
12059 window: &mut Window,
12060 cx: &mut Context<Self>,
12061 ) {
12062 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12063 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12064 s.move_cursors_with(|map, head, _| {
12065 (
12066 movement::indented_line_beginning(
12067 map,
12068 head,
12069 action.stop_at_soft_wraps,
12070 action.stop_at_indent,
12071 ),
12072 SelectionGoal::None,
12073 )
12074 });
12075 })
12076 }
12077
12078 pub fn select_to_beginning_of_line(
12079 &mut self,
12080 action: &SelectToBeginningOfLine,
12081 window: &mut Window,
12082 cx: &mut Context<Self>,
12083 ) {
12084 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12085 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12086 s.move_heads_with(|map, head, _| {
12087 (
12088 movement::indented_line_beginning(
12089 map,
12090 head,
12091 action.stop_at_soft_wraps,
12092 action.stop_at_indent,
12093 ),
12094 SelectionGoal::None,
12095 )
12096 });
12097 });
12098 }
12099
12100 pub fn delete_to_beginning_of_line(
12101 &mut self,
12102 action: &DeleteToBeginningOfLine,
12103 window: &mut Window,
12104 cx: &mut Context<Self>,
12105 ) {
12106 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12107 self.transact(window, cx, |this, window, cx| {
12108 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12109 s.move_with(|_, selection| {
12110 selection.reversed = true;
12111 });
12112 });
12113
12114 this.select_to_beginning_of_line(
12115 &SelectToBeginningOfLine {
12116 stop_at_soft_wraps: false,
12117 stop_at_indent: action.stop_at_indent,
12118 },
12119 window,
12120 cx,
12121 );
12122 this.backspace(&Backspace, window, cx);
12123 });
12124 }
12125
12126 pub fn move_to_end_of_line(
12127 &mut self,
12128 action: &MoveToEndOfLine,
12129 window: &mut Window,
12130 cx: &mut Context<Self>,
12131 ) {
12132 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12133 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12134 s.move_cursors_with(|map, head, _| {
12135 (
12136 movement::line_end(map, head, action.stop_at_soft_wraps),
12137 SelectionGoal::None,
12138 )
12139 });
12140 })
12141 }
12142
12143 pub fn select_to_end_of_line(
12144 &mut self,
12145 action: &SelectToEndOfLine,
12146 window: &mut Window,
12147 cx: &mut Context<Self>,
12148 ) {
12149 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12150 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12151 s.move_heads_with(|map, head, _| {
12152 (
12153 movement::line_end(map, head, action.stop_at_soft_wraps),
12154 SelectionGoal::None,
12155 )
12156 });
12157 })
12158 }
12159
12160 pub fn delete_to_end_of_line(
12161 &mut self,
12162 _: &DeleteToEndOfLine,
12163 window: &mut Window,
12164 cx: &mut Context<Self>,
12165 ) {
12166 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12167 self.transact(window, cx, |this, window, cx| {
12168 this.select_to_end_of_line(
12169 &SelectToEndOfLine {
12170 stop_at_soft_wraps: false,
12171 },
12172 window,
12173 cx,
12174 );
12175 this.delete(&Delete, window, cx);
12176 });
12177 }
12178
12179 pub fn cut_to_end_of_line(
12180 &mut self,
12181 _: &CutToEndOfLine,
12182 window: &mut Window,
12183 cx: &mut Context<Self>,
12184 ) {
12185 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12186 self.transact(window, cx, |this, window, cx| {
12187 this.select_to_end_of_line(
12188 &SelectToEndOfLine {
12189 stop_at_soft_wraps: false,
12190 },
12191 window,
12192 cx,
12193 );
12194 this.cut(&Cut, window, cx);
12195 });
12196 }
12197
12198 pub fn move_to_start_of_paragraph(
12199 &mut self,
12200 _: &MoveToStartOfParagraph,
12201 window: &mut Window,
12202 cx: &mut Context<Self>,
12203 ) {
12204 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12205 cx.propagate();
12206 return;
12207 }
12208 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12209 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12210 s.move_with(|map, selection| {
12211 selection.collapse_to(
12212 movement::start_of_paragraph(map, selection.head(), 1),
12213 SelectionGoal::None,
12214 )
12215 });
12216 })
12217 }
12218
12219 pub fn move_to_end_of_paragraph(
12220 &mut self,
12221 _: &MoveToEndOfParagraph,
12222 window: &mut Window,
12223 cx: &mut Context<Self>,
12224 ) {
12225 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12226 cx.propagate();
12227 return;
12228 }
12229 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12230 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12231 s.move_with(|map, selection| {
12232 selection.collapse_to(
12233 movement::end_of_paragraph(map, selection.head(), 1),
12234 SelectionGoal::None,
12235 )
12236 });
12237 })
12238 }
12239
12240 pub fn select_to_start_of_paragraph(
12241 &mut self,
12242 _: &SelectToStartOfParagraph,
12243 window: &mut Window,
12244 cx: &mut Context<Self>,
12245 ) {
12246 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12247 cx.propagate();
12248 return;
12249 }
12250 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12251 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12252 s.move_heads_with(|map, head, _| {
12253 (
12254 movement::start_of_paragraph(map, head, 1),
12255 SelectionGoal::None,
12256 )
12257 });
12258 })
12259 }
12260
12261 pub fn select_to_end_of_paragraph(
12262 &mut self,
12263 _: &SelectToEndOfParagraph,
12264 window: &mut Window,
12265 cx: &mut Context<Self>,
12266 ) {
12267 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12268 cx.propagate();
12269 return;
12270 }
12271 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12272 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12273 s.move_heads_with(|map, head, _| {
12274 (
12275 movement::end_of_paragraph(map, head, 1),
12276 SelectionGoal::None,
12277 )
12278 });
12279 })
12280 }
12281
12282 pub fn move_to_start_of_excerpt(
12283 &mut self,
12284 _: &MoveToStartOfExcerpt,
12285 window: &mut Window,
12286 cx: &mut Context<Self>,
12287 ) {
12288 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12289 cx.propagate();
12290 return;
12291 }
12292 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12293 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12294 s.move_with(|map, selection| {
12295 selection.collapse_to(
12296 movement::start_of_excerpt(
12297 map,
12298 selection.head(),
12299 workspace::searchable::Direction::Prev,
12300 ),
12301 SelectionGoal::None,
12302 )
12303 });
12304 })
12305 }
12306
12307 pub fn move_to_start_of_next_excerpt(
12308 &mut self,
12309 _: &MoveToStartOfNextExcerpt,
12310 window: &mut Window,
12311 cx: &mut Context<Self>,
12312 ) {
12313 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12314 cx.propagate();
12315 return;
12316 }
12317
12318 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12319 s.move_with(|map, selection| {
12320 selection.collapse_to(
12321 movement::start_of_excerpt(
12322 map,
12323 selection.head(),
12324 workspace::searchable::Direction::Next,
12325 ),
12326 SelectionGoal::None,
12327 )
12328 });
12329 })
12330 }
12331
12332 pub fn move_to_end_of_excerpt(
12333 &mut self,
12334 _: &MoveToEndOfExcerpt,
12335 window: &mut Window,
12336 cx: &mut Context<Self>,
12337 ) {
12338 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12339 cx.propagate();
12340 return;
12341 }
12342 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12343 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12344 s.move_with(|map, selection| {
12345 selection.collapse_to(
12346 movement::end_of_excerpt(
12347 map,
12348 selection.head(),
12349 workspace::searchable::Direction::Next,
12350 ),
12351 SelectionGoal::None,
12352 )
12353 });
12354 })
12355 }
12356
12357 pub fn move_to_end_of_previous_excerpt(
12358 &mut self,
12359 _: &MoveToEndOfPreviousExcerpt,
12360 window: &mut Window,
12361 cx: &mut Context<Self>,
12362 ) {
12363 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12364 cx.propagate();
12365 return;
12366 }
12367 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12368 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12369 s.move_with(|map, selection| {
12370 selection.collapse_to(
12371 movement::end_of_excerpt(
12372 map,
12373 selection.head(),
12374 workspace::searchable::Direction::Prev,
12375 ),
12376 SelectionGoal::None,
12377 )
12378 });
12379 })
12380 }
12381
12382 pub fn select_to_start_of_excerpt(
12383 &mut self,
12384 _: &SelectToStartOfExcerpt,
12385 window: &mut Window,
12386 cx: &mut Context<Self>,
12387 ) {
12388 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12389 cx.propagate();
12390 return;
12391 }
12392 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12393 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12394 s.move_heads_with(|map, head, _| {
12395 (
12396 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12397 SelectionGoal::None,
12398 )
12399 });
12400 })
12401 }
12402
12403 pub fn select_to_start_of_next_excerpt(
12404 &mut self,
12405 _: &SelectToStartOfNextExcerpt,
12406 window: &mut Window,
12407 cx: &mut Context<Self>,
12408 ) {
12409 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12410 cx.propagate();
12411 return;
12412 }
12413 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12414 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12415 s.move_heads_with(|map, head, _| {
12416 (
12417 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
12418 SelectionGoal::None,
12419 )
12420 });
12421 })
12422 }
12423
12424 pub fn select_to_end_of_excerpt(
12425 &mut self,
12426 _: &SelectToEndOfExcerpt,
12427 window: &mut Window,
12428 cx: &mut Context<Self>,
12429 ) {
12430 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12431 cx.propagate();
12432 return;
12433 }
12434 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12435 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12436 s.move_heads_with(|map, head, _| {
12437 (
12438 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
12439 SelectionGoal::None,
12440 )
12441 });
12442 })
12443 }
12444
12445 pub fn select_to_end_of_previous_excerpt(
12446 &mut self,
12447 _: &SelectToEndOfPreviousExcerpt,
12448 window: &mut Window,
12449 cx: &mut Context<Self>,
12450 ) {
12451 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12452 cx.propagate();
12453 return;
12454 }
12455 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12456 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12457 s.move_heads_with(|map, head, _| {
12458 (
12459 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12460 SelectionGoal::None,
12461 )
12462 });
12463 })
12464 }
12465
12466 pub fn move_to_beginning(
12467 &mut self,
12468 _: &MoveToBeginning,
12469 window: &mut Window,
12470 cx: &mut Context<Self>,
12471 ) {
12472 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12473 cx.propagate();
12474 return;
12475 }
12476 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12477 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12478 s.select_ranges(vec![0..0]);
12479 });
12480 }
12481
12482 pub fn select_to_beginning(
12483 &mut self,
12484 _: &SelectToBeginning,
12485 window: &mut Window,
12486 cx: &mut Context<Self>,
12487 ) {
12488 let mut selection = self.selections.last::<Point>(cx);
12489 selection.set_head(Point::zero(), SelectionGoal::None);
12490 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12491 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12492 s.select(vec![selection]);
12493 });
12494 }
12495
12496 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
12497 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12498 cx.propagate();
12499 return;
12500 }
12501 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12502 let cursor = self.buffer.read(cx).read(cx).len();
12503 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12504 s.select_ranges(vec![cursor..cursor])
12505 });
12506 }
12507
12508 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
12509 self.nav_history = nav_history;
12510 }
12511
12512 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
12513 self.nav_history.as_ref()
12514 }
12515
12516 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
12517 self.push_to_nav_history(self.selections.newest_anchor().head(), None, false, cx);
12518 }
12519
12520 fn push_to_nav_history(
12521 &mut self,
12522 cursor_anchor: Anchor,
12523 new_position: Option<Point>,
12524 is_deactivate: bool,
12525 cx: &mut Context<Self>,
12526 ) {
12527 if let Some(nav_history) = self.nav_history.as_mut() {
12528 let buffer = self.buffer.read(cx).read(cx);
12529 let cursor_position = cursor_anchor.to_point(&buffer);
12530 let scroll_state = self.scroll_manager.anchor();
12531 let scroll_top_row = scroll_state.top_row(&buffer);
12532 drop(buffer);
12533
12534 if let Some(new_position) = new_position {
12535 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
12536 if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA {
12537 return;
12538 }
12539 }
12540
12541 nav_history.push(
12542 Some(NavigationData {
12543 cursor_anchor,
12544 cursor_position,
12545 scroll_anchor: scroll_state,
12546 scroll_top_row,
12547 }),
12548 cx,
12549 );
12550 cx.emit(EditorEvent::PushedToNavHistory {
12551 anchor: cursor_anchor,
12552 is_deactivate,
12553 })
12554 }
12555 }
12556
12557 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
12558 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12559 let buffer = self.buffer.read(cx).snapshot(cx);
12560 let mut selection = self.selections.first::<usize>(cx);
12561 selection.set_head(buffer.len(), SelectionGoal::None);
12562 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12563 s.select(vec![selection]);
12564 });
12565 }
12566
12567 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
12568 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12569 let end = self.buffer.read(cx).read(cx).len();
12570 self.change_selections(None, window, cx, |s| {
12571 s.select_ranges(vec![0..end]);
12572 });
12573 }
12574
12575 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
12576 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12577 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12578 let mut selections = self.selections.all::<Point>(cx);
12579 let max_point = display_map.buffer_snapshot.max_point();
12580 for selection in &mut selections {
12581 let rows = selection.spanned_rows(true, &display_map);
12582 selection.start = Point::new(rows.start.0, 0);
12583 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
12584 selection.reversed = false;
12585 }
12586 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12587 s.select(selections);
12588 });
12589 }
12590
12591 pub fn split_selection_into_lines(
12592 &mut self,
12593 _: &SplitSelectionIntoLines,
12594 window: &mut Window,
12595 cx: &mut Context<Self>,
12596 ) {
12597 let selections = self
12598 .selections
12599 .all::<Point>(cx)
12600 .into_iter()
12601 .map(|selection| selection.start..selection.end)
12602 .collect::<Vec<_>>();
12603 self.unfold_ranges(&selections, true, true, cx);
12604
12605 let mut new_selection_ranges = Vec::new();
12606 {
12607 let buffer = self.buffer.read(cx).read(cx);
12608 for selection in selections {
12609 for row in selection.start.row..selection.end.row {
12610 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12611 new_selection_ranges.push(cursor..cursor);
12612 }
12613
12614 let is_multiline_selection = selection.start.row != selection.end.row;
12615 // Don't insert last one if it's a multi-line selection ending at the start of a line,
12616 // so this action feels more ergonomic when paired with other selection operations
12617 let should_skip_last = is_multiline_selection && selection.end.column == 0;
12618 if !should_skip_last {
12619 new_selection_ranges.push(selection.end..selection.end);
12620 }
12621 }
12622 }
12623 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12624 s.select_ranges(new_selection_ranges);
12625 });
12626 }
12627
12628 pub fn add_selection_above(
12629 &mut self,
12630 _: &AddSelectionAbove,
12631 window: &mut Window,
12632 cx: &mut Context<Self>,
12633 ) {
12634 self.add_selection(true, window, cx);
12635 }
12636
12637 pub fn add_selection_below(
12638 &mut self,
12639 _: &AddSelectionBelow,
12640 window: &mut Window,
12641 cx: &mut Context<Self>,
12642 ) {
12643 self.add_selection(false, window, cx);
12644 }
12645
12646 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
12647 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12648
12649 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12650 let mut selections = self.selections.all::<Point>(cx);
12651 let text_layout_details = self.text_layout_details(window);
12652 let mut state = self.add_selections_state.take().unwrap_or_else(|| {
12653 let oldest_selection = selections.iter().min_by_key(|s| s.id).unwrap().clone();
12654 let range = oldest_selection.display_range(&display_map).sorted();
12655
12656 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
12657 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
12658 let positions = start_x.min(end_x)..start_x.max(end_x);
12659
12660 selections.clear();
12661 let mut stack = Vec::new();
12662 for row in range.start.row().0..=range.end.row().0 {
12663 if let Some(selection) = self.selections.build_columnar_selection(
12664 &display_map,
12665 DisplayRow(row),
12666 &positions,
12667 oldest_selection.reversed,
12668 &text_layout_details,
12669 ) {
12670 stack.push(selection.id);
12671 selections.push(selection);
12672 }
12673 }
12674
12675 if above {
12676 stack.reverse();
12677 }
12678
12679 AddSelectionsState { above, stack }
12680 });
12681
12682 let last_added_selection = *state.stack.last().unwrap();
12683 let mut new_selections = Vec::new();
12684 if above == state.above {
12685 let end_row = if above {
12686 DisplayRow(0)
12687 } else {
12688 display_map.max_point().row()
12689 };
12690
12691 'outer: for selection in selections {
12692 if selection.id == last_added_selection {
12693 let range = selection.display_range(&display_map).sorted();
12694 debug_assert_eq!(range.start.row(), range.end.row());
12695 let mut row = range.start.row();
12696 let positions =
12697 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
12698 px(start)..px(end)
12699 } else {
12700 let start_x =
12701 display_map.x_for_display_point(range.start, &text_layout_details);
12702 let end_x =
12703 display_map.x_for_display_point(range.end, &text_layout_details);
12704 start_x.min(end_x)..start_x.max(end_x)
12705 };
12706
12707 while row != end_row {
12708 if above {
12709 row.0 -= 1;
12710 } else {
12711 row.0 += 1;
12712 }
12713
12714 if let Some(new_selection) = self.selections.build_columnar_selection(
12715 &display_map,
12716 row,
12717 &positions,
12718 selection.reversed,
12719 &text_layout_details,
12720 ) {
12721 state.stack.push(new_selection.id);
12722 if above {
12723 new_selections.push(new_selection);
12724 new_selections.push(selection);
12725 } else {
12726 new_selections.push(selection);
12727 new_selections.push(new_selection);
12728 }
12729
12730 continue 'outer;
12731 }
12732 }
12733 }
12734
12735 new_selections.push(selection);
12736 }
12737 } else {
12738 new_selections = selections;
12739 new_selections.retain(|s| s.id != last_added_selection);
12740 state.stack.pop();
12741 }
12742
12743 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12744 s.select(new_selections);
12745 });
12746 if state.stack.len() > 1 {
12747 self.add_selections_state = Some(state);
12748 }
12749 }
12750
12751 fn select_match_ranges(
12752 &mut self,
12753 range: Range<usize>,
12754 reversed: bool,
12755 replace_newest: bool,
12756 auto_scroll: Option<Autoscroll>,
12757 window: &mut Window,
12758 cx: &mut Context<Editor>,
12759 ) {
12760 self.unfold_ranges(&[range.clone()], false, auto_scroll.is_some(), cx);
12761 self.change_selections(auto_scroll, window, cx, |s| {
12762 if replace_newest {
12763 s.delete(s.newest_anchor().id);
12764 }
12765 if reversed {
12766 s.insert_range(range.end..range.start);
12767 } else {
12768 s.insert_range(range);
12769 }
12770 });
12771 }
12772
12773 pub fn select_next_match_internal(
12774 &mut self,
12775 display_map: &DisplaySnapshot,
12776 replace_newest: bool,
12777 autoscroll: Option<Autoscroll>,
12778 window: &mut Window,
12779 cx: &mut Context<Self>,
12780 ) -> Result<()> {
12781 let buffer = &display_map.buffer_snapshot;
12782 let mut selections = self.selections.all::<usize>(cx);
12783 if let Some(mut select_next_state) = self.select_next_state.take() {
12784 let query = &select_next_state.query;
12785 if !select_next_state.done {
12786 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
12787 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
12788 let mut next_selected_range = None;
12789
12790 let bytes_after_last_selection =
12791 buffer.bytes_in_range(last_selection.end..buffer.len());
12792 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
12793 let query_matches = query
12794 .stream_find_iter(bytes_after_last_selection)
12795 .map(|result| (last_selection.end, result))
12796 .chain(
12797 query
12798 .stream_find_iter(bytes_before_first_selection)
12799 .map(|result| (0, result)),
12800 );
12801
12802 for (start_offset, query_match) in query_matches {
12803 let query_match = query_match.unwrap(); // can only fail due to I/O
12804 let offset_range =
12805 start_offset + query_match.start()..start_offset + query_match.end();
12806 let display_range = offset_range.start.to_display_point(display_map)
12807 ..offset_range.end.to_display_point(display_map);
12808
12809 if !select_next_state.wordwise
12810 || (!movement::is_inside_word(display_map, display_range.start)
12811 && !movement::is_inside_word(display_map, display_range.end))
12812 {
12813 // TODO: This is n^2, because we might check all the selections
12814 if !selections
12815 .iter()
12816 .any(|selection| selection.range().overlaps(&offset_range))
12817 {
12818 next_selected_range = Some(offset_range);
12819 break;
12820 }
12821 }
12822 }
12823
12824 if let Some(next_selected_range) = next_selected_range {
12825 self.select_match_ranges(
12826 next_selected_range,
12827 last_selection.reversed,
12828 replace_newest,
12829 autoscroll,
12830 window,
12831 cx,
12832 );
12833 } else {
12834 select_next_state.done = true;
12835 }
12836 }
12837
12838 self.select_next_state = Some(select_next_state);
12839 } else {
12840 let mut only_carets = true;
12841 let mut same_text_selected = true;
12842 let mut selected_text = None;
12843
12844 let mut selections_iter = selections.iter().peekable();
12845 while let Some(selection) = selections_iter.next() {
12846 if selection.start != selection.end {
12847 only_carets = false;
12848 }
12849
12850 if same_text_selected {
12851 if selected_text.is_none() {
12852 selected_text =
12853 Some(buffer.text_for_range(selection.range()).collect::<String>());
12854 }
12855
12856 if let Some(next_selection) = selections_iter.peek() {
12857 if next_selection.range().len() == selection.range().len() {
12858 let next_selected_text = buffer
12859 .text_for_range(next_selection.range())
12860 .collect::<String>();
12861 if Some(next_selected_text) != selected_text {
12862 same_text_selected = false;
12863 selected_text = None;
12864 }
12865 } else {
12866 same_text_selected = false;
12867 selected_text = None;
12868 }
12869 }
12870 }
12871 }
12872
12873 if only_carets {
12874 for selection in &mut selections {
12875 let word_range = movement::surrounding_word(
12876 display_map,
12877 selection.start.to_display_point(display_map),
12878 );
12879 selection.start = word_range.start.to_offset(display_map, Bias::Left);
12880 selection.end = word_range.end.to_offset(display_map, Bias::Left);
12881 selection.goal = SelectionGoal::None;
12882 selection.reversed = false;
12883 self.select_match_ranges(
12884 selection.start..selection.end,
12885 selection.reversed,
12886 replace_newest,
12887 autoscroll,
12888 window,
12889 cx,
12890 );
12891 }
12892
12893 if selections.len() == 1 {
12894 let selection = selections
12895 .last()
12896 .expect("ensured that there's only one selection");
12897 let query = buffer
12898 .text_for_range(selection.start..selection.end)
12899 .collect::<String>();
12900 let is_empty = query.is_empty();
12901 let select_state = SelectNextState {
12902 query: AhoCorasick::new(&[query])?,
12903 wordwise: true,
12904 done: is_empty,
12905 };
12906 self.select_next_state = Some(select_state);
12907 } else {
12908 self.select_next_state = None;
12909 }
12910 } else if let Some(selected_text) = selected_text {
12911 self.select_next_state = Some(SelectNextState {
12912 query: AhoCorasick::new(&[selected_text])?,
12913 wordwise: false,
12914 done: false,
12915 });
12916 self.select_next_match_internal(
12917 display_map,
12918 replace_newest,
12919 autoscroll,
12920 window,
12921 cx,
12922 )?;
12923 }
12924 }
12925 Ok(())
12926 }
12927
12928 pub fn select_all_matches(
12929 &mut self,
12930 _action: &SelectAllMatches,
12931 window: &mut Window,
12932 cx: &mut Context<Self>,
12933 ) -> Result<()> {
12934 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12935
12936 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12937
12938 self.select_next_match_internal(&display_map, false, None, window, cx)?;
12939 let Some(select_next_state) = self.select_next_state.as_mut() else {
12940 return Ok(());
12941 };
12942 if select_next_state.done {
12943 return Ok(());
12944 }
12945
12946 let mut new_selections = Vec::new();
12947
12948 let reversed = self.selections.oldest::<usize>(cx).reversed;
12949 let buffer = &display_map.buffer_snapshot;
12950 let query_matches = select_next_state
12951 .query
12952 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
12953
12954 for query_match in query_matches.into_iter() {
12955 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
12956 let offset_range = if reversed {
12957 query_match.end()..query_match.start()
12958 } else {
12959 query_match.start()..query_match.end()
12960 };
12961 let display_range = offset_range.start.to_display_point(&display_map)
12962 ..offset_range.end.to_display_point(&display_map);
12963
12964 if !select_next_state.wordwise
12965 || (!movement::is_inside_word(&display_map, display_range.start)
12966 && !movement::is_inside_word(&display_map, display_range.end))
12967 {
12968 new_selections.push(offset_range.start..offset_range.end);
12969 }
12970 }
12971
12972 select_next_state.done = true;
12973 self.unfold_ranges(&new_selections.clone(), false, false, cx);
12974 self.change_selections(None, window, cx, |selections| {
12975 selections.select_ranges(new_selections)
12976 });
12977
12978 Ok(())
12979 }
12980
12981 pub fn select_next(
12982 &mut self,
12983 action: &SelectNext,
12984 window: &mut Window,
12985 cx: &mut Context<Self>,
12986 ) -> Result<()> {
12987 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12988 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12989 self.select_next_match_internal(
12990 &display_map,
12991 action.replace_newest,
12992 Some(Autoscroll::newest()),
12993 window,
12994 cx,
12995 )?;
12996 Ok(())
12997 }
12998
12999 pub fn select_previous(
13000 &mut self,
13001 action: &SelectPrevious,
13002 window: &mut Window,
13003 cx: &mut Context<Self>,
13004 ) -> Result<()> {
13005 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13006 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13007 let buffer = &display_map.buffer_snapshot;
13008 let mut selections = self.selections.all::<usize>(cx);
13009 if let Some(mut select_prev_state) = self.select_prev_state.take() {
13010 let query = &select_prev_state.query;
13011 if !select_prev_state.done {
13012 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13013 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13014 let mut next_selected_range = None;
13015 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
13016 let bytes_before_last_selection =
13017 buffer.reversed_bytes_in_range(0..last_selection.start);
13018 let bytes_after_first_selection =
13019 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
13020 let query_matches = query
13021 .stream_find_iter(bytes_before_last_selection)
13022 .map(|result| (last_selection.start, result))
13023 .chain(
13024 query
13025 .stream_find_iter(bytes_after_first_selection)
13026 .map(|result| (buffer.len(), result)),
13027 );
13028 for (end_offset, query_match) in query_matches {
13029 let query_match = query_match.unwrap(); // can only fail due to I/O
13030 let offset_range =
13031 end_offset - query_match.end()..end_offset - query_match.start();
13032 let display_range = offset_range.start.to_display_point(&display_map)
13033 ..offset_range.end.to_display_point(&display_map);
13034
13035 if !select_prev_state.wordwise
13036 || (!movement::is_inside_word(&display_map, display_range.start)
13037 && !movement::is_inside_word(&display_map, display_range.end))
13038 {
13039 next_selected_range = Some(offset_range);
13040 break;
13041 }
13042 }
13043
13044 if let Some(next_selected_range) = next_selected_range {
13045 self.select_match_ranges(
13046 next_selected_range,
13047 last_selection.reversed,
13048 action.replace_newest,
13049 Some(Autoscroll::newest()),
13050 window,
13051 cx,
13052 );
13053 } else {
13054 select_prev_state.done = true;
13055 }
13056 }
13057
13058 self.select_prev_state = Some(select_prev_state);
13059 } else {
13060 let mut only_carets = true;
13061 let mut same_text_selected = true;
13062 let mut selected_text = None;
13063
13064 let mut selections_iter = selections.iter().peekable();
13065 while let Some(selection) = selections_iter.next() {
13066 if selection.start != selection.end {
13067 only_carets = false;
13068 }
13069
13070 if same_text_selected {
13071 if selected_text.is_none() {
13072 selected_text =
13073 Some(buffer.text_for_range(selection.range()).collect::<String>());
13074 }
13075
13076 if let Some(next_selection) = selections_iter.peek() {
13077 if next_selection.range().len() == selection.range().len() {
13078 let next_selected_text = buffer
13079 .text_for_range(next_selection.range())
13080 .collect::<String>();
13081 if Some(next_selected_text) != selected_text {
13082 same_text_selected = false;
13083 selected_text = None;
13084 }
13085 } else {
13086 same_text_selected = false;
13087 selected_text = None;
13088 }
13089 }
13090 }
13091 }
13092
13093 if only_carets {
13094 for selection in &mut selections {
13095 let word_range = movement::surrounding_word(
13096 &display_map,
13097 selection.start.to_display_point(&display_map),
13098 );
13099 selection.start = word_range.start.to_offset(&display_map, Bias::Left);
13100 selection.end = word_range.end.to_offset(&display_map, Bias::Left);
13101 selection.goal = SelectionGoal::None;
13102 selection.reversed = false;
13103 self.select_match_ranges(
13104 selection.start..selection.end,
13105 selection.reversed,
13106 action.replace_newest,
13107 Some(Autoscroll::newest()),
13108 window,
13109 cx,
13110 );
13111 }
13112 if selections.len() == 1 {
13113 let selection = selections
13114 .last()
13115 .expect("ensured that there's only one selection");
13116 let query = buffer
13117 .text_for_range(selection.start..selection.end)
13118 .collect::<String>();
13119 let is_empty = query.is_empty();
13120 let select_state = SelectNextState {
13121 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
13122 wordwise: true,
13123 done: is_empty,
13124 };
13125 self.select_prev_state = Some(select_state);
13126 } else {
13127 self.select_prev_state = None;
13128 }
13129 } else if let Some(selected_text) = selected_text {
13130 self.select_prev_state = Some(SelectNextState {
13131 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
13132 wordwise: false,
13133 done: false,
13134 });
13135 self.select_previous(action, window, cx)?;
13136 }
13137 }
13138 Ok(())
13139 }
13140
13141 pub fn find_next_match(
13142 &mut self,
13143 _: &FindNextMatch,
13144 window: &mut Window,
13145 cx: &mut Context<Self>,
13146 ) -> Result<()> {
13147 let selections = self.selections.disjoint_anchors();
13148 match selections.first() {
13149 Some(first) if selections.len() >= 2 => {
13150 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13151 s.select_ranges([first.range()]);
13152 });
13153 }
13154 _ => self.select_next(
13155 &SelectNext {
13156 replace_newest: true,
13157 },
13158 window,
13159 cx,
13160 )?,
13161 }
13162 Ok(())
13163 }
13164
13165 pub fn find_previous_match(
13166 &mut self,
13167 _: &FindPreviousMatch,
13168 window: &mut Window,
13169 cx: &mut Context<Self>,
13170 ) -> Result<()> {
13171 let selections = self.selections.disjoint_anchors();
13172 match selections.last() {
13173 Some(last) if selections.len() >= 2 => {
13174 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13175 s.select_ranges([last.range()]);
13176 });
13177 }
13178 _ => self.select_previous(
13179 &SelectPrevious {
13180 replace_newest: true,
13181 },
13182 window,
13183 cx,
13184 )?,
13185 }
13186 Ok(())
13187 }
13188
13189 pub fn toggle_comments(
13190 &mut self,
13191 action: &ToggleComments,
13192 window: &mut Window,
13193 cx: &mut Context<Self>,
13194 ) {
13195 if self.read_only(cx) {
13196 return;
13197 }
13198 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
13199 let text_layout_details = &self.text_layout_details(window);
13200 self.transact(window, cx, |this, window, cx| {
13201 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
13202 let mut edits = Vec::new();
13203 let mut selection_edit_ranges = Vec::new();
13204 let mut last_toggled_row = None;
13205 let snapshot = this.buffer.read(cx).read(cx);
13206 let empty_str: Arc<str> = Arc::default();
13207 let mut suffixes_inserted = Vec::new();
13208 let ignore_indent = action.ignore_indent;
13209
13210 fn comment_prefix_range(
13211 snapshot: &MultiBufferSnapshot,
13212 row: MultiBufferRow,
13213 comment_prefix: &str,
13214 comment_prefix_whitespace: &str,
13215 ignore_indent: bool,
13216 ) -> Range<Point> {
13217 let indent_size = if ignore_indent {
13218 0
13219 } else {
13220 snapshot.indent_size_for_line(row).len
13221 };
13222
13223 let start = Point::new(row.0, indent_size);
13224
13225 let mut line_bytes = snapshot
13226 .bytes_in_range(start..snapshot.max_point())
13227 .flatten()
13228 .copied();
13229
13230 // If this line currently begins with the line comment prefix, then record
13231 // the range containing the prefix.
13232 if line_bytes
13233 .by_ref()
13234 .take(comment_prefix.len())
13235 .eq(comment_prefix.bytes())
13236 {
13237 // Include any whitespace that matches the comment prefix.
13238 let matching_whitespace_len = line_bytes
13239 .zip(comment_prefix_whitespace.bytes())
13240 .take_while(|(a, b)| a == b)
13241 .count() as u32;
13242 let end = Point::new(
13243 start.row,
13244 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
13245 );
13246 start..end
13247 } else {
13248 start..start
13249 }
13250 }
13251
13252 fn comment_suffix_range(
13253 snapshot: &MultiBufferSnapshot,
13254 row: MultiBufferRow,
13255 comment_suffix: &str,
13256 comment_suffix_has_leading_space: bool,
13257 ) -> Range<Point> {
13258 let end = Point::new(row.0, snapshot.line_len(row));
13259 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
13260
13261 let mut line_end_bytes = snapshot
13262 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
13263 .flatten()
13264 .copied();
13265
13266 let leading_space_len = if suffix_start_column > 0
13267 && line_end_bytes.next() == Some(b' ')
13268 && comment_suffix_has_leading_space
13269 {
13270 1
13271 } else {
13272 0
13273 };
13274
13275 // If this line currently begins with the line comment prefix, then record
13276 // the range containing the prefix.
13277 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
13278 let start = Point::new(end.row, suffix_start_column - leading_space_len);
13279 start..end
13280 } else {
13281 end..end
13282 }
13283 }
13284
13285 // TODO: Handle selections that cross excerpts
13286 for selection in &mut selections {
13287 let start_column = snapshot
13288 .indent_size_for_line(MultiBufferRow(selection.start.row))
13289 .len;
13290 let language = if let Some(language) =
13291 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
13292 {
13293 language
13294 } else {
13295 continue;
13296 };
13297
13298 selection_edit_ranges.clear();
13299
13300 // If multiple selections contain a given row, avoid processing that
13301 // row more than once.
13302 let mut start_row = MultiBufferRow(selection.start.row);
13303 if last_toggled_row == Some(start_row) {
13304 start_row = start_row.next_row();
13305 }
13306 let end_row =
13307 if selection.end.row > selection.start.row && selection.end.column == 0 {
13308 MultiBufferRow(selection.end.row - 1)
13309 } else {
13310 MultiBufferRow(selection.end.row)
13311 };
13312 last_toggled_row = Some(end_row);
13313
13314 if start_row > end_row {
13315 continue;
13316 }
13317
13318 // If the language has line comments, toggle those.
13319 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
13320
13321 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
13322 if ignore_indent {
13323 full_comment_prefixes = full_comment_prefixes
13324 .into_iter()
13325 .map(|s| Arc::from(s.trim_end()))
13326 .collect();
13327 }
13328
13329 if !full_comment_prefixes.is_empty() {
13330 let first_prefix = full_comment_prefixes
13331 .first()
13332 .expect("prefixes is non-empty");
13333 let prefix_trimmed_lengths = full_comment_prefixes
13334 .iter()
13335 .map(|p| p.trim_end_matches(' ').len())
13336 .collect::<SmallVec<[usize; 4]>>();
13337
13338 let mut all_selection_lines_are_comments = true;
13339
13340 for row in start_row.0..=end_row.0 {
13341 let row = MultiBufferRow(row);
13342 if start_row < end_row && snapshot.is_line_blank(row) {
13343 continue;
13344 }
13345
13346 let prefix_range = full_comment_prefixes
13347 .iter()
13348 .zip(prefix_trimmed_lengths.iter().copied())
13349 .map(|(prefix, trimmed_prefix_len)| {
13350 comment_prefix_range(
13351 snapshot.deref(),
13352 row,
13353 &prefix[..trimmed_prefix_len],
13354 &prefix[trimmed_prefix_len..],
13355 ignore_indent,
13356 )
13357 })
13358 .max_by_key(|range| range.end.column - range.start.column)
13359 .expect("prefixes is non-empty");
13360
13361 if prefix_range.is_empty() {
13362 all_selection_lines_are_comments = false;
13363 }
13364
13365 selection_edit_ranges.push(prefix_range);
13366 }
13367
13368 if all_selection_lines_are_comments {
13369 edits.extend(
13370 selection_edit_ranges
13371 .iter()
13372 .cloned()
13373 .map(|range| (range, empty_str.clone())),
13374 );
13375 } else {
13376 let min_column = selection_edit_ranges
13377 .iter()
13378 .map(|range| range.start.column)
13379 .min()
13380 .unwrap_or(0);
13381 edits.extend(selection_edit_ranges.iter().map(|range| {
13382 let position = Point::new(range.start.row, min_column);
13383 (position..position, first_prefix.clone())
13384 }));
13385 }
13386 } else if let Some((full_comment_prefix, comment_suffix)) =
13387 language.block_comment_delimiters()
13388 {
13389 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
13390 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
13391 let prefix_range = comment_prefix_range(
13392 snapshot.deref(),
13393 start_row,
13394 comment_prefix,
13395 comment_prefix_whitespace,
13396 ignore_indent,
13397 );
13398 let suffix_range = comment_suffix_range(
13399 snapshot.deref(),
13400 end_row,
13401 comment_suffix.trim_start_matches(' '),
13402 comment_suffix.starts_with(' '),
13403 );
13404
13405 if prefix_range.is_empty() || suffix_range.is_empty() {
13406 edits.push((
13407 prefix_range.start..prefix_range.start,
13408 full_comment_prefix.clone(),
13409 ));
13410 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
13411 suffixes_inserted.push((end_row, comment_suffix.len()));
13412 } else {
13413 edits.push((prefix_range, empty_str.clone()));
13414 edits.push((suffix_range, empty_str.clone()));
13415 }
13416 } else {
13417 continue;
13418 }
13419 }
13420
13421 drop(snapshot);
13422 this.buffer.update(cx, |buffer, cx| {
13423 buffer.edit(edits, None, cx);
13424 });
13425
13426 // Adjust selections so that they end before any comment suffixes that
13427 // were inserted.
13428 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
13429 let mut selections = this.selections.all::<Point>(cx);
13430 let snapshot = this.buffer.read(cx).read(cx);
13431 for selection in &mut selections {
13432 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
13433 match row.cmp(&MultiBufferRow(selection.end.row)) {
13434 Ordering::Less => {
13435 suffixes_inserted.next();
13436 continue;
13437 }
13438 Ordering::Greater => break,
13439 Ordering::Equal => {
13440 if selection.end.column == snapshot.line_len(row) {
13441 if selection.is_empty() {
13442 selection.start.column -= suffix_len as u32;
13443 }
13444 selection.end.column -= suffix_len as u32;
13445 }
13446 break;
13447 }
13448 }
13449 }
13450 }
13451
13452 drop(snapshot);
13453 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13454 s.select(selections)
13455 });
13456
13457 let selections = this.selections.all::<Point>(cx);
13458 let selections_on_single_row = selections.windows(2).all(|selections| {
13459 selections[0].start.row == selections[1].start.row
13460 && selections[0].end.row == selections[1].end.row
13461 && selections[0].start.row == selections[0].end.row
13462 });
13463 let selections_selecting = selections
13464 .iter()
13465 .any(|selection| selection.start != selection.end);
13466 let advance_downwards = action.advance_downwards
13467 && selections_on_single_row
13468 && !selections_selecting
13469 && !matches!(this.mode, EditorMode::SingleLine { .. });
13470
13471 if advance_downwards {
13472 let snapshot = this.buffer.read(cx).snapshot(cx);
13473
13474 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13475 s.move_cursors_with(|display_snapshot, display_point, _| {
13476 let mut point = display_point.to_point(display_snapshot);
13477 point.row += 1;
13478 point = snapshot.clip_point(point, Bias::Left);
13479 let display_point = point.to_display_point(display_snapshot);
13480 let goal = SelectionGoal::HorizontalPosition(
13481 display_snapshot
13482 .x_for_display_point(display_point, text_layout_details)
13483 .into(),
13484 );
13485 (display_point, goal)
13486 })
13487 });
13488 }
13489 });
13490 }
13491
13492 pub fn select_enclosing_symbol(
13493 &mut self,
13494 _: &SelectEnclosingSymbol,
13495 window: &mut Window,
13496 cx: &mut Context<Self>,
13497 ) {
13498 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13499
13500 let buffer = self.buffer.read(cx).snapshot(cx);
13501 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
13502
13503 fn update_selection(
13504 selection: &Selection<usize>,
13505 buffer_snap: &MultiBufferSnapshot,
13506 ) -> Option<Selection<usize>> {
13507 let cursor = selection.head();
13508 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
13509 for symbol in symbols.iter().rev() {
13510 let start = symbol.range.start.to_offset(buffer_snap);
13511 let end = symbol.range.end.to_offset(buffer_snap);
13512 let new_range = start..end;
13513 if start < selection.start || end > selection.end {
13514 return Some(Selection {
13515 id: selection.id,
13516 start: new_range.start,
13517 end: new_range.end,
13518 goal: SelectionGoal::None,
13519 reversed: selection.reversed,
13520 });
13521 }
13522 }
13523 None
13524 }
13525
13526 let mut selected_larger_symbol = false;
13527 let new_selections = old_selections
13528 .iter()
13529 .map(|selection| match update_selection(selection, &buffer) {
13530 Some(new_selection) => {
13531 if new_selection.range() != selection.range() {
13532 selected_larger_symbol = true;
13533 }
13534 new_selection
13535 }
13536 None => selection.clone(),
13537 })
13538 .collect::<Vec<_>>();
13539
13540 if selected_larger_symbol {
13541 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13542 s.select(new_selections);
13543 });
13544 }
13545 }
13546
13547 pub fn select_larger_syntax_node(
13548 &mut self,
13549 _: &SelectLargerSyntaxNode,
13550 window: &mut Window,
13551 cx: &mut Context<Self>,
13552 ) {
13553 let Some(visible_row_count) = self.visible_row_count() else {
13554 return;
13555 };
13556 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
13557 if old_selections.is_empty() {
13558 return;
13559 }
13560
13561 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13562
13563 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13564 let buffer = self.buffer.read(cx).snapshot(cx);
13565
13566 let mut selected_larger_node = false;
13567 let mut new_selections = old_selections
13568 .iter()
13569 .map(|selection| {
13570 let old_range = selection.start..selection.end;
13571
13572 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
13573 // manually select word at selection
13574 if ["string_content", "inline"].contains(&node.kind()) {
13575 let word_range = {
13576 let display_point = buffer
13577 .offset_to_point(old_range.start)
13578 .to_display_point(&display_map);
13579 let Range { start, end } =
13580 movement::surrounding_word(&display_map, display_point);
13581 start.to_point(&display_map).to_offset(&buffer)
13582 ..end.to_point(&display_map).to_offset(&buffer)
13583 };
13584 // ignore if word is already selected
13585 if !word_range.is_empty() && old_range != word_range {
13586 let last_word_range = {
13587 let display_point = buffer
13588 .offset_to_point(old_range.end)
13589 .to_display_point(&display_map);
13590 let Range { start, end } =
13591 movement::surrounding_word(&display_map, display_point);
13592 start.to_point(&display_map).to_offset(&buffer)
13593 ..end.to_point(&display_map).to_offset(&buffer)
13594 };
13595 // only select word if start and end point belongs to same word
13596 if word_range == last_word_range {
13597 selected_larger_node = true;
13598 return Selection {
13599 id: selection.id,
13600 start: word_range.start,
13601 end: word_range.end,
13602 goal: SelectionGoal::None,
13603 reversed: selection.reversed,
13604 };
13605 }
13606 }
13607 }
13608 }
13609
13610 let mut new_range = old_range.clone();
13611 while let Some((_node, containing_range)) =
13612 buffer.syntax_ancestor(new_range.clone())
13613 {
13614 new_range = match containing_range {
13615 MultiOrSingleBufferOffsetRange::Single(_) => break,
13616 MultiOrSingleBufferOffsetRange::Multi(range) => range,
13617 };
13618 if !display_map.intersects_fold(new_range.start)
13619 && !display_map.intersects_fold(new_range.end)
13620 {
13621 break;
13622 }
13623 }
13624
13625 selected_larger_node |= new_range != old_range;
13626 Selection {
13627 id: selection.id,
13628 start: new_range.start,
13629 end: new_range.end,
13630 goal: SelectionGoal::None,
13631 reversed: selection.reversed,
13632 }
13633 })
13634 .collect::<Vec<_>>();
13635
13636 if !selected_larger_node {
13637 return; // don't put this call in the history
13638 }
13639
13640 // scroll based on transformation done to the last selection created by the user
13641 let (last_old, last_new) = old_selections
13642 .last()
13643 .zip(new_selections.last().cloned())
13644 .expect("old_selections isn't empty");
13645
13646 // revert selection
13647 let is_selection_reversed = {
13648 let should_newest_selection_be_reversed = last_old.start != last_new.start;
13649 new_selections.last_mut().expect("checked above").reversed =
13650 should_newest_selection_be_reversed;
13651 should_newest_selection_be_reversed
13652 };
13653
13654 if selected_larger_node {
13655 self.select_syntax_node_history.disable_clearing = true;
13656 self.change_selections(None, window, cx, |s| {
13657 s.select(new_selections.clone());
13658 });
13659 self.select_syntax_node_history.disable_clearing = false;
13660 }
13661
13662 let start_row = last_new.start.to_display_point(&display_map).row().0;
13663 let end_row = last_new.end.to_display_point(&display_map).row().0;
13664 let selection_height = end_row - start_row + 1;
13665 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
13666
13667 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
13668 let scroll_behavior = if fits_on_the_screen {
13669 self.request_autoscroll(Autoscroll::fit(), cx);
13670 SelectSyntaxNodeScrollBehavior::FitSelection
13671 } else if is_selection_reversed {
13672 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13673 SelectSyntaxNodeScrollBehavior::CursorTop
13674 } else {
13675 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13676 SelectSyntaxNodeScrollBehavior::CursorBottom
13677 };
13678
13679 self.select_syntax_node_history.push((
13680 old_selections,
13681 scroll_behavior,
13682 is_selection_reversed,
13683 ));
13684 }
13685
13686 pub fn select_smaller_syntax_node(
13687 &mut self,
13688 _: &SelectSmallerSyntaxNode,
13689 window: &mut Window,
13690 cx: &mut Context<Self>,
13691 ) {
13692 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13693
13694 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
13695 self.select_syntax_node_history.pop()
13696 {
13697 if let Some(selection) = selections.last_mut() {
13698 selection.reversed = is_selection_reversed;
13699 }
13700
13701 self.select_syntax_node_history.disable_clearing = true;
13702 self.change_selections(None, window, cx, |s| {
13703 s.select(selections.to_vec());
13704 });
13705 self.select_syntax_node_history.disable_clearing = false;
13706
13707 match scroll_behavior {
13708 SelectSyntaxNodeScrollBehavior::CursorTop => {
13709 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13710 }
13711 SelectSyntaxNodeScrollBehavior::FitSelection => {
13712 self.request_autoscroll(Autoscroll::fit(), cx);
13713 }
13714 SelectSyntaxNodeScrollBehavior::CursorBottom => {
13715 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13716 }
13717 }
13718 }
13719 }
13720
13721 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
13722 if !EditorSettings::get_global(cx).gutter.runnables {
13723 self.clear_tasks();
13724 return Task::ready(());
13725 }
13726 let project = self.project.as_ref().map(Entity::downgrade);
13727 let task_sources = self.lsp_task_sources(cx);
13728 let multi_buffer = self.buffer.downgrade();
13729 cx.spawn_in(window, async move |editor, cx| {
13730 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
13731 let Some(project) = project.and_then(|p| p.upgrade()) else {
13732 return;
13733 };
13734 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
13735 this.display_map.update(cx, |map, cx| map.snapshot(cx))
13736 }) else {
13737 return;
13738 };
13739
13740 let hide_runnables = project
13741 .update(cx, |project, cx| {
13742 // Do not display any test indicators in non-dev server remote projects.
13743 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
13744 })
13745 .unwrap_or(true);
13746 if hide_runnables {
13747 return;
13748 }
13749 let new_rows =
13750 cx.background_spawn({
13751 let snapshot = display_snapshot.clone();
13752 async move {
13753 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
13754 }
13755 })
13756 .await;
13757 let Ok(lsp_tasks) =
13758 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
13759 else {
13760 return;
13761 };
13762 let lsp_tasks = lsp_tasks.await;
13763
13764 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
13765 lsp_tasks
13766 .into_iter()
13767 .flat_map(|(kind, tasks)| {
13768 tasks.into_iter().filter_map(move |(location, task)| {
13769 Some((kind.clone(), location?, task))
13770 })
13771 })
13772 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
13773 let buffer = location.target.buffer;
13774 let buffer_snapshot = buffer.read(cx).snapshot();
13775 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
13776 |(excerpt_id, snapshot, _)| {
13777 if snapshot.remote_id() == buffer_snapshot.remote_id() {
13778 display_snapshot
13779 .buffer_snapshot
13780 .anchor_in_excerpt(excerpt_id, location.target.range.start)
13781 } else {
13782 None
13783 }
13784 },
13785 );
13786 if let Some(offset) = offset {
13787 let task_buffer_range =
13788 location.target.range.to_point(&buffer_snapshot);
13789 let context_buffer_range =
13790 task_buffer_range.to_offset(&buffer_snapshot);
13791 let context_range = BufferOffset(context_buffer_range.start)
13792 ..BufferOffset(context_buffer_range.end);
13793
13794 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
13795 .or_insert_with(|| RunnableTasks {
13796 templates: Vec::new(),
13797 offset,
13798 column: task_buffer_range.start.column,
13799 extra_variables: HashMap::default(),
13800 context_range,
13801 })
13802 .templates
13803 .push((kind, task.original_task().clone()));
13804 }
13805
13806 acc
13807 })
13808 }) else {
13809 return;
13810 };
13811
13812 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
13813 buffer.language_settings(cx).tasks.prefer_lsp
13814 }) else {
13815 return;
13816 };
13817
13818 let rows = Self::runnable_rows(
13819 project,
13820 display_snapshot,
13821 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
13822 new_rows,
13823 cx.clone(),
13824 );
13825 editor
13826 .update(cx, |editor, _| {
13827 editor.clear_tasks();
13828 for (key, mut value) in rows {
13829 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
13830 value.templates.extend(lsp_tasks.templates);
13831 }
13832
13833 editor.insert_tasks(key, value);
13834 }
13835 for (key, value) in lsp_tasks_by_rows {
13836 editor.insert_tasks(key, value);
13837 }
13838 })
13839 .ok();
13840 })
13841 }
13842 fn fetch_runnable_ranges(
13843 snapshot: &DisplaySnapshot,
13844 range: Range<Anchor>,
13845 ) -> Vec<language::RunnableRange> {
13846 snapshot.buffer_snapshot.runnable_ranges(range).collect()
13847 }
13848
13849 fn runnable_rows(
13850 project: Entity<Project>,
13851 snapshot: DisplaySnapshot,
13852 prefer_lsp: bool,
13853 runnable_ranges: Vec<RunnableRange>,
13854 mut cx: AsyncWindowContext,
13855 ) -> Vec<((BufferId, BufferRow), RunnableTasks)> {
13856 runnable_ranges
13857 .into_iter()
13858 .filter_map(|mut runnable| {
13859 let mut tasks = cx
13860 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
13861 .ok()?;
13862 if prefer_lsp {
13863 tasks.retain(|(task_kind, _)| {
13864 !matches!(task_kind, TaskSourceKind::Language { .. })
13865 });
13866 }
13867 if tasks.is_empty() {
13868 return None;
13869 }
13870
13871 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
13872
13873 let row = snapshot
13874 .buffer_snapshot
13875 .buffer_line_for_row(MultiBufferRow(point.row))?
13876 .1
13877 .start
13878 .row;
13879
13880 let context_range =
13881 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
13882 Some((
13883 (runnable.buffer_id, row),
13884 RunnableTasks {
13885 templates: tasks,
13886 offset: snapshot
13887 .buffer_snapshot
13888 .anchor_before(runnable.run_range.start),
13889 context_range,
13890 column: point.column,
13891 extra_variables: runnable.extra_captures,
13892 },
13893 ))
13894 })
13895 .collect()
13896 }
13897
13898 fn templates_with_tags(
13899 project: &Entity<Project>,
13900 runnable: &mut Runnable,
13901 cx: &mut App,
13902 ) -> Vec<(TaskSourceKind, TaskTemplate)> {
13903 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
13904 let (worktree_id, file) = project
13905 .buffer_for_id(runnable.buffer, cx)
13906 .and_then(|buffer| buffer.read(cx).file())
13907 .map(|file| (file.worktree_id(cx), file.clone()))
13908 .unzip();
13909
13910 (
13911 project.task_store().read(cx).task_inventory().cloned(),
13912 worktree_id,
13913 file,
13914 )
13915 });
13916
13917 let mut templates_with_tags = mem::take(&mut runnable.tags)
13918 .into_iter()
13919 .flat_map(|RunnableTag(tag)| {
13920 inventory
13921 .as_ref()
13922 .into_iter()
13923 .flat_map(|inventory| {
13924 inventory.read(cx).list_tasks(
13925 file.clone(),
13926 Some(runnable.language.clone()),
13927 worktree_id,
13928 cx,
13929 )
13930 })
13931 .filter(move |(_, template)| {
13932 template.tags.iter().any(|source_tag| source_tag == &tag)
13933 })
13934 })
13935 .sorted_by_key(|(kind, _)| kind.to_owned())
13936 .collect::<Vec<_>>();
13937 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
13938 // Strongest source wins; if we have worktree tag binding, prefer that to
13939 // global and language bindings;
13940 // if we have a global binding, prefer that to language binding.
13941 let first_mismatch = templates_with_tags
13942 .iter()
13943 .position(|(tag_source, _)| tag_source != leading_tag_source);
13944 if let Some(index) = first_mismatch {
13945 templates_with_tags.truncate(index);
13946 }
13947 }
13948
13949 templates_with_tags
13950 }
13951
13952 pub fn move_to_enclosing_bracket(
13953 &mut self,
13954 _: &MoveToEnclosingBracket,
13955 window: &mut Window,
13956 cx: &mut Context<Self>,
13957 ) {
13958 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13959 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13960 s.move_offsets_with(|snapshot, selection| {
13961 let Some(enclosing_bracket_ranges) =
13962 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
13963 else {
13964 return;
13965 };
13966
13967 let mut best_length = usize::MAX;
13968 let mut best_inside = false;
13969 let mut best_in_bracket_range = false;
13970 let mut best_destination = None;
13971 for (open, close) in enclosing_bracket_ranges {
13972 let close = close.to_inclusive();
13973 let length = close.end() - open.start;
13974 let inside = selection.start >= open.end && selection.end <= *close.start();
13975 let in_bracket_range = open.to_inclusive().contains(&selection.head())
13976 || close.contains(&selection.head());
13977
13978 // If best is next to a bracket and current isn't, skip
13979 if !in_bracket_range && best_in_bracket_range {
13980 continue;
13981 }
13982
13983 // Prefer smaller lengths unless best is inside and current isn't
13984 if length > best_length && (best_inside || !inside) {
13985 continue;
13986 }
13987
13988 best_length = length;
13989 best_inside = inside;
13990 best_in_bracket_range = in_bracket_range;
13991 best_destination = Some(
13992 if close.contains(&selection.start) && close.contains(&selection.end) {
13993 if inside { open.end } else { open.start }
13994 } else if inside {
13995 *close.start()
13996 } else {
13997 *close.end()
13998 },
13999 );
14000 }
14001
14002 if let Some(destination) = best_destination {
14003 selection.collapse_to(destination, SelectionGoal::None);
14004 }
14005 })
14006 });
14007 }
14008
14009 pub fn undo_selection(
14010 &mut self,
14011 _: &UndoSelection,
14012 window: &mut Window,
14013 cx: &mut Context<Self>,
14014 ) {
14015 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14016 self.end_selection(window, cx);
14017 self.selection_history.mode = SelectionHistoryMode::Undoing;
14018 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
14019 self.change_selections(None, window, cx, |s| {
14020 s.select_anchors(entry.selections.to_vec())
14021 });
14022 self.select_next_state = entry.select_next_state;
14023 self.select_prev_state = entry.select_prev_state;
14024 self.add_selections_state = entry.add_selections_state;
14025 self.request_autoscroll(Autoscroll::newest(), cx);
14026 }
14027 self.selection_history.mode = SelectionHistoryMode::Normal;
14028 }
14029
14030 pub fn redo_selection(
14031 &mut self,
14032 _: &RedoSelection,
14033 window: &mut Window,
14034 cx: &mut Context<Self>,
14035 ) {
14036 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14037 self.end_selection(window, cx);
14038 self.selection_history.mode = SelectionHistoryMode::Redoing;
14039 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
14040 self.change_selections(None, window, cx, |s| {
14041 s.select_anchors(entry.selections.to_vec())
14042 });
14043 self.select_next_state = entry.select_next_state;
14044 self.select_prev_state = entry.select_prev_state;
14045 self.add_selections_state = entry.add_selections_state;
14046 self.request_autoscroll(Autoscroll::newest(), cx);
14047 }
14048 self.selection_history.mode = SelectionHistoryMode::Normal;
14049 }
14050
14051 pub fn expand_excerpts(
14052 &mut self,
14053 action: &ExpandExcerpts,
14054 _: &mut Window,
14055 cx: &mut Context<Self>,
14056 ) {
14057 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
14058 }
14059
14060 pub fn expand_excerpts_down(
14061 &mut self,
14062 action: &ExpandExcerptsDown,
14063 _: &mut Window,
14064 cx: &mut Context<Self>,
14065 ) {
14066 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
14067 }
14068
14069 pub fn expand_excerpts_up(
14070 &mut self,
14071 action: &ExpandExcerptsUp,
14072 _: &mut Window,
14073 cx: &mut Context<Self>,
14074 ) {
14075 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
14076 }
14077
14078 pub fn expand_excerpts_for_direction(
14079 &mut self,
14080 lines: u32,
14081 direction: ExpandExcerptDirection,
14082
14083 cx: &mut Context<Self>,
14084 ) {
14085 let selections = self.selections.disjoint_anchors();
14086
14087 let lines = if lines == 0 {
14088 EditorSettings::get_global(cx).expand_excerpt_lines
14089 } else {
14090 lines
14091 };
14092
14093 self.buffer.update(cx, |buffer, cx| {
14094 let snapshot = buffer.snapshot(cx);
14095 let mut excerpt_ids = selections
14096 .iter()
14097 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
14098 .collect::<Vec<_>>();
14099 excerpt_ids.sort();
14100 excerpt_ids.dedup();
14101 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
14102 })
14103 }
14104
14105 pub fn expand_excerpt(
14106 &mut self,
14107 excerpt: ExcerptId,
14108 direction: ExpandExcerptDirection,
14109 window: &mut Window,
14110 cx: &mut Context<Self>,
14111 ) {
14112 let current_scroll_position = self.scroll_position(cx);
14113 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
14114 let mut should_scroll_up = false;
14115
14116 if direction == ExpandExcerptDirection::Down {
14117 let multi_buffer = self.buffer.read(cx);
14118 let snapshot = multi_buffer.snapshot(cx);
14119 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
14120 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
14121 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
14122 let buffer_snapshot = buffer.read(cx).snapshot();
14123 let excerpt_end_row =
14124 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
14125 let last_row = buffer_snapshot.max_point().row;
14126 let lines_below = last_row.saturating_sub(excerpt_end_row);
14127 should_scroll_up = lines_below >= lines_to_expand;
14128 }
14129 }
14130 }
14131 }
14132
14133 self.buffer.update(cx, |buffer, cx| {
14134 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
14135 });
14136
14137 if should_scroll_up {
14138 let new_scroll_position =
14139 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
14140 self.set_scroll_position(new_scroll_position, window, cx);
14141 }
14142 }
14143
14144 pub fn go_to_singleton_buffer_point(
14145 &mut self,
14146 point: Point,
14147 window: &mut Window,
14148 cx: &mut Context<Self>,
14149 ) {
14150 self.go_to_singleton_buffer_range(point..point, window, cx);
14151 }
14152
14153 pub fn go_to_singleton_buffer_range(
14154 &mut self,
14155 range: Range<Point>,
14156 window: &mut Window,
14157 cx: &mut Context<Self>,
14158 ) {
14159 let multibuffer = self.buffer().read(cx);
14160 let Some(buffer) = multibuffer.as_singleton() else {
14161 return;
14162 };
14163 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
14164 return;
14165 };
14166 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
14167 return;
14168 };
14169 self.change_selections(Some(Autoscroll::center()), window, cx, |s| {
14170 s.select_anchor_ranges([start..end])
14171 });
14172 }
14173
14174 pub fn go_to_diagnostic(
14175 &mut self,
14176 _: &GoToDiagnostic,
14177 window: &mut Window,
14178 cx: &mut Context<Self>,
14179 ) {
14180 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14181 self.go_to_diagnostic_impl(Direction::Next, window, cx)
14182 }
14183
14184 pub fn go_to_prev_diagnostic(
14185 &mut self,
14186 _: &GoToPreviousDiagnostic,
14187 window: &mut Window,
14188 cx: &mut Context<Self>,
14189 ) {
14190 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14191 self.go_to_diagnostic_impl(Direction::Prev, window, cx)
14192 }
14193
14194 pub fn go_to_diagnostic_impl(
14195 &mut self,
14196 direction: Direction,
14197 window: &mut Window,
14198 cx: &mut Context<Self>,
14199 ) {
14200 let buffer = self.buffer.read(cx).snapshot(cx);
14201 let selection = self.selections.newest::<usize>(cx);
14202
14203 let mut active_group_id = None;
14204 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
14205 if active_group.active_range.start.to_offset(&buffer) == selection.start {
14206 active_group_id = Some(active_group.group_id);
14207 }
14208 }
14209
14210 fn filtered(
14211 snapshot: EditorSnapshot,
14212 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
14213 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
14214 diagnostics
14215 .filter(|entry| entry.range.start != entry.range.end)
14216 .filter(|entry| !entry.diagnostic.is_unnecessary)
14217 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
14218 }
14219
14220 let snapshot = self.snapshot(window, cx);
14221 let before = filtered(
14222 snapshot.clone(),
14223 buffer
14224 .diagnostics_in_range(0..selection.start)
14225 .filter(|entry| entry.range.start <= selection.start),
14226 );
14227 let after = filtered(
14228 snapshot,
14229 buffer
14230 .diagnostics_in_range(selection.start..buffer.len())
14231 .filter(|entry| entry.range.start >= selection.start),
14232 );
14233
14234 let mut found: Option<DiagnosticEntry<usize>> = None;
14235 if direction == Direction::Prev {
14236 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
14237 {
14238 for diagnostic in prev_diagnostics.into_iter().rev() {
14239 if diagnostic.range.start != selection.start
14240 || active_group_id
14241 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
14242 {
14243 found = Some(diagnostic);
14244 break 'outer;
14245 }
14246 }
14247 }
14248 } else {
14249 for diagnostic in after.chain(before) {
14250 if diagnostic.range.start != selection.start
14251 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
14252 {
14253 found = Some(diagnostic);
14254 break;
14255 }
14256 }
14257 }
14258 let Some(next_diagnostic) = found else {
14259 return;
14260 };
14261
14262 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
14263 return;
14264 };
14265 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14266 s.select_ranges(vec![
14267 next_diagnostic.range.start..next_diagnostic.range.start,
14268 ])
14269 });
14270 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
14271 self.refresh_inline_completion(false, true, window, cx);
14272 }
14273
14274 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
14275 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14276 let snapshot = self.snapshot(window, cx);
14277 let selection = self.selections.newest::<Point>(cx);
14278 self.go_to_hunk_before_or_after_position(
14279 &snapshot,
14280 selection.head(),
14281 Direction::Next,
14282 window,
14283 cx,
14284 );
14285 }
14286
14287 pub fn go_to_hunk_before_or_after_position(
14288 &mut self,
14289 snapshot: &EditorSnapshot,
14290 position: Point,
14291 direction: Direction,
14292 window: &mut Window,
14293 cx: &mut Context<Editor>,
14294 ) {
14295 let row = if direction == Direction::Next {
14296 self.hunk_after_position(snapshot, position)
14297 .map(|hunk| hunk.row_range.start)
14298 } else {
14299 self.hunk_before_position(snapshot, position)
14300 };
14301
14302 if let Some(row) = row {
14303 let destination = Point::new(row.0, 0);
14304 let autoscroll = Autoscroll::center();
14305
14306 self.unfold_ranges(&[destination..destination], false, false, cx);
14307 self.change_selections(Some(autoscroll), window, cx, |s| {
14308 s.select_ranges([destination..destination]);
14309 });
14310 }
14311 }
14312
14313 fn hunk_after_position(
14314 &mut self,
14315 snapshot: &EditorSnapshot,
14316 position: Point,
14317 ) -> Option<MultiBufferDiffHunk> {
14318 snapshot
14319 .buffer_snapshot
14320 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
14321 .find(|hunk| hunk.row_range.start.0 > position.row)
14322 .or_else(|| {
14323 snapshot
14324 .buffer_snapshot
14325 .diff_hunks_in_range(Point::zero()..position)
14326 .find(|hunk| hunk.row_range.end.0 < position.row)
14327 })
14328 }
14329
14330 fn go_to_prev_hunk(
14331 &mut self,
14332 _: &GoToPreviousHunk,
14333 window: &mut Window,
14334 cx: &mut Context<Self>,
14335 ) {
14336 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14337 let snapshot = self.snapshot(window, cx);
14338 let selection = self.selections.newest::<Point>(cx);
14339 self.go_to_hunk_before_or_after_position(
14340 &snapshot,
14341 selection.head(),
14342 Direction::Prev,
14343 window,
14344 cx,
14345 );
14346 }
14347
14348 fn hunk_before_position(
14349 &mut self,
14350 snapshot: &EditorSnapshot,
14351 position: Point,
14352 ) -> Option<MultiBufferRow> {
14353 snapshot
14354 .buffer_snapshot
14355 .diff_hunk_before(position)
14356 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
14357 }
14358
14359 fn go_to_next_change(
14360 &mut self,
14361 _: &GoToNextChange,
14362 window: &mut Window,
14363 cx: &mut Context<Self>,
14364 ) {
14365 if let Some(selections) = self
14366 .change_list
14367 .next_change(1, Direction::Next)
14368 .map(|s| s.to_vec())
14369 {
14370 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14371 let map = s.display_map();
14372 s.select_display_ranges(selections.iter().map(|a| {
14373 let point = a.to_display_point(&map);
14374 point..point
14375 }))
14376 })
14377 }
14378 }
14379
14380 fn go_to_previous_change(
14381 &mut self,
14382 _: &GoToPreviousChange,
14383 window: &mut Window,
14384 cx: &mut Context<Self>,
14385 ) {
14386 if let Some(selections) = self
14387 .change_list
14388 .next_change(1, Direction::Prev)
14389 .map(|s| s.to_vec())
14390 {
14391 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14392 let map = s.display_map();
14393 s.select_display_ranges(selections.iter().map(|a| {
14394 let point = a.to_display_point(&map);
14395 point..point
14396 }))
14397 })
14398 }
14399 }
14400
14401 fn go_to_line<T: 'static>(
14402 &mut self,
14403 position: Anchor,
14404 highlight_color: Option<Hsla>,
14405 window: &mut Window,
14406 cx: &mut Context<Self>,
14407 ) {
14408 let snapshot = self.snapshot(window, cx).display_snapshot;
14409 let position = position.to_point(&snapshot.buffer_snapshot);
14410 let start = snapshot
14411 .buffer_snapshot
14412 .clip_point(Point::new(position.row, 0), Bias::Left);
14413 let end = start + Point::new(1, 0);
14414 let start = snapshot.buffer_snapshot.anchor_before(start);
14415 let end = snapshot.buffer_snapshot.anchor_before(end);
14416
14417 self.highlight_rows::<T>(
14418 start..end,
14419 highlight_color
14420 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
14421 Default::default(),
14422 cx,
14423 );
14424
14425 if self.buffer.read(cx).is_singleton() {
14426 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
14427 }
14428 }
14429
14430 pub fn go_to_definition(
14431 &mut self,
14432 _: &GoToDefinition,
14433 window: &mut Window,
14434 cx: &mut Context<Self>,
14435 ) -> Task<Result<Navigated>> {
14436 let definition =
14437 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
14438 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
14439 cx.spawn_in(window, async move |editor, cx| {
14440 if definition.await? == Navigated::Yes {
14441 return Ok(Navigated::Yes);
14442 }
14443 match fallback_strategy {
14444 GoToDefinitionFallback::None => Ok(Navigated::No),
14445 GoToDefinitionFallback::FindAllReferences => {
14446 match editor.update_in(cx, |editor, window, cx| {
14447 editor.find_all_references(&FindAllReferences, window, cx)
14448 })? {
14449 Some(references) => references.await,
14450 None => Ok(Navigated::No),
14451 }
14452 }
14453 }
14454 })
14455 }
14456
14457 pub fn go_to_declaration(
14458 &mut self,
14459 _: &GoToDeclaration,
14460 window: &mut Window,
14461 cx: &mut Context<Self>,
14462 ) -> Task<Result<Navigated>> {
14463 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
14464 }
14465
14466 pub fn go_to_declaration_split(
14467 &mut self,
14468 _: &GoToDeclaration,
14469 window: &mut Window,
14470 cx: &mut Context<Self>,
14471 ) -> Task<Result<Navigated>> {
14472 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
14473 }
14474
14475 pub fn go_to_implementation(
14476 &mut self,
14477 _: &GoToImplementation,
14478 window: &mut Window,
14479 cx: &mut Context<Self>,
14480 ) -> Task<Result<Navigated>> {
14481 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
14482 }
14483
14484 pub fn go_to_implementation_split(
14485 &mut self,
14486 _: &GoToImplementationSplit,
14487 window: &mut Window,
14488 cx: &mut Context<Self>,
14489 ) -> Task<Result<Navigated>> {
14490 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
14491 }
14492
14493 pub fn go_to_type_definition(
14494 &mut self,
14495 _: &GoToTypeDefinition,
14496 window: &mut Window,
14497 cx: &mut Context<Self>,
14498 ) -> Task<Result<Navigated>> {
14499 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
14500 }
14501
14502 pub fn go_to_definition_split(
14503 &mut self,
14504 _: &GoToDefinitionSplit,
14505 window: &mut Window,
14506 cx: &mut Context<Self>,
14507 ) -> Task<Result<Navigated>> {
14508 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
14509 }
14510
14511 pub fn go_to_type_definition_split(
14512 &mut self,
14513 _: &GoToTypeDefinitionSplit,
14514 window: &mut Window,
14515 cx: &mut Context<Self>,
14516 ) -> Task<Result<Navigated>> {
14517 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
14518 }
14519
14520 fn go_to_definition_of_kind(
14521 &mut self,
14522 kind: GotoDefinitionKind,
14523 split: bool,
14524 window: &mut Window,
14525 cx: &mut Context<Self>,
14526 ) -> Task<Result<Navigated>> {
14527 let Some(provider) = self.semantics_provider.clone() else {
14528 return Task::ready(Ok(Navigated::No));
14529 };
14530 let head = self.selections.newest::<usize>(cx).head();
14531 let buffer = self.buffer.read(cx);
14532 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
14533 text_anchor
14534 } else {
14535 return Task::ready(Ok(Navigated::No));
14536 };
14537
14538 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
14539 return Task::ready(Ok(Navigated::No));
14540 };
14541
14542 cx.spawn_in(window, async move |editor, cx| {
14543 let definitions = definitions.await?;
14544 let navigated = editor
14545 .update_in(cx, |editor, window, cx| {
14546 editor.navigate_to_hover_links(
14547 Some(kind),
14548 definitions
14549 .into_iter()
14550 .filter(|location| {
14551 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
14552 })
14553 .map(HoverLink::Text)
14554 .collect::<Vec<_>>(),
14555 split,
14556 window,
14557 cx,
14558 )
14559 })?
14560 .await?;
14561 anyhow::Ok(navigated)
14562 })
14563 }
14564
14565 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
14566 let selection = self.selections.newest_anchor();
14567 let head = selection.head();
14568 let tail = selection.tail();
14569
14570 let Some((buffer, start_position)) =
14571 self.buffer.read(cx).text_anchor_for_position(head, cx)
14572 else {
14573 return;
14574 };
14575
14576 let end_position = if head != tail {
14577 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
14578 return;
14579 };
14580 Some(pos)
14581 } else {
14582 None
14583 };
14584
14585 let url_finder = cx.spawn_in(window, async move |editor, cx| {
14586 let url = if let Some(end_pos) = end_position {
14587 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
14588 } else {
14589 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
14590 };
14591
14592 if let Some(url) = url {
14593 editor.update(cx, |_, cx| {
14594 cx.open_url(&url);
14595 })
14596 } else {
14597 Ok(())
14598 }
14599 });
14600
14601 url_finder.detach();
14602 }
14603
14604 pub fn open_selected_filename(
14605 &mut self,
14606 _: &OpenSelectedFilename,
14607 window: &mut Window,
14608 cx: &mut Context<Self>,
14609 ) {
14610 let Some(workspace) = self.workspace() else {
14611 return;
14612 };
14613
14614 let position = self.selections.newest_anchor().head();
14615
14616 let Some((buffer, buffer_position)) =
14617 self.buffer.read(cx).text_anchor_for_position(position, cx)
14618 else {
14619 return;
14620 };
14621
14622 let project = self.project.clone();
14623
14624 cx.spawn_in(window, async move |_, cx| {
14625 let result = find_file(&buffer, project, buffer_position, cx).await;
14626
14627 if let Some((_, path)) = result {
14628 workspace
14629 .update_in(cx, |workspace, window, cx| {
14630 workspace.open_resolved_path(path, window, cx)
14631 })?
14632 .await?;
14633 }
14634 anyhow::Ok(())
14635 })
14636 .detach();
14637 }
14638
14639 pub(crate) fn navigate_to_hover_links(
14640 &mut self,
14641 kind: Option<GotoDefinitionKind>,
14642 mut definitions: Vec<HoverLink>,
14643 split: bool,
14644 window: &mut Window,
14645 cx: &mut Context<Editor>,
14646 ) -> Task<Result<Navigated>> {
14647 // If there is one definition, just open it directly
14648 if definitions.len() == 1 {
14649 let definition = definitions.pop().unwrap();
14650
14651 enum TargetTaskResult {
14652 Location(Option<Location>),
14653 AlreadyNavigated,
14654 }
14655
14656 let target_task = match definition {
14657 HoverLink::Text(link) => {
14658 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
14659 }
14660 HoverLink::InlayHint(lsp_location, server_id) => {
14661 let computation =
14662 self.compute_target_location(lsp_location, server_id, window, cx);
14663 cx.background_spawn(async move {
14664 let location = computation.await?;
14665 Ok(TargetTaskResult::Location(location))
14666 })
14667 }
14668 HoverLink::Url(url) => {
14669 cx.open_url(&url);
14670 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
14671 }
14672 HoverLink::File(path) => {
14673 if let Some(workspace) = self.workspace() {
14674 cx.spawn_in(window, async move |_, cx| {
14675 workspace
14676 .update_in(cx, |workspace, window, cx| {
14677 workspace.open_resolved_path(path, window, cx)
14678 })?
14679 .await
14680 .map(|_| TargetTaskResult::AlreadyNavigated)
14681 })
14682 } else {
14683 Task::ready(Ok(TargetTaskResult::Location(None)))
14684 }
14685 }
14686 };
14687 cx.spawn_in(window, async move |editor, cx| {
14688 let target = match target_task.await.context("target resolution task")? {
14689 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
14690 TargetTaskResult::Location(None) => return Ok(Navigated::No),
14691 TargetTaskResult::Location(Some(target)) => target,
14692 };
14693
14694 editor.update_in(cx, |editor, window, cx| {
14695 let Some(workspace) = editor.workspace() else {
14696 return Navigated::No;
14697 };
14698 let pane = workspace.read(cx).active_pane().clone();
14699
14700 let range = target.range.to_point(target.buffer.read(cx));
14701 let range = editor.range_for_match(&range);
14702 let range = collapse_multiline_range(range);
14703
14704 if !split
14705 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
14706 {
14707 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
14708 } else {
14709 window.defer(cx, move |window, cx| {
14710 let target_editor: Entity<Self> =
14711 workspace.update(cx, |workspace, cx| {
14712 let pane = if split {
14713 workspace.adjacent_pane(window, cx)
14714 } else {
14715 workspace.active_pane().clone()
14716 };
14717
14718 workspace.open_project_item(
14719 pane,
14720 target.buffer.clone(),
14721 true,
14722 true,
14723 window,
14724 cx,
14725 )
14726 });
14727 target_editor.update(cx, |target_editor, cx| {
14728 // When selecting a definition in a different buffer, disable the nav history
14729 // to avoid creating a history entry at the previous cursor location.
14730 pane.update(cx, |pane, _| pane.disable_history());
14731 target_editor.go_to_singleton_buffer_range(range, window, cx);
14732 pane.update(cx, |pane, _| pane.enable_history());
14733 });
14734 });
14735 }
14736 Navigated::Yes
14737 })
14738 })
14739 } else if !definitions.is_empty() {
14740 cx.spawn_in(window, async move |editor, cx| {
14741 let (title, location_tasks, workspace) = editor
14742 .update_in(cx, |editor, window, cx| {
14743 let tab_kind = match kind {
14744 Some(GotoDefinitionKind::Implementation) => "Implementations",
14745 _ => "Definitions",
14746 };
14747 let title = definitions
14748 .iter()
14749 .find_map(|definition| match definition {
14750 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
14751 let buffer = origin.buffer.read(cx);
14752 format!(
14753 "{} for {}",
14754 tab_kind,
14755 buffer
14756 .text_for_range(origin.range.clone())
14757 .collect::<String>()
14758 )
14759 }),
14760 HoverLink::InlayHint(_, _) => None,
14761 HoverLink::Url(_) => None,
14762 HoverLink::File(_) => None,
14763 })
14764 .unwrap_or(tab_kind.to_string());
14765 let location_tasks = definitions
14766 .into_iter()
14767 .map(|definition| match definition {
14768 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
14769 HoverLink::InlayHint(lsp_location, server_id) => editor
14770 .compute_target_location(lsp_location, server_id, window, cx),
14771 HoverLink::Url(_) => Task::ready(Ok(None)),
14772 HoverLink::File(_) => Task::ready(Ok(None)),
14773 })
14774 .collect::<Vec<_>>();
14775 (title, location_tasks, editor.workspace().clone())
14776 })
14777 .context("location tasks preparation")?;
14778
14779 let locations = future::join_all(location_tasks)
14780 .await
14781 .into_iter()
14782 .filter_map(|location| location.transpose())
14783 .collect::<Result<_>>()
14784 .context("location tasks")?;
14785
14786 let Some(workspace) = workspace else {
14787 return Ok(Navigated::No);
14788 };
14789 let opened = workspace
14790 .update_in(cx, |workspace, window, cx| {
14791 Self::open_locations_in_multibuffer(
14792 workspace,
14793 locations,
14794 title,
14795 split,
14796 MultibufferSelectionMode::First,
14797 window,
14798 cx,
14799 )
14800 })
14801 .ok();
14802
14803 anyhow::Ok(Navigated::from_bool(opened.is_some()))
14804 })
14805 } else {
14806 Task::ready(Ok(Navigated::No))
14807 }
14808 }
14809
14810 fn compute_target_location(
14811 &self,
14812 lsp_location: lsp::Location,
14813 server_id: LanguageServerId,
14814 window: &mut Window,
14815 cx: &mut Context<Self>,
14816 ) -> Task<anyhow::Result<Option<Location>>> {
14817 let Some(project) = self.project.clone() else {
14818 return Task::ready(Ok(None));
14819 };
14820
14821 cx.spawn_in(window, async move |editor, cx| {
14822 let location_task = editor.update(cx, |_, cx| {
14823 project.update(cx, |project, cx| {
14824 let language_server_name = project
14825 .language_server_statuses(cx)
14826 .find(|(id, _)| server_id == *id)
14827 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
14828 language_server_name.map(|language_server_name| {
14829 project.open_local_buffer_via_lsp(
14830 lsp_location.uri.clone(),
14831 server_id,
14832 language_server_name,
14833 cx,
14834 )
14835 })
14836 })
14837 })?;
14838 let location = match location_task {
14839 Some(task) => Some({
14840 let target_buffer_handle = task.await.context("open local buffer")?;
14841 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
14842 let target_start = target_buffer
14843 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
14844 let target_end = target_buffer
14845 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
14846 target_buffer.anchor_after(target_start)
14847 ..target_buffer.anchor_before(target_end)
14848 })?;
14849 Location {
14850 buffer: target_buffer_handle,
14851 range,
14852 }
14853 }),
14854 None => None,
14855 };
14856 Ok(location)
14857 })
14858 }
14859
14860 pub fn find_all_references(
14861 &mut self,
14862 _: &FindAllReferences,
14863 window: &mut Window,
14864 cx: &mut Context<Self>,
14865 ) -> Option<Task<Result<Navigated>>> {
14866 let selection = self.selections.newest::<usize>(cx);
14867 let multi_buffer = self.buffer.read(cx);
14868 let head = selection.head();
14869
14870 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
14871 let head_anchor = multi_buffer_snapshot.anchor_at(
14872 head,
14873 if head < selection.tail() {
14874 Bias::Right
14875 } else {
14876 Bias::Left
14877 },
14878 );
14879
14880 match self
14881 .find_all_references_task_sources
14882 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
14883 {
14884 Ok(_) => {
14885 log::info!(
14886 "Ignoring repeated FindAllReferences invocation with the position of already running task"
14887 );
14888 return None;
14889 }
14890 Err(i) => {
14891 self.find_all_references_task_sources.insert(i, head_anchor);
14892 }
14893 }
14894
14895 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
14896 let workspace = self.workspace()?;
14897 let project = workspace.read(cx).project().clone();
14898 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
14899 Some(cx.spawn_in(window, async move |editor, cx| {
14900 let _cleanup = cx.on_drop(&editor, move |editor, _| {
14901 if let Ok(i) = editor
14902 .find_all_references_task_sources
14903 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
14904 {
14905 editor.find_all_references_task_sources.remove(i);
14906 }
14907 });
14908
14909 let locations = references.await?;
14910 if locations.is_empty() {
14911 return anyhow::Ok(Navigated::No);
14912 }
14913
14914 workspace.update_in(cx, |workspace, window, cx| {
14915 let title = locations
14916 .first()
14917 .as_ref()
14918 .map(|location| {
14919 let buffer = location.buffer.read(cx);
14920 format!(
14921 "References to `{}`",
14922 buffer
14923 .text_for_range(location.range.clone())
14924 .collect::<String>()
14925 )
14926 })
14927 .unwrap();
14928 Self::open_locations_in_multibuffer(
14929 workspace,
14930 locations,
14931 title,
14932 false,
14933 MultibufferSelectionMode::First,
14934 window,
14935 cx,
14936 );
14937 Navigated::Yes
14938 })
14939 }))
14940 }
14941
14942 /// Opens a multibuffer with the given project locations in it
14943 pub fn open_locations_in_multibuffer(
14944 workspace: &mut Workspace,
14945 mut locations: Vec<Location>,
14946 title: String,
14947 split: bool,
14948 multibuffer_selection_mode: MultibufferSelectionMode,
14949 window: &mut Window,
14950 cx: &mut Context<Workspace>,
14951 ) {
14952 // If there are multiple definitions, open them in a multibuffer
14953 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
14954 let mut locations = locations.into_iter().peekable();
14955 let mut ranges: Vec<Range<Anchor>> = Vec::new();
14956 let capability = workspace.project().read(cx).capability();
14957
14958 let excerpt_buffer = cx.new(|cx| {
14959 let mut multibuffer = MultiBuffer::new(capability);
14960 while let Some(location) = locations.next() {
14961 let buffer = location.buffer.read(cx);
14962 let mut ranges_for_buffer = Vec::new();
14963 let range = location.range.to_point(buffer);
14964 ranges_for_buffer.push(range.clone());
14965
14966 while let Some(next_location) = locations.peek() {
14967 if next_location.buffer == location.buffer {
14968 ranges_for_buffer.push(next_location.range.to_point(buffer));
14969 locations.next();
14970 } else {
14971 break;
14972 }
14973 }
14974
14975 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
14976 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
14977 PathKey::for_buffer(&location.buffer, cx),
14978 location.buffer.clone(),
14979 ranges_for_buffer,
14980 DEFAULT_MULTIBUFFER_CONTEXT,
14981 cx,
14982 );
14983 ranges.extend(new_ranges)
14984 }
14985
14986 multibuffer.with_title(title)
14987 });
14988
14989 let editor = cx.new(|cx| {
14990 Editor::for_multibuffer(
14991 excerpt_buffer,
14992 Some(workspace.project().clone()),
14993 window,
14994 cx,
14995 )
14996 });
14997 editor.update(cx, |editor, cx| {
14998 match multibuffer_selection_mode {
14999 MultibufferSelectionMode::First => {
15000 if let Some(first_range) = ranges.first() {
15001 editor.change_selections(None, window, cx, |selections| {
15002 selections.clear_disjoint();
15003 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
15004 });
15005 }
15006 editor.highlight_background::<Self>(
15007 &ranges,
15008 |theme| theme.editor_highlighted_line_background,
15009 cx,
15010 );
15011 }
15012 MultibufferSelectionMode::All => {
15013 editor.change_selections(None, window, cx, |selections| {
15014 selections.clear_disjoint();
15015 selections.select_anchor_ranges(ranges);
15016 });
15017 }
15018 }
15019 editor.register_buffers_with_language_servers(cx);
15020 });
15021
15022 let item = Box::new(editor);
15023 let item_id = item.item_id();
15024
15025 if split {
15026 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
15027 } else {
15028 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
15029 let (preview_item_id, preview_item_idx) =
15030 workspace.active_pane().read_with(cx, |pane, _| {
15031 (pane.preview_item_id(), pane.preview_item_idx())
15032 });
15033
15034 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
15035
15036 if let Some(preview_item_id) = preview_item_id {
15037 workspace.active_pane().update(cx, |pane, cx| {
15038 pane.remove_item(preview_item_id, false, false, window, cx);
15039 });
15040 }
15041 } else {
15042 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
15043 }
15044 }
15045 workspace.active_pane().update(cx, |pane, cx| {
15046 pane.set_preview_item_id(Some(item_id), cx);
15047 });
15048 }
15049
15050 pub fn rename(
15051 &mut self,
15052 _: &Rename,
15053 window: &mut Window,
15054 cx: &mut Context<Self>,
15055 ) -> Option<Task<Result<()>>> {
15056 use language::ToOffset as _;
15057
15058 let provider = self.semantics_provider.clone()?;
15059 let selection = self.selections.newest_anchor().clone();
15060 let (cursor_buffer, cursor_buffer_position) = self
15061 .buffer
15062 .read(cx)
15063 .text_anchor_for_position(selection.head(), cx)?;
15064 let (tail_buffer, cursor_buffer_position_end) = self
15065 .buffer
15066 .read(cx)
15067 .text_anchor_for_position(selection.tail(), cx)?;
15068 if tail_buffer != cursor_buffer {
15069 return None;
15070 }
15071
15072 let snapshot = cursor_buffer.read(cx).snapshot();
15073 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
15074 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
15075 let prepare_rename = provider
15076 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
15077 .unwrap_or_else(|| Task::ready(Ok(None)));
15078 drop(snapshot);
15079
15080 Some(cx.spawn_in(window, async move |this, cx| {
15081 let rename_range = if let Some(range) = prepare_rename.await? {
15082 Some(range)
15083 } else {
15084 this.update(cx, |this, cx| {
15085 let buffer = this.buffer.read(cx).snapshot(cx);
15086 let mut buffer_highlights = this
15087 .document_highlights_for_position(selection.head(), &buffer)
15088 .filter(|highlight| {
15089 highlight.start.excerpt_id == selection.head().excerpt_id
15090 && highlight.end.excerpt_id == selection.head().excerpt_id
15091 });
15092 buffer_highlights
15093 .next()
15094 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
15095 })?
15096 };
15097 if let Some(rename_range) = rename_range {
15098 this.update_in(cx, |this, window, cx| {
15099 let snapshot = cursor_buffer.read(cx).snapshot();
15100 let rename_buffer_range = rename_range.to_offset(&snapshot);
15101 let cursor_offset_in_rename_range =
15102 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
15103 let cursor_offset_in_rename_range_end =
15104 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
15105
15106 this.take_rename(false, window, cx);
15107 let buffer = this.buffer.read(cx).read(cx);
15108 let cursor_offset = selection.head().to_offset(&buffer);
15109 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
15110 let rename_end = rename_start + rename_buffer_range.len();
15111 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
15112 let mut old_highlight_id = None;
15113 let old_name: Arc<str> = buffer
15114 .chunks(rename_start..rename_end, true)
15115 .map(|chunk| {
15116 if old_highlight_id.is_none() {
15117 old_highlight_id = chunk.syntax_highlight_id;
15118 }
15119 chunk.text
15120 })
15121 .collect::<String>()
15122 .into();
15123
15124 drop(buffer);
15125
15126 // Position the selection in the rename editor so that it matches the current selection.
15127 this.show_local_selections = false;
15128 let rename_editor = cx.new(|cx| {
15129 let mut editor = Editor::single_line(window, cx);
15130 editor.buffer.update(cx, |buffer, cx| {
15131 buffer.edit([(0..0, old_name.clone())], None, cx)
15132 });
15133 let rename_selection_range = match cursor_offset_in_rename_range
15134 .cmp(&cursor_offset_in_rename_range_end)
15135 {
15136 Ordering::Equal => {
15137 editor.select_all(&SelectAll, window, cx);
15138 return editor;
15139 }
15140 Ordering::Less => {
15141 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
15142 }
15143 Ordering::Greater => {
15144 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
15145 }
15146 };
15147 if rename_selection_range.end > old_name.len() {
15148 editor.select_all(&SelectAll, window, cx);
15149 } else {
15150 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
15151 s.select_ranges([rename_selection_range]);
15152 });
15153 }
15154 editor
15155 });
15156 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
15157 if e == &EditorEvent::Focused {
15158 cx.emit(EditorEvent::FocusedIn)
15159 }
15160 })
15161 .detach();
15162
15163 let write_highlights =
15164 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
15165 let read_highlights =
15166 this.clear_background_highlights::<DocumentHighlightRead>(cx);
15167 let ranges = write_highlights
15168 .iter()
15169 .flat_map(|(_, ranges)| ranges.iter())
15170 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
15171 .cloned()
15172 .collect();
15173
15174 this.highlight_text::<Rename>(
15175 ranges,
15176 HighlightStyle {
15177 fade_out: Some(0.6),
15178 ..Default::default()
15179 },
15180 cx,
15181 );
15182 let rename_focus_handle = rename_editor.focus_handle(cx);
15183 window.focus(&rename_focus_handle);
15184 let block_id = this.insert_blocks(
15185 [BlockProperties {
15186 style: BlockStyle::Flex,
15187 placement: BlockPlacement::Below(range.start),
15188 height: Some(1),
15189 render: Arc::new({
15190 let rename_editor = rename_editor.clone();
15191 move |cx: &mut BlockContext| {
15192 let mut text_style = cx.editor_style.text.clone();
15193 if let Some(highlight_style) = old_highlight_id
15194 .and_then(|h| h.style(&cx.editor_style.syntax))
15195 {
15196 text_style = text_style.highlight(highlight_style);
15197 }
15198 div()
15199 .block_mouse_except_scroll()
15200 .pl(cx.anchor_x)
15201 .child(EditorElement::new(
15202 &rename_editor,
15203 EditorStyle {
15204 background: cx.theme().system().transparent,
15205 local_player: cx.editor_style.local_player,
15206 text: text_style,
15207 scrollbar_width: cx.editor_style.scrollbar_width,
15208 syntax: cx.editor_style.syntax.clone(),
15209 status: cx.editor_style.status.clone(),
15210 inlay_hints_style: HighlightStyle {
15211 font_weight: Some(FontWeight::BOLD),
15212 ..make_inlay_hints_style(cx.app)
15213 },
15214 inline_completion_styles: make_suggestion_styles(
15215 cx.app,
15216 ),
15217 ..EditorStyle::default()
15218 },
15219 ))
15220 .into_any_element()
15221 }
15222 }),
15223 priority: 0,
15224 render_in_minimap: true,
15225 }],
15226 Some(Autoscroll::fit()),
15227 cx,
15228 )[0];
15229 this.pending_rename = Some(RenameState {
15230 range,
15231 old_name,
15232 editor: rename_editor,
15233 block_id,
15234 });
15235 })?;
15236 }
15237
15238 Ok(())
15239 }))
15240 }
15241
15242 pub fn confirm_rename(
15243 &mut self,
15244 _: &ConfirmRename,
15245 window: &mut Window,
15246 cx: &mut Context<Self>,
15247 ) -> Option<Task<Result<()>>> {
15248 let rename = self.take_rename(false, window, cx)?;
15249 let workspace = self.workspace()?.downgrade();
15250 let (buffer, start) = self
15251 .buffer
15252 .read(cx)
15253 .text_anchor_for_position(rename.range.start, cx)?;
15254 let (end_buffer, _) = self
15255 .buffer
15256 .read(cx)
15257 .text_anchor_for_position(rename.range.end, cx)?;
15258 if buffer != end_buffer {
15259 return None;
15260 }
15261
15262 let old_name = rename.old_name;
15263 let new_name = rename.editor.read(cx).text(cx);
15264
15265 let rename = self.semantics_provider.as_ref()?.perform_rename(
15266 &buffer,
15267 start,
15268 new_name.clone(),
15269 cx,
15270 )?;
15271
15272 Some(cx.spawn_in(window, async move |editor, cx| {
15273 let project_transaction = rename.await?;
15274 Self::open_project_transaction(
15275 &editor,
15276 workspace,
15277 project_transaction,
15278 format!("Rename: {} → {}", old_name, new_name),
15279 cx,
15280 )
15281 .await?;
15282
15283 editor.update(cx, |editor, cx| {
15284 editor.refresh_document_highlights(cx);
15285 })?;
15286 Ok(())
15287 }))
15288 }
15289
15290 fn take_rename(
15291 &mut self,
15292 moving_cursor: bool,
15293 window: &mut Window,
15294 cx: &mut Context<Self>,
15295 ) -> Option<RenameState> {
15296 let rename = self.pending_rename.take()?;
15297 if rename.editor.focus_handle(cx).is_focused(window) {
15298 window.focus(&self.focus_handle);
15299 }
15300
15301 self.remove_blocks(
15302 [rename.block_id].into_iter().collect(),
15303 Some(Autoscroll::fit()),
15304 cx,
15305 );
15306 self.clear_highlights::<Rename>(cx);
15307 self.show_local_selections = true;
15308
15309 if moving_cursor {
15310 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
15311 editor.selections.newest::<usize>(cx).head()
15312 });
15313
15314 // Update the selection to match the position of the selection inside
15315 // the rename editor.
15316 let snapshot = self.buffer.read(cx).read(cx);
15317 let rename_range = rename.range.to_offset(&snapshot);
15318 let cursor_in_editor = snapshot
15319 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
15320 .min(rename_range.end);
15321 drop(snapshot);
15322
15323 self.change_selections(None, window, cx, |s| {
15324 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
15325 });
15326 } else {
15327 self.refresh_document_highlights(cx);
15328 }
15329
15330 Some(rename)
15331 }
15332
15333 pub fn pending_rename(&self) -> Option<&RenameState> {
15334 self.pending_rename.as_ref()
15335 }
15336
15337 fn format(
15338 &mut self,
15339 _: &Format,
15340 window: &mut Window,
15341 cx: &mut Context<Self>,
15342 ) -> Option<Task<Result<()>>> {
15343 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15344
15345 let project = match &self.project {
15346 Some(project) => project.clone(),
15347 None => return None,
15348 };
15349
15350 Some(self.perform_format(
15351 project,
15352 FormatTrigger::Manual,
15353 FormatTarget::Buffers,
15354 window,
15355 cx,
15356 ))
15357 }
15358
15359 fn format_selections(
15360 &mut self,
15361 _: &FormatSelections,
15362 window: &mut Window,
15363 cx: &mut Context<Self>,
15364 ) -> Option<Task<Result<()>>> {
15365 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15366
15367 let project = match &self.project {
15368 Some(project) => project.clone(),
15369 None => return None,
15370 };
15371
15372 let ranges = self
15373 .selections
15374 .all_adjusted(cx)
15375 .into_iter()
15376 .map(|selection| selection.range())
15377 .collect_vec();
15378
15379 Some(self.perform_format(
15380 project,
15381 FormatTrigger::Manual,
15382 FormatTarget::Ranges(ranges),
15383 window,
15384 cx,
15385 ))
15386 }
15387
15388 fn perform_format(
15389 &mut self,
15390 project: Entity<Project>,
15391 trigger: FormatTrigger,
15392 target: FormatTarget,
15393 window: &mut Window,
15394 cx: &mut Context<Self>,
15395 ) -> Task<Result<()>> {
15396 let buffer = self.buffer.clone();
15397 let (buffers, target) = match target {
15398 FormatTarget::Buffers => {
15399 let mut buffers = buffer.read(cx).all_buffers();
15400 if trigger == FormatTrigger::Save {
15401 buffers.retain(|buffer| buffer.read(cx).is_dirty());
15402 }
15403 (buffers, LspFormatTarget::Buffers)
15404 }
15405 FormatTarget::Ranges(selection_ranges) => {
15406 let multi_buffer = buffer.read(cx);
15407 let snapshot = multi_buffer.read(cx);
15408 let mut buffers = HashSet::default();
15409 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
15410 BTreeMap::new();
15411 for selection_range in selection_ranges {
15412 for (buffer, buffer_range, _) in
15413 snapshot.range_to_buffer_ranges(selection_range)
15414 {
15415 let buffer_id = buffer.remote_id();
15416 let start = buffer.anchor_before(buffer_range.start);
15417 let end = buffer.anchor_after(buffer_range.end);
15418 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
15419 buffer_id_to_ranges
15420 .entry(buffer_id)
15421 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
15422 .or_insert_with(|| vec![start..end]);
15423 }
15424 }
15425 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
15426 }
15427 };
15428
15429 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
15430 let selections_prev = transaction_id_prev
15431 .and_then(|transaction_id_prev| {
15432 // default to selections as they were after the last edit, if we have them,
15433 // instead of how they are now.
15434 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
15435 // will take you back to where you made the last edit, instead of staying where you scrolled
15436 self.selection_history
15437 .transaction(transaction_id_prev)
15438 .map(|t| t.0.clone())
15439 })
15440 .unwrap_or_else(|| {
15441 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
15442 self.selections.disjoint_anchors()
15443 });
15444
15445 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
15446 let format = project.update(cx, |project, cx| {
15447 project.format(buffers, target, true, trigger, cx)
15448 });
15449
15450 cx.spawn_in(window, async move |editor, cx| {
15451 let transaction = futures::select_biased! {
15452 transaction = format.log_err().fuse() => transaction,
15453 () = timeout => {
15454 log::warn!("timed out waiting for formatting");
15455 None
15456 }
15457 };
15458
15459 buffer
15460 .update(cx, |buffer, cx| {
15461 if let Some(transaction) = transaction {
15462 if !buffer.is_singleton() {
15463 buffer.push_transaction(&transaction.0, cx);
15464 }
15465 }
15466 cx.notify();
15467 })
15468 .ok();
15469
15470 if let Some(transaction_id_now) =
15471 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
15472 {
15473 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
15474 if has_new_transaction {
15475 _ = editor.update(cx, |editor, _| {
15476 editor
15477 .selection_history
15478 .insert_transaction(transaction_id_now, selections_prev);
15479 });
15480 }
15481 }
15482
15483 Ok(())
15484 })
15485 }
15486
15487 fn organize_imports(
15488 &mut self,
15489 _: &OrganizeImports,
15490 window: &mut Window,
15491 cx: &mut Context<Self>,
15492 ) -> Option<Task<Result<()>>> {
15493 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15494 let project = match &self.project {
15495 Some(project) => project.clone(),
15496 None => return None,
15497 };
15498 Some(self.perform_code_action_kind(
15499 project,
15500 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
15501 window,
15502 cx,
15503 ))
15504 }
15505
15506 fn perform_code_action_kind(
15507 &mut self,
15508 project: Entity<Project>,
15509 kind: CodeActionKind,
15510 window: &mut Window,
15511 cx: &mut Context<Self>,
15512 ) -> Task<Result<()>> {
15513 let buffer = self.buffer.clone();
15514 let buffers = buffer.read(cx).all_buffers();
15515 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
15516 let apply_action = project.update(cx, |project, cx| {
15517 project.apply_code_action_kind(buffers, kind, true, cx)
15518 });
15519 cx.spawn_in(window, async move |_, cx| {
15520 let transaction = futures::select_biased! {
15521 () = timeout => {
15522 log::warn!("timed out waiting for executing code action");
15523 None
15524 }
15525 transaction = apply_action.log_err().fuse() => transaction,
15526 };
15527 buffer
15528 .update(cx, |buffer, cx| {
15529 // check if we need this
15530 if let Some(transaction) = transaction {
15531 if !buffer.is_singleton() {
15532 buffer.push_transaction(&transaction.0, cx);
15533 }
15534 }
15535 cx.notify();
15536 })
15537 .ok();
15538 Ok(())
15539 })
15540 }
15541
15542 fn restart_language_server(
15543 &mut self,
15544 _: &RestartLanguageServer,
15545 _: &mut Window,
15546 cx: &mut Context<Self>,
15547 ) {
15548 if let Some(project) = self.project.clone() {
15549 self.buffer.update(cx, |multi_buffer, cx| {
15550 project.update(cx, |project, cx| {
15551 project.restart_language_servers_for_buffers(
15552 multi_buffer.all_buffers().into_iter().collect(),
15553 cx,
15554 );
15555 });
15556 })
15557 }
15558 }
15559
15560 fn stop_language_server(
15561 &mut self,
15562 _: &StopLanguageServer,
15563 _: &mut Window,
15564 cx: &mut Context<Self>,
15565 ) {
15566 if let Some(project) = self.project.clone() {
15567 self.buffer.update(cx, |multi_buffer, cx| {
15568 project.update(cx, |project, cx| {
15569 project.stop_language_servers_for_buffers(
15570 multi_buffer.all_buffers().into_iter().collect(),
15571 cx,
15572 );
15573 cx.emit(project::Event::RefreshInlayHints);
15574 });
15575 });
15576 }
15577 }
15578
15579 fn cancel_language_server_work(
15580 workspace: &mut Workspace,
15581 _: &actions::CancelLanguageServerWork,
15582 _: &mut Window,
15583 cx: &mut Context<Workspace>,
15584 ) {
15585 let project = workspace.project();
15586 let buffers = workspace
15587 .active_item(cx)
15588 .and_then(|item| item.act_as::<Editor>(cx))
15589 .map_or(HashSet::default(), |editor| {
15590 editor.read(cx).buffer.read(cx).all_buffers()
15591 });
15592 project.update(cx, |project, cx| {
15593 project.cancel_language_server_work_for_buffers(buffers, cx);
15594 });
15595 }
15596
15597 fn show_character_palette(
15598 &mut self,
15599 _: &ShowCharacterPalette,
15600 window: &mut Window,
15601 _: &mut Context<Self>,
15602 ) {
15603 window.show_character_palette();
15604 }
15605
15606 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
15607 if self.mode.is_minimap() {
15608 return;
15609 }
15610
15611 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
15612 let buffer = self.buffer.read(cx).snapshot(cx);
15613 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
15614 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
15615 let is_valid = buffer
15616 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
15617 .any(|entry| {
15618 entry.diagnostic.is_primary
15619 && !entry.range.is_empty()
15620 && entry.range.start == primary_range_start
15621 && entry.diagnostic.message == active_diagnostics.active_message
15622 });
15623
15624 if !is_valid {
15625 self.dismiss_diagnostics(cx);
15626 }
15627 }
15628 }
15629
15630 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
15631 match &self.active_diagnostics {
15632 ActiveDiagnostic::Group(group) => Some(group),
15633 _ => None,
15634 }
15635 }
15636
15637 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
15638 self.dismiss_diagnostics(cx);
15639 self.active_diagnostics = ActiveDiagnostic::All;
15640 }
15641
15642 fn activate_diagnostics(
15643 &mut self,
15644 buffer_id: BufferId,
15645 diagnostic: DiagnosticEntry<usize>,
15646 window: &mut Window,
15647 cx: &mut Context<Self>,
15648 ) {
15649 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15650 return;
15651 }
15652 self.dismiss_diagnostics(cx);
15653 let snapshot = self.snapshot(window, cx);
15654 let buffer = self.buffer.read(cx).snapshot(cx);
15655 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
15656 return;
15657 };
15658
15659 let diagnostic_group = buffer
15660 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
15661 .collect::<Vec<_>>();
15662
15663 let blocks =
15664 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
15665
15666 let blocks = self.display_map.update(cx, |display_map, cx| {
15667 display_map.insert_blocks(blocks, cx).into_iter().collect()
15668 });
15669 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
15670 active_range: buffer.anchor_before(diagnostic.range.start)
15671 ..buffer.anchor_after(diagnostic.range.end),
15672 active_message: diagnostic.diagnostic.message.clone(),
15673 group_id: diagnostic.diagnostic.group_id,
15674 blocks,
15675 });
15676 cx.notify();
15677 }
15678
15679 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
15680 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15681 return;
15682 };
15683
15684 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
15685 if let ActiveDiagnostic::Group(group) = prev {
15686 self.display_map.update(cx, |display_map, cx| {
15687 display_map.remove_blocks(group.blocks, cx);
15688 });
15689 cx.notify();
15690 }
15691 }
15692
15693 /// Disable inline diagnostics rendering for this editor.
15694 pub fn disable_inline_diagnostics(&mut self) {
15695 self.inline_diagnostics_enabled = false;
15696 self.inline_diagnostics_update = Task::ready(());
15697 self.inline_diagnostics.clear();
15698 }
15699
15700 pub fn diagnostics_enabled(&self) -> bool {
15701 self.mode.is_full()
15702 }
15703
15704 pub fn inline_diagnostics_enabled(&self) -> bool {
15705 self.diagnostics_enabled() && self.inline_diagnostics_enabled
15706 }
15707
15708 pub fn show_inline_diagnostics(&self) -> bool {
15709 self.show_inline_diagnostics
15710 }
15711
15712 pub fn toggle_inline_diagnostics(
15713 &mut self,
15714 _: &ToggleInlineDiagnostics,
15715 window: &mut Window,
15716 cx: &mut Context<Editor>,
15717 ) {
15718 self.show_inline_diagnostics = !self.show_inline_diagnostics;
15719 self.refresh_inline_diagnostics(false, window, cx);
15720 }
15721
15722 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
15723 self.diagnostics_max_severity = severity;
15724 self.display_map.update(cx, |display_map, _| {
15725 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
15726 });
15727 }
15728
15729 pub fn toggle_diagnostics(
15730 &mut self,
15731 _: &ToggleDiagnostics,
15732 window: &mut Window,
15733 cx: &mut Context<Editor>,
15734 ) {
15735 if !self.diagnostics_enabled() {
15736 return;
15737 }
15738
15739 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
15740 EditorSettings::get_global(cx)
15741 .diagnostics_max_severity
15742 .filter(|severity| severity != &DiagnosticSeverity::Off)
15743 .unwrap_or(DiagnosticSeverity::Hint)
15744 } else {
15745 DiagnosticSeverity::Off
15746 };
15747 self.set_max_diagnostics_severity(new_severity, cx);
15748 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
15749 self.active_diagnostics = ActiveDiagnostic::None;
15750 self.inline_diagnostics_update = Task::ready(());
15751 self.inline_diagnostics.clear();
15752 } else {
15753 self.refresh_inline_diagnostics(false, window, cx);
15754 }
15755
15756 cx.notify();
15757 }
15758
15759 pub fn toggle_minimap(
15760 &mut self,
15761 _: &ToggleMinimap,
15762 window: &mut Window,
15763 cx: &mut Context<Editor>,
15764 ) {
15765 if self.supports_minimap(cx) {
15766 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
15767 }
15768 }
15769
15770 fn refresh_inline_diagnostics(
15771 &mut self,
15772 debounce: bool,
15773 window: &mut Window,
15774 cx: &mut Context<Self>,
15775 ) {
15776 let max_severity = ProjectSettings::get_global(cx)
15777 .diagnostics
15778 .inline
15779 .max_severity
15780 .unwrap_or(self.diagnostics_max_severity);
15781
15782 if !self.inline_diagnostics_enabled()
15783 || !self.show_inline_diagnostics
15784 || max_severity == DiagnosticSeverity::Off
15785 {
15786 self.inline_diagnostics_update = Task::ready(());
15787 self.inline_diagnostics.clear();
15788 return;
15789 }
15790
15791 let debounce_ms = ProjectSettings::get_global(cx)
15792 .diagnostics
15793 .inline
15794 .update_debounce_ms;
15795 let debounce = if debounce && debounce_ms > 0 {
15796 Some(Duration::from_millis(debounce_ms))
15797 } else {
15798 None
15799 };
15800 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
15801 if let Some(debounce) = debounce {
15802 cx.background_executor().timer(debounce).await;
15803 }
15804 let Some(snapshot) = editor.upgrade().and_then(|editor| {
15805 editor
15806 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
15807 .ok()
15808 }) else {
15809 return;
15810 };
15811
15812 let new_inline_diagnostics = cx
15813 .background_spawn(async move {
15814 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
15815 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
15816 let message = diagnostic_entry
15817 .diagnostic
15818 .message
15819 .split_once('\n')
15820 .map(|(line, _)| line)
15821 .map(SharedString::new)
15822 .unwrap_or_else(|| {
15823 SharedString::from(diagnostic_entry.diagnostic.message)
15824 });
15825 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
15826 let (Ok(i) | Err(i)) = inline_diagnostics
15827 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
15828 inline_diagnostics.insert(
15829 i,
15830 (
15831 start_anchor,
15832 InlineDiagnostic {
15833 message,
15834 group_id: diagnostic_entry.diagnostic.group_id,
15835 start: diagnostic_entry.range.start.to_point(&snapshot),
15836 is_primary: diagnostic_entry.diagnostic.is_primary,
15837 severity: diagnostic_entry.diagnostic.severity,
15838 },
15839 ),
15840 );
15841 }
15842 inline_diagnostics
15843 })
15844 .await;
15845
15846 editor
15847 .update(cx, |editor, cx| {
15848 editor.inline_diagnostics = new_inline_diagnostics;
15849 cx.notify();
15850 })
15851 .ok();
15852 });
15853 }
15854
15855 pub fn set_selections_from_remote(
15856 &mut self,
15857 selections: Vec<Selection<Anchor>>,
15858 pending_selection: Option<Selection<Anchor>>,
15859 window: &mut Window,
15860 cx: &mut Context<Self>,
15861 ) {
15862 let old_cursor_position = self.selections.newest_anchor().head();
15863 self.selections.change_with(cx, |s| {
15864 s.select_anchors(selections);
15865 if let Some(pending_selection) = pending_selection {
15866 s.set_pending(pending_selection, SelectMode::Character);
15867 } else {
15868 s.clear_pending();
15869 }
15870 });
15871 self.selections_did_change(false, &old_cursor_position, true, window, cx);
15872 }
15873
15874 pub fn transact(
15875 &mut self,
15876 window: &mut Window,
15877 cx: &mut Context<Self>,
15878 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
15879 ) -> Option<TransactionId> {
15880 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15881 this.start_transaction_at(Instant::now(), window, cx);
15882 update(this, window, cx);
15883 this.end_transaction_at(Instant::now(), cx)
15884 })
15885 }
15886
15887 pub fn start_transaction_at(
15888 &mut self,
15889 now: Instant,
15890 window: &mut Window,
15891 cx: &mut Context<Self>,
15892 ) {
15893 self.end_selection(window, cx);
15894 if let Some(tx_id) = self
15895 .buffer
15896 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
15897 {
15898 self.selection_history
15899 .insert_transaction(tx_id, self.selections.disjoint_anchors());
15900 cx.emit(EditorEvent::TransactionBegun {
15901 transaction_id: tx_id,
15902 })
15903 }
15904 }
15905
15906 pub fn end_transaction_at(
15907 &mut self,
15908 now: Instant,
15909 cx: &mut Context<Self>,
15910 ) -> Option<TransactionId> {
15911 if let Some(transaction_id) = self
15912 .buffer
15913 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
15914 {
15915 if let Some((_, end_selections)) =
15916 self.selection_history.transaction_mut(transaction_id)
15917 {
15918 *end_selections = Some(self.selections.disjoint_anchors());
15919 } else {
15920 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
15921 }
15922
15923 cx.emit(EditorEvent::Edited { transaction_id });
15924 Some(transaction_id)
15925 } else {
15926 None
15927 }
15928 }
15929
15930 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
15931 if self.selection_mark_mode {
15932 self.change_selections(None, window, cx, |s| {
15933 s.move_with(|_, sel| {
15934 sel.collapse_to(sel.head(), SelectionGoal::None);
15935 });
15936 })
15937 }
15938 self.selection_mark_mode = true;
15939 cx.notify();
15940 }
15941
15942 pub fn swap_selection_ends(
15943 &mut self,
15944 _: &actions::SwapSelectionEnds,
15945 window: &mut Window,
15946 cx: &mut Context<Self>,
15947 ) {
15948 self.change_selections(None, window, cx, |s| {
15949 s.move_with(|_, sel| {
15950 if sel.start != sel.end {
15951 sel.reversed = !sel.reversed
15952 }
15953 });
15954 });
15955 self.request_autoscroll(Autoscroll::newest(), cx);
15956 cx.notify();
15957 }
15958
15959 pub fn toggle_fold(
15960 &mut self,
15961 _: &actions::ToggleFold,
15962 window: &mut Window,
15963 cx: &mut Context<Self>,
15964 ) {
15965 if self.is_singleton(cx) {
15966 let selection = self.selections.newest::<Point>(cx);
15967
15968 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15969 let range = if selection.is_empty() {
15970 let point = selection.head().to_display_point(&display_map);
15971 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
15972 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
15973 .to_point(&display_map);
15974 start..end
15975 } else {
15976 selection.range()
15977 };
15978 if display_map.folds_in_range(range).next().is_some() {
15979 self.unfold_lines(&Default::default(), window, cx)
15980 } else {
15981 self.fold(&Default::default(), window, cx)
15982 }
15983 } else {
15984 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15985 let buffer_ids: HashSet<_> = self
15986 .selections
15987 .disjoint_anchor_ranges()
15988 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15989 .collect();
15990
15991 let should_unfold = buffer_ids
15992 .iter()
15993 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
15994
15995 for buffer_id in buffer_ids {
15996 if should_unfold {
15997 self.unfold_buffer(buffer_id, cx);
15998 } else {
15999 self.fold_buffer(buffer_id, cx);
16000 }
16001 }
16002 }
16003 }
16004
16005 pub fn toggle_fold_recursive(
16006 &mut self,
16007 _: &actions::ToggleFoldRecursive,
16008 window: &mut Window,
16009 cx: &mut Context<Self>,
16010 ) {
16011 let selection = self.selections.newest::<Point>(cx);
16012
16013 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16014 let range = if selection.is_empty() {
16015 let point = selection.head().to_display_point(&display_map);
16016 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
16017 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
16018 .to_point(&display_map);
16019 start..end
16020 } else {
16021 selection.range()
16022 };
16023 if display_map.folds_in_range(range).next().is_some() {
16024 self.unfold_recursive(&Default::default(), window, cx)
16025 } else {
16026 self.fold_recursive(&Default::default(), window, cx)
16027 }
16028 }
16029
16030 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
16031 if self.is_singleton(cx) {
16032 let mut to_fold = Vec::new();
16033 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16034 let selections = self.selections.all_adjusted(cx);
16035
16036 for selection in selections {
16037 let range = selection.range().sorted();
16038 let buffer_start_row = range.start.row;
16039
16040 if range.start.row != range.end.row {
16041 let mut found = false;
16042 let mut row = range.start.row;
16043 while row <= range.end.row {
16044 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
16045 {
16046 found = true;
16047 row = crease.range().end.row + 1;
16048 to_fold.push(crease);
16049 } else {
16050 row += 1
16051 }
16052 }
16053 if found {
16054 continue;
16055 }
16056 }
16057
16058 for row in (0..=range.start.row).rev() {
16059 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16060 if crease.range().end.row >= buffer_start_row {
16061 to_fold.push(crease);
16062 if row <= range.start.row {
16063 break;
16064 }
16065 }
16066 }
16067 }
16068 }
16069
16070 self.fold_creases(to_fold, true, window, cx);
16071 } else {
16072 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16073 let buffer_ids = self
16074 .selections
16075 .disjoint_anchor_ranges()
16076 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16077 .collect::<HashSet<_>>();
16078 for buffer_id in buffer_ids {
16079 self.fold_buffer(buffer_id, cx);
16080 }
16081 }
16082 }
16083
16084 fn fold_at_level(
16085 &mut self,
16086 fold_at: &FoldAtLevel,
16087 window: &mut Window,
16088 cx: &mut Context<Self>,
16089 ) {
16090 if !self.buffer.read(cx).is_singleton() {
16091 return;
16092 }
16093
16094 let fold_at_level = fold_at.0;
16095 let snapshot = self.buffer.read(cx).snapshot(cx);
16096 let mut to_fold = Vec::new();
16097 let mut stack = vec![(0, snapshot.max_row().0, 1)];
16098
16099 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
16100 while start_row < end_row {
16101 match self
16102 .snapshot(window, cx)
16103 .crease_for_buffer_row(MultiBufferRow(start_row))
16104 {
16105 Some(crease) => {
16106 let nested_start_row = crease.range().start.row + 1;
16107 let nested_end_row = crease.range().end.row;
16108
16109 if current_level < fold_at_level {
16110 stack.push((nested_start_row, nested_end_row, current_level + 1));
16111 } else if current_level == fold_at_level {
16112 to_fold.push(crease);
16113 }
16114
16115 start_row = nested_end_row + 1;
16116 }
16117 None => start_row += 1,
16118 }
16119 }
16120 }
16121
16122 self.fold_creases(to_fold, true, window, cx);
16123 }
16124
16125 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
16126 if self.buffer.read(cx).is_singleton() {
16127 let mut fold_ranges = Vec::new();
16128 let snapshot = self.buffer.read(cx).snapshot(cx);
16129
16130 for row in 0..snapshot.max_row().0 {
16131 if let Some(foldable_range) = self
16132 .snapshot(window, cx)
16133 .crease_for_buffer_row(MultiBufferRow(row))
16134 {
16135 fold_ranges.push(foldable_range);
16136 }
16137 }
16138
16139 self.fold_creases(fold_ranges, true, window, cx);
16140 } else {
16141 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
16142 editor
16143 .update_in(cx, |editor, _, cx| {
16144 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
16145 editor.fold_buffer(buffer_id, cx);
16146 }
16147 })
16148 .ok();
16149 });
16150 }
16151 }
16152
16153 pub fn fold_function_bodies(
16154 &mut self,
16155 _: &actions::FoldFunctionBodies,
16156 window: &mut Window,
16157 cx: &mut Context<Self>,
16158 ) {
16159 let snapshot = self.buffer.read(cx).snapshot(cx);
16160
16161 let ranges = snapshot
16162 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
16163 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
16164 .collect::<Vec<_>>();
16165
16166 let creases = ranges
16167 .into_iter()
16168 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
16169 .collect();
16170
16171 self.fold_creases(creases, true, window, cx);
16172 }
16173
16174 pub fn fold_recursive(
16175 &mut self,
16176 _: &actions::FoldRecursive,
16177 window: &mut Window,
16178 cx: &mut Context<Self>,
16179 ) {
16180 let mut to_fold = Vec::new();
16181 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16182 let selections = self.selections.all_adjusted(cx);
16183
16184 for selection in selections {
16185 let range = selection.range().sorted();
16186 let buffer_start_row = range.start.row;
16187
16188 if range.start.row != range.end.row {
16189 let mut found = false;
16190 for row in range.start.row..=range.end.row {
16191 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16192 found = true;
16193 to_fold.push(crease);
16194 }
16195 }
16196 if found {
16197 continue;
16198 }
16199 }
16200
16201 for row in (0..=range.start.row).rev() {
16202 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16203 if crease.range().end.row >= buffer_start_row {
16204 to_fold.push(crease);
16205 } else {
16206 break;
16207 }
16208 }
16209 }
16210 }
16211
16212 self.fold_creases(to_fold, true, window, cx);
16213 }
16214
16215 pub fn fold_at(
16216 &mut self,
16217 buffer_row: MultiBufferRow,
16218 window: &mut Window,
16219 cx: &mut Context<Self>,
16220 ) {
16221 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16222
16223 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
16224 let autoscroll = self
16225 .selections
16226 .all::<Point>(cx)
16227 .iter()
16228 .any(|selection| crease.range().overlaps(&selection.range()));
16229
16230 self.fold_creases(vec![crease], autoscroll, window, cx);
16231 }
16232 }
16233
16234 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
16235 if self.is_singleton(cx) {
16236 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16237 let buffer = &display_map.buffer_snapshot;
16238 let selections = self.selections.all::<Point>(cx);
16239 let ranges = selections
16240 .iter()
16241 .map(|s| {
16242 let range = s.display_range(&display_map).sorted();
16243 let mut start = range.start.to_point(&display_map);
16244 let mut end = range.end.to_point(&display_map);
16245 start.column = 0;
16246 end.column = buffer.line_len(MultiBufferRow(end.row));
16247 start..end
16248 })
16249 .collect::<Vec<_>>();
16250
16251 self.unfold_ranges(&ranges, true, true, cx);
16252 } else {
16253 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16254 let buffer_ids = self
16255 .selections
16256 .disjoint_anchor_ranges()
16257 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16258 .collect::<HashSet<_>>();
16259 for buffer_id in buffer_ids {
16260 self.unfold_buffer(buffer_id, cx);
16261 }
16262 }
16263 }
16264
16265 pub fn unfold_recursive(
16266 &mut self,
16267 _: &UnfoldRecursive,
16268 _window: &mut Window,
16269 cx: &mut Context<Self>,
16270 ) {
16271 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16272 let selections = self.selections.all::<Point>(cx);
16273 let ranges = selections
16274 .iter()
16275 .map(|s| {
16276 let mut range = s.display_range(&display_map).sorted();
16277 *range.start.column_mut() = 0;
16278 *range.end.column_mut() = display_map.line_len(range.end.row());
16279 let start = range.start.to_point(&display_map);
16280 let end = range.end.to_point(&display_map);
16281 start..end
16282 })
16283 .collect::<Vec<_>>();
16284
16285 self.unfold_ranges(&ranges, true, true, cx);
16286 }
16287
16288 pub fn unfold_at(
16289 &mut self,
16290 buffer_row: MultiBufferRow,
16291 _window: &mut Window,
16292 cx: &mut Context<Self>,
16293 ) {
16294 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16295
16296 let intersection_range = Point::new(buffer_row.0, 0)
16297 ..Point::new(
16298 buffer_row.0,
16299 display_map.buffer_snapshot.line_len(buffer_row),
16300 );
16301
16302 let autoscroll = self
16303 .selections
16304 .all::<Point>(cx)
16305 .iter()
16306 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
16307
16308 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
16309 }
16310
16311 pub fn unfold_all(
16312 &mut self,
16313 _: &actions::UnfoldAll,
16314 _window: &mut Window,
16315 cx: &mut Context<Self>,
16316 ) {
16317 if self.buffer.read(cx).is_singleton() {
16318 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16319 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
16320 } else {
16321 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
16322 editor
16323 .update(cx, |editor, cx| {
16324 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
16325 editor.unfold_buffer(buffer_id, cx);
16326 }
16327 })
16328 .ok();
16329 });
16330 }
16331 }
16332
16333 pub fn fold_selected_ranges(
16334 &mut self,
16335 _: &FoldSelectedRanges,
16336 window: &mut Window,
16337 cx: &mut Context<Self>,
16338 ) {
16339 let selections = self.selections.all_adjusted(cx);
16340 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16341 let ranges = selections
16342 .into_iter()
16343 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
16344 .collect::<Vec<_>>();
16345 self.fold_creases(ranges, true, window, cx);
16346 }
16347
16348 pub fn fold_ranges<T: ToOffset + Clone>(
16349 &mut self,
16350 ranges: Vec<Range<T>>,
16351 auto_scroll: bool,
16352 window: &mut Window,
16353 cx: &mut Context<Self>,
16354 ) {
16355 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16356 let ranges = ranges
16357 .into_iter()
16358 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
16359 .collect::<Vec<_>>();
16360 self.fold_creases(ranges, auto_scroll, window, cx);
16361 }
16362
16363 pub fn fold_creases<T: ToOffset + Clone>(
16364 &mut self,
16365 creases: Vec<Crease<T>>,
16366 auto_scroll: bool,
16367 _window: &mut Window,
16368 cx: &mut Context<Self>,
16369 ) {
16370 if creases.is_empty() {
16371 return;
16372 }
16373
16374 let mut buffers_affected = HashSet::default();
16375 let multi_buffer = self.buffer().read(cx);
16376 for crease in &creases {
16377 if let Some((_, buffer, _)) =
16378 multi_buffer.excerpt_containing(crease.range().start.clone(), cx)
16379 {
16380 buffers_affected.insert(buffer.read(cx).remote_id());
16381 };
16382 }
16383
16384 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
16385
16386 if auto_scroll {
16387 self.request_autoscroll(Autoscroll::fit(), cx);
16388 }
16389
16390 cx.notify();
16391
16392 self.scrollbar_marker_state.dirty = true;
16393 self.folds_did_change(cx);
16394 }
16395
16396 /// Removes any folds whose ranges intersect any of the given ranges.
16397 pub fn unfold_ranges<T: ToOffset + Clone>(
16398 &mut self,
16399 ranges: &[Range<T>],
16400 inclusive: bool,
16401 auto_scroll: bool,
16402 cx: &mut Context<Self>,
16403 ) {
16404 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16405 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
16406 });
16407 self.folds_did_change(cx);
16408 }
16409
16410 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16411 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
16412 return;
16413 }
16414 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16415 self.display_map.update(cx, |display_map, cx| {
16416 display_map.fold_buffers([buffer_id], cx)
16417 });
16418 cx.emit(EditorEvent::BufferFoldToggled {
16419 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
16420 folded: true,
16421 });
16422 cx.notify();
16423 }
16424
16425 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16426 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
16427 return;
16428 }
16429 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16430 self.display_map.update(cx, |display_map, cx| {
16431 display_map.unfold_buffers([buffer_id], cx);
16432 });
16433 cx.emit(EditorEvent::BufferFoldToggled {
16434 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
16435 folded: false,
16436 });
16437 cx.notify();
16438 }
16439
16440 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
16441 self.display_map.read(cx).is_buffer_folded(buffer)
16442 }
16443
16444 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
16445 self.display_map.read(cx).folded_buffers()
16446 }
16447
16448 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16449 self.display_map.update(cx, |display_map, cx| {
16450 display_map.disable_header_for_buffer(buffer_id, cx);
16451 });
16452 cx.notify();
16453 }
16454
16455 /// Removes any folds with the given ranges.
16456 pub fn remove_folds_with_type<T: ToOffset + Clone>(
16457 &mut self,
16458 ranges: &[Range<T>],
16459 type_id: TypeId,
16460 auto_scroll: bool,
16461 cx: &mut Context<Self>,
16462 ) {
16463 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16464 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
16465 });
16466 self.folds_did_change(cx);
16467 }
16468
16469 fn remove_folds_with<T: ToOffset + Clone>(
16470 &mut self,
16471 ranges: &[Range<T>],
16472 auto_scroll: bool,
16473 cx: &mut Context<Self>,
16474 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
16475 ) {
16476 if ranges.is_empty() {
16477 return;
16478 }
16479
16480 let mut buffers_affected = HashSet::default();
16481 let multi_buffer = self.buffer().read(cx);
16482 for range in ranges {
16483 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
16484 buffers_affected.insert(buffer.read(cx).remote_id());
16485 };
16486 }
16487
16488 self.display_map.update(cx, update);
16489
16490 if auto_scroll {
16491 self.request_autoscroll(Autoscroll::fit(), cx);
16492 }
16493
16494 cx.notify();
16495 self.scrollbar_marker_state.dirty = true;
16496 self.active_indent_guides_state.dirty = true;
16497 }
16498
16499 pub fn update_fold_widths(
16500 &mut self,
16501 widths: impl IntoIterator<Item = (FoldId, Pixels)>,
16502 cx: &mut Context<Self>,
16503 ) -> bool {
16504 self.display_map
16505 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
16506 }
16507
16508 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
16509 self.display_map.read(cx).fold_placeholder.clone()
16510 }
16511
16512 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
16513 self.buffer.update(cx, |buffer, cx| {
16514 buffer.set_all_diff_hunks_expanded(cx);
16515 });
16516 }
16517
16518 pub fn expand_all_diff_hunks(
16519 &mut self,
16520 _: &ExpandAllDiffHunks,
16521 _window: &mut Window,
16522 cx: &mut Context<Self>,
16523 ) {
16524 self.buffer.update(cx, |buffer, cx| {
16525 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
16526 });
16527 }
16528
16529 pub fn toggle_selected_diff_hunks(
16530 &mut self,
16531 _: &ToggleSelectedDiffHunks,
16532 _window: &mut Window,
16533 cx: &mut Context<Self>,
16534 ) {
16535 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16536 self.toggle_diff_hunks_in_ranges(ranges, cx);
16537 }
16538
16539 pub fn diff_hunks_in_ranges<'a>(
16540 &'a self,
16541 ranges: &'a [Range<Anchor>],
16542 buffer: &'a MultiBufferSnapshot,
16543 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
16544 ranges.iter().flat_map(move |range| {
16545 let end_excerpt_id = range.end.excerpt_id;
16546 let range = range.to_point(buffer);
16547 let mut peek_end = range.end;
16548 if range.end.row < buffer.max_row().0 {
16549 peek_end = Point::new(range.end.row + 1, 0);
16550 }
16551 buffer
16552 .diff_hunks_in_range(range.start..peek_end)
16553 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
16554 })
16555 }
16556
16557 pub fn has_stageable_diff_hunks_in_ranges(
16558 &self,
16559 ranges: &[Range<Anchor>],
16560 snapshot: &MultiBufferSnapshot,
16561 ) -> bool {
16562 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
16563 hunks.any(|hunk| hunk.status().has_secondary_hunk())
16564 }
16565
16566 pub fn toggle_staged_selected_diff_hunks(
16567 &mut self,
16568 _: &::git::ToggleStaged,
16569 _: &mut Window,
16570 cx: &mut Context<Self>,
16571 ) {
16572 let snapshot = self.buffer.read(cx).snapshot(cx);
16573 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16574 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
16575 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16576 }
16577
16578 pub fn set_render_diff_hunk_controls(
16579 &mut self,
16580 render_diff_hunk_controls: RenderDiffHunkControlsFn,
16581 cx: &mut Context<Self>,
16582 ) {
16583 self.render_diff_hunk_controls = render_diff_hunk_controls;
16584 cx.notify();
16585 }
16586
16587 pub fn stage_and_next(
16588 &mut self,
16589 _: &::git::StageAndNext,
16590 window: &mut Window,
16591 cx: &mut Context<Self>,
16592 ) {
16593 self.do_stage_or_unstage_and_next(true, window, cx);
16594 }
16595
16596 pub fn unstage_and_next(
16597 &mut self,
16598 _: &::git::UnstageAndNext,
16599 window: &mut Window,
16600 cx: &mut Context<Self>,
16601 ) {
16602 self.do_stage_or_unstage_and_next(false, window, cx);
16603 }
16604
16605 pub fn stage_or_unstage_diff_hunks(
16606 &mut self,
16607 stage: bool,
16608 ranges: Vec<Range<Anchor>>,
16609 cx: &mut Context<Self>,
16610 ) {
16611 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
16612 cx.spawn(async move |this, cx| {
16613 task.await?;
16614 this.update(cx, |this, cx| {
16615 let snapshot = this.buffer.read(cx).snapshot(cx);
16616 let chunk_by = this
16617 .diff_hunks_in_ranges(&ranges, &snapshot)
16618 .chunk_by(|hunk| hunk.buffer_id);
16619 for (buffer_id, hunks) in &chunk_by {
16620 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
16621 }
16622 })
16623 })
16624 .detach_and_log_err(cx);
16625 }
16626
16627 fn save_buffers_for_ranges_if_needed(
16628 &mut self,
16629 ranges: &[Range<Anchor>],
16630 cx: &mut Context<Editor>,
16631 ) -> Task<Result<()>> {
16632 let multibuffer = self.buffer.read(cx);
16633 let snapshot = multibuffer.read(cx);
16634 let buffer_ids: HashSet<_> = ranges
16635 .iter()
16636 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
16637 .collect();
16638 drop(snapshot);
16639
16640 let mut buffers = HashSet::default();
16641 for buffer_id in buffer_ids {
16642 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
16643 let buffer = buffer_entity.read(cx);
16644 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
16645 {
16646 buffers.insert(buffer_entity);
16647 }
16648 }
16649 }
16650
16651 if let Some(project) = &self.project {
16652 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
16653 } else {
16654 Task::ready(Ok(()))
16655 }
16656 }
16657
16658 fn do_stage_or_unstage_and_next(
16659 &mut self,
16660 stage: bool,
16661 window: &mut Window,
16662 cx: &mut Context<Self>,
16663 ) {
16664 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
16665
16666 if ranges.iter().any(|range| range.start != range.end) {
16667 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16668 return;
16669 }
16670
16671 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16672 let snapshot = self.snapshot(window, cx);
16673 let position = self.selections.newest::<Point>(cx).head();
16674 let mut row = snapshot
16675 .buffer_snapshot
16676 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
16677 .find(|hunk| hunk.row_range.start.0 > position.row)
16678 .map(|hunk| hunk.row_range.start);
16679
16680 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
16681 // Outside of the project diff editor, wrap around to the beginning.
16682 if !all_diff_hunks_expanded {
16683 row = row.or_else(|| {
16684 snapshot
16685 .buffer_snapshot
16686 .diff_hunks_in_range(Point::zero()..position)
16687 .find(|hunk| hunk.row_range.end.0 < position.row)
16688 .map(|hunk| hunk.row_range.start)
16689 });
16690 }
16691
16692 if let Some(row) = row {
16693 let destination = Point::new(row.0, 0);
16694 let autoscroll = Autoscroll::center();
16695
16696 self.unfold_ranges(&[destination..destination], false, false, cx);
16697 self.change_selections(Some(autoscroll), window, cx, |s| {
16698 s.select_ranges([destination..destination]);
16699 });
16700 }
16701 }
16702
16703 fn do_stage_or_unstage(
16704 &self,
16705 stage: bool,
16706 buffer_id: BufferId,
16707 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
16708 cx: &mut App,
16709 ) -> Option<()> {
16710 let project = self.project.as_ref()?;
16711 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
16712 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
16713 let buffer_snapshot = buffer.read(cx).snapshot();
16714 let file_exists = buffer_snapshot
16715 .file()
16716 .is_some_and(|file| file.disk_state().exists());
16717 diff.update(cx, |diff, cx| {
16718 diff.stage_or_unstage_hunks(
16719 stage,
16720 &hunks
16721 .map(|hunk| buffer_diff::DiffHunk {
16722 buffer_range: hunk.buffer_range,
16723 diff_base_byte_range: hunk.diff_base_byte_range,
16724 secondary_status: hunk.secondary_status,
16725 range: Point::zero()..Point::zero(), // unused
16726 })
16727 .collect::<Vec<_>>(),
16728 &buffer_snapshot,
16729 file_exists,
16730 cx,
16731 )
16732 });
16733 None
16734 }
16735
16736 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
16737 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16738 self.buffer
16739 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
16740 }
16741
16742 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
16743 self.buffer.update(cx, |buffer, cx| {
16744 let ranges = vec![Anchor::min()..Anchor::max()];
16745 if !buffer.all_diff_hunks_expanded()
16746 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
16747 {
16748 buffer.collapse_diff_hunks(ranges, cx);
16749 true
16750 } else {
16751 false
16752 }
16753 })
16754 }
16755
16756 fn toggle_diff_hunks_in_ranges(
16757 &mut self,
16758 ranges: Vec<Range<Anchor>>,
16759 cx: &mut Context<Editor>,
16760 ) {
16761 self.buffer.update(cx, |buffer, cx| {
16762 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
16763 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
16764 })
16765 }
16766
16767 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
16768 self.buffer.update(cx, |buffer, cx| {
16769 let snapshot = buffer.snapshot(cx);
16770 let excerpt_id = range.end.excerpt_id;
16771 let point_range = range.to_point(&snapshot);
16772 let expand = !buffer.single_hunk_is_expanded(range, cx);
16773 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
16774 })
16775 }
16776
16777 pub(crate) fn apply_all_diff_hunks(
16778 &mut self,
16779 _: &ApplyAllDiffHunks,
16780 window: &mut Window,
16781 cx: &mut Context<Self>,
16782 ) {
16783 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16784
16785 let buffers = self.buffer.read(cx).all_buffers();
16786 for branch_buffer in buffers {
16787 branch_buffer.update(cx, |branch_buffer, cx| {
16788 branch_buffer.merge_into_base(Vec::new(), cx);
16789 });
16790 }
16791
16792 if let Some(project) = self.project.clone() {
16793 self.save(true, project, window, cx).detach_and_log_err(cx);
16794 }
16795 }
16796
16797 pub(crate) fn apply_selected_diff_hunks(
16798 &mut self,
16799 _: &ApplyDiffHunk,
16800 window: &mut Window,
16801 cx: &mut Context<Self>,
16802 ) {
16803 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16804 let snapshot = self.snapshot(window, cx);
16805 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
16806 let mut ranges_by_buffer = HashMap::default();
16807 self.transact(window, cx, |editor, _window, cx| {
16808 for hunk in hunks {
16809 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
16810 ranges_by_buffer
16811 .entry(buffer.clone())
16812 .or_insert_with(Vec::new)
16813 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
16814 }
16815 }
16816
16817 for (buffer, ranges) in ranges_by_buffer {
16818 buffer.update(cx, |buffer, cx| {
16819 buffer.merge_into_base(ranges, cx);
16820 });
16821 }
16822 });
16823
16824 if let Some(project) = self.project.clone() {
16825 self.save(true, project, window, cx).detach_and_log_err(cx);
16826 }
16827 }
16828
16829 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
16830 if hovered != self.gutter_hovered {
16831 self.gutter_hovered = hovered;
16832 cx.notify();
16833 }
16834 }
16835
16836 pub fn insert_blocks(
16837 &mut self,
16838 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
16839 autoscroll: Option<Autoscroll>,
16840 cx: &mut Context<Self>,
16841 ) -> Vec<CustomBlockId> {
16842 let blocks = self
16843 .display_map
16844 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
16845 if let Some(autoscroll) = autoscroll {
16846 self.request_autoscroll(autoscroll, cx);
16847 }
16848 cx.notify();
16849 blocks
16850 }
16851
16852 pub fn resize_blocks(
16853 &mut self,
16854 heights: HashMap<CustomBlockId, u32>,
16855 autoscroll: Option<Autoscroll>,
16856 cx: &mut Context<Self>,
16857 ) {
16858 self.display_map
16859 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
16860 if let Some(autoscroll) = autoscroll {
16861 self.request_autoscroll(autoscroll, cx);
16862 }
16863 cx.notify();
16864 }
16865
16866 pub fn replace_blocks(
16867 &mut self,
16868 renderers: HashMap<CustomBlockId, RenderBlock>,
16869 autoscroll: Option<Autoscroll>,
16870 cx: &mut Context<Self>,
16871 ) {
16872 self.display_map
16873 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
16874 if let Some(autoscroll) = autoscroll {
16875 self.request_autoscroll(autoscroll, cx);
16876 }
16877 cx.notify();
16878 }
16879
16880 pub fn remove_blocks(
16881 &mut self,
16882 block_ids: HashSet<CustomBlockId>,
16883 autoscroll: Option<Autoscroll>,
16884 cx: &mut Context<Self>,
16885 ) {
16886 self.display_map.update(cx, |display_map, cx| {
16887 display_map.remove_blocks(block_ids, cx)
16888 });
16889 if let Some(autoscroll) = autoscroll {
16890 self.request_autoscroll(autoscroll, cx);
16891 }
16892 cx.notify();
16893 }
16894
16895 pub fn row_for_block(
16896 &self,
16897 block_id: CustomBlockId,
16898 cx: &mut Context<Self>,
16899 ) -> Option<DisplayRow> {
16900 self.display_map
16901 .update(cx, |map, cx| map.row_for_block(block_id, cx))
16902 }
16903
16904 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
16905 self.focused_block = Some(focused_block);
16906 }
16907
16908 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
16909 self.focused_block.take()
16910 }
16911
16912 pub fn insert_creases(
16913 &mut self,
16914 creases: impl IntoIterator<Item = Crease<Anchor>>,
16915 cx: &mut Context<Self>,
16916 ) -> Vec<CreaseId> {
16917 self.display_map
16918 .update(cx, |map, cx| map.insert_creases(creases, cx))
16919 }
16920
16921 pub fn remove_creases(
16922 &mut self,
16923 ids: impl IntoIterator<Item = CreaseId>,
16924 cx: &mut Context<Self>,
16925 ) -> Vec<(CreaseId, Range<Anchor>)> {
16926 self.display_map
16927 .update(cx, |map, cx| map.remove_creases(ids, cx))
16928 }
16929
16930 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
16931 self.display_map
16932 .update(cx, |map, cx| map.snapshot(cx))
16933 .longest_row()
16934 }
16935
16936 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
16937 self.display_map
16938 .update(cx, |map, cx| map.snapshot(cx))
16939 .max_point()
16940 }
16941
16942 pub fn text(&self, cx: &App) -> String {
16943 self.buffer.read(cx).read(cx).text()
16944 }
16945
16946 pub fn is_empty(&self, cx: &App) -> bool {
16947 self.buffer.read(cx).read(cx).is_empty()
16948 }
16949
16950 pub fn text_option(&self, cx: &App) -> Option<String> {
16951 let text = self.text(cx);
16952 let text = text.trim();
16953
16954 if text.is_empty() {
16955 return None;
16956 }
16957
16958 Some(text.to_string())
16959 }
16960
16961 pub fn set_text(
16962 &mut self,
16963 text: impl Into<Arc<str>>,
16964 window: &mut Window,
16965 cx: &mut Context<Self>,
16966 ) {
16967 self.transact(window, cx, |this, _, cx| {
16968 this.buffer
16969 .read(cx)
16970 .as_singleton()
16971 .expect("you can only call set_text on editors for singleton buffers")
16972 .update(cx, |buffer, cx| buffer.set_text(text, cx));
16973 });
16974 }
16975
16976 pub fn display_text(&self, cx: &mut App) -> String {
16977 self.display_map
16978 .update(cx, |map, cx| map.snapshot(cx))
16979 .text()
16980 }
16981
16982 fn create_minimap(
16983 &self,
16984 minimap_settings: MinimapSettings,
16985 window: &mut Window,
16986 cx: &mut Context<Self>,
16987 ) -> Option<Entity<Self>> {
16988 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
16989 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
16990 }
16991
16992 fn initialize_new_minimap(
16993 &self,
16994 minimap_settings: MinimapSettings,
16995 window: &mut Window,
16996 cx: &mut Context<Self>,
16997 ) -> Entity<Self> {
16998 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
16999
17000 let mut minimap = Editor::new_internal(
17001 EditorMode::Minimap {
17002 parent: cx.weak_entity(),
17003 },
17004 self.buffer.clone(),
17005 self.project.clone(),
17006 Some(self.display_map.clone()),
17007 window,
17008 cx,
17009 );
17010 minimap.scroll_manager.clone_state(&self.scroll_manager);
17011 minimap.set_text_style_refinement(TextStyleRefinement {
17012 font_size: Some(MINIMAP_FONT_SIZE),
17013 font_weight: Some(MINIMAP_FONT_WEIGHT),
17014 ..Default::default()
17015 });
17016 minimap.update_minimap_configuration(minimap_settings, cx);
17017 cx.new(|_| minimap)
17018 }
17019
17020 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
17021 let current_line_highlight = minimap_settings
17022 .current_line_highlight
17023 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
17024 self.set_current_line_highlight(Some(current_line_highlight));
17025 }
17026
17027 pub fn minimap(&self) -> Option<&Entity<Self>> {
17028 self.minimap
17029 .as_ref()
17030 .filter(|_| self.minimap_visibility.visible())
17031 }
17032
17033 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
17034 let mut wrap_guides = smallvec![];
17035
17036 if self.show_wrap_guides == Some(false) {
17037 return wrap_guides;
17038 }
17039
17040 let settings = self.buffer.read(cx).language_settings(cx);
17041 if settings.show_wrap_guides {
17042 match self.soft_wrap_mode(cx) {
17043 SoftWrap::Column(soft_wrap) => {
17044 wrap_guides.push((soft_wrap as usize, true));
17045 }
17046 SoftWrap::Bounded(soft_wrap) => {
17047 wrap_guides.push((soft_wrap as usize, true));
17048 }
17049 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
17050 }
17051 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
17052 }
17053
17054 wrap_guides
17055 }
17056
17057 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
17058 let settings = self.buffer.read(cx).language_settings(cx);
17059 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
17060 match mode {
17061 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
17062 SoftWrap::None
17063 }
17064 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
17065 language_settings::SoftWrap::PreferredLineLength => {
17066 SoftWrap::Column(settings.preferred_line_length)
17067 }
17068 language_settings::SoftWrap::Bounded => {
17069 SoftWrap::Bounded(settings.preferred_line_length)
17070 }
17071 }
17072 }
17073
17074 pub fn set_soft_wrap_mode(
17075 &mut self,
17076 mode: language_settings::SoftWrap,
17077
17078 cx: &mut Context<Self>,
17079 ) {
17080 self.soft_wrap_mode_override = Some(mode);
17081 cx.notify();
17082 }
17083
17084 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
17085 self.hard_wrap = hard_wrap;
17086 cx.notify();
17087 }
17088
17089 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
17090 self.text_style_refinement = Some(style);
17091 }
17092
17093 /// called by the Element so we know what style we were most recently rendered with.
17094 pub(crate) fn set_style(
17095 &mut self,
17096 style: EditorStyle,
17097 window: &mut Window,
17098 cx: &mut Context<Self>,
17099 ) {
17100 // We intentionally do not inform the display map about the minimap style
17101 // so that wrapping is not recalculated and stays consistent for the editor
17102 // and its linked minimap.
17103 if !self.mode.is_minimap() {
17104 let rem_size = window.rem_size();
17105 self.display_map.update(cx, |map, cx| {
17106 map.set_font(
17107 style.text.font(),
17108 style.text.font_size.to_pixels(rem_size),
17109 cx,
17110 )
17111 });
17112 }
17113 self.style = Some(style);
17114 }
17115
17116 pub fn style(&self) -> Option<&EditorStyle> {
17117 self.style.as_ref()
17118 }
17119
17120 // Called by the element. This method is not designed to be called outside of the editor
17121 // element's layout code because it does not notify when rewrapping is computed synchronously.
17122 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
17123 self.display_map
17124 .update(cx, |map, cx| map.set_wrap_width(width, cx))
17125 }
17126
17127 pub fn set_soft_wrap(&mut self) {
17128 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
17129 }
17130
17131 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
17132 if self.soft_wrap_mode_override.is_some() {
17133 self.soft_wrap_mode_override.take();
17134 } else {
17135 let soft_wrap = match self.soft_wrap_mode(cx) {
17136 SoftWrap::GitDiff => return,
17137 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
17138 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
17139 language_settings::SoftWrap::None
17140 }
17141 };
17142 self.soft_wrap_mode_override = Some(soft_wrap);
17143 }
17144 cx.notify();
17145 }
17146
17147 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
17148 let Some(workspace) = self.workspace() else {
17149 return;
17150 };
17151 let fs = workspace.read(cx).app_state().fs.clone();
17152 let current_show = TabBarSettings::get_global(cx).show;
17153 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
17154 setting.show = Some(!current_show);
17155 });
17156 }
17157
17158 pub fn toggle_indent_guides(
17159 &mut self,
17160 _: &ToggleIndentGuides,
17161 _: &mut Window,
17162 cx: &mut Context<Self>,
17163 ) {
17164 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
17165 self.buffer
17166 .read(cx)
17167 .language_settings(cx)
17168 .indent_guides
17169 .enabled
17170 });
17171 self.show_indent_guides = Some(!currently_enabled);
17172 cx.notify();
17173 }
17174
17175 fn should_show_indent_guides(&self) -> Option<bool> {
17176 self.show_indent_guides
17177 }
17178
17179 pub fn toggle_line_numbers(
17180 &mut self,
17181 _: &ToggleLineNumbers,
17182 _: &mut Window,
17183 cx: &mut Context<Self>,
17184 ) {
17185 let mut editor_settings = EditorSettings::get_global(cx).clone();
17186 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
17187 EditorSettings::override_global(editor_settings, cx);
17188 }
17189
17190 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
17191 if let Some(show_line_numbers) = self.show_line_numbers {
17192 return show_line_numbers;
17193 }
17194 EditorSettings::get_global(cx).gutter.line_numbers
17195 }
17196
17197 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
17198 self.use_relative_line_numbers
17199 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
17200 }
17201
17202 pub fn toggle_relative_line_numbers(
17203 &mut self,
17204 _: &ToggleRelativeLineNumbers,
17205 _: &mut Window,
17206 cx: &mut Context<Self>,
17207 ) {
17208 let is_relative = self.should_use_relative_line_numbers(cx);
17209 self.set_relative_line_number(Some(!is_relative), cx)
17210 }
17211
17212 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
17213 self.use_relative_line_numbers = is_relative;
17214 cx.notify();
17215 }
17216
17217 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
17218 self.show_gutter = show_gutter;
17219 cx.notify();
17220 }
17221
17222 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
17223 self.show_scrollbars = ScrollbarAxes {
17224 horizontal: show,
17225 vertical: show,
17226 };
17227 cx.notify();
17228 }
17229
17230 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
17231 self.show_scrollbars.vertical = show;
17232 cx.notify();
17233 }
17234
17235 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
17236 self.show_scrollbars.horizontal = show;
17237 cx.notify();
17238 }
17239
17240 pub fn set_minimap_visibility(
17241 &mut self,
17242 minimap_visibility: MinimapVisibility,
17243 window: &mut Window,
17244 cx: &mut Context<Self>,
17245 ) {
17246 if self.minimap_visibility != minimap_visibility {
17247 if minimap_visibility.visible() && self.minimap.is_none() {
17248 let minimap_settings = EditorSettings::get_global(cx).minimap;
17249 self.minimap =
17250 self.create_minimap(minimap_settings.with_show_override(), window, cx);
17251 }
17252 self.minimap_visibility = minimap_visibility;
17253 cx.notify();
17254 }
17255 }
17256
17257 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17258 self.set_show_scrollbars(false, cx);
17259 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
17260 }
17261
17262 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17263 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
17264 }
17265
17266 /// Normally the text in full mode and auto height editors is padded on the
17267 /// left side by roughly half a character width for improved hit testing.
17268 ///
17269 /// Use this method to disable this for cases where this is not wanted (e.g.
17270 /// if you want to align the editor text with some other text above or below)
17271 /// or if you want to add this padding to single-line editors.
17272 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
17273 self.offset_content = offset_content;
17274 cx.notify();
17275 }
17276
17277 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
17278 self.show_line_numbers = Some(show_line_numbers);
17279 cx.notify();
17280 }
17281
17282 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
17283 self.disable_expand_excerpt_buttons = true;
17284 cx.notify();
17285 }
17286
17287 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
17288 self.show_git_diff_gutter = Some(show_git_diff_gutter);
17289 cx.notify();
17290 }
17291
17292 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
17293 self.show_code_actions = Some(show_code_actions);
17294 cx.notify();
17295 }
17296
17297 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
17298 self.show_runnables = Some(show_runnables);
17299 cx.notify();
17300 }
17301
17302 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
17303 self.show_breakpoints = Some(show_breakpoints);
17304 cx.notify();
17305 }
17306
17307 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
17308 if self.display_map.read(cx).masked != masked {
17309 self.display_map.update(cx, |map, _| map.masked = masked);
17310 }
17311 cx.notify()
17312 }
17313
17314 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
17315 self.show_wrap_guides = Some(show_wrap_guides);
17316 cx.notify();
17317 }
17318
17319 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
17320 self.show_indent_guides = Some(show_indent_guides);
17321 cx.notify();
17322 }
17323
17324 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
17325 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
17326 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
17327 if let Some(dir) = file.abs_path(cx).parent() {
17328 return Some(dir.to_owned());
17329 }
17330 }
17331
17332 if let Some(project_path) = buffer.read(cx).project_path(cx) {
17333 return Some(project_path.path.to_path_buf());
17334 }
17335 }
17336
17337 None
17338 }
17339
17340 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
17341 self.active_excerpt(cx)?
17342 .1
17343 .read(cx)
17344 .file()
17345 .and_then(|f| f.as_local())
17346 }
17347
17348 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
17349 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
17350 let buffer = buffer.read(cx);
17351 if let Some(project_path) = buffer.project_path(cx) {
17352 let project = self.project.as_ref()?.read(cx);
17353 project.absolute_path(&project_path, cx)
17354 } else {
17355 buffer
17356 .file()
17357 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
17358 }
17359 })
17360 }
17361
17362 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
17363 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
17364 let project_path = buffer.read(cx).project_path(cx)?;
17365 let project = self.project.as_ref()?.read(cx);
17366 let entry = project.entry_for_path(&project_path, cx)?;
17367 let path = entry.path.to_path_buf();
17368 Some(path)
17369 })
17370 }
17371
17372 pub fn reveal_in_finder(
17373 &mut self,
17374 _: &RevealInFileManager,
17375 _window: &mut Window,
17376 cx: &mut Context<Self>,
17377 ) {
17378 if let Some(target) = self.target_file(cx) {
17379 cx.reveal_path(&target.abs_path(cx));
17380 }
17381 }
17382
17383 pub fn copy_path(
17384 &mut self,
17385 _: &zed_actions::workspace::CopyPath,
17386 _window: &mut Window,
17387 cx: &mut Context<Self>,
17388 ) {
17389 if let Some(path) = self.target_file_abs_path(cx) {
17390 if let Some(path) = path.to_str() {
17391 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17392 }
17393 }
17394 }
17395
17396 pub fn copy_relative_path(
17397 &mut self,
17398 _: &zed_actions::workspace::CopyRelativePath,
17399 _window: &mut Window,
17400 cx: &mut Context<Self>,
17401 ) {
17402 if let Some(path) = self.target_file_path(cx) {
17403 if let Some(path) = path.to_str() {
17404 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17405 }
17406 }
17407 }
17408
17409 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
17410 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
17411 buffer.read(cx).project_path(cx)
17412 } else {
17413 None
17414 }
17415 }
17416
17417 // Returns true if the editor handled a go-to-line request
17418 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
17419 maybe!({
17420 let breakpoint_store = self.breakpoint_store.as_ref()?;
17421
17422 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
17423 else {
17424 self.clear_row_highlights::<ActiveDebugLine>();
17425 return None;
17426 };
17427
17428 let position = active_stack_frame.position;
17429 let buffer_id = position.buffer_id?;
17430 let snapshot = self
17431 .project
17432 .as_ref()?
17433 .read(cx)
17434 .buffer_for_id(buffer_id, cx)?
17435 .read(cx)
17436 .snapshot();
17437
17438 let mut handled = false;
17439 for (id, ExcerptRange { context, .. }) in
17440 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
17441 {
17442 if context.start.cmp(&position, &snapshot).is_ge()
17443 || context.end.cmp(&position, &snapshot).is_lt()
17444 {
17445 continue;
17446 }
17447 let snapshot = self.buffer.read(cx).snapshot(cx);
17448 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
17449
17450 handled = true;
17451 self.clear_row_highlights::<ActiveDebugLine>();
17452
17453 self.go_to_line::<ActiveDebugLine>(
17454 multibuffer_anchor,
17455 Some(cx.theme().colors().editor_debugger_active_line_background),
17456 window,
17457 cx,
17458 );
17459
17460 cx.notify();
17461 }
17462
17463 handled.then_some(())
17464 })
17465 .is_some()
17466 }
17467
17468 pub fn copy_file_name_without_extension(
17469 &mut self,
17470 _: &CopyFileNameWithoutExtension,
17471 _: &mut Window,
17472 cx: &mut Context<Self>,
17473 ) {
17474 if let Some(file) = self.target_file(cx) {
17475 if let Some(file_stem) = file.path().file_stem() {
17476 if let Some(name) = file_stem.to_str() {
17477 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17478 }
17479 }
17480 }
17481 }
17482
17483 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
17484 if let Some(file) = self.target_file(cx) {
17485 if let Some(file_name) = file.path().file_name() {
17486 if let Some(name) = file_name.to_str() {
17487 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17488 }
17489 }
17490 }
17491 }
17492
17493 pub fn toggle_git_blame(
17494 &mut self,
17495 _: &::git::Blame,
17496 window: &mut Window,
17497 cx: &mut Context<Self>,
17498 ) {
17499 self.show_git_blame_gutter = !self.show_git_blame_gutter;
17500
17501 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
17502 self.start_git_blame(true, window, cx);
17503 }
17504
17505 cx.notify();
17506 }
17507
17508 pub fn toggle_git_blame_inline(
17509 &mut self,
17510 _: &ToggleGitBlameInline,
17511 window: &mut Window,
17512 cx: &mut Context<Self>,
17513 ) {
17514 self.toggle_git_blame_inline_internal(true, window, cx);
17515 cx.notify();
17516 }
17517
17518 pub fn open_git_blame_commit(
17519 &mut self,
17520 _: &OpenGitBlameCommit,
17521 window: &mut Window,
17522 cx: &mut Context<Self>,
17523 ) {
17524 self.open_git_blame_commit_internal(window, cx);
17525 }
17526
17527 fn open_git_blame_commit_internal(
17528 &mut self,
17529 window: &mut Window,
17530 cx: &mut Context<Self>,
17531 ) -> Option<()> {
17532 let blame = self.blame.as_ref()?;
17533 let snapshot = self.snapshot(window, cx);
17534 let cursor = self.selections.newest::<Point>(cx).head();
17535 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
17536 let blame_entry = blame
17537 .update(cx, |blame, cx| {
17538 blame
17539 .blame_for_rows(
17540 &[RowInfo {
17541 buffer_id: Some(buffer.remote_id()),
17542 buffer_row: Some(point.row),
17543 ..Default::default()
17544 }],
17545 cx,
17546 )
17547 .next()
17548 })
17549 .flatten()?;
17550 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
17551 let repo = blame.read(cx).repository(cx)?;
17552 let workspace = self.workspace()?.downgrade();
17553 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
17554 None
17555 }
17556
17557 pub fn git_blame_inline_enabled(&self) -> bool {
17558 self.git_blame_inline_enabled
17559 }
17560
17561 pub fn toggle_selection_menu(
17562 &mut self,
17563 _: &ToggleSelectionMenu,
17564 _: &mut Window,
17565 cx: &mut Context<Self>,
17566 ) {
17567 self.show_selection_menu = self
17568 .show_selection_menu
17569 .map(|show_selections_menu| !show_selections_menu)
17570 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
17571
17572 cx.notify();
17573 }
17574
17575 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
17576 self.show_selection_menu
17577 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
17578 }
17579
17580 fn start_git_blame(
17581 &mut self,
17582 user_triggered: bool,
17583 window: &mut Window,
17584 cx: &mut Context<Self>,
17585 ) {
17586 if let Some(project) = self.project.as_ref() {
17587 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
17588 return;
17589 };
17590
17591 if buffer.read(cx).file().is_none() {
17592 return;
17593 }
17594
17595 let focused = self.focus_handle(cx).contains_focused(window, cx);
17596
17597 let project = project.clone();
17598 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
17599 self.blame_subscription =
17600 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
17601 self.blame = Some(blame);
17602 }
17603 }
17604
17605 fn toggle_git_blame_inline_internal(
17606 &mut self,
17607 user_triggered: bool,
17608 window: &mut Window,
17609 cx: &mut Context<Self>,
17610 ) {
17611 if self.git_blame_inline_enabled {
17612 self.git_blame_inline_enabled = false;
17613 self.show_git_blame_inline = false;
17614 self.show_git_blame_inline_delay_task.take();
17615 } else {
17616 self.git_blame_inline_enabled = true;
17617 self.start_git_blame_inline(user_triggered, window, cx);
17618 }
17619
17620 cx.notify();
17621 }
17622
17623 fn start_git_blame_inline(
17624 &mut self,
17625 user_triggered: bool,
17626 window: &mut Window,
17627 cx: &mut Context<Self>,
17628 ) {
17629 self.start_git_blame(user_triggered, window, cx);
17630
17631 if ProjectSettings::get_global(cx)
17632 .git
17633 .inline_blame_delay()
17634 .is_some()
17635 {
17636 self.start_inline_blame_timer(window, cx);
17637 } else {
17638 self.show_git_blame_inline = true
17639 }
17640 }
17641
17642 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
17643 self.blame.as_ref()
17644 }
17645
17646 pub fn show_git_blame_gutter(&self) -> bool {
17647 self.show_git_blame_gutter
17648 }
17649
17650 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
17651 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
17652 }
17653
17654 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
17655 self.show_git_blame_inline
17656 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
17657 && !self.newest_selection_head_on_empty_line(cx)
17658 && self.has_blame_entries(cx)
17659 }
17660
17661 fn has_blame_entries(&self, cx: &App) -> bool {
17662 self.blame()
17663 .map_or(false, |blame| blame.read(cx).has_generated_entries())
17664 }
17665
17666 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
17667 let cursor_anchor = self.selections.newest_anchor().head();
17668
17669 let snapshot = self.buffer.read(cx).snapshot(cx);
17670 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
17671
17672 snapshot.line_len(buffer_row) == 0
17673 }
17674
17675 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
17676 let buffer_and_selection = maybe!({
17677 let selection = self.selections.newest::<Point>(cx);
17678 let selection_range = selection.range();
17679
17680 let multi_buffer = self.buffer().read(cx);
17681 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17682 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
17683
17684 let (buffer, range, _) = if selection.reversed {
17685 buffer_ranges.first()
17686 } else {
17687 buffer_ranges.last()
17688 }?;
17689
17690 let selection = text::ToPoint::to_point(&range.start, &buffer).row
17691 ..text::ToPoint::to_point(&range.end, &buffer).row;
17692 Some((
17693 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
17694 selection,
17695 ))
17696 });
17697
17698 let Some((buffer, selection)) = buffer_and_selection else {
17699 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
17700 };
17701
17702 let Some(project) = self.project.as_ref() else {
17703 return Task::ready(Err(anyhow!("editor does not have project")));
17704 };
17705
17706 project.update(cx, |project, cx| {
17707 project.get_permalink_to_line(&buffer, selection, cx)
17708 })
17709 }
17710
17711 pub fn copy_permalink_to_line(
17712 &mut self,
17713 _: &CopyPermalinkToLine,
17714 window: &mut Window,
17715 cx: &mut Context<Self>,
17716 ) {
17717 let permalink_task = self.get_permalink_to_line(cx);
17718 let workspace = self.workspace();
17719
17720 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
17721 Ok(permalink) => {
17722 cx.update(|_, cx| {
17723 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
17724 })
17725 .ok();
17726 }
17727 Err(err) => {
17728 let message = format!("Failed to copy permalink: {err}");
17729
17730 anyhow::Result::<()>::Err(err).log_err();
17731
17732 if let Some(workspace) = workspace {
17733 workspace
17734 .update_in(cx, |workspace, _, cx| {
17735 struct CopyPermalinkToLine;
17736
17737 workspace.show_toast(
17738 Toast::new(
17739 NotificationId::unique::<CopyPermalinkToLine>(),
17740 message,
17741 ),
17742 cx,
17743 )
17744 })
17745 .ok();
17746 }
17747 }
17748 })
17749 .detach();
17750 }
17751
17752 pub fn copy_file_location(
17753 &mut self,
17754 _: &CopyFileLocation,
17755 _: &mut Window,
17756 cx: &mut Context<Self>,
17757 ) {
17758 let selection = self.selections.newest::<Point>(cx).start.row + 1;
17759 if let Some(file) = self.target_file(cx) {
17760 if let Some(path) = file.path().to_str() {
17761 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
17762 }
17763 }
17764 }
17765
17766 pub fn open_permalink_to_line(
17767 &mut self,
17768 _: &OpenPermalinkToLine,
17769 window: &mut Window,
17770 cx: &mut Context<Self>,
17771 ) {
17772 let permalink_task = self.get_permalink_to_line(cx);
17773 let workspace = self.workspace();
17774
17775 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
17776 Ok(permalink) => {
17777 cx.update(|_, cx| {
17778 cx.open_url(permalink.as_ref());
17779 })
17780 .ok();
17781 }
17782 Err(err) => {
17783 let message = format!("Failed to open permalink: {err}");
17784
17785 anyhow::Result::<()>::Err(err).log_err();
17786
17787 if let Some(workspace) = workspace {
17788 workspace
17789 .update(cx, |workspace, cx| {
17790 struct OpenPermalinkToLine;
17791
17792 workspace.show_toast(
17793 Toast::new(
17794 NotificationId::unique::<OpenPermalinkToLine>(),
17795 message,
17796 ),
17797 cx,
17798 )
17799 })
17800 .ok();
17801 }
17802 }
17803 })
17804 .detach();
17805 }
17806
17807 pub fn insert_uuid_v4(
17808 &mut self,
17809 _: &InsertUuidV4,
17810 window: &mut Window,
17811 cx: &mut Context<Self>,
17812 ) {
17813 self.insert_uuid(UuidVersion::V4, window, cx);
17814 }
17815
17816 pub fn insert_uuid_v7(
17817 &mut self,
17818 _: &InsertUuidV7,
17819 window: &mut Window,
17820 cx: &mut Context<Self>,
17821 ) {
17822 self.insert_uuid(UuidVersion::V7, window, cx);
17823 }
17824
17825 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
17826 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
17827 self.transact(window, cx, |this, window, cx| {
17828 let edits = this
17829 .selections
17830 .all::<Point>(cx)
17831 .into_iter()
17832 .map(|selection| {
17833 let uuid = match version {
17834 UuidVersion::V4 => uuid::Uuid::new_v4(),
17835 UuidVersion::V7 => uuid::Uuid::now_v7(),
17836 };
17837
17838 (selection.range(), uuid.to_string())
17839 });
17840 this.edit(edits, cx);
17841 this.refresh_inline_completion(true, false, window, cx);
17842 });
17843 }
17844
17845 pub fn open_selections_in_multibuffer(
17846 &mut self,
17847 _: &OpenSelectionsInMultibuffer,
17848 window: &mut Window,
17849 cx: &mut Context<Self>,
17850 ) {
17851 let multibuffer = self.buffer.read(cx);
17852
17853 let Some(buffer) = multibuffer.as_singleton() else {
17854 return;
17855 };
17856
17857 let Some(workspace) = self.workspace() else {
17858 return;
17859 };
17860
17861 let locations = self
17862 .selections
17863 .disjoint_anchors()
17864 .iter()
17865 .map(|selection| {
17866 let range = if selection.reversed {
17867 selection.end.text_anchor..selection.start.text_anchor
17868 } else {
17869 selection.start.text_anchor..selection.end.text_anchor
17870 };
17871 Location {
17872 buffer: buffer.clone(),
17873 range,
17874 }
17875 })
17876 .collect::<Vec<_>>();
17877
17878 let title = multibuffer.title(cx).to_string();
17879
17880 cx.spawn_in(window, async move |_, cx| {
17881 workspace.update_in(cx, |workspace, window, cx| {
17882 Self::open_locations_in_multibuffer(
17883 workspace,
17884 locations,
17885 format!("Selections for '{title}'"),
17886 false,
17887 MultibufferSelectionMode::All,
17888 window,
17889 cx,
17890 );
17891 })
17892 })
17893 .detach();
17894 }
17895
17896 /// Adds a row highlight for the given range. If a row has multiple highlights, the
17897 /// last highlight added will be used.
17898 ///
17899 /// If the range ends at the beginning of a line, then that line will not be highlighted.
17900 pub fn highlight_rows<T: 'static>(
17901 &mut self,
17902 range: Range<Anchor>,
17903 color: Hsla,
17904 options: RowHighlightOptions,
17905 cx: &mut Context<Self>,
17906 ) {
17907 let snapshot = self.buffer().read(cx).snapshot(cx);
17908 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
17909 let ix = row_highlights.binary_search_by(|highlight| {
17910 Ordering::Equal
17911 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
17912 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
17913 });
17914
17915 if let Err(mut ix) = ix {
17916 let index = post_inc(&mut self.highlight_order);
17917
17918 // If this range intersects with the preceding highlight, then merge it with
17919 // the preceding highlight. Otherwise insert a new highlight.
17920 let mut merged = false;
17921 if ix > 0 {
17922 let prev_highlight = &mut row_highlights[ix - 1];
17923 if prev_highlight
17924 .range
17925 .end
17926 .cmp(&range.start, &snapshot)
17927 .is_ge()
17928 {
17929 ix -= 1;
17930 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
17931 prev_highlight.range.end = range.end;
17932 }
17933 merged = true;
17934 prev_highlight.index = index;
17935 prev_highlight.color = color;
17936 prev_highlight.options = options;
17937 }
17938 }
17939
17940 if !merged {
17941 row_highlights.insert(
17942 ix,
17943 RowHighlight {
17944 range: range.clone(),
17945 index,
17946 color,
17947 options,
17948 type_id: TypeId::of::<T>(),
17949 },
17950 );
17951 }
17952
17953 // If any of the following highlights intersect with this one, merge them.
17954 while let Some(next_highlight) = row_highlights.get(ix + 1) {
17955 let highlight = &row_highlights[ix];
17956 if next_highlight
17957 .range
17958 .start
17959 .cmp(&highlight.range.end, &snapshot)
17960 .is_le()
17961 {
17962 if next_highlight
17963 .range
17964 .end
17965 .cmp(&highlight.range.end, &snapshot)
17966 .is_gt()
17967 {
17968 row_highlights[ix].range.end = next_highlight.range.end;
17969 }
17970 row_highlights.remove(ix + 1);
17971 } else {
17972 break;
17973 }
17974 }
17975 }
17976 }
17977
17978 /// Remove any highlighted row ranges of the given type that intersect the
17979 /// given ranges.
17980 pub fn remove_highlighted_rows<T: 'static>(
17981 &mut self,
17982 ranges_to_remove: Vec<Range<Anchor>>,
17983 cx: &mut Context<Self>,
17984 ) {
17985 let snapshot = self.buffer().read(cx).snapshot(cx);
17986 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
17987 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
17988 row_highlights.retain(|highlight| {
17989 while let Some(range_to_remove) = ranges_to_remove.peek() {
17990 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
17991 Ordering::Less | Ordering::Equal => {
17992 ranges_to_remove.next();
17993 }
17994 Ordering::Greater => {
17995 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
17996 Ordering::Less | Ordering::Equal => {
17997 return false;
17998 }
17999 Ordering::Greater => break,
18000 }
18001 }
18002 }
18003 }
18004
18005 true
18006 })
18007 }
18008
18009 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
18010 pub fn clear_row_highlights<T: 'static>(&mut self) {
18011 self.highlighted_rows.remove(&TypeId::of::<T>());
18012 }
18013
18014 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
18015 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
18016 self.highlighted_rows
18017 .get(&TypeId::of::<T>())
18018 .map_or(&[] as &[_], |vec| vec.as_slice())
18019 .iter()
18020 .map(|highlight| (highlight.range.clone(), highlight.color))
18021 }
18022
18023 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
18024 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
18025 /// Allows to ignore certain kinds of highlights.
18026 pub fn highlighted_display_rows(
18027 &self,
18028 window: &mut Window,
18029 cx: &mut App,
18030 ) -> BTreeMap<DisplayRow, LineHighlight> {
18031 let snapshot = self.snapshot(window, cx);
18032 let mut used_highlight_orders = HashMap::default();
18033 self.highlighted_rows
18034 .iter()
18035 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
18036 .fold(
18037 BTreeMap::<DisplayRow, LineHighlight>::new(),
18038 |mut unique_rows, highlight| {
18039 let start = highlight.range.start.to_display_point(&snapshot);
18040 let end = highlight.range.end.to_display_point(&snapshot);
18041 let start_row = start.row().0;
18042 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
18043 && end.column() == 0
18044 {
18045 end.row().0.saturating_sub(1)
18046 } else {
18047 end.row().0
18048 };
18049 for row in start_row..=end_row {
18050 let used_index =
18051 used_highlight_orders.entry(row).or_insert(highlight.index);
18052 if highlight.index >= *used_index {
18053 *used_index = highlight.index;
18054 unique_rows.insert(
18055 DisplayRow(row),
18056 LineHighlight {
18057 include_gutter: highlight.options.include_gutter,
18058 border: None,
18059 background: highlight.color.into(),
18060 type_id: Some(highlight.type_id),
18061 },
18062 );
18063 }
18064 }
18065 unique_rows
18066 },
18067 )
18068 }
18069
18070 pub fn highlighted_display_row_for_autoscroll(
18071 &self,
18072 snapshot: &DisplaySnapshot,
18073 ) -> Option<DisplayRow> {
18074 self.highlighted_rows
18075 .values()
18076 .flat_map(|highlighted_rows| highlighted_rows.iter())
18077 .filter_map(|highlight| {
18078 if highlight.options.autoscroll {
18079 Some(highlight.range.start.to_display_point(snapshot).row())
18080 } else {
18081 None
18082 }
18083 })
18084 .min()
18085 }
18086
18087 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
18088 self.highlight_background::<SearchWithinRange>(
18089 ranges,
18090 |colors| colors.editor_document_highlight_read_background,
18091 cx,
18092 )
18093 }
18094
18095 pub fn set_breadcrumb_header(&mut self, new_header: String) {
18096 self.breadcrumb_header = Some(new_header);
18097 }
18098
18099 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
18100 self.clear_background_highlights::<SearchWithinRange>(cx);
18101 }
18102
18103 pub fn highlight_background<T: 'static>(
18104 &mut self,
18105 ranges: &[Range<Anchor>],
18106 color_fetcher: fn(&ThemeColors) -> Hsla,
18107 cx: &mut Context<Self>,
18108 ) {
18109 self.background_highlights
18110 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
18111 self.scrollbar_marker_state.dirty = true;
18112 cx.notify();
18113 }
18114
18115 pub fn clear_background_highlights<T: 'static>(
18116 &mut self,
18117 cx: &mut Context<Self>,
18118 ) -> Option<BackgroundHighlight> {
18119 let text_highlights = self.background_highlights.remove(&TypeId::of::<T>())?;
18120 if !text_highlights.1.is_empty() {
18121 self.scrollbar_marker_state.dirty = true;
18122 cx.notify();
18123 }
18124 Some(text_highlights)
18125 }
18126
18127 pub fn highlight_gutter<T: 'static>(
18128 &mut self,
18129 ranges: &[Range<Anchor>],
18130 color_fetcher: fn(&App) -> Hsla,
18131 cx: &mut Context<Self>,
18132 ) {
18133 self.gutter_highlights
18134 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
18135 cx.notify();
18136 }
18137
18138 pub fn clear_gutter_highlights<T: 'static>(
18139 &mut self,
18140 cx: &mut Context<Self>,
18141 ) -> Option<GutterHighlight> {
18142 cx.notify();
18143 self.gutter_highlights.remove(&TypeId::of::<T>())
18144 }
18145
18146 #[cfg(feature = "test-support")]
18147 pub fn all_text_background_highlights(
18148 &self,
18149 window: &mut Window,
18150 cx: &mut Context<Self>,
18151 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18152 let snapshot = self.snapshot(window, cx);
18153 let buffer = &snapshot.buffer_snapshot;
18154 let start = buffer.anchor_before(0);
18155 let end = buffer.anchor_after(buffer.len());
18156 let theme = cx.theme().colors();
18157 self.background_highlights_in_range(start..end, &snapshot, theme)
18158 }
18159
18160 #[cfg(feature = "test-support")]
18161 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
18162 let snapshot = self.buffer().read(cx).snapshot(cx);
18163
18164 let highlights = self
18165 .background_highlights
18166 .get(&TypeId::of::<items::BufferSearchHighlights>());
18167
18168 if let Some((_color, ranges)) = highlights {
18169 ranges
18170 .iter()
18171 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
18172 .collect_vec()
18173 } else {
18174 vec![]
18175 }
18176 }
18177
18178 fn document_highlights_for_position<'a>(
18179 &'a self,
18180 position: Anchor,
18181 buffer: &'a MultiBufferSnapshot,
18182 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
18183 let read_highlights = self
18184 .background_highlights
18185 .get(&TypeId::of::<DocumentHighlightRead>())
18186 .map(|h| &h.1);
18187 let write_highlights = self
18188 .background_highlights
18189 .get(&TypeId::of::<DocumentHighlightWrite>())
18190 .map(|h| &h.1);
18191 let left_position = position.bias_left(buffer);
18192 let right_position = position.bias_right(buffer);
18193 read_highlights
18194 .into_iter()
18195 .chain(write_highlights)
18196 .flat_map(move |ranges| {
18197 let start_ix = match ranges.binary_search_by(|probe| {
18198 let cmp = probe.end.cmp(&left_position, buffer);
18199 if cmp.is_ge() {
18200 Ordering::Greater
18201 } else {
18202 Ordering::Less
18203 }
18204 }) {
18205 Ok(i) | Err(i) => i,
18206 };
18207
18208 ranges[start_ix..]
18209 .iter()
18210 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
18211 })
18212 }
18213
18214 pub fn has_background_highlights<T: 'static>(&self) -> bool {
18215 self.background_highlights
18216 .get(&TypeId::of::<T>())
18217 .map_or(false, |(_, highlights)| !highlights.is_empty())
18218 }
18219
18220 pub fn background_highlights_in_range(
18221 &self,
18222 search_range: Range<Anchor>,
18223 display_snapshot: &DisplaySnapshot,
18224 theme: &ThemeColors,
18225 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18226 let mut results = Vec::new();
18227 for (color_fetcher, ranges) in self.background_highlights.values() {
18228 let color = color_fetcher(theme);
18229 let start_ix = match ranges.binary_search_by(|probe| {
18230 let cmp = probe
18231 .end
18232 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18233 if cmp.is_gt() {
18234 Ordering::Greater
18235 } else {
18236 Ordering::Less
18237 }
18238 }) {
18239 Ok(i) | Err(i) => i,
18240 };
18241 for range in &ranges[start_ix..] {
18242 if range
18243 .start
18244 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18245 .is_ge()
18246 {
18247 break;
18248 }
18249
18250 let start = range.start.to_display_point(display_snapshot);
18251 let end = range.end.to_display_point(display_snapshot);
18252 results.push((start..end, color))
18253 }
18254 }
18255 results
18256 }
18257
18258 pub fn background_highlight_row_ranges<T: 'static>(
18259 &self,
18260 search_range: Range<Anchor>,
18261 display_snapshot: &DisplaySnapshot,
18262 count: usize,
18263 ) -> Vec<RangeInclusive<DisplayPoint>> {
18264 let mut results = Vec::new();
18265 let Some((_, ranges)) = self.background_highlights.get(&TypeId::of::<T>()) else {
18266 return vec![];
18267 };
18268
18269 let start_ix = match ranges.binary_search_by(|probe| {
18270 let cmp = probe
18271 .end
18272 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18273 if cmp.is_gt() {
18274 Ordering::Greater
18275 } else {
18276 Ordering::Less
18277 }
18278 }) {
18279 Ok(i) | Err(i) => i,
18280 };
18281 let mut push_region = |start: Option<Point>, end: Option<Point>| {
18282 if let (Some(start_display), Some(end_display)) = (start, end) {
18283 results.push(
18284 start_display.to_display_point(display_snapshot)
18285 ..=end_display.to_display_point(display_snapshot),
18286 );
18287 }
18288 };
18289 let mut start_row: Option<Point> = None;
18290 let mut end_row: Option<Point> = None;
18291 if ranges.len() > count {
18292 return Vec::new();
18293 }
18294 for range in &ranges[start_ix..] {
18295 if range
18296 .start
18297 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18298 .is_ge()
18299 {
18300 break;
18301 }
18302 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
18303 if let Some(current_row) = &end_row {
18304 if end.row == current_row.row {
18305 continue;
18306 }
18307 }
18308 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
18309 if start_row.is_none() {
18310 assert_eq!(end_row, None);
18311 start_row = Some(start);
18312 end_row = Some(end);
18313 continue;
18314 }
18315 if let Some(current_end) = end_row.as_mut() {
18316 if start.row > current_end.row + 1 {
18317 push_region(start_row, end_row);
18318 start_row = Some(start);
18319 end_row = Some(end);
18320 } else {
18321 // Merge two hunks.
18322 *current_end = end;
18323 }
18324 } else {
18325 unreachable!();
18326 }
18327 }
18328 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
18329 push_region(start_row, end_row);
18330 results
18331 }
18332
18333 pub fn gutter_highlights_in_range(
18334 &self,
18335 search_range: Range<Anchor>,
18336 display_snapshot: &DisplaySnapshot,
18337 cx: &App,
18338 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18339 let mut results = Vec::new();
18340 for (color_fetcher, ranges) in self.gutter_highlights.values() {
18341 let color = color_fetcher(cx);
18342 let start_ix = match ranges.binary_search_by(|probe| {
18343 let cmp = probe
18344 .end
18345 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18346 if cmp.is_gt() {
18347 Ordering::Greater
18348 } else {
18349 Ordering::Less
18350 }
18351 }) {
18352 Ok(i) | Err(i) => i,
18353 };
18354 for range in &ranges[start_ix..] {
18355 if range
18356 .start
18357 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18358 .is_ge()
18359 {
18360 break;
18361 }
18362
18363 let start = range.start.to_display_point(display_snapshot);
18364 let end = range.end.to_display_point(display_snapshot);
18365 results.push((start..end, color))
18366 }
18367 }
18368 results
18369 }
18370
18371 /// Get the text ranges corresponding to the redaction query
18372 pub fn redacted_ranges(
18373 &self,
18374 search_range: Range<Anchor>,
18375 display_snapshot: &DisplaySnapshot,
18376 cx: &App,
18377 ) -> Vec<Range<DisplayPoint>> {
18378 display_snapshot
18379 .buffer_snapshot
18380 .redacted_ranges(search_range, |file| {
18381 if let Some(file) = file {
18382 file.is_private()
18383 && EditorSettings::get(
18384 Some(SettingsLocation {
18385 worktree_id: file.worktree_id(cx),
18386 path: file.path().as_ref(),
18387 }),
18388 cx,
18389 )
18390 .redact_private_values
18391 } else {
18392 false
18393 }
18394 })
18395 .map(|range| {
18396 range.start.to_display_point(display_snapshot)
18397 ..range.end.to_display_point(display_snapshot)
18398 })
18399 .collect()
18400 }
18401
18402 pub fn highlight_text<T: 'static>(
18403 &mut self,
18404 ranges: Vec<Range<Anchor>>,
18405 style: HighlightStyle,
18406 cx: &mut Context<Self>,
18407 ) {
18408 self.display_map.update(cx, |map, _| {
18409 map.highlight_text(TypeId::of::<T>(), ranges, style)
18410 });
18411 cx.notify();
18412 }
18413
18414 pub(crate) fn highlight_inlays<T: 'static>(
18415 &mut self,
18416 highlights: Vec<InlayHighlight>,
18417 style: HighlightStyle,
18418 cx: &mut Context<Self>,
18419 ) {
18420 self.display_map.update(cx, |map, _| {
18421 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
18422 });
18423 cx.notify();
18424 }
18425
18426 pub fn text_highlights<'a, T: 'static>(
18427 &'a self,
18428 cx: &'a App,
18429 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
18430 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
18431 }
18432
18433 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
18434 let cleared = self
18435 .display_map
18436 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
18437 if cleared {
18438 cx.notify();
18439 }
18440 }
18441
18442 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
18443 (self.read_only(cx) || self.blink_manager.read(cx).visible())
18444 && self.focus_handle.is_focused(window)
18445 }
18446
18447 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
18448 self.show_cursor_when_unfocused = is_enabled;
18449 cx.notify();
18450 }
18451
18452 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
18453 cx.notify();
18454 }
18455
18456 fn on_debug_session_event(
18457 &mut self,
18458 _session: Entity<Session>,
18459 event: &SessionEvent,
18460 cx: &mut Context<Self>,
18461 ) {
18462 match event {
18463 SessionEvent::InvalidateInlineValue => {
18464 self.refresh_inline_values(cx);
18465 }
18466 _ => {}
18467 }
18468 }
18469
18470 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
18471 let Some(project) = self.project.clone() else {
18472 return;
18473 };
18474
18475 if !self.inline_value_cache.enabled {
18476 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
18477 self.splice_inlays(&inlays, Vec::new(), cx);
18478 return;
18479 }
18480
18481 let current_execution_position = self
18482 .highlighted_rows
18483 .get(&TypeId::of::<ActiveDebugLine>())
18484 .and_then(|lines| lines.last().map(|line| line.range.start));
18485
18486 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
18487 let inline_values = editor
18488 .update(cx, |editor, cx| {
18489 let Some(current_execution_position) = current_execution_position else {
18490 return Some(Task::ready(Ok(Vec::new())));
18491 };
18492
18493 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
18494 let snapshot = buffer.snapshot(cx);
18495
18496 let excerpt = snapshot.excerpt_containing(
18497 current_execution_position..current_execution_position,
18498 )?;
18499
18500 editor.buffer.read(cx).buffer(excerpt.buffer_id())
18501 })?;
18502
18503 let range =
18504 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
18505
18506 project.inline_values(buffer, range, cx)
18507 })
18508 .ok()
18509 .flatten()?
18510 .await
18511 .context("refreshing debugger inlays")
18512 .log_err()?;
18513
18514 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
18515
18516 for (buffer_id, inline_value) in inline_values
18517 .into_iter()
18518 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
18519 {
18520 buffer_inline_values
18521 .entry(buffer_id)
18522 .or_default()
18523 .push(inline_value);
18524 }
18525
18526 editor
18527 .update(cx, |editor, cx| {
18528 let snapshot = editor.buffer.read(cx).snapshot(cx);
18529 let mut new_inlays = Vec::default();
18530
18531 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
18532 let buffer_id = buffer_snapshot.remote_id();
18533 buffer_inline_values
18534 .get(&buffer_id)
18535 .into_iter()
18536 .flatten()
18537 .for_each(|hint| {
18538 let inlay = Inlay::debugger_hint(
18539 post_inc(&mut editor.next_inlay_id),
18540 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
18541 hint.text(),
18542 );
18543
18544 new_inlays.push(inlay);
18545 });
18546 }
18547
18548 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
18549 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
18550
18551 editor.splice_inlays(&inlay_ids, new_inlays, cx);
18552 })
18553 .ok()?;
18554 Some(())
18555 });
18556 }
18557
18558 fn on_buffer_event(
18559 &mut self,
18560 multibuffer: &Entity<MultiBuffer>,
18561 event: &multi_buffer::Event,
18562 window: &mut Window,
18563 cx: &mut Context<Self>,
18564 ) {
18565 match event {
18566 multi_buffer::Event::Edited {
18567 singleton_buffer_edited,
18568 edited_buffer: buffer_edited,
18569 } => {
18570 self.scrollbar_marker_state.dirty = true;
18571 self.active_indent_guides_state.dirty = true;
18572 self.refresh_active_diagnostics(cx);
18573 self.refresh_code_actions(window, cx);
18574 self.refresh_selected_text_highlights(true, window, cx);
18575 refresh_matching_bracket_highlights(self, window, cx);
18576 if self.has_active_inline_completion() {
18577 self.update_visible_inline_completion(window, cx);
18578 }
18579 if let Some(buffer) = buffer_edited {
18580 let buffer_id = buffer.read(cx).remote_id();
18581 if !self.registered_buffers.contains_key(&buffer_id) {
18582 if let Some(project) = self.project.as_ref() {
18583 project.update(cx, |project, cx| {
18584 self.registered_buffers.insert(
18585 buffer_id,
18586 project.register_buffer_with_language_servers(&buffer, cx),
18587 );
18588 })
18589 }
18590 }
18591 }
18592 cx.emit(EditorEvent::BufferEdited);
18593 cx.emit(SearchEvent::MatchesInvalidated);
18594 if *singleton_buffer_edited {
18595 if let Some(project) = &self.project {
18596 #[allow(clippy::mutable_key_type)]
18597 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
18598 multibuffer
18599 .all_buffers()
18600 .into_iter()
18601 .filter_map(|buffer| {
18602 buffer.update(cx, |buffer, cx| {
18603 let language = buffer.language()?;
18604 let should_discard = project.update(cx, |project, cx| {
18605 project.is_local()
18606 && !project.has_language_servers_for(buffer, cx)
18607 });
18608 should_discard.not().then_some(language.clone())
18609 })
18610 })
18611 .collect::<HashSet<_>>()
18612 });
18613 if !languages_affected.is_empty() {
18614 self.refresh_inlay_hints(
18615 InlayHintRefreshReason::BufferEdited(languages_affected),
18616 cx,
18617 );
18618 }
18619 }
18620 }
18621
18622 let Some(project) = &self.project else { return };
18623 let (telemetry, is_via_ssh) = {
18624 let project = project.read(cx);
18625 let telemetry = project.client().telemetry().clone();
18626 let is_via_ssh = project.is_via_ssh();
18627 (telemetry, is_via_ssh)
18628 };
18629 refresh_linked_ranges(self, window, cx);
18630 telemetry.log_edit_event("editor", is_via_ssh);
18631 }
18632 multi_buffer::Event::ExcerptsAdded {
18633 buffer,
18634 predecessor,
18635 excerpts,
18636 } => {
18637 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18638 let buffer_id = buffer.read(cx).remote_id();
18639 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
18640 if let Some(project) = &self.project {
18641 update_uncommitted_diff_for_buffer(
18642 cx.entity(),
18643 project,
18644 [buffer.clone()],
18645 self.buffer.clone(),
18646 cx,
18647 )
18648 .detach();
18649 }
18650 }
18651 cx.emit(EditorEvent::ExcerptsAdded {
18652 buffer: buffer.clone(),
18653 predecessor: *predecessor,
18654 excerpts: excerpts.clone(),
18655 });
18656 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18657 }
18658 multi_buffer::Event::ExcerptsRemoved {
18659 ids,
18660 removed_buffer_ids,
18661 } => {
18662 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
18663 let buffer = self.buffer.read(cx);
18664 self.registered_buffers
18665 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
18666 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18667 cx.emit(EditorEvent::ExcerptsRemoved {
18668 ids: ids.clone(),
18669 removed_buffer_ids: removed_buffer_ids.clone(),
18670 })
18671 }
18672 multi_buffer::Event::ExcerptsEdited {
18673 excerpt_ids,
18674 buffer_ids,
18675 } => {
18676 self.display_map.update(cx, |map, cx| {
18677 map.unfold_buffers(buffer_ids.iter().copied(), cx)
18678 });
18679 cx.emit(EditorEvent::ExcerptsEdited {
18680 ids: excerpt_ids.clone(),
18681 })
18682 }
18683 multi_buffer::Event::ExcerptsExpanded { ids } => {
18684 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18685 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
18686 }
18687 multi_buffer::Event::Reparsed(buffer_id) => {
18688 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18689 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18690
18691 cx.emit(EditorEvent::Reparsed(*buffer_id));
18692 }
18693 multi_buffer::Event::DiffHunksToggled => {
18694 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18695 }
18696 multi_buffer::Event::LanguageChanged(buffer_id) => {
18697 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
18698 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18699 cx.emit(EditorEvent::Reparsed(*buffer_id));
18700 cx.notify();
18701 }
18702 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
18703 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
18704 multi_buffer::Event::FileHandleChanged
18705 | multi_buffer::Event::Reloaded
18706 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
18707 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
18708 multi_buffer::Event::DiagnosticsUpdated => {
18709 self.refresh_active_diagnostics(cx);
18710 self.refresh_inline_diagnostics(true, window, cx);
18711 self.scrollbar_marker_state.dirty = true;
18712 cx.notify();
18713 }
18714 _ => {}
18715 };
18716 }
18717
18718 pub fn start_temporary_diff_override(&mut self) {
18719 self.load_diff_task.take();
18720 self.temporary_diff_override = true;
18721 }
18722
18723 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
18724 self.temporary_diff_override = false;
18725 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
18726 self.buffer.update(cx, |buffer, cx| {
18727 buffer.set_all_diff_hunks_collapsed(cx);
18728 });
18729
18730 if let Some(project) = self.project.clone() {
18731 self.load_diff_task = Some(
18732 update_uncommitted_diff_for_buffer(
18733 cx.entity(),
18734 &project,
18735 self.buffer.read(cx).all_buffers(),
18736 self.buffer.clone(),
18737 cx,
18738 )
18739 .shared(),
18740 );
18741 }
18742 }
18743
18744 fn on_display_map_changed(
18745 &mut self,
18746 _: Entity<DisplayMap>,
18747 _: &mut Window,
18748 cx: &mut Context<Self>,
18749 ) {
18750 cx.notify();
18751 }
18752
18753 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18754 let new_severity = if self.diagnostics_enabled() {
18755 EditorSettings::get_global(cx)
18756 .diagnostics_max_severity
18757 .unwrap_or(DiagnosticSeverity::Hint)
18758 } else {
18759 DiagnosticSeverity::Off
18760 };
18761 self.set_max_diagnostics_severity(new_severity, cx);
18762 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18763 self.update_edit_prediction_settings(cx);
18764 self.refresh_inline_completion(true, false, window, cx);
18765 self.refresh_inlay_hints(
18766 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
18767 self.selections.newest_anchor().head(),
18768 &self.buffer.read(cx).snapshot(cx),
18769 cx,
18770 )),
18771 cx,
18772 );
18773
18774 let old_cursor_shape = self.cursor_shape;
18775
18776 {
18777 let editor_settings = EditorSettings::get_global(cx);
18778 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
18779 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
18780 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
18781 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
18782 }
18783
18784 if old_cursor_shape != self.cursor_shape {
18785 cx.emit(EditorEvent::CursorShapeChanged);
18786 }
18787
18788 let project_settings = ProjectSettings::get_global(cx);
18789 self.serialize_dirty_buffers =
18790 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
18791
18792 if self.mode.is_full() {
18793 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
18794 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
18795 if self.show_inline_diagnostics != show_inline_diagnostics {
18796 self.show_inline_diagnostics = show_inline_diagnostics;
18797 self.refresh_inline_diagnostics(false, window, cx);
18798 }
18799
18800 if self.git_blame_inline_enabled != inline_blame_enabled {
18801 self.toggle_git_blame_inline_internal(false, window, cx);
18802 }
18803
18804 let minimap_settings = EditorSettings::get_global(cx).minimap;
18805 if self.minimap_visibility != MinimapVisibility::Disabled {
18806 if self.minimap_visibility.settings_visibility()
18807 != minimap_settings.minimap_enabled()
18808 {
18809 self.set_minimap_visibility(
18810 MinimapVisibility::for_mode(self.mode(), cx),
18811 window,
18812 cx,
18813 );
18814 } else if let Some(minimap_entity) = self.minimap.as_ref() {
18815 minimap_entity.update(cx, |minimap_editor, cx| {
18816 minimap_editor.update_minimap_configuration(minimap_settings, cx)
18817 })
18818 }
18819 }
18820 }
18821
18822 cx.notify();
18823 }
18824
18825 pub fn set_searchable(&mut self, searchable: bool) {
18826 self.searchable = searchable;
18827 }
18828
18829 pub fn searchable(&self) -> bool {
18830 self.searchable
18831 }
18832
18833 fn open_proposed_changes_editor(
18834 &mut self,
18835 _: &OpenProposedChangesEditor,
18836 window: &mut Window,
18837 cx: &mut Context<Self>,
18838 ) {
18839 let Some(workspace) = self.workspace() else {
18840 cx.propagate();
18841 return;
18842 };
18843
18844 let selections = self.selections.all::<usize>(cx);
18845 let multi_buffer = self.buffer.read(cx);
18846 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18847 let mut new_selections_by_buffer = HashMap::default();
18848 for selection in selections {
18849 for (buffer, range, _) in
18850 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
18851 {
18852 let mut range = range.to_point(buffer);
18853 range.start.column = 0;
18854 range.end.column = buffer.line_len(range.end.row);
18855 new_selections_by_buffer
18856 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
18857 .or_insert(Vec::new())
18858 .push(range)
18859 }
18860 }
18861
18862 let proposed_changes_buffers = new_selections_by_buffer
18863 .into_iter()
18864 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
18865 .collect::<Vec<_>>();
18866 let proposed_changes_editor = cx.new(|cx| {
18867 ProposedChangesEditor::new(
18868 "Proposed changes",
18869 proposed_changes_buffers,
18870 self.project.clone(),
18871 window,
18872 cx,
18873 )
18874 });
18875
18876 window.defer(cx, move |window, cx| {
18877 workspace.update(cx, |workspace, cx| {
18878 workspace.active_pane().update(cx, |pane, cx| {
18879 pane.add_item(
18880 Box::new(proposed_changes_editor),
18881 true,
18882 true,
18883 None,
18884 window,
18885 cx,
18886 );
18887 });
18888 });
18889 });
18890 }
18891
18892 pub fn open_excerpts_in_split(
18893 &mut self,
18894 _: &OpenExcerptsSplit,
18895 window: &mut Window,
18896 cx: &mut Context<Self>,
18897 ) {
18898 self.open_excerpts_common(None, true, window, cx)
18899 }
18900
18901 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
18902 self.open_excerpts_common(None, false, window, cx)
18903 }
18904
18905 fn open_excerpts_common(
18906 &mut self,
18907 jump_data: Option<JumpData>,
18908 split: bool,
18909 window: &mut Window,
18910 cx: &mut Context<Self>,
18911 ) {
18912 let Some(workspace) = self.workspace() else {
18913 cx.propagate();
18914 return;
18915 };
18916
18917 if self.buffer.read(cx).is_singleton() {
18918 cx.propagate();
18919 return;
18920 }
18921
18922 let mut new_selections_by_buffer = HashMap::default();
18923 match &jump_data {
18924 Some(JumpData::MultiBufferPoint {
18925 excerpt_id,
18926 position,
18927 anchor,
18928 line_offset_from_top,
18929 }) => {
18930 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18931 if let Some(buffer) = multi_buffer_snapshot
18932 .buffer_id_for_excerpt(*excerpt_id)
18933 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
18934 {
18935 let buffer_snapshot = buffer.read(cx).snapshot();
18936 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
18937 language::ToPoint::to_point(anchor, &buffer_snapshot)
18938 } else {
18939 buffer_snapshot.clip_point(*position, Bias::Left)
18940 };
18941 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
18942 new_selections_by_buffer.insert(
18943 buffer,
18944 (
18945 vec![jump_to_offset..jump_to_offset],
18946 Some(*line_offset_from_top),
18947 ),
18948 );
18949 }
18950 }
18951 Some(JumpData::MultiBufferRow {
18952 row,
18953 line_offset_from_top,
18954 }) => {
18955 let point = MultiBufferPoint::new(row.0, 0);
18956 if let Some((buffer, buffer_point, _)) =
18957 self.buffer.read(cx).point_to_buffer_point(point, cx)
18958 {
18959 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
18960 new_selections_by_buffer
18961 .entry(buffer)
18962 .or_insert((Vec::new(), Some(*line_offset_from_top)))
18963 .0
18964 .push(buffer_offset..buffer_offset)
18965 }
18966 }
18967 None => {
18968 let selections = self.selections.all::<usize>(cx);
18969 let multi_buffer = self.buffer.read(cx);
18970 for selection in selections {
18971 for (snapshot, range, _, anchor) in multi_buffer
18972 .snapshot(cx)
18973 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
18974 {
18975 if let Some(anchor) = anchor {
18976 // selection is in a deleted hunk
18977 let Some(buffer_id) = anchor.buffer_id else {
18978 continue;
18979 };
18980 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
18981 continue;
18982 };
18983 let offset = text::ToOffset::to_offset(
18984 &anchor.text_anchor,
18985 &buffer_handle.read(cx).snapshot(),
18986 );
18987 let range = offset..offset;
18988 new_selections_by_buffer
18989 .entry(buffer_handle)
18990 .or_insert((Vec::new(), None))
18991 .0
18992 .push(range)
18993 } else {
18994 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
18995 else {
18996 continue;
18997 };
18998 new_selections_by_buffer
18999 .entry(buffer_handle)
19000 .or_insert((Vec::new(), None))
19001 .0
19002 .push(range)
19003 }
19004 }
19005 }
19006 }
19007 }
19008
19009 new_selections_by_buffer
19010 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
19011
19012 if new_selections_by_buffer.is_empty() {
19013 return;
19014 }
19015
19016 // We defer the pane interaction because we ourselves are a workspace item
19017 // and activating a new item causes the pane to call a method on us reentrantly,
19018 // which panics if we're on the stack.
19019 window.defer(cx, move |window, cx| {
19020 workspace.update(cx, |workspace, cx| {
19021 let pane = if split {
19022 workspace.adjacent_pane(window, cx)
19023 } else {
19024 workspace.active_pane().clone()
19025 };
19026
19027 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
19028 let editor = buffer
19029 .read(cx)
19030 .file()
19031 .is_none()
19032 .then(|| {
19033 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
19034 // so `workspace.open_project_item` will never find them, always opening a new editor.
19035 // Instead, we try to activate the existing editor in the pane first.
19036 let (editor, pane_item_index) =
19037 pane.read(cx).items().enumerate().find_map(|(i, item)| {
19038 let editor = item.downcast::<Editor>()?;
19039 let singleton_buffer =
19040 editor.read(cx).buffer().read(cx).as_singleton()?;
19041 if singleton_buffer == buffer {
19042 Some((editor, i))
19043 } else {
19044 None
19045 }
19046 })?;
19047 pane.update(cx, |pane, cx| {
19048 pane.activate_item(pane_item_index, true, true, window, cx)
19049 });
19050 Some(editor)
19051 })
19052 .flatten()
19053 .unwrap_or_else(|| {
19054 workspace.open_project_item::<Self>(
19055 pane.clone(),
19056 buffer,
19057 true,
19058 true,
19059 window,
19060 cx,
19061 )
19062 });
19063
19064 editor.update(cx, |editor, cx| {
19065 let autoscroll = match scroll_offset {
19066 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
19067 None => Autoscroll::newest(),
19068 };
19069 let nav_history = editor.nav_history.take();
19070 editor.change_selections(Some(autoscroll), window, cx, |s| {
19071 s.select_ranges(ranges);
19072 });
19073 editor.nav_history = nav_history;
19074 });
19075 }
19076 })
19077 });
19078 }
19079
19080 // For now, don't allow opening excerpts in buffers that aren't backed by
19081 // regular project files.
19082 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
19083 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
19084 }
19085
19086 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
19087 let snapshot = self.buffer.read(cx).read(cx);
19088 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
19089 Some(
19090 ranges
19091 .iter()
19092 .map(move |range| {
19093 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
19094 })
19095 .collect(),
19096 )
19097 }
19098
19099 fn selection_replacement_ranges(
19100 &self,
19101 range: Range<OffsetUtf16>,
19102 cx: &mut App,
19103 ) -> Vec<Range<OffsetUtf16>> {
19104 let selections = self.selections.all::<OffsetUtf16>(cx);
19105 let newest_selection = selections
19106 .iter()
19107 .max_by_key(|selection| selection.id)
19108 .unwrap();
19109 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
19110 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
19111 let snapshot = self.buffer.read(cx).read(cx);
19112 selections
19113 .into_iter()
19114 .map(|mut selection| {
19115 selection.start.0 =
19116 (selection.start.0 as isize).saturating_add(start_delta) as usize;
19117 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
19118 snapshot.clip_offset_utf16(selection.start, Bias::Left)
19119 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
19120 })
19121 .collect()
19122 }
19123
19124 fn report_editor_event(
19125 &self,
19126 event_type: &'static str,
19127 file_extension: Option<String>,
19128 cx: &App,
19129 ) {
19130 if cfg!(any(test, feature = "test-support")) {
19131 return;
19132 }
19133
19134 let Some(project) = &self.project else { return };
19135
19136 // If None, we are in a file without an extension
19137 let file = self
19138 .buffer
19139 .read(cx)
19140 .as_singleton()
19141 .and_then(|b| b.read(cx).file());
19142 let file_extension = file_extension.or(file
19143 .as_ref()
19144 .and_then(|file| Path::new(file.file_name(cx)).extension())
19145 .and_then(|e| e.to_str())
19146 .map(|a| a.to_string()));
19147
19148 let vim_mode = vim_enabled(cx);
19149
19150 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
19151 let copilot_enabled = edit_predictions_provider
19152 == language::language_settings::EditPredictionProvider::Copilot;
19153 let copilot_enabled_for_language = self
19154 .buffer
19155 .read(cx)
19156 .language_settings(cx)
19157 .show_edit_predictions;
19158
19159 let project = project.read(cx);
19160 telemetry::event!(
19161 event_type,
19162 file_extension,
19163 vim_mode,
19164 copilot_enabled,
19165 copilot_enabled_for_language,
19166 edit_predictions_provider,
19167 is_via_ssh = project.is_via_ssh(),
19168 );
19169 }
19170
19171 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
19172 /// with each line being an array of {text, highlight} objects.
19173 fn copy_highlight_json(
19174 &mut self,
19175 _: &CopyHighlightJson,
19176 window: &mut Window,
19177 cx: &mut Context<Self>,
19178 ) {
19179 #[derive(Serialize)]
19180 struct Chunk<'a> {
19181 text: String,
19182 highlight: Option<&'a str>,
19183 }
19184
19185 let snapshot = self.buffer.read(cx).snapshot(cx);
19186 let range = self
19187 .selected_text_range(false, window, cx)
19188 .and_then(|selection| {
19189 if selection.range.is_empty() {
19190 None
19191 } else {
19192 Some(selection.range)
19193 }
19194 })
19195 .unwrap_or_else(|| 0..snapshot.len());
19196
19197 let chunks = snapshot.chunks(range, true);
19198 let mut lines = Vec::new();
19199 let mut line: VecDeque<Chunk> = VecDeque::new();
19200
19201 let Some(style) = self.style.as_ref() else {
19202 return;
19203 };
19204
19205 for chunk in chunks {
19206 let highlight = chunk
19207 .syntax_highlight_id
19208 .and_then(|id| id.name(&style.syntax));
19209 let mut chunk_lines = chunk.text.split('\n').peekable();
19210 while let Some(text) = chunk_lines.next() {
19211 let mut merged_with_last_token = false;
19212 if let Some(last_token) = line.back_mut() {
19213 if last_token.highlight == highlight {
19214 last_token.text.push_str(text);
19215 merged_with_last_token = true;
19216 }
19217 }
19218
19219 if !merged_with_last_token {
19220 line.push_back(Chunk {
19221 text: text.into(),
19222 highlight,
19223 });
19224 }
19225
19226 if chunk_lines.peek().is_some() {
19227 if line.len() > 1 && line.front().unwrap().text.is_empty() {
19228 line.pop_front();
19229 }
19230 if line.len() > 1 && line.back().unwrap().text.is_empty() {
19231 line.pop_back();
19232 }
19233
19234 lines.push(mem::take(&mut line));
19235 }
19236 }
19237 }
19238
19239 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
19240 return;
19241 };
19242 cx.write_to_clipboard(ClipboardItem::new_string(lines));
19243 }
19244
19245 pub fn open_context_menu(
19246 &mut self,
19247 _: &OpenContextMenu,
19248 window: &mut Window,
19249 cx: &mut Context<Self>,
19250 ) {
19251 self.request_autoscroll(Autoscroll::newest(), cx);
19252 let position = self.selections.newest_display(cx).start;
19253 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
19254 }
19255
19256 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
19257 &self.inlay_hint_cache
19258 }
19259
19260 pub fn replay_insert_event(
19261 &mut self,
19262 text: &str,
19263 relative_utf16_range: Option<Range<isize>>,
19264 window: &mut Window,
19265 cx: &mut Context<Self>,
19266 ) {
19267 if !self.input_enabled {
19268 cx.emit(EditorEvent::InputIgnored { text: text.into() });
19269 return;
19270 }
19271 if let Some(relative_utf16_range) = relative_utf16_range {
19272 let selections = self.selections.all::<OffsetUtf16>(cx);
19273 self.change_selections(None, window, cx, |s| {
19274 let new_ranges = selections.into_iter().map(|range| {
19275 let start = OffsetUtf16(
19276 range
19277 .head()
19278 .0
19279 .saturating_add_signed(relative_utf16_range.start),
19280 );
19281 let end = OffsetUtf16(
19282 range
19283 .head()
19284 .0
19285 .saturating_add_signed(relative_utf16_range.end),
19286 );
19287 start..end
19288 });
19289 s.select_ranges(new_ranges);
19290 });
19291 }
19292
19293 self.handle_input(text, window, cx);
19294 }
19295
19296 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
19297 let Some(provider) = self.semantics_provider.as_ref() else {
19298 return false;
19299 };
19300
19301 let mut supports = false;
19302 self.buffer().update(cx, |this, cx| {
19303 this.for_each_buffer(|buffer| {
19304 supports |= provider.supports_inlay_hints(buffer, cx);
19305 });
19306 });
19307
19308 supports
19309 }
19310
19311 pub fn is_focused(&self, window: &Window) -> bool {
19312 self.focus_handle.is_focused(window)
19313 }
19314
19315 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19316 cx.emit(EditorEvent::Focused);
19317
19318 if let Some(descendant) = self
19319 .last_focused_descendant
19320 .take()
19321 .and_then(|descendant| descendant.upgrade())
19322 {
19323 window.focus(&descendant);
19324 } else {
19325 if let Some(blame) = self.blame.as_ref() {
19326 blame.update(cx, GitBlame::focus)
19327 }
19328
19329 self.blink_manager.update(cx, BlinkManager::enable);
19330 self.show_cursor_names(window, cx);
19331 self.buffer.update(cx, |buffer, cx| {
19332 buffer.finalize_last_transaction(cx);
19333 if self.leader_id.is_none() {
19334 buffer.set_active_selections(
19335 &self.selections.disjoint_anchors(),
19336 self.selections.line_mode,
19337 self.cursor_shape,
19338 cx,
19339 );
19340 }
19341 });
19342 }
19343 }
19344
19345 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
19346 cx.emit(EditorEvent::FocusedIn)
19347 }
19348
19349 fn handle_focus_out(
19350 &mut self,
19351 event: FocusOutEvent,
19352 _window: &mut Window,
19353 cx: &mut Context<Self>,
19354 ) {
19355 if event.blurred != self.focus_handle {
19356 self.last_focused_descendant = Some(event.blurred);
19357 }
19358 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
19359 }
19360
19361 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19362 self.blink_manager.update(cx, BlinkManager::disable);
19363 self.buffer
19364 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
19365
19366 if let Some(blame) = self.blame.as_ref() {
19367 blame.update(cx, GitBlame::blur)
19368 }
19369 if !self.hover_state.focused(window, cx) {
19370 hide_hover(self, cx);
19371 }
19372 if !self
19373 .context_menu
19374 .borrow()
19375 .as_ref()
19376 .is_some_and(|context_menu| context_menu.focused(window, cx))
19377 {
19378 self.hide_context_menu(window, cx);
19379 }
19380 self.discard_inline_completion(false, cx);
19381 cx.emit(EditorEvent::Blurred);
19382 cx.notify();
19383 }
19384
19385 pub fn register_action<A: Action>(
19386 &mut self,
19387 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
19388 ) -> Subscription {
19389 let id = self.next_editor_action_id.post_inc();
19390 let listener = Arc::new(listener);
19391 self.editor_actions.borrow_mut().insert(
19392 id,
19393 Box::new(move |window, _| {
19394 let listener = listener.clone();
19395 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
19396 let action = action.downcast_ref().unwrap();
19397 if phase == DispatchPhase::Bubble {
19398 listener(action, window, cx)
19399 }
19400 })
19401 }),
19402 );
19403
19404 let editor_actions = self.editor_actions.clone();
19405 Subscription::new(move || {
19406 editor_actions.borrow_mut().remove(&id);
19407 })
19408 }
19409
19410 pub fn file_header_size(&self) -> u32 {
19411 FILE_HEADER_HEIGHT
19412 }
19413
19414 pub fn restore(
19415 &mut self,
19416 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
19417 window: &mut Window,
19418 cx: &mut Context<Self>,
19419 ) {
19420 let workspace = self.workspace();
19421 let project = self.project.as_ref();
19422 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
19423 let mut tasks = Vec::new();
19424 for (buffer_id, changes) in revert_changes {
19425 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
19426 buffer.update(cx, |buffer, cx| {
19427 buffer.edit(
19428 changes
19429 .into_iter()
19430 .map(|(range, text)| (range, text.to_string())),
19431 None,
19432 cx,
19433 );
19434 });
19435
19436 if let Some(project) =
19437 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
19438 {
19439 project.update(cx, |project, cx| {
19440 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
19441 })
19442 }
19443 }
19444 }
19445 tasks
19446 });
19447 cx.spawn_in(window, async move |_, cx| {
19448 for (buffer, task) in save_tasks {
19449 let result = task.await;
19450 if result.is_err() {
19451 let Some(path) = buffer
19452 .read_with(cx, |buffer, cx| buffer.project_path(cx))
19453 .ok()
19454 else {
19455 continue;
19456 };
19457 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
19458 let Some(task) = cx
19459 .update_window_entity(&workspace, |workspace, window, cx| {
19460 workspace
19461 .open_path_preview(path, None, false, false, false, window, cx)
19462 })
19463 .ok()
19464 else {
19465 continue;
19466 };
19467 task.await.log_err();
19468 }
19469 }
19470 }
19471 })
19472 .detach();
19473 self.change_selections(None, window, cx, |selections| selections.refresh());
19474 }
19475
19476 pub fn to_pixel_point(
19477 &self,
19478 source: multi_buffer::Anchor,
19479 editor_snapshot: &EditorSnapshot,
19480 window: &mut Window,
19481 ) -> Option<gpui::Point<Pixels>> {
19482 let source_point = source.to_display_point(editor_snapshot);
19483 self.display_to_pixel_point(source_point, editor_snapshot, window)
19484 }
19485
19486 pub fn display_to_pixel_point(
19487 &self,
19488 source: DisplayPoint,
19489 editor_snapshot: &EditorSnapshot,
19490 window: &mut Window,
19491 ) -> Option<gpui::Point<Pixels>> {
19492 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
19493 let text_layout_details = self.text_layout_details(window);
19494 let scroll_top = text_layout_details
19495 .scroll_anchor
19496 .scroll_position(editor_snapshot)
19497 .y;
19498
19499 if source.row().as_f32() < scroll_top.floor() {
19500 return None;
19501 }
19502 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
19503 let source_y = line_height * (source.row().as_f32() - scroll_top);
19504 Some(gpui::Point::new(source_x, source_y))
19505 }
19506
19507 pub fn has_visible_completions_menu(&self) -> bool {
19508 !self.edit_prediction_preview_is_active()
19509 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
19510 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
19511 })
19512 }
19513
19514 pub fn register_addon<T: Addon>(&mut self, instance: T) {
19515 if self.mode.is_minimap() {
19516 return;
19517 }
19518 self.addons
19519 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
19520 }
19521
19522 pub fn unregister_addon<T: Addon>(&mut self) {
19523 self.addons.remove(&std::any::TypeId::of::<T>());
19524 }
19525
19526 pub fn addon<T: Addon>(&self) -> Option<&T> {
19527 let type_id = std::any::TypeId::of::<T>();
19528 self.addons
19529 .get(&type_id)
19530 .and_then(|item| item.to_any().downcast_ref::<T>())
19531 }
19532
19533 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
19534 let type_id = std::any::TypeId::of::<T>();
19535 self.addons
19536 .get_mut(&type_id)
19537 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
19538 }
19539
19540 fn character_size(&self, window: &mut Window) -> gpui::Size<Pixels> {
19541 let text_layout_details = self.text_layout_details(window);
19542 let style = &text_layout_details.editor_style;
19543 let font_id = window.text_system().resolve_font(&style.text.font());
19544 let font_size = style.text.font_size.to_pixels(window.rem_size());
19545 let line_height = style.text.line_height_in_pixels(window.rem_size());
19546 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
19547
19548 gpui::Size::new(em_width, line_height)
19549 }
19550
19551 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
19552 self.load_diff_task.clone()
19553 }
19554
19555 fn read_metadata_from_db(
19556 &mut self,
19557 item_id: u64,
19558 workspace_id: WorkspaceId,
19559 window: &mut Window,
19560 cx: &mut Context<Editor>,
19561 ) {
19562 if self.is_singleton(cx)
19563 && !self.mode.is_minimap()
19564 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
19565 {
19566 let buffer_snapshot = OnceCell::new();
19567
19568 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
19569 if !folds.is_empty() {
19570 let snapshot =
19571 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
19572 self.fold_ranges(
19573 folds
19574 .into_iter()
19575 .map(|(start, end)| {
19576 snapshot.clip_offset(start, Bias::Left)
19577 ..snapshot.clip_offset(end, Bias::Right)
19578 })
19579 .collect(),
19580 false,
19581 window,
19582 cx,
19583 );
19584 }
19585 }
19586
19587 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
19588 if !selections.is_empty() {
19589 let snapshot =
19590 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
19591 self.change_selections(None, window, cx, |s| {
19592 s.select_ranges(selections.into_iter().map(|(start, end)| {
19593 snapshot.clip_offset(start, Bias::Left)
19594 ..snapshot.clip_offset(end, Bias::Right)
19595 }));
19596 });
19597 }
19598 };
19599 }
19600
19601 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
19602 }
19603}
19604
19605fn vim_enabled(cx: &App) -> bool {
19606 cx.global::<SettingsStore>()
19607 .raw_user_settings()
19608 .get("vim_mode")
19609 == Some(&serde_json::Value::Bool(true))
19610}
19611
19612fn process_completion_for_edit(
19613 completion: &Completion,
19614 intent: CompletionIntent,
19615 buffer: &Entity<Buffer>,
19616 cursor_position: &text::Anchor,
19617 cx: &mut Context<Editor>,
19618) -> CompletionEdit {
19619 let buffer = buffer.read(cx);
19620 let buffer_snapshot = buffer.snapshot();
19621 let (snippet, new_text) = if completion.is_snippet() {
19622 let mut snippet_source = completion.new_text.clone();
19623 if let Some(scope) = buffer_snapshot.language_scope_at(cursor_position) {
19624 if scope.prefers_label_for_snippet_in_completion() {
19625 if let Some(label) = completion.label() {
19626 if matches!(
19627 completion.kind(),
19628 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
19629 ) {
19630 snippet_source = label;
19631 }
19632 }
19633 }
19634 }
19635 match Snippet::parse(&snippet_source).log_err() {
19636 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
19637 None => (None, completion.new_text.clone()),
19638 }
19639 } else {
19640 (None, completion.new_text.clone())
19641 };
19642
19643 let mut range_to_replace = {
19644 let replace_range = &completion.replace_range;
19645 if let CompletionSource::Lsp {
19646 insert_range: Some(insert_range),
19647 ..
19648 } = &completion.source
19649 {
19650 debug_assert_eq!(
19651 insert_range.start, replace_range.start,
19652 "insert_range and replace_range should start at the same position"
19653 );
19654 debug_assert!(
19655 insert_range
19656 .start
19657 .cmp(&cursor_position, &buffer_snapshot)
19658 .is_le(),
19659 "insert_range should start before or at cursor position"
19660 );
19661 debug_assert!(
19662 replace_range
19663 .start
19664 .cmp(&cursor_position, &buffer_snapshot)
19665 .is_le(),
19666 "replace_range should start before or at cursor position"
19667 );
19668 debug_assert!(
19669 insert_range
19670 .end
19671 .cmp(&cursor_position, &buffer_snapshot)
19672 .is_le(),
19673 "insert_range should end before or at cursor position"
19674 );
19675
19676 let should_replace = match intent {
19677 CompletionIntent::CompleteWithInsert => false,
19678 CompletionIntent::CompleteWithReplace => true,
19679 CompletionIntent::Complete | CompletionIntent::Compose => {
19680 let insert_mode =
19681 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
19682 .completions
19683 .lsp_insert_mode;
19684 match insert_mode {
19685 LspInsertMode::Insert => false,
19686 LspInsertMode::Replace => true,
19687 LspInsertMode::ReplaceSubsequence => {
19688 let mut text_to_replace = buffer.chars_for_range(
19689 buffer.anchor_before(replace_range.start)
19690 ..buffer.anchor_after(replace_range.end),
19691 );
19692 let mut current_needle = text_to_replace.next();
19693 for haystack_ch in completion.label.text.chars() {
19694 if let Some(needle_ch) = current_needle {
19695 if haystack_ch.eq_ignore_ascii_case(&needle_ch) {
19696 current_needle = text_to_replace.next();
19697 }
19698 }
19699 }
19700 current_needle.is_none()
19701 }
19702 LspInsertMode::ReplaceSuffix => {
19703 if replace_range
19704 .end
19705 .cmp(&cursor_position, &buffer_snapshot)
19706 .is_gt()
19707 {
19708 let range_after_cursor = *cursor_position..replace_range.end;
19709 let text_after_cursor = buffer
19710 .text_for_range(
19711 buffer.anchor_before(range_after_cursor.start)
19712 ..buffer.anchor_after(range_after_cursor.end),
19713 )
19714 .collect::<String>()
19715 .to_ascii_lowercase();
19716 completion
19717 .label
19718 .text
19719 .to_ascii_lowercase()
19720 .ends_with(&text_after_cursor)
19721 } else {
19722 true
19723 }
19724 }
19725 }
19726 }
19727 };
19728
19729 if should_replace {
19730 replace_range.clone()
19731 } else {
19732 insert_range.clone()
19733 }
19734 } else {
19735 replace_range.clone()
19736 }
19737 };
19738
19739 if range_to_replace
19740 .end
19741 .cmp(&cursor_position, &buffer_snapshot)
19742 .is_lt()
19743 {
19744 range_to_replace.end = *cursor_position;
19745 }
19746
19747 CompletionEdit {
19748 new_text,
19749 replace_range: range_to_replace.to_offset(&buffer),
19750 snippet,
19751 }
19752}
19753
19754struct CompletionEdit {
19755 new_text: String,
19756 replace_range: Range<usize>,
19757 snippet: Option<Snippet>,
19758}
19759
19760fn insert_extra_newline_brackets(
19761 buffer: &MultiBufferSnapshot,
19762 range: Range<usize>,
19763 language: &language::LanguageScope,
19764) -> bool {
19765 let leading_whitespace_len = buffer
19766 .reversed_chars_at(range.start)
19767 .take_while(|c| c.is_whitespace() && *c != '\n')
19768 .map(|c| c.len_utf8())
19769 .sum::<usize>();
19770 let trailing_whitespace_len = buffer
19771 .chars_at(range.end)
19772 .take_while(|c| c.is_whitespace() && *c != '\n')
19773 .map(|c| c.len_utf8())
19774 .sum::<usize>();
19775 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
19776
19777 language.brackets().any(|(pair, enabled)| {
19778 let pair_start = pair.start.trim_end();
19779 let pair_end = pair.end.trim_start();
19780
19781 enabled
19782 && pair.newline
19783 && buffer.contains_str_at(range.end, pair_end)
19784 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
19785 })
19786}
19787
19788fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
19789 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
19790 [(buffer, range, _)] => (*buffer, range.clone()),
19791 _ => return false,
19792 };
19793 let pair = {
19794 let mut result: Option<BracketMatch> = None;
19795
19796 for pair in buffer
19797 .all_bracket_ranges(range.clone())
19798 .filter(move |pair| {
19799 pair.open_range.start <= range.start && pair.close_range.end >= range.end
19800 })
19801 {
19802 let len = pair.close_range.end - pair.open_range.start;
19803
19804 if let Some(existing) = &result {
19805 let existing_len = existing.close_range.end - existing.open_range.start;
19806 if len > existing_len {
19807 continue;
19808 }
19809 }
19810
19811 result = Some(pair);
19812 }
19813
19814 result
19815 };
19816 let Some(pair) = pair else {
19817 return false;
19818 };
19819 pair.newline_only
19820 && buffer
19821 .chars_for_range(pair.open_range.end..range.start)
19822 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
19823 .all(|c| c.is_whitespace() && c != '\n')
19824}
19825
19826fn update_uncommitted_diff_for_buffer(
19827 editor: Entity<Editor>,
19828 project: &Entity<Project>,
19829 buffers: impl IntoIterator<Item = Entity<Buffer>>,
19830 buffer: Entity<MultiBuffer>,
19831 cx: &mut App,
19832) -> Task<()> {
19833 let mut tasks = Vec::new();
19834 project.update(cx, |project, cx| {
19835 for buffer in buffers {
19836 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
19837 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
19838 }
19839 }
19840 });
19841 cx.spawn(async move |cx| {
19842 let diffs = future::join_all(tasks).await;
19843 if editor
19844 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
19845 .unwrap_or(false)
19846 {
19847 return;
19848 }
19849
19850 buffer
19851 .update(cx, |buffer, cx| {
19852 for diff in diffs.into_iter().flatten() {
19853 buffer.add_diff(diff, cx);
19854 }
19855 })
19856 .ok();
19857 })
19858}
19859
19860fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
19861 let tab_size = tab_size.get() as usize;
19862 let mut width = offset;
19863
19864 for ch in text.chars() {
19865 width += if ch == '\t' {
19866 tab_size - (width % tab_size)
19867 } else {
19868 1
19869 };
19870 }
19871
19872 width - offset
19873}
19874
19875#[cfg(test)]
19876mod tests {
19877 use super::*;
19878
19879 #[test]
19880 fn test_string_size_with_expanded_tabs() {
19881 let nz = |val| NonZeroU32::new(val).unwrap();
19882 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
19883 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
19884 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
19885 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
19886 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
19887 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
19888 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
19889 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
19890 }
19891}
19892
19893/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
19894struct WordBreakingTokenizer<'a> {
19895 input: &'a str,
19896}
19897
19898impl<'a> WordBreakingTokenizer<'a> {
19899 fn new(input: &'a str) -> Self {
19900 Self { input }
19901 }
19902}
19903
19904fn is_char_ideographic(ch: char) -> bool {
19905 use unicode_script::Script::*;
19906 use unicode_script::UnicodeScript;
19907 matches!(ch.script(), Han | Tangut | Yi)
19908}
19909
19910fn is_grapheme_ideographic(text: &str) -> bool {
19911 text.chars().any(is_char_ideographic)
19912}
19913
19914fn is_grapheme_whitespace(text: &str) -> bool {
19915 text.chars().any(|x| x.is_whitespace())
19916}
19917
19918fn should_stay_with_preceding_ideograph(text: &str) -> bool {
19919 text.chars().next().map_or(false, |ch| {
19920 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
19921 })
19922}
19923
19924#[derive(PartialEq, Eq, Debug, Clone, Copy)]
19925enum WordBreakToken<'a> {
19926 Word { token: &'a str, grapheme_len: usize },
19927 InlineWhitespace { token: &'a str, grapheme_len: usize },
19928 Newline,
19929}
19930
19931impl<'a> Iterator for WordBreakingTokenizer<'a> {
19932 /// Yields a span, the count of graphemes in the token, and whether it was
19933 /// whitespace. Note that it also breaks at word boundaries.
19934 type Item = WordBreakToken<'a>;
19935
19936 fn next(&mut self) -> Option<Self::Item> {
19937 use unicode_segmentation::UnicodeSegmentation;
19938 if self.input.is_empty() {
19939 return None;
19940 }
19941
19942 let mut iter = self.input.graphemes(true).peekable();
19943 let mut offset = 0;
19944 let mut grapheme_len = 0;
19945 if let Some(first_grapheme) = iter.next() {
19946 let is_newline = first_grapheme == "\n";
19947 let is_whitespace = is_grapheme_whitespace(first_grapheme);
19948 offset += first_grapheme.len();
19949 grapheme_len += 1;
19950 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
19951 if let Some(grapheme) = iter.peek().copied() {
19952 if should_stay_with_preceding_ideograph(grapheme) {
19953 offset += grapheme.len();
19954 grapheme_len += 1;
19955 }
19956 }
19957 } else {
19958 let mut words = self.input[offset..].split_word_bound_indices().peekable();
19959 let mut next_word_bound = words.peek().copied();
19960 if next_word_bound.map_or(false, |(i, _)| i == 0) {
19961 next_word_bound = words.next();
19962 }
19963 while let Some(grapheme) = iter.peek().copied() {
19964 if next_word_bound.map_or(false, |(i, _)| i == offset) {
19965 break;
19966 };
19967 if is_grapheme_whitespace(grapheme) != is_whitespace
19968 || (grapheme == "\n") != is_newline
19969 {
19970 break;
19971 };
19972 offset += grapheme.len();
19973 grapheme_len += 1;
19974 iter.next();
19975 }
19976 }
19977 let token = &self.input[..offset];
19978 self.input = &self.input[offset..];
19979 if token == "\n" {
19980 Some(WordBreakToken::Newline)
19981 } else if is_whitespace {
19982 Some(WordBreakToken::InlineWhitespace {
19983 token,
19984 grapheme_len,
19985 })
19986 } else {
19987 Some(WordBreakToken::Word {
19988 token,
19989 grapheme_len,
19990 })
19991 }
19992 } else {
19993 None
19994 }
19995 }
19996}
19997
19998#[test]
19999fn test_word_breaking_tokenizer() {
20000 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
20001 ("", &[]),
20002 (" ", &[whitespace(" ", 2)]),
20003 ("Ʒ", &[word("Ʒ", 1)]),
20004 ("Ǽ", &[word("Ǽ", 1)]),
20005 ("⋑", &[word("⋑", 1)]),
20006 ("⋑⋑", &[word("⋑⋑", 2)]),
20007 (
20008 "原理,进而",
20009 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
20010 ),
20011 (
20012 "hello world",
20013 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
20014 ),
20015 (
20016 "hello, world",
20017 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
20018 ),
20019 (
20020 " hello world",
20021 &[
20022 whitespace(" ", 2),
20023 word("hello", 5),
20024 whitespace(" ", 1),
20025 word("world", 5),
20026 ],
20027 ),
20028 (
20029 "这是什么 \n 钢笔",
20030 &[
20031 word("这", 1),
20032 word("是", 1),
20033 word("什", 1),
20034 word("么", 1),
20035 whitespace(" ", 1),
20036 newline(),
20037 whitespace(" ", 1),
20038 word("钢", 1),
20039 word("笔", 1),
20040 ],
20041 ),
20042 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
20043 ];
20044
20045 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
20046 WordBreakToken::Word {
20047 token,
20048 grapheme_len,
20049 }
20050 }
20051
20052 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
20053 WordBreakToken::InlineWhitespace {
20054 token,
20055 grapheme_len,
20056 }
20057 }
20058
20059 fn newline() -> WordBreakToken<'static> {
20060 WordBreakToken::Newline
20061 }
20062
20063 for (input, result) in tests {
20064 assert_eq!(
20065 WordBreakingTokenizer::new(input)
20066 .collect::<Vec<_>>()
20067 .as_slice(),
20068 *result,
20069 );
20070 }
20071}
20072
20073fn wrap_with_prefix(
20074 line_prefix: String,
20075 unwrapped_text: String,
20076 wrap_column: usize,
20077 tab_size: NonZeroU32,
20078 preserve_existing_whitespace: bool,
20079) -> String {
20080 let line_prefix_len = char_len_with_expanded_tabs(0, &line_prefix, tab_size);
20081 let mut wrapped_text = String::new();
20082 let mut current_line = line_prefix.clone();
20083
20084 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
20085 let mut current_line_len = line_prefix_len;
20086 let mut in_whitespace = false;
20087 for token in tokenizer {
20088 let have_preceding_whitespace = in_whitespace;
20089 match token {
20090 WordBreakToken::Word {
20091 token,
20092 grapheme_len,
20093 } => {
20094 in_whitespace = false;
20095 if current_line_len + grapheme_len > wrap_column
20096 && current_line_len != line_prefix_len
20097 {
20098 wrapped_text.push_str(current_line.trim_end());
20099 wrapped_text.push('\n');
20100 current_line.truncate(line_prefix.len());
20101 current_line_len = line_prefix_len;
20102 }
20103 current_line.push_str(token);
20104 current_line_len += grapheme_len;
20105 }
20106 WordBreakToken::InlineWhitespace {
20107 mut token,
20108 mut grapheme_len,
20109 } => {
20110 in_whitespace = true;
20111 if have_preceding_whitespace && !preserve_existing_whitespace {
20112 continue;
20113 }
20114 if !preserve_existing_whitespace {
20115 token = " ";
20116 grapheme_len = 1;
20117 }
20118 if current_line_len + grapheme_len > wrap_column {
20119 wrapped_text.push_str(current_line.trim_end());
20120 wrapped_text.push('\n');
20121 current_line.truncate(line_prefix.len());
20122 current_line_len = line_prefix_len;
20123 } else if current_line_len != line_prefix_len || preserve_existing_whitespace {
20124 current_line.push_str(token);
20125 current_line_len += grapheme_len;
20126 }
20127 }
20128 WordBreakToken::Newline => {
20129 in_whitespace = true;
20130 if preserve_existing_whitespace {
20131 wrapped_text.push_str(current_line.trim_end());
20132 wrapped_text.push('\n');
20133 current_line.truncate(line_prefix.len());
20134 current_line_len = line_prefix_len;
20135 } else if have_preceding_whitespace {
20136 continue;
20137 } else if current_line_len + 1 > wrap_column && current_line_len != line_prefix_len
20138 {
20139 wrapped_text.push_str(current_line.trim_end());
20140 wrapped_text.push('\n');
20141 current_line.truncate(line_prefix.len());
20142 current_line_len = line_prefix_len;
20143 } else if current_line_len != line_prefix_len {
20144 current_line.push(' ');
20145 current_line_len += 1;
20146 }
20147 }
20148 }
20149 }
20150
20151 if !current_line.is_empty() {
20152 wrapped_text.push_str(¤t_line);
20153 }
20154 wrapped_text
20155}
20156
20157#[test]
20158fn test_wrap_with_prefix() {
20159 assert_eq!(
20160 wrap_with_prefix(
20161 "# ".to_string(),
20162 "abcdefg".to_string(),
20163 4,
20164 NonZeroU32::new(4).unwrap(),
20165 false,
20166 ),
20167 "# abcdefg"
20168 );
20169 assert_eq!(
20170 wrap_with_prefix(
20171 "".to_string(),
20172 "\thello world".to_string(),
20173 8,
20174 NonZeroU32::new(4).unwrap(),
20175 false,
20176 ),
20177 "hello\nworld"
20178 );
20179 assert_eq!(
20180 wrap_with_prefix(
20181 "// ".to_string(),
20182 "xx \nyy zz aa bb cc".to_string(),
20183 12,
20184 NonZeroU32::new(4).unwrap(),
20185 false,
20186 ),
20187 "// xx yy zz\n// aa bb cc"
20188 );
20189 assert_eq!(
20190 wrap_with_prefix(
20191 String::new(),
20192 "这是什么 \n 钢笔".to_string(),
20193 3,
20194 NonZeroU32::new(4).unwrap(),
20195 false,
20196 ),
20197 "这是什\n么 钢\n笔"
20198 );
20199}
20200
20201pub trait CollaborationHub {
20202 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
20203 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
20204 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
20205}
20206
20207impl CollaborationHub for Entity<Project> {
20208 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
20209 self.read(cx).collaborators()
20210 }
20211
20212 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
20213 self.read(cx).user_store().read(cx).participant_indices()
20214 }
20215
20216 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
20217 let this = self.read(cx);
20218 let user_ids = this.collaborators().values().map(|c| c.user_id);
20219 this.user_store().read(cx).participant_names(user_ids, cx)
20220 }
20221}
20222
20223pub trait SemanticsProvider {
20224 fn hover(
20225 &self,
20226 buffer: &Entity<Buffer>,
20227 position: text::Anchor,
20228 cx: &mut App,
20229 ) -> Option<Task<Vec<project::Hover>>>;
20230
20231 fn inline_values(
20232 &self,
20233 buffer_handle: Entity<Buffer>,
20234 range: Range<text::Anchor>,
20235 cx: &mut App,
20236 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
20237
20238 fn inlay_hints(
20239 &self,
20240 buffer_handle: Entity<Buffer>,
20241 range: Range<text::Anchor>,
20242 cx: &mut App,
20243 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
20244
20245 fn resolve_inlay_hint(
20246 &self,
20247 hint: InlayHint,
20248 buffer_handle: Entity<Buffer>,
20249 server_id: LanguageServerId,
20250 cx: &mut App,
20251 ) -> Option<Task<anyhow::Result<InlayHint>>>;
20252
20253 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
20254
20255 fn document_highlights(
20256 &self,
20257 buffer: &Entity<Buffer>,
20258 position: text::Anchor,
20259 cx: &mut App,
20260 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
20261
20262 fn definitions(
20263 &self,
20264 buffer: &Entity<Buffer>,
20265 position: text::Anchor,
20266 kind: GotoDefinitionKind,
20267 cx: &mut App,
20268 ) -> Option<Task<Result<Vec<LocationLink>>>>;
20269
20270 fn range_for_rename(
20271 &self,
20272 buffer: &Entity<Buffer>,
20273 position: text::Anchor,
20274 cx: &mut App,
20275 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
20276
20277 fn perform_rename(
20278 &self,
20279 buffer: &Entity<Buffer>,
20280 position: text::Anchor,
20281 new_name: String,
20282 cx: &mut App,
20283 ) -> Option<Task<Result<ProjectTransaction>>>;
20284}
20285
20286pub trait CompletionProvider {
20287 fn completions(
20288 &self,
20289 excerpt_id: ExcerptId,
20290 buffer: &Entity<Buffer>,
20291 buffer_position: text::Anchor,
20292 trigger: CompletionContext,
20293 window: &mut Window,
20294 cx: &mut Context<Editor>,
20295 ) -> Task<Result<Vec<CompletionResponse>>>;
20296
20297 fn resolve_completions(
20298 &self,
20299 buffer: Entity<Buffer>,
20300 completion_indices: Vec<usize>,
20301 completions: Rc<RefCell<Box<[Completion]>>>,
20302 cx: &mut Context<Editor>,
20303 ) -> Task<Result<bool>>;
20304
20305 fn apply_additional_edits_for_completion(
20306 &self,
20307 _buffer: Entity<Buffer>,
20308 _completions: Rc<RefCell<Box<[Completion]>>>,
20309 _completion_index: usize,
20310 _push_to_history: bool,
20311 _cx: &mut Context<Editor>,
20312 ) -> Task<Result<Option<language::Transaction>>> {
20313 Task::ready(Ok(None))
20314 }
20315
20316 fn is_completion_trigger(
20317 &self,
20318 buffer: &Entity<Buffer>,
20319 position: language::Anchor,
20320 text: &str,
20321 trigger_in_words: bool,
20322 menu_is_open: bool,
20323 cx: &mut Context<Editor>,
20324 ) -> bool;
20325
20326 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
20327
20328 fn sort_completions(&self) -> bool {
20329 true
20330 }
20331
20332 fn filter_completions(&self) -> bool {
20333 true
20334 }
20335}
20336
20337pub trait CodeActionProvider {
20338 fn id(&self) -> Arc<str>;
20339
20340 fn code_actions(
20341 &self,
20342 buffer: &Entity<Buffer>,
20343 range: Range<text::Anchor>,
20344 window: &mut Window,
20345 cx: &mut App,
20346 ) -> Task<Result<Vec<CodeAction>>>;
20347
20348 fn apply_code_action(
20349 &self,
20350 buffer_handle: Entity<Buffer>,
20351 action: CodeAction,
20352 excerpt_id: ExcerptId,
20353 push_to_history: bool,
20354 window: &mut Window,
20355 cx: &mut App,
20356 ) -> Task<Result<ProjectTransaction>>;
20357}
20358
20359impl CodeActionProvider for Entity<Project> {
20360 fn id(&self) -> Arc<str> {
20361 "project".into()
20362 }
20363
20364 fn code_actions(
20365 &self,
20366 buffer: &Entity<Buffer>,
20367 range: Range<text::Anchor>,
20368 _window: &mut Window,
20369 cx: &mut App,
20370 ) -> Task<Result<Vec<CodeAction>>> {
20371 self.update(cx, |project, cx| {
20372 let code_lens = project.code_lens(buffer, range.clone(), cx);
20373 let code_actions = project.code_actions(buffer, range, None, cx);
20374 cx.background_spawn(async move {
20375 let (code_lens, code_actions) = join(code_lens, code_actions).await;
20376 Ok(code_lens
20377 .context("code lens fetch")?
20378 .into_iter()
20379 .chain(code_actions.context("code action fetch")?)
20380 .collect())
20381 })
20382 })
20383 }
20384
20385 fn apply_code_action(
20386 &self,
20387 buffer_handle: Entity<Buffer>,
20388 action: CodeAction,
20389 _excerpt_id: ExcerptId,
20390 push_to_history: bool,
20391 _window: &mut Window,
20392 cx: &mut App,
20393 ) -> Task<Result<ProjectTransaction>> {
20394 self.update(cx, |project, cx| {
20395 project.apply_code_action(buffer_handle, action, push_to_history, cx)
20396 })
20397 }
20398}
20399
20400fn snippet_completions(
20401 project: &Project,
20402 buffer: &Entity<Buffer>,
20403 buffer_position: text::Anchor,
20404 cx: &mut App,
20405) -> Task<Result<CompletionResponse>> {
20406 let languages = buffer.read(cx).languages_at(buffer_position);
20407 let snippet_store = project.snippets().read(cx);
20408
20409 let scopes: Vec<_> = languages
20410 .iter()
20411 .filter_map(|language| {
20412 let language_name = language.lsp_id();
20413 let snippets = snippet_store.snippets_for(Some(language_name), cx);
20414
20415 if snippets.is_empty() {
20416 None
20417 } else {
20418 Some((language.default_scope(), snippets))
20419 }
20420 })
20421 .collect();
20422
20423 if scopes.is_empty() {
20424 return Task::ready(Ok(CompletionResponse {
20425 completions: vec![],
20426 is_incomplete: false,
20427 }));
20428 }
20429
20430 let snapshot = buffer.read(cx).text_snapshot();
20431 let chars: String = snapshot
20432 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
20433 .collect();
20434 let executor = cx.background_executor().clone();
20435
20436 cx.background_spawn(async move {
20437 let mut is_incomplete = false;
20438 let mut completions: Vec<Completion> = Vec::new();
20439 for (scope, snippets) in scopes.into_iter() {
20440 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
20441 let mut last_word = chars
20442 .chars()
20443 .take_while(|c| classifier.is_word(*c))
20444 .collect::<String>();
20445 last_word = last_word.chars().rev().collect();
20446
20447 if last_word.is_empty() {
20448 return Ok(CompletionResponse {
20449 completions: vec![],
20450 is_incomplete: true,
20451 });
20452 }
20453
20454 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
20455 let to_lsp = |point: &text::Anchor| {
20456 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
20457 point_to_lsp(end)
20458 };
20459 let lsp_end = to_lsp(&buffer_position);
20460
20461 let candidates = snippets
20462 .iter()
20463 .enumerate()
20464 .flat_map(|(ix, snippet)| {
20465 snippet
20466 .prefix
20467 .iter()
20468 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
20469 })
20470 .collect::<Vec<StringMatchCandidate>>();
20471
20472 const MAX_RESULTS: usize = 100;
20473 let mut matches = fuzzy::match_strings(
20474 &candidates,
20475 &last_word,
20476 last_word.chars().any(|c| c.is_uppercase()),
20477 MAX_RESULTS,
20478 &Default::default(),
20479 executor.clone(),
20480 )
20481 .await;
20482
20483 if matches.len() >= MAX_RESULTS {
20484 is_incomplete = true;
20485 }
20486
20487 // Remove all candidates where the query's start does not match the start of any word in the candidate
20488 if let Some(query_start) = last_word.chars().next() {
20489 matches.retain(|string_match| {
20490 split_words(&string_match.string).any(|word| {
20491 // Check that the first codepoint of the word as lowercase matches the first
20492 // codepoint of the query as lowercase
20493 word.chars()
20494 .flat_map(|codepoint| codepoint.to_lowercase())
20495 .zip(query_start.to_lowercase())
20496 .all(|(word_cp, query_cp)| word_cp == query_cp)
20497 })
20498 });
20499 }
20500
20501 let matched_strings = matches
20502 .into_iter()
20503 .map(|m| m.string)
20504 .collect::<HashSet<_>>();
20505
20506 completions.extend(snippets.iter().filter_map(|snippet| {
20507 let matching_prefix = snippet
20508 .prefix
20509 .iter()
20510 .find(|prefix| matched_strings.contains(*prefix))?;
20511 let start = as_offset - last_word.len();
20512 let start = snapshot.anchor_before(start);
20513 let range = start..buffer_position;
20514 let lsp_start = to_lsp(&start);
20515 let lsp_range = lsp::Range {
20516 start: lsp_start,
20517 end: lsp_end,
20518 };
20519 Some(Completion {
20520 replace_range: range,
20521 new_text: snippet.body.clone(),
20522 source: CompletionSource::Lsp {
20523 insert_range: None,
20524 server_id: LanguageServerId(usize::MAX),
20525 resolved: true,
20526 lsp_completion: Box::new(lsp::CompletionItem {
20527 label: snippet.prefix.first().unwrap().clone(),
20528 kind: Some(CompletionItemKind::SNIPPET),
20529 label_details: snippet.description.as_ref().map(|description| {
20530 lsp::CompletionItemLabelDetails {
20531 detail: Some(description.clone()),
20532 description: None,
20533 }
20534 }),
20535 insert_text_format: Some(InsertTextFormat::SNIPPET),
20536 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
20537 lsp::InsertReplaceEdit {
20538 new_text: snippet.body.clone(),
20539 insert: lsp_range,
20540 replace: lsp_range,
20541 },
20542 )),
20543 filter_text: Some(snippet.body.clone()),
20544 sort_text: Some(char::MAX.to_string()),
20545 ..lsp::CompletionItem::default()
20546 }),
20547 lsp_defaults: None,
20548 },
20549 label: CodeLabel {
20550 text: matching_prefix.clone(),
20551 runs: Vec::new(),
20552 filter_range: 0..matching_prefix.len(),
20553 },
20554 icon_path: None,
20555 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
20556 single_line: snippet.name.clone().into(),
20557 plain_text: snippet
20558 .description
20559 .clone()
20560 .map(|description| description.into()),
20561 }),
20562 insert_text_mode: None,
20563 confirm: None,
20564 })
20565 }))
20566 }
20567
20568 Ok(CompletionResponse {
20569 completions,
20570 is_incomplete,
20571 })
20572 })
20573}
20574
20575impl CompletionProvider for Entity<Project> {
20576 fn completions(
20577 &self,
20578 _excerpt_id: ExcerptId,
20579 buffer: &Entity<Buffer>,
20580 buffer_position: text::Anchor,
20581 options: CompletionContext,
20582 _window: &mut Window,
20583 cx: &mut Context<Editor>,
20584 ) -> Task<Result<Vec<CompletionResponse>>> {
20585 self.update(cx, |project, cx| {
20586 let snippets = snippet_completions(project, buffer, buffer_position, cx);
20587 let project_completions = project.completions(buffer, buffer_position, options, cx);
20588 cx.background_spawn(async move {
20589 let mut responses = project_completions.await?;
20590 let snippets = snippets.await?;
20591 if !snippets.completions.is_empty() {
20592 responses.push(snippets);
20593 }
20594 Ok(responses)
20595 })
20596 })
20597 }
20598
20599 fn resolve_completions(
20600 &self,
20601 buffer: Entity<Buffer>,
20602 completion_indices: Vec<usize>,
20603 completions: Rc<RefCell<Box<[Completion]>>>,
20604 cx: &mut Context<Editor>,
20605 ) -> Task<Result<bool>> {
20606 self.update(cx, |project, cx| {
20607 project.lsp_store().update(cx, |lsp_store, cx| {
20608 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
20609 })
20610 })
20611 }
20612
20613 fn apply_additional_edits_for_completion(
20614 &self,
20615 buffer: Entity<Buffer>,
20616 completions: Rc<RefCell<Box<[Completion]>>>,
20617 completion_index: usize,
20618 push_to_history: bool,
20619 cx: &mut Context<Editor>,
20620 ) -> Task<Result<Option<language::Transaction>>> {
20621 self.update(cx, |project, cx| {
20622 project.lsp_store().update(cx, |lsp_store, cx| {
20623 lsp_store.apply_additional_edits_for_completion(
20624 buffer,
20625 completions,
20626 completion_index,
20627 push_to_history,
20628 cx,
20629 )
20630 })
20631 })
20632 }
20633
20634 fn is_completion_trigger(
20635 &self,
20636 buffer: &Entity<Buffer>,
20637 position: language::Anchor,
20638 text: &str,
20639 trigger_in_words: bool,
20640 menu_is_open: bool,
20641 cx: &mut Context<Editor>,
20642 ) -> bool {
20643 let mut chars = text.chars();
20644 let char = if let Some(char) = chars.next() {
20645 char
20646 } else {
20647 return false;
20648 };
20649 if chars.next().is_some() {
20650 return false;
20651 }
20652
20653 let buffer = buffer.read(cx);
20654 let snapshot = buffer.snapshot();
20655 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
20656 return false;
20657 }
20658 let classifier = snapshot.char_classifier_at(position).for_completion(true);
20659 if trigger_in_words && classifier.is_word(char) {
20660 return true;
20661 }
20662
20663 buffer.completion_triggers().contains(text)
20664 }
20665}
20666
20667impl SemanticsProvider for Entity<Project> {
20668 fn hover(
20669 &self,
20670 buffer: &Entity<Buffer>,
20671 position: text::Anchor,
20672 cx: &mut App,
20673 ) -> Option<Task<Vec<project::Hover>>> {
20674 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
20675 }
20676
20677 fn document_highlights(
20678 &self,
20679 buffer: &Entity<Buffer>,
20680 position: text::Anchor,
20681 cx: &mut App,
20682 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
20683 Some(self.update(cx, |project, cx| {
20684 project.document_highlights(buffer, position, cx)
20685 }))
20686 }
20687
20688 fn definitions(
20689 &self,
20690 buffer: &Entity<Buffer>,
20691 position: text::Anchor,
20692 kind: GotoDefinitionKind,
20693 cx: &mut App,
20694 ) -> Option<Task<Result<Vec<LocationLink>>>> {
20695 Some(self.update(cx, |project, cx| match kind {
20696 GotoDefinitionKind::Symbol => project.definition(&buffer, position, cx),
20697 GotoDefinitionKind::Declaration => project.declaration(&buffer, position, cx),
20698 GotoDefinitionKind::Type => project.type_definition(&buffer, position, cx),
20699 GotoDefinitionKind::Implementation => project.implementation(&buffer, position, cx),
20700 }))
20701 }
20702
20703 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
20704 // TODO: make this work for remote projects
20705 self.update(cx, |project, cx| {
20706 if project
20707 .active_debug_session(cx)
20708 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
20709 {
20710 return true;
20711 }
20712
20713 buffer.update(cx, |buffer, cx| {
20714 project.any_language_server_supports_inlay_hints(buffer, cx)
20715 })
20716 })
20717 }
20718
20719 fn inline_values(
20720 &self,
20721 buffer_handle: Entity<Buffer>,
20722
20723 range: Range<text::Anchor>,
20724 cx: &mut App,
20725 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
20726 self.update(cx, |project, cx| {
20727 let (session, active_stack_frame) = project.active_debug_session(cx)?;
20728
20729 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
20730 })
20731 }
20732
20733 fn inlay_hints(
20734 &self,
20735 buffer_handle: Entity<Buffer>,
20736 range: Range<text::Anchor>,
20737 cx: &mut App,
20738 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
20739 Some(self.update(cx, |project, cx| {
20740 project.inlay_hints(buffer_handle, range, cx)
20741 }))
20742 }
20743
20744 fn resolve_inlay_hint(
20745 &self,
20746 hint: InlayHint,
20747 buffer_handle: Entity<Buffer>,
20748 server_id: LanguageServerId,
20749 cx: &mut App,
20750 ) -> Option<Task<anyhow::Result<InlayHint>>> {
20751 Some(self.update(cx, |project, cx| {
20752 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
20753 }))
20754 }
20755
20756 fn range_for_rename(
20757 &self,
20758 buffer: &Entity<Buffer>,
20759 position: text::Anchor,
20760 cx: &mut App,
20761 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
20762 Some(self.update(cx, |project, cx| {
20763 let buffer = buffer.clone();
20764 let task = project.prepare_rename(buffer.clone(), position, cx);
20765 cx.spawn(async move |_, cx| {
20766 Ok(match task.await? {
20767 PrepareRenameResponse::Success(range) => Some(range),
20768 PrepareRenameResponse::InvalidPosition => None,
20769 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
20770 // Fallback on using TreeSitter info to determine identifier range
20771 buffer.read_with(cx, |buffer, _| {
20772 let snapshot = buffer.snapshot();
20773 let (range, kind) = snapshot.surrounding_word(position);
20774 if kind != Some(CharKind::Word) {
20775 return None;
20776 }
20777 Some(
20778 snapshot.anchor_before(range.start)
20779 ..snapshot.anchor_after(range.end),
20780 )
20781 })?
20782 }
20783 })
20784 })
20785 }))
20786 }
20787
20788 fn perform_rename(
20789 &self,
20790 buffer: &Entity<Buffer>,
20791 position: text::Anchor,
20792 new_name: String,
20793 cx: &mut App,
20794 ) -> Option<Task<Result<ProjectTransaction>>> {
20795 Some(self.update(cx, |project, cx| {
20796 project.perform_rename(buffer.clone(), position, new_name, cx)
20797 }))
20798 }
20799}
20800
20801fn inlay_hint_settings(
20802 location: Anchor,
20803 snapshot: &MultiBufferSnapshot,
20804 cx: &mut Context<Editor>,
20805) -> InlayHintSettings {
20806 let file = snapshot.file_at(location);
20807 let language = snapshot.language_at(location).map(|l| l.name());
20808 language_settings(language, file, cx).inlay_hints
20809}
20810
20811fn consume_contiguous_rows(
20812 contiguous_row_selections: &mut Vec<Selection<Point>>,
20813 selection: &Selection<Point>,
20814 display_map: &DisplaySnapshot,
20815 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
20816) -> (MultiBufferRow, MultiBufferRow) {
20817 contiguous_row_selections.push(selection.clone());
20818 let start_row = MultiBufferRow(selection.start.row);
20819 let mut end_row = ending_row(selection, display_map);
20820
20821 while let Some(next_selection) = selections.peek() {
20822 if next_selection.start.row <= end_row.0 {
20823 end_row = ending_row(next_selection, display_map);
20824 contiguous_row_selections.push(selections.next().unwrap().clone());
20825 } else {
20826 break;
20827 }
20828 }
20829 (start_row, end_row)
20830}
20831
20832fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
20833 if next_selection.end.column > 0 || next_selection.is_empty() {
20834 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
20835 } else {
20836 MultiBufferRow(next_selection.end.row)
20837 }
20838}
20839
20840impl EditorSnapshot {
20841 pub fn remote_selections_in_range<'a>(
20842 &'a self,
20843 range: &'a Range<Anchor>,
20844 collaboration_hub: &dyn CollaborationHub,
20845 cx: &'a App,
20846 ) -> impl 'a + Iterator<Item = RemoteSelection> {
20847 let participant_names = collaboration_hub.user_names(cx);
20848 let participant_indices = collaboration_hub.user_participant_indices(cx);
20849 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
20850 let collaborators_by_replica_id = collaborators_by_peer_id
20851 .values()
20852 .map(|collaborator| (collaborator.replica_id, collaborator))
20853 .collect::<HashMap<_, _>>();
20854 self.buffer_snapshot
20855 .selections_in_range(range, false)
20856 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
20857 if replica_id == AGENT_REPLICA_ID {
20858 Some(RemoteSelection {
20859 replica_id,
20860 selection,
20861 cursor_shape,
20862 line_mode,
20863 collaborator_id: CollaboratorId::Agent,
20864 user_name: Some("Agent".into()),
20865 color: cx.theme().players().agent(),
20866 })
20867 } else {
20868 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
20869 let participant_index = participant_indices.get(&collaborator.user_id).copied();
20870 let user_name = participant_names.get(&collaborator.user_id).cloned();
20871 Some(RemoteSelection {
20872 replica_id,
20873 selection,
20874 cursor_shape,
20875 line_mode,
20876 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
20877 user_name,
20878 color: if let Some(index) = participant_index {
20879 cx.theme().players().color_for_participant(index.0)
20880 } else {
20881 cx.theme().players().absent()
20882 },
20883 })
20884 }
20885 })
20886 }
20887
20888 pub fn hunks_for_ranges(
20889 &self,
20890 ranges: impl IntoIterator<Item = Range<Point>>,
20891 ) -> Vec<MultiBufferDiffHunk> {
20892 let mut hunks = Vec::new();
20893 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
20894 HashMap::default();
20895 for query_range in ranges {
20896 let query_rows =
20897 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
20898 for hunk in self.buffer_snapshot.diff_hunks_in_range(
20899 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
20900 ) {
20901 // Include deleted hunks that are adjacent to the query range, because
20902 // otherwise they would be missed.
20903 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
20904 if hunk.status().is_deleted() {
20905 intersects_range |= hunk.row_range.start == query_rows.end;
20906 intersects_range |= hunk.row_range.end == query_rows.start;
20907 }
20908 if intersects_range {
20909 if !processed_buffer_rows
20910 .entry(hunk.buffer_id)
20911 .or_default()
20912 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
20913 {
20914 continue;
20915 }
20916 hunks.push(hunk);
20917 }
20918 }
20919 }
20920
20921 hunks
20922 }
20923
20924 fn display_diff_hunks_for_rows<'a>(
20925 &'a self,
20926 display_rows: Range<DisplayRow>,
20927 folded_buffers: &'a HashSet<BufferId>,
20928 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
20929 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
20930 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
20931
20932 self.buffer_snapshot
20933 .diff_hunks_in_range(buffer_start..buffer_end)
20934 .filter_map(|hunk| {
20935 if folded_buffers.contains(&hunk.buffer_id) {
20936 return None;
20937 }
20938
20939 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
20940 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
20941
20942 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
20943 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
20944
20945 let display_hunk = if hunk_display_start.column() != 0 {
20946 DisplayDiffHunk::Folded {
20947 display_row: hunk_display_start.row(),
20948 }
20949 } else {
20950 let mut end_row = hunk_display_end.row();
20951 if hunk_display_end.column() > 0 {
20952 end_row.0 += 1;
20953 }
20954 let is_created_file = hunk.is_created_file();
20955 DisplayDiffHunk::Unfolded {
20956 status: hunk.status(),
20957 diff_base_byte_range: hunk.diff_base_byte_range,
20958 display_row_range: hunk_display_start.row()..end_row,
20959 multi_buffer_range: Anchor::range_in_buffer(
20960 hunk.excerpt_id,
20961 hunk.buffer_id,
20962 hunk.buffer_range,
20963 ),
20964 is_created_file,
20965 }
20966 };
20967
20968 Some(display_hunk)
20969 })
20970 }
20971
20972 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
20973 self.display_snapshot.buffer_snapshot.language_at(position)
20974 }
20975
20976 pub fn is_focused(&self) -> bool {
20977 self.is_focused
20978 }
20979
20980 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
20981 self.placeholder_text.as_ref()
20982 }
20983
20984 pub fn scroll_position(&self) -> gpui::Point<f32> {
20985 self.scroll_anchor.scroll_position(&self.display_snapshot)
20986 }
20987
20988 fn gutter_dimensions(
20989 &self,
20990 font_id: FontId,
20991 font_size: Pixels,
20992 max_line_number_width: Pixels,
20993 cx: &App,
20994 ) -> Option<GutterDimensions> {
20995 if !self.show_gutter {
20996 return None;
20997 }
20998
20999 let em_width = cx.text_system().em_width(font_id, font_size).log_err()?;
21000 let em_advance = cx.text_system().em_advance(font_id, font_size).log_err()?;
21001
21002 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
21003 matches!(
21004 ProjectSettings::get_global(cx).git.git_gutter,
21005 Some(GitGutterSetting::TrackedFiles)
21006 )
21007 });
21008 let gutter_settings = EditorSettings::get_global(cx).gutter;
21009 let show_line_numbers = self
21010 .show_line_numbers
21011 .unwrap_or(gutter_settings.line_numbers);
21012 let line_gutter_width = if show_line_numbers {
21013 // Avoid flicker-like gutter resizes when the line number gains another digit and only resize the gutter on files with N*10^5 lines.
21014 let min_width_for_number_on_gutter = em_advance * MIN_LINE_NUMBER_DIGITS as f32;
21015 max_line_number_width.max(min_width_for_number_on_gutter)
21016 } else {
21017 0.0.into()
21018 };
21019
21020 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
21021 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
21022
21023 let git_blame_entries_width =
21024 self.git_blame_gutter_max_author_length
21025 .map(|max_author_length| {
21026 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
21027 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
21028
21029 /// The number of characters to dedicate to gaps and margins.
21030 const SPACING_WIDTH: usize = 4;
21031
21032 let max_char_count = max_author_length.min(renderer.max_author_length())
21033 + ::git::SHORT_SHA_LENGTH
21034 + MAX_RELATIVE_TIMESTAMP.len()
21035 + SPACING_WIDTH;
21036
21037 em_advance * max_char_count
21038 });
21039
21040 let is_singleton = self.buffer_snapshot.is_singleton();
21041
21042 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
21043 left_padding += if !is_singleton {
21044 em_width * 4.0
21045 } else if show_runnables || show_breakpoints {
21046 em_width * 3.0
21047 } else if show_git_gutter && show_line_numbers {
21048 em_width * 2.0
21049 } else if show_git_gutter || show_line_numbers {
21050 em_width
21051 } else {
21052 px(0.)
21053 };
21054
21055 let shows_folds = is_singleton && gutter_settings.folds;
21056
21057 let right_padding = if shows_folds && show_line_numbers {
21058 em_width * 4.0
21059 } else if shows_folds || (!is_singleton && show_line_numbers) {
21060 em_width * 3.0
21061 } else if show_line_numbers {
21062 em_width
21063 } else {
21064 px(0.)
21065 };
21066
21067 Some(GutterDimensions {
21068 left_padding,
21069 right_padding,
21070 width: line_gutter_width + left_padding + right_padding,
21071 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
21072 git_blame_entries_width,
21073 })
21074 }
21075
21076 pub fn render_crease_toggle(
21077 &self,
21078 buffer_row: MultiBufferRow,
21079 row_contains_cursor: bool,
21080 editor: Entity<Editor>,
21081 window: &mut Window,
21082 cx: &mut App,
21083 ) -> Option<AnyElement> {
21084 let folded = self.is_line_folded(buffer_row);
21085 let mut is_foldable = false;
21086
21087 if let Some(crease) = self
21088 .crease_snapshot
21089 .query_row(buffer_row, &self.buffer_snapshot)
21090 {
21091 is_foldable = true;
21092 match crease {
21093 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
21094 if let Some(render_toggle) = render_toggle {
21095 let toggle_callback =
21096 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
21097 if folded {
21098 editor.update(cx, |editor, cx| {
21099 editor.fold_at(buffer_row, window, cx)
21100 });
21101 } else {
21102 editor.update(cx, |editor, cx| {
21103 editor.unfold_at(buffer_row, window, cx)
21104 });
21105 }
21106 });
21107 return Some((render_toggle)(
21108 buffer_row,
21109 folded,
21110 toggle_callback,
21111 window,
21112 cx,
21113 ));
21114 }
21115 }
21116 }
21117 }
21118
21119 is_foldable |= self.starts_indent(buffer_row);
21120
21121 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
21122 Some(
21123 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
21124 .toggle_state(folded)
21125 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
21126 if folded {
21127 this.unfold_at(buffer_row, window, cx);
21128 } else {
21129 this.fold_at(buffer_row, window, cx);
21130 }
21131 }))
21132 .into_any_element(),
21133 )
21134 } else {
21135 None
21136 }
21137 }
21138
21139 pub fn render_crease_trailer(
21140 &self,
21141 buffer_row: MultiBufferRow,
21142 window: &mut Window,
21143 cx: &mut App,
21144 ) -> Option<AnyElement> {
21145 let folded = self.is_line_folded(buffer_row);
21146 if let Crease::Inline { render_trailer, .. } = self
21147 .crease_snapshot
21148 .query_row(buffer_row, &self.buffer_snapshot)?
21149 {
21150 let render_trailer = render_trailer.as_ref()?;
21151 Some(render_trailer(buffer_row, folded, window, cx))
21152 } else {
21153 None
21154 }
21155 }
21156}
21157
21158impl Deref for EditorSnapshot {
21159 type Target = DisplaySnapshot;
21160
21161 fn deref(&self) -> &Self::Target {
21162 &self.display_snapshot
21163 }
21164}
21165
21166#[derive(Clone, Debug, PartialEq, Eq)]
21167pub enum EditorEvent {
21168 InputIgnored {
21169 text: Arc<str>,
21170 },
21171 InputHandled {
21172 utf16_range_to_replace: Option<Range<isize>>,
21173 text: Arc<str>,
21174 },
21175 ExcerptsAdded {
21176 buffer: Entity<Buffer>,
21177 predecessor: ExcerptId,
21178 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
21179 },
21180 ExcerptsRemoved {
21181 ids: Vec<ExcerptId>,
21182 removed_buffer_ids: Vec<BufferId>,
21183 },
21184 BufferFoldToggled {
21185 ids: Vec<ExcerptId>,
21186 folded: bool,
21187 },
21188 ExcerptsEdited {
21189 ids: Vec<ExcerptId>,
21190 },
21191 ExcerptsExpanded {
21192 ids: Vec<ExcerptId>,
21193 },
21194 BufferEdited,
21195 Edited {
21196 transaction_id: clock::Lamport,
21197 },
21198 Reparsed(BufferId),
21199 Focused,
21200 FocusedIn,
21201 Blurred,
21202 DirtyChanged,
21203 Saved,
21204 TitleChanged,
21205 DiffBaseChanged,
21206 SelectionsChanged {
21207 local: bool,
21208 },
21209 ScrollPositionChanged {
21210 local: bool,
21211 autoscroll: bool,
21212 },
21213 Closed,
21214 TransactionUndone {
21215 transaction_id: clock::Lamport,
21216 },
21217 TransactionBegun {
21218 transaction_id: clock::Lamport,
21219 },
21220 Reloaded,
21221 CursorShapeChanged,
21222 PushedToNavHistory {
21223 anchor: Anchor,
21224 is_deactivate: bool,
21225 },
21226}
21227
21228impl EventEmitter<EditorEvent> for Editor {}
21229
21230impl Focusable for Editor {
21231 fn focus_handle(&self, _cx: &App) -> FocusHandle {
21232 self.focus_handle.clone()
21233 }
21234}
21235
21236impl Render for Editor {
21237 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21238 let settings = ThemeSettings::get_global(cx);
21239
21240 let mut text_style = match self.mode {
21241 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
21242 color: cx.theme().colors().editor_foreground,
21243 font_family: settings.ui_font.family.clone(),
21244 font_features: settings.ui_font.features.clone(),
21245 font_fallbacks: settings.ui_font.fallbacks.clone(),
21246 font_size: rems(0.875).into(),
21247 font_weight: settings.ui_font.weight,
21248 line_height: relative(settings.buffer_line_height.value()),
21249 ..Default::default()
21250 },
21251 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
21252 color: cx.theme().colors().editor_foreground,
21253 font_family: settings.buffer_font.family.clone(),
21254 font_features: settings.buffer_font.features.clone(),
21255 font_fallbacks: settings.buffer_font.fallbacks.clone(),
21256 font_size: settings.buffer_font_size(cx).into(),
21257 font_weight: settings.buffer_font.weight,
21258 line_height: relative(settings.buffer_line_height.value()),
21259 ..Default::default()
21260 },
21261 };
21262 if let Some(text_style_refinement) = &self.text_style_refinement {
21263 text_style.refine(text_style_refinement)
21264 }
21265
21266 let background = match self.mode {
21267 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
21268 EditorMode::AutoHeight { max_lines: _ } => cx.theme().system().transparent,
21269 EditorMode::Full { .. } => cx.theme().colors().editor_background,
21270 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
21271 };
21272
21273 EditorElement::new(
21274 &cx.entity(),
21275 EditorStyle {
21276 background,
21277 local_player: cx.theme().players().local(),
21278 text: text_style,
21279 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
21280 syntax: cx.theme().syntax().clone(),
21281 status: cx.theme().status().clone(),
21282 inlay_hints_style: make_inlay_hints_style(cx),
21283 inline_completion_styles: make_suggestion_styles(cx),
21284 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
21285 show_underlines: !self.mode.is_minimap(),
21286 },
21287 )
21288 }
21289}
21290
21291impl EntityInputHandler for Editor {
21292 fn text_for_range(
21293 &mut self,
21294 range_utf16: Range<usize>,
21295 adjusted_range: &mut Option<Range<usize>>,
21296 _: &mut Window,
21297 cx: &mut Context<Self>,
21298 ) -> Option<String> {
21299 let snapshot = self.buffer.read(cx).read(cx);
21300 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
21301 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
21302 if (start.0..end.0) != range_utf16 {
21303 adjusted_range.replace(start.0..end.0);
21304 }
21305 Some(snapshot.text_for_range(start..end).collect())
21306 }
21307
21308 fn selected_text_range(
21309 &mut self,
21310 ignore_disabled_input: bool,
21311 _: &mut Window,
21312 cx: &mut Context<Self>,
21313 ) -> Option<UTF16Selection> {
21314 // Prevent the IME menu from appearing when holding down an alphabetic key
21315 // while input is disabled.
21316 if !ignore_disabled_input && !self.input_enabled {
21317 return None;
21318 }
21319
21320 let selection = self.selections.newest::<OffsetUtf16>(cx);
21321 let range = selection.range();
21322
21323 Some(UTF16Selection {
21324 range: range.start.0..range.end.0,
21325 reversed: selection.reversed,
21326 })
21327 }
21328
21329 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
21330 let snapshot = self.buffer.read(cx).read(cx);
21331 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
21332 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
21333 }
21334
21335 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
21336 self.clear_highlights::<InputComposition>(cx);
21337 self.ime_transaction.take();
21338 }
21339
21340 fn replace_text_in_range(
21341 &mut self,
21342 range_utf16: Option<Range<usize>>,
21343 text: &str,
21344 window: &mut Window,
21345 cx: &mut Context<Self>,
21346 ) {
21347 if !self.input_enabled {
21348 cx.emit(EditorEvent::InputIgnored { text: text.into() });
21349 return;
21350 }
21351
21352 self.transact(window, cx, |this, window, cx| {
21353 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
21354 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
21355 Some(this.selection_replacement_ranges(range_utf16, cx))
21356 } else {
21357 this.marked_text_ranges(cx)
21358 };
21359
21360 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
21361 let newest_selection_id = this.selections.newest_anchor().id;
21362 this.selections
21363 .all::<OffsetUtf16>(cx)
21364 .iter()
21365 .zip(ranges_to_replace.iter())
21366 .find_map(|(selection, range)| {
21367 if selection.id == newest_selection_id {
21368 Some(
21369 (range.start.0 as isize - selection.head().0 as isize)
21370 ..(range.end.0 as isize - selection.head().0 as isize),
21371 )
21372 } else {
21373 None
21374 }
21375 })
21376 });
21377
21378 cx.emit(EditorEvent::InputHandled {
21379 utf16_range_to_replace: range_to_replace,
21380 text: text.into(),
21381 });
21382
21383 if let Some(new_selected_ranges) = new_selected_ranges {
21384 this.change_selections(None, window, cx, |selections| {
21385 selections.select_ranges(new_selected_ranges)
21386 });
21387 this.backspace(&Default::default(), window, cx);
21388 }
21389
21390 this.handle_input(text, window, cx);
21391 });
21392
21393 if let Some(transaction) = self.ime_transaction {
21394 self.buffer.update(cx, |buffer, cx| {
21395 buffer.group_until_transaction(transaction, cx);
21396 });
21397 }
21398
21399 self.unmark_text(window, cx);
21400 }
21401
21402 fn replace_and_mark_text_in_range(
21403 &mut self,
21404 range_utf16: Option<Range<usize>>,
21405 text: &str,
21406 new_selected_range_utf16: Option<Range<usize>>,
21407 window: &mut Window,
21408 cx: &mut Context<Self>,
21409 ) {
21410 if !self.input_enabled {
21411 return;
21412 }
21413
21414 let transaction = self.transact(window, cx, |this, window, cx| {
21415 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
21416 let snapshot = this.buffer.read(cx).read(cx);
21417 if let Some(relative_range_utf16) = range_utf16.as_ref() {
21418 for marked_range in &mut marked_ranges {
21419 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
21420 marked_range.start.0 += relative_range_utf16.start;
21421 marked_range.start =
21422 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
21423 marked_range.end =
21424 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
21425 }
21426 }
21427 Some(marked_ranges)
21428 } else if let Some(range_utf16) = range_utf16 {
21429 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
21430 Some(this.selection_replacement_ranges(range_utf16, cx))
21431 } else {
21432 None
21433 };
21434
21435 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
21436 let newest_selection_id = this.selections.newest_anchor().id;
21437 this.selections
21438 .all::<OffsetUtf16>(cx)
21439 .iter()
21440 .zip(ranges_to_replace.iter())
21441 .find_map(|(selection, range)| {
21442 if selection.id == newest_selection_id {
21443 Some(
21444 (range.start.0 as isize - selection.head().0 as isize)
21445 ..(range.end.0 as isize - selection.head().0 as isize),
21446 )
21447 } else {
21448 None
21449 }
21450 })
21451 });
21452
21453 cx.emit(EditorEvent::InputHandled {
21454 utf16_range_to_replace: range_to_replace,
21455 text: text.into(),
21456 });
21457
21458 if let Some(ranges) = ranges_to_replace {
21459 this.change_selections(None, window, cx, |s| s.select_ranges(ranges));
21460 }
21461
21462 let marked_ranges = {
21463 let snapshot = this.buffer.read(cx).read(cx);
21464 this.selections
21465 .disjoint_anchors()
21466 .iter()
21467 .map(|selection| {
21468 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
21469 })
21470 .collect::<Vec<_>>()
21471 };
21472
21473 if text.is_empty() {
21474 this.unmark_text(window, cx);
21475 } else {
21476 this.highlight_text::<InputComposition>(
21477 marked_ranges.clone(),
21478 HighlightStyle {
21479 underline: Some(UnderlineStyle {
21480 thickness: px(1.),
21481 color: None,
21482 wavy: false,
21483 }),
21484 ..Default::default()
21485 },
21486 cx,
21487 );
21488 }
21489
21490 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
21491 let use_autoclose = this.use_autoclose;
21492 let use_auto_surround = this.use_auto_surround;
21493 this.set_use_autoclose(false);
21494 this.set_use_auto_surround(false);
21495 this.handle_input(text, window, cx);
21496 this.set_use_autoclose(use_autoclose);
21497 this.set_use_auto_surround(use_auto_surround);
21498
21499 if let Some(new_selected_range) = new_selected_range_utf16 {
21500 let snapshot = this.buffer.read(cx).read(cx);
21501 let new_selected_ranges = marked_ranges
21502 .into_iter()
21503 .map(|marked_range| {
21504 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
21505 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
21506 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
21507 snapshot.clip_offset_utf16(new_start, Bias::Left)
21508 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
21509 })
21510 .collect::<Vec<_>>();
21511
21512 drop(snapshot);
21513 this.change_selections(None, window, cx, |selections| {
21514 selections.select_ranges(new_selected_ranges)
21515 });
21516 }
21517 });
21518
21519 self.ime_transaction = self.ime_transaction.or(transaction);
21520 if let Some(transaction) = self.ime_transaction {
21521 self.buffer.update(cx, |buffer, cx| {
21522 buffer.group_until_transaction(transaction, cx);
21523 });
21524 }
21525
21526 if self.text_highlights::<InputComposition>(cx).is_none() {
21527 self.ime_transaction.take();
21528 }
21529 }
21530
21531 fn bounds_for_range(
21532 &mut self,
21533 range_utf16: Range<usize>,
21534 element_bounds: gpui::Bounds<Pixels>,
21535 window: &mut Window,
21536 cx: &mut Context<Self>,
21537 ) -> Option<gpui::Bounds<Pixels>> {
21538 let text_layout_details = self.text_layout_details(window);
21539 let gpui::Size {
21540 width: em_width,
21541 height: line_height,
21542 } = self.character_size(window);
21543
21544 let snapshot = self.snapshot(window, cx);
21545 let scroll_position = snapshot.scroll_position();
21546 let scroll_left = scroll_position.x * em_width;
21547
21548 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
21549 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
21550 + self.gutter_dimensions.width
21551 + self.gutter_dimensions.margin;
21552 let y = line_height * (start.row().as_f32() - scroll_position.y);
21553
21554 Some(Bounds {
21555 origin: element_bounds.origin + point(x, y),
21556 size: size(em_width, line_height),
21557 })
21558 }
21559
21560 fn character_index_for_point(
21561 &mut self,
21562 point: gpui::Point<Pixels>,
21563 _window: &mut Window,
21564 _cx: &mut Context<Self>,
21565 ) -> Option<usize> {
21566 let position_map = self.last_position_map.as_ref()?;
21567 if !position_map.text_hitbox.contains(&point) {
21568 return None;
21569 }
21570 let display_point = position_map.point_for_position(point).previous_valid;
21571 let anchor = position_map
21572 .snapshot
21573 .display_point_to_anchor(display_point, Bias::Left);
21574 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
21575 Some(utf16_offset.0)
21576 }
21577}
21578
21579trait SelectionExt {
21580 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
21581 fn spanned_rows(
21582 &self,
21583 include_end_if_at_line_start: bool,
21584 map: &DisplaySnapshot,
21585 ) -> Range<MultiBufferRow>;
21586}
21587
21588impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
21589 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
21590 let start = self
21591 .start
21592 .to_point(&map.buffer_snapshot)
21593 .to_display_point(map);
21594 let end = self
21595 .end
21596 .to_point(&map.buffer_snapshot)
21597 .to_display_point(map);
21598 if self.reversed {
21599 end..start
21600 } else {
21601 start..end
21602 }
21603 }
21604
21605 fn spanned_rows(
21606 &self,
21607 include_end_if_at_line_start: bool,
21608 map: &DisplaySnapshot,
21609 ) -> Range<MultiBufferRow> {
21610 let start = self.start.to_point(&map.buffer_snapshot);
21611 let mut end = self.end.to_point(&map.buffer_snapshot);
21612 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
21613 end.row -= 1;
21614 }
21615
21616 let buffer_start = map.prev_line_boundary(start).0;
21617 let buffer_end = map.next_line_boundary(end).0;
21618 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
21619 }
21620}
21621
21622impl<T: InvalidationRegion> InvalidationStack<T> {
21623 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
21624 where
21625 S: Clone + ToOffset,
21626 {
21627 while let Some(region) = self.last() {
21628 let all_selections_inside_invalidation_ranges =
21629 if selections.len() == region.ranges().len() {
21630 selections
21631 .iter()
21632 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
21633 .all(|(selection, invalidation_range)| {
21634 let head = selection.head().to_offset(buffer);
21635 invalidation_range.start <= head && invalidation_range.end >= head
21636 })
21637 } else {
21638 false
21639 };
21640
21641 if all_selections_inside_invalidation_ranges {
21642 break;
21643 } else {
21644 self.pop();
21645 }
21646 }
21647 }
21648}
21649
21650impl<T> Default for InvalidationStack<T> {
21651 fn default() -> Self {
21652 Self(Default::default())
21653 }
21654}
21655
21656impl<T> Deref for InvalidationStack<T> {
21657 type Target = Vec<T>;
21658
21659 fn deref(&self) -> &Self::Target {
21660 &self.0
21661 }
21662}
21663
21664impl<T> DerefMut for InvalidationStack<T> {
21665 fn deref_mut(&mut self) -> &mut Self::Target {
21666 &mut self.0
21667 }
21668}
21669
21670impl InvalidationRegion for SnippetState {
21671 fn ranges(&self) -> &[Range<Anchor>] {
21672 &self.ranges[self.active_index]
21673 }
21674}
21675
21676fn inline_completion_edit_text(
21677 current_snapshot: &BufferSnapshot,
21678 edits: &[(Range<Anchor>, String)],
21679 edit_preview: &EditPreview,
21680 include_deletions: bool,
21681 cx: &App,
21682) -> HighlightedText {
21683 let edits = edits
21684 .iter()
21685 .map(|(anchor, text)| {
21686 (
21687 anchor.start.text_anchor..anchor.end.text_anchor,
21688 text.clone(),
21689 )
21690 })
21691 .collect::<Vec<_>>();
21692
21693 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
21694}
21695
21696pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
21697 match severity {
21698 lsp::DiagnosticSeverity::ERROR => colors.error,
21699 lsp::DiagnosticSeverity::WARNING => colors.warning,
21700 lsp::DiagnosticSeverity::INFORMATION => colors.info,
21701 lsp::DiagnosticSeverity::HINT => colors.info,
21702 _ => colors.ignored,
21703 }
21704}
21705
21706pub fn styled_runs_for_code_label<'a>(
21707 label: &'a CodeLabel,
21708 syntax_theme: &'a theme::SyntaxTheme,
21709) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
21710 let fade_out = HighlightStyle {
21711 fade_out: Some(0.35),
21712 ..Default::default()
21713 };
21714
21715 let mut prev_end = label.filter_range.end;
21716 label
21717 .runs
21718 .iter()
21719 .enumerate()
21720 .flat_map(move |(ix, (range, highlight_id))| {
21721 let style = if let Some(style) = highlight_id.style(syntax_theme) {
21722 style
21723 } else {
21724 return Default::default();
21725 };
21726 let mut muted_style = style;
21727 muted_style.highlight(fade_out);
21728
21729 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
21730 if range.start >= label.filter_range.end {
21731 if range.start > prev_end {
21732 runs.push((prev_end..range.start, fade_out));
21733 }
21734 runs.push((range.clone(), muted_style));
21735 } else if range.end <= label.filter_range.end {
21736 runs.push((range.clone(), style));
21737 } else {
21738 runs.push((range.start..label.filter_range.end, style));
21739 runs.push((label.filter_range.end..range.end, muted_style));
21740 }
21741 prev_end = cmp::max(prev_end, range.end);
21742
21743 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
21744 runs.push((prev_end..label.text.len(), fade_out));
21745 }
21746
21747 runs
21748 })
21749}
21750
21751pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
21752 let mut prev_index = 0;
21753 let mut prev_codepoint: Option<char> = None;
21754 text.char_indices()
21755 .chain([(text.len(), '\0')])
21756 .filter_map(move |(index, codepoint)| {
21757 let prev_codepoint = prev_codepoint.replace(codepoint)?;
21758 let is_boundary = index == text.len()
21759 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
21760 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
21761 if is_boundary {
21762 let chunk = &text[prev_index..index];
21763 prev_index = index;
21764 Some(chunk)
21765 } else {
21766 None
21767 }
21768 })
21769}
21770
21771pub trait RangeToAnchorExt: Sized {
21772 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
21773
21774 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
21775 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
21776 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
21777 }
21778}
21779
21780impl<T: ToOffset> RangeToAnchorExt for Range<T> {
21781 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
21782 let start_offset = self.start.to_offset(snapshot);
21783 let end_offset = self.end.to_offset(snapshot);
21784 if start_offset == end_offset {
21785 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
21786 } else {
21787 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
21788 }
21789 }
21790}
21791
21792pub trait RowExt {
21793 fn as_f32(&self) -> f32;
21794
21795 fn next_row(&self) -> Self;
21796
21797 fn previous_row(&self) -> Self;
21798
21799 fn minus(&self, other: Self) -> u32;
21800}
21801
21802impl RowExt for DisplayRow {
21803 fn as_f32(&self) -> f32 {
21804 self.0 as f32
21805 }
21806
21807 fn next_row(&self) -> Self {
21808 Self(self.0 + 1)
21809 }
21810
21811 fn previous_row(&self) -> Self {
21812 Self(self.0.saturating_sub(1))
21813 }
21814
21815 fn minus(&self, other: Self) -> u32 {
21816 self.0 - other.0
21817 }
21818}
21819
21820impl RowExt for MultiBufferRow {
21821 fn as_f32(&self) -> f32 {
21822 self.0 as f32
21823 }
21824
21825 fn next_row(&self) -> Self {
21826 Self(self.0 + 1)
21827 }
21828
21829 fn previous_row(&self) -> Self {
21830 Self(self.0.saturating_sub(1))
21831 }
21832
21833 fn minus(&self, other: Self) -> u32 {
21834 self.0 - other.0
21835 }
21836}
21837
21838trait RowRangeExt {
21839 type Row;
21840
21841 fn len(&self) -> usize;
21842
21843 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
21844}
21845
21846impl RowRangeExt for Range<MultiBufferRow> {
21847 type Row = MultiBufferRow;
21848
21849 fn len(&self) -> usize {
21850 (self.end.0 - self.start.0) as usize
21851 }
21852
21853 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
21854 (self.start.0..self.end.0).map(MultiBufferRow)
21855 }
21856}
21857
21858impl RowRangeExt for Range<DisplayRow> {
21859 type Row = DisplayRow;
21860
21861 fn len(&self) -> usize {
21862 (self.end.0 - self.start.0) as usize
21863 }
21864
21865 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
21866 (self.start.0..self.end.0).map(DisplayRow)
21867 }
21868}
21869
21870/// If select range has more than one line, we
21871/// just point the cursor to range.start.
21872fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
21873 if range.start.row == range.end.row {
21874 range
21875 } else {
21876 range.start..range.start
21877 }
21878}
21879pub struct KillRing(ClipboardItem);
21880impl Global for KillRing {}
21881
21882const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
21883
21884enum BreakpointPromptEditAction {
21885 Log,
21886 Condition,
21887 HitCondition,
21888}
21889
21890struct BreakpointPromptEditor {
21891 pub(crate) prompt: Entity<Editor>,
21892 editor: WeakEntity<Editor>,
21893 breakpoint_anchor: Anchor,
21894 breakpoint: Breakpoint,
21895 edit_action: BreakpointPromptEditAction,
21896 block_ids: HashSet<CustomBlockId>,
21897 editor_margins: Arc<Mutex<EditorMargins>>,
21898 _subscriptions: Vec<Subscription>,
21899}
21900
21901impl BreakpointPromptEditor {
21902 const MAX_LINES: u8 = 4;
21903
21904 fn new(
21905 editor: WeakEntity<Editor>,
21906 breakpoint_anchor: Anchor,
21907 breakpoint: Breakpoint,
21908 edit_action: BreakpointPromptEditAction,
21909 window: &mut Window,
21910 cx: &mut Context<Self>,
21911 ) -> Self {
21912 let base_text = match edit_action {
21913 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
21914 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
21915 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
21916 }
21917 .map(|msg| msg.to_string())
21918 .unwrap_or_default();
21919
21920 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
21921 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
21922
21923 let prompt = cx.new(|cx| {
21924 let mut prompt = Editor::new(
21925 EditorMode::AutoHeight {
21926 max_lines: Self::MAX_LINES as usize,
21927 },
21928 buffer,
21929 None,
21930 window,
21931 cx,
21932 );
21933 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
21934 prompt.set_show_cursor_when_unfocused(false, cx);
21935 prompt.set_placeholder_text(
21936 match edit_action {
21937 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
21938 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
21939 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
21940 },
21941 cx,
21942 );
21943
21944 prompt
21945 });
21946
21947 Self {
21948 prompt,
21949 editor,
21950 breakpoint_anchor,
21951 breakpoint,
21952 edit_action,
21953 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
21954 block_ids: Default::default(),
21955 _subscriptions: vec![],
21956 }
21957 }
21958
21959 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
21960 self.block_ids.extend(block_ids)
21961 }
21962
21963 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
21964 if let Some(editor) = self.editor.upgrade() {
21965 let message = self
21966 .prompt
21967 .read(cx)
21968 .buffer
21969 .read(cx)
21970 .as_singleton()
21971 .expect("A multi buffer in breakpoint prompt isn't possible")
21972 .read(cx)
21973 .as_rope()
21974 .to_string();
21975
21976 editor.update(cx, |editor, cx| {
21977 editor.edit_breakpoint_at_anchor(
21978 self.breakpoint_anchor,
21979 self.breakpoint.clone(),
21980 match self.edit_action {
21981 BreakpointPromptEditAction::Log => {
21982 BreakpointEditAction::EditLogMessage(message.into())
21983 }
21984 BreakpointPromptEditAction::Condition => {
21985 BreakpointEditAction::EditCondition(message.into())
21986 }
21987 BreakpointPromptEditAction::HitCondition => {
21988 BreakpointEditAction::EditHitCondition(message.into())
21989 }
21990 },
21991 cx,
21992 );
21993
21994 editor.remove_blocks(self.block_ids.clone(), None, cx);
21995 cx.focus_self(window);
21996 });
21997 }
21998 }
21999
22000 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
22001 self.editor
22002 .update(cx, |editor, cx| {
22003 editor.remove_blocks(self.block_ids.clone(), None, cx);
22004 window.focus(&editor.focus_handle);
22005 })
22006 .log_err();
22007 }
22008
22009 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
22010 let settings = ThemeSettings::get_global(cx);
22011 let text_style = TextStyle {
22012 color: if self.prompt.read(cx).read_only(cx) {
22013 cx.theme().colors().text_disabled
22014 } else {
22015 cx.theme().colors().text
22016 },
22017 font_family: settings.buffer_font.family.clone(),
22018 font_fallbacks: settings.buffer_font.fallbacks.clone(),
22019 font_size: settings.buffer_font_size(cx).into(),
22020 font_weight: settings.buffer_font.weight,
22021 line_height: relative(settings.buffer_line_height.value()),
22022 ..Default::default()
22023 };
22024 EditorElement::new(
22025 &self.prompt,
22026 EditorStyle {
22027 background: cx.theme().colors().editor_background,
22028 local_player: cx.theme().players().local(),
22029 text: text_style,
22030 ..Default::default()
22031 },
22032 )
22033 }
22034}
22035
22036impl Render for BreakpointPromptEditor {
22037 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22038 let editor_margins = *self.editor_margins.lock();
22039 let gutter_dimensions = editor_margins.gutter;
22040 h_flex()
22041 .key_context("Editor")
22042 .bg(cx.theme().colors().editor_background)
22043 .border_y_1()
22044 .border_color(cx.theme().status().info_border)
22045 .size_full()
22046 .py(window.line_height() / 2.5)
22047 .on_action(cx.listener(Self::confirm))
22048 .on_action(cx.listener(Self::cancel))
22049 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
22050 .child(div().flex_1().child(self.render_prompt_editor(cx)))
22051 }
22052}
22053
22054impl Focusable for BreakpointPromptEditor {
22055 fn focus_handle(&self, cx: &App) -> FocusHandle {
22056 self.prompt.focus_handle(cx)
22057 }
22058}
22059
22060fn all_edits_insertions_or_deletions(
22061 edits: &Vec<(Range<Anchor>, String)>,
22062 snapshot: &MultiBufferSnapshot,
22063) -> bool {
22064 let mut all_insertions = true;
22065 let mut all_deletions = true;
22066
22067 for (range, new_text) in edits.iter() {
22068 let range_is_empty = range.to_offset(&snapshot).is_empty();
22069 let text_is_empty = new_text.is_empty();
22070
22071 if range_is_empty != text_is_empty {
22072 if range_is_empty {
22073 all_deletions = false;
22074 } else {
22075 all_insertions = false;
22076 }
22077 } else {
22078 return false;
22079 }
22080
22081 if !all_insertions && !all_deletions {
22082 return false;
22083 }
22084 }
22085 all_insertions || all_deletions
22086}
22087
22088struct MissingEditPredictionKeybindingTooltip;
22089
22090impl Render for MissingEditPredictionKeybindingTooltip {
22091 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22092 ui::tooltip_container(window, cx, |container, _, cx| {
22093 container
22094 .flex_shrink_0()
22095 .max_w_80()
22096 .min_h(rems_from_px(124.))
22097 .justify_between()
22098 .child(
22099 v_flex()
22100 .flex_1()
22101 .text_ui_sm(cx)
22102 .child(Label::new("Conflict with Accept Keybinding"))
22103 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
22104 )
22105 .child(
22106 h_flex()
22107 .pb_1()
22108 .gap_1()
22109 .items_end()
22110 .w_full()
22111 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
22112 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
22113 }))
22114 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
22115 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
22116 })),
22117 )
22118 })
22119 }
22120}
22121
22122#[derive(Debug, Clone, Copy, PartialEq)]
22123pub struct LineHighlight {
22124 pub background: Background,
22125 pub border: Option<gpui::Hsla>,
22126 pub include_gutter: bool,
22127 pub type_id: Option<TypeId>,
22128}
22129
22130fn render_diff_hunk_controls(
22131 row: u32,
22132 status: &DiffHunkStatus,
22133 hunk_range: Range<Anchor>,
22134 is_created_file: bool,
22135 line_height: Pixels,
22136 editor: &Entity<Editor>,
22137 _window: &mut Window,
22138 cx: &mut App,
22139) -> AnyElement {
22140 h_flex()
22141 .h(line_height)
22142 .mr_1()
22143 .gap_1()
22144 .px_0p5()
22145 .pb_1()
22146 .border_x_1()
22147 .border_b_1()
22148 .border_color(cx.theme().colors().border_variant)
22149 .rounded_b_lg()
22150 .bg(cx.theme().colors().editor_background)
22151 .gap_1()
22152 .block_mouse_except_scroll()
22153 .shadow_md()
22154 .child(if status.has_secondary_hunk() {
22155 Button::new(("stage", row as u64), "Stage")
22156 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
22157 .tooltip({
22158 let focus_handle = editor.focus_handle(cx);
22159 move |window, cx| {
22160 Tooltip::for_action_in(
22161 "Stage Hunk",
22162 &::git::ToggleStaged,
22163 &focus_handle,
22164 window,
22165 cx,
22166 )
22167 }
22168 })
22169 .on_click({
22170 let editor = editor.clone();
22171 move |_event, _window, cx| {
22172 editor.update(cx, |editor, cx| {
22173 editor.stage_or_unstage_diff_hunks(
22174 true,
22175 vec![hunk_range.start..hunk_range.start],
22176 cx,
22177 );
22178 });
22179 }
22180 })
22181 } else {
22182 Button::new(("unstage", row as u64), "Unstage")
22183 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
22184 .tooltip({
22185 let focus_handle = editor.focus_handle(cx);
22186 move |window, cx| {
22187 Tooltip::for_action_in(
22188 "Unstage Hunk",
22189 &::git::ToggleStaged,
22190 &focus_handle,
22191 window,
22192 cx,
22193 )
22194 }
22195 })
22196 .on_click({
22197 let editor = editor.clone();
22198 move |_event, _window, cx| {
22199 editor.update(cx, |editor, cx| {
22200 editor.stage_or_unstage_diff_hunks(
22201 false,
22202 vec![hunk_range.start..hunk_range.start],
22203 cx,
22204 );
22205 });
22206 }
22207 })
22208 })
22209 .child(
22210 Button::new(("restore", row as u64), "Restore")
22211 .tooltip({
22212 let focus_handle = editor.focus_handle(cx);
22213 move |window, cx| {
22214 Tooltip::for_action_in(
22215 "Restore Hunk",
22216 &::git::Restore,
22217 &focus_handle,
22218 window,
22219 cx,
22220 )
22221 }
22222 })
22223 .on_click({
22224 let editor = editor.clone();
22225 move |_event, window, cx| {
22226 editor.update(cx, |editor, cx| {
22227 let snapshot = editor.snapshot(window, cx);
22228 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
22229 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
22230 });
22231 }
22232 })
22233 .disabled(is_created_file),
22234 )
22235 .when(
22236 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
22237 |el| {
22238 el.child(
22239 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
22240 .shape(IconButtonShape::Square)
22241 .icon_size(IconSize::Small)
22242 // .disabled(!has_multiple_hunks)
22243 .tooltip({
22244 let focus_handle = editor.focus_handle(cx);
22245 move |window, cx| {
22246 Tooltip::for_action_in(
22247 "Next Hunk",
22248 &GoToHunk,
22249 &focus_handle,
22250 window,
22251 cx,
22252 )
22253 }
22254 })
22255 .on_click({
22256 let editor = editor.clone();
22257 move |_event, window, cx| {
22258 editor.update(cx, |editor, cx| {
22259 let snapshot = editor.snapshot(window, cx);
22260 let position =
22261 hunk_range.end.to_point(&snapshot.buffer_snapshot);
22262 editor.go_to_hunk_before_or_after_position(
22263 &snapshot,
22264 position,
22265 Direction::Next,
22266 window,
22267 cx,
22268 );
22269 editor.expand_selected_diff_hunks(cx);
22270 });
22271 }
22272 }),
22273 )
22274 .child(
22275 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
22276 .shape(IconButtonShape::Square)
22277 .icon_size(IconSize::Small)
22278 // .disabled(!has_multiple_hunks)
22279 .tooltip({
22280 let focus_handle = editor.focus_handle(cx);
22281 move |window, cx| {
22282 Tooltip::for_action_in(
22283 "Previous Hunk",
22284 &GoToPreviousHunk,
22285 &focus_handle,
22286 window,
22287 cx,
22288 )
22289 }
22290 })
22291 .on_click({
22292 let editor = editor.clone();
22293 move |_event, window, cx| {
22294 editor.update(cx, |editor, cx| {
22295 let snapshot = editor.snapshot(window, cx);
22296 let point =
22297 hunk_range.start.to_point(&snapshot.buffer_snapshot);
22298 editor.go_to_hunk_before_or_after_position(
22299 &snapshot,
22300 point,
22301 Direction::Prev,
22302 window,
22303 cx,
22304 );
22305 editor.expand_selected_diff_hunks(cx);
22306 });
22307 }
22308 }),
22309 )
22310 },
22311 )
22312 .into_any_element()
22313}