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 #[track_caller]
1225 fn insert_transaction(
1226 &mut self,
1227 transaction_id: TransactionId,
1228 selections: Arc<[Selection<Anchor>]>,
1229 ) {
1230 if selections.is_empty() {
1231 log::error!(
1232 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1233 std::panic::Location::caller()
1234 );
1235 return;
1236 }
1237 self.selections_by_transaction
1238 .insert(transaction_id, (selections, None));
1239 }
1240
1241 #[allow(clippy::type_complexity)]
1242 fn transaction(
1243 &self,
1244 transaction_id: TransactionId,
1245 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1246 self.selections_by_transaction.get(&transaction_id)
1247 }
1248
1249 #[allow(clippy::type_complexity)]
1250 fn transaction_mut(
1251 &mut self,
1252 transaction_id: TransactionId,
1253 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1254 self.selections_by_transaction.get_mut(&transaction_id)
1255 }
1256
1257 fn push(&mut self, entry: SelectionHistoryEntry) {
1258 if !entry.selections.is_empty() {
1259 match self.mode {
1260 SelectionHistoryMode::Normal => {
1261 self.push_undo(entry);
1262 self.redo_stack.clear();
1263 }
1264 SelectionHistoryMode::Undoing => self.push_redo(entry),
1265 SelectionHistoryMode::Redoing => self.push_undo(entry),
1266 }
1267 }
1268 }
1269
1270 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1271 if self
1272 .undo_stack
1273 .back()
1274 .map_or(true, |e| e.selections != entry.selections)
1275 {
1276 self.undo_stack.push_back(entry);
1277 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1278 self.undo_stack.pop_front();
1279 }
1280 }
1281 }
1282
1283 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1284 if self
1285 .redo_stack
1286 .back()
1287 .map_or(true, |e| e.selections != entry.selections)
1288 {
1289 self.redo_stack.push_back(entry);
1290 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1291 self.redo_stack.pop_front();
1292 }
1293 }
1294 }
1295}
1296
1297#[derive(Clone, Copy)]
1298pub struct RowHighlightOptions {
1299 pub autoscroll: bool,
1300 pub include_gutter: bool,
1301}
1302
1303impl Default for RowHighlightOptions {
1304 fn default() -> Self {
1305 Self {
1306 autoscroll: Default::default(),
1307 include_gutter: true,
1308 }
1309 }
1310}
1311
1312struct RowHighlight {
1313 index: usize,
1314 range: Range<Anchor>,
1315 color: Hsla,
1316 options: RowHighlightOptions,
1317 type_id: TypeId,
1318}
1319
1320#[derive(Clone, Debug)]
1321struct AddSelectionsState {
1322 above: bool,
1323 stack: Vec<usize>,
1324}
1325
1326#[derive(Clone)]
1327struct SelectNextState {
1328 query: AhoCorasick,
1329 wordwise: bool,
1330 done: bool,
1331}
1332
1333impl std::fmt::Debug for SelectNextState {
1334 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1335 f.debug_struct(std::any::type_name::<Self>())
1336 .field("wordwise", &self.wordwise)
1337 .field("done", &self.done)
1338 .finish()
1339 }
1340}
1341
1342#[derive(Debug)]
1343struct AutocloseRegion {
1344 selection_id: usize,
1345 range: Range<Anchor>,
1346 pair: BracketPair,
1347}
1348
1349#[derive(Debug)]
1350struct SnippetState {
1351 ranges: Vec<Vec<Range<Anchor>>>,
1352 active_index: usize,
1353 choices: Vec<Option<Vec<String>>>,
1354}
1355
1356#[doc(hidden)]
1357pub struct RenameState {
1358 pub range: Range<Anchor>,
1359 pub old_name: Arc<str>,
1360 pub editor: Entity<Editor>,
1361 block_id: CustomBlockId,
1362}
1363
1364struct InvalidationStack<T>(Vec<T>);
1365
1366struct RegisteredInlineCompletionProvider {
1367 provider: Arc<dyn InlineCompletionProviderHandle>,
1368 _subscription: Subscription,
1369}
1370
1371#[derive(Debug, PartialEq, Eq)]
1372pub struct ActiveDiagnosticGroup {
1373 pub active_range: Range<Anchor>,
1374 pub active_message: String,
1375 pub group_id: usize,
1376 pub blocks: HashSet<CustomBlockId>,
1377}
1378
1379#[derive(Debug, PartialEq, Eq)]
1380
1381pub(crate) enum ActiveDiagnostic {
1382 None,
1383 All,
1384 Group(ActiveDiagnosticGroup),
1385}
1386
1387#[derive(Serialize, Deserialize, Clone, Debug)]
1388pub struct ClipboardSelection {
1389 /// The number of bytes in this selection.
1390 pub len: usize,
1391 /// Whether this was a full-line selection.
1392 pub is_entire_line: bool,
1393 /// The indentation of the first line when this content was originally copied.
1394 pub first_line_indent: u32,
1395}
1396
1397// selections, scroll behavior, was newest selection reversed
1398type SelectSyntaxNodeHistoryState = (
1399 Box<[Selection<usize>]>,
1400 SelectSyntaxNodeScrollBehavior,
1401 bool,
1402);
1403
1404#[derive(Default)]
1405struct SelectSyntaxNodeHistory {
1406 stack: Vec<SelectSyntaxNodeHistoryState>,
1407 // disable temporarily to allow changing selections without losing the stack
1408 pub disable_clearing: bool,
1409}
1410
1411impl SelectSyntaxNodeHistory {
1412 pub fn try_clear(&mut self) {
1413 if !self.disable_clearing {
1414 self.stack.clear();
1415 }
1416 }
1417
1418 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1419 self.stack.push(selection);
1420 }
1421
1422 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1423 self.stack.pop()
1424 }
1425}
1426
1427enum SelectSyntaxNodeScrollBehavior {
1428 CursorTop,
1429 FitSelection,
1430 CursorBottom,
1431}
1432
1433#[derive(Debug)]
1434pub(crate) struct NavigationData {
1435 cursor_anchor: Anchor,
1436 cursor_position: Point,
1437 scroll_anchor: ScrollAnchor,
1438 scroll_top_row: u32,
1439}
1440
1441#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1442pub enum GotoDefinitionKind {
1443 Symbol,
1444 Declaration,
1445 Type,
1446 Implementation,
1447}
1448
1449#[derive(Debug, Clone)]
1450enum InlayHintRefreshReason {
1451 ModifiersChanged(bool),
1452 Toggle(bool),
1453 SettingsChange(InlayHintSettings),
1454 NewLinesShown,
1455 BufferEdited(HashSet<Arc<Language>>),
1456 RefreshRequested,
1457 ExcerptsRemoved(Vec<ExcerptId>),
1458}
1459
1460impl InlayHintRefreshReason {
1461 fn description(&self) -> &'static str {
1462 match self {
1463 Self::ModifiersChanged(_) => "modifiers changed",
1464 Self::Toggle(_) => "toggle",
1465 Self::SettingsChange(_) => "settings change",
1466 Self::NewLinesShown => "new lines shown",
1467 Self::BufferEdited(_) => "buffer edited",
1468 Self::RefreshRequested => "refresh requested",
1469 Self::ExcerptsRemoved(_) => "excerpts removed",
1470 }
1471 }
1472}
1473
1474pub enum FormatTarget {
1475 Buffers,
1476 Ranges(Vec<Range<MultiBufferPoint>>),
1477}
1478
1479pub(crate) struct FocusedBlock {
1480 id: BlockId,
1481 focus_handle: WeakFocusHandle,
1482}
1483
1484#[derive(Clone)]
1485enum JumpData {
1486 MultiBufferRow {
1487 row: MultiBufferRow,
1488 line_offset_from_top: u32,
1489 },
1490 MultiBufferPoint {
1491 excerpt_id: ExcerptId,
1492 position: Point,
1493 anchor: text::Anchor,
1494 line_offset_from_top: u32,
1495 },
1496}
1497
1498pub enum MultibufferSelectionMode {
1499 First,
1500 All,
1501}
1502
1503#[derive(Clone, Copy, Debug, Default)]
1504pub struct RewrapOptions {
1505 pub override_language_settings: bool,
1506 pub preserve_existing_whitespace: bool,
1507}
1508
1509impl Editor {
1510 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1511 let buffer = cx.new(|cx| Buffer::local("", cx));
1512 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1513 Self::new(
1514 EditorMode::SingleLine { auto_width: false },
1515 buffer,
1516 None,
1517 window,
1518 cx,
1519 )
1520 }
1521
1522 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1523 let buffer = cx.new(|cx| Buffer::local("", cx));
1524 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1525 Self::new(EditorMode::full(), buffer, None, window, cx)
1526 }
1527
1528 pub fn auto_width(window: &mut Window, cx: &mut Context<Self>) -> Self {
1529 let buffer = cx.new(|cx| Buffer::local("", cx));
1530 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1531 Self::new(
1532 EditorMode::SingleLine { auto_width: true },
1533 buffer,
1534 None,
1535 window,
1536 cx,
1537 )
1538 }
1539
1540 pub fn auto_height(max_lines: usize, window: &mut Window, cx: &mut Context<Self>) -> Self {
1541 let buffer = cx.new(|cx| Buffer::local("", cx));
1542 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1543 Self::new(
1544 EditorMode::AutoHeight { max_lines },
1545 buffer,
1546 None,
1547 window,
1548 cx,
1549 )
1550 }
1551
1552 pub fn for_buffer(
1553 buffer: Entity<Buffer>,
1554 project: Option<Entity<Project>>,
1555 window: &mut Window,
1556 cx: &mut Context<Self>,
1557 ) -> Self {
1558 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1559 Self::new(EditorMode::full(), buffer, project, window, cx)
1560 }
1561
1562 pub fn for_multibuffer(
1563 buffer: Entity<MultiBuffer>,
1564 project: Option<Entity<Project>>,
1565 window: &mut Window,
1566 cx: &mut Context<Self>,
1567 ) -> Self {
1568 Self::new(EditorMode::full(), buffer, project, window, cx)
1569 }
1570
1571 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1572 let mut clone = Self::new(
1573 self.mode.clone(),
1574 self.buffer.clone(),
1575 self.project.clone(),
1576 window,
1577 cx,
1578 );
1579 self.display_map.update(cx, |display_map, cx| {
1580 let snapshot = display_map.snapshot(cx);
1581 clone.display_map.update(cx, |display_map, cx| {
1582 display_map.set_state(&snapshot, cx);
1583 });
1584 });
1585 clone.folds_did_change(cx);
1586 clone.selections.clone_state(&self.selections);
1587 clone.scroll_manager.clone_state(&self.scroll_manager);
1588 clone.searchable = self.searchable;
1589 clone.read_only = self.read_only;
1590 clone
1591 }
1592
1593 pub fn new(
1594 mode: EditorMode,
1595 buffer: Entity<MultiBuffer>,
1596 project: Option<Entity<Project>>,
1597 window: &mut Window,
1598 cx: &mut Context<Self>,
1599 ) -> Self {
1600 Editor::new_internal(mode, buffer, project, None, window, cx)
1601 }
1602
1603 fn new_internal(
1604 mode: EditorMode,
1605 buffer: Entity<MultiBuffer>,
1606 project: Option<Entity<Project>>,
1607 display_map: Option<Entity<DisplayMap>>,
1608 window: &mut Window,
1609 cx: &mut Context<Self>,
1610 ) -> Self {
1611 debug_assert!(
1612 display_map.is_none() || mode.is_minimap(),
1613 "Providing a display map for a new editor is only intended for the minimap and might have unindended side effects otherwise!"
1614 );
1615
1616 let full_mode = mode.is_full();
1617 let diagnostics_max_severity = if full_mode {
1618 EditorSettings::get_global(cx)
1619 .diagnostics_max_severity
1620 .unwrap_or(DiagnosticSeverity::Hint)
1621 } else {
1622 DiagnosticSeverity::Off
1623 };
1624 let style = window.text_style();
1625 let font_size = style.font_size.to_pixels(window.rem_size());
1626 let editor = cx.entity().downgrade();
1627 let fold_placeholder = FoldPlaceholder {
1628 constrain_width: true,
1629 render: Arc::new(move |fold_id, fold_range, cx| {
1630 let editor = editor.clone();
1631 div()
1632 .id(fold_id)
1633 .bg(cx.theme().colors().ghost_element_background)
1634 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1635 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1636 .rounded_xs()
1637 .size_full()
1638 .cursor_pointer()
1639 .child("⋯")
1640 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1641 .on_click(move |_, _window, cx| {
1642 editor
1643 .update(cx, |editor, cx| {
1644 editor.unfold_ranges(
1645 &[fold_range.start..fold_range.end],
1646 true,
1647 false,
1648 cx,
1649 );
1650 cx.stop_propagation();
1651 })
1652 .ok();
1653 })
1654 .into_any()
1655 }),
1656 merge_adjacent: true,
1657 ..FoldPlaceholder::default()
1658 };
1659 let display_map = display_map.unwrap_or_else(|| {
1660 cx.new(|cx| {
1661 DisplayMap::new(
1662 buffer.clone(),
1663 style.font(),
1664 font_size,
1665 None,
1666 FILE_HEADER_HEIGHT,
1667 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1668 fold_placeholder,
1669 diagnostics_max_severity,
1670 cx,
1671 )
1672 })
1673 });
1674
1675 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1676
1677 let blink_manager = cx.new(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx));
1678
1679 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1680 .then(|| language_settings::SoftWrap::None);
1681
1682 let mut project_subscriptions = Vec::new();
1683 if mode.is_full() {
1684 if let Some(project) = project.as_ref() {
1685 project_subscriptions.push(cx.subscribe_in(
1686 project,
1687 window,
1688 |editor, _, event, window, cx| match event {
1689 project::Event::RefreshCodeLens => {
1690 // we always query lens with actions, without storing them, always refreshing them
1691 }
1692 project::Event::RefreshInlayHints => {
1693 editor
1694 .refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1695 }
1696 project::Event::LanguageServerAdded(..)
1697 | project::Event::LanguageServerRemoved(..) => {
1698 if editor.tasks_update_task.is_none() {
1699 editor.tasks_update_task =
1700 Some(editor.refresh_runnables(window, cx));
1701 }
1702 }
1703 project::Event::SnippetEdit(id, snippet_edits) => {
1704 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1705 let focus_handle = editor.focus_handle(cx);
1706 if focus_handle.is_focused(window) {
1707 let snapshot = buffer.read(cx).snapshot();
1708 for (range, snippet) in snippet_edits {
1709 let editor_range =
1710 language::range_from_lsp(*range).to_offset(&snapshot);
1711 editor
1712 .insert_snippet(
1713 &[editor_range],
1714 snippet.clone(),
1715 window,
1716 cx,
1717 )
1718 .ok();
1719 }
1720 }
1721 }
1722 }
1723 _ => {}
1724 },
1725 ));
1726 if let Some(task_inventory) = project
1727 .read(cx)
1728 .task_store()
1729 .read(cx)
1730 .task_inventory()
1731 .cloned()
1732 {
1733 project_subscriptions.push(cx.observe_in(
1734 &task_inventory,
1735 window,
1736 |editor, _, window, cx| {
1737 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1738 },
1739 ));
1740 };
1741
1742 project_subscriptions.push(cx.subscribe_in(
1743 &project.read(cx).breakpoint_store(),
1744 window,
1745 |editor, _, event, window, cx| match event {
1746 BreakpointStoreEvent::ClearDebugLines => {
1747 editor.clear_row_highlights::<ActiveDebugLine>();
1748 editor.refresh_inline_values(cx);
1749 }
1750 BreakpointStoreEvent::SetDebugLine => {
1751 if editor.go_to_active_debug_line(window, cx) {
1752 cx.stop_propagation();
1753 }
1754
1755 editor.refresh_inline_values(cx);
1756 }
1757 _ => {}
1758 },
1759 ));
1760 }
1761 }
1762
1763 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1764
1765 let inlay_hint_settings =
1766 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1767 let focus_handle = cx.focus_handle();
1768 cx.on_focus(&focus_handle, window, Self::handle_focus)
1769 .detach();
1770 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1771 .detach();
1772 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1773 .detach();
1774 cx.on_blur(&focus_handle, window, Self::handle_blur)
1775 .detach();
1776
1777 let show_indent_guides = if matches!(mode, EditorMode::SingleLine { .. }) {
1778 Some(false)
1779 } else {
1780 None
1781 };
1782
1783 let breakpoint_store = match (&mode, project.as_ref()) {
1784 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
1785 _ => None,
1786 };
1787
1788 let mut code_action_providers = Vec::new();
1789 let mut load_uncommitted_diff = None;
1790 if let Some(project) = project.clone() {
1791 load_uncommitted_diff = Some(
1792 update_uncommitted_diff_for_buffer(
1793 cx.entity(),
1794 &project,
1795 buffer.read(cx).all_buffers(),
1796 buffer.clone(),
1797 cx,
1798 )
1799 .shared(),
1800 );
1801 code_action_providers.push(Rc::new(project) as Rc<_>);
1802 }
1803
1804 let mut this = Self {
1805 focus_handle,
1806 show_cursor_when_unfocused: false,
1807 last_focused_descendant: None,
1808 buffer: buffer.clone(),
1809 display_map: display_map.clone(),
1810 selections,
1811 scroll_manager: ScrollManager::new(cx),
1812 columnar_selection_tail: None,
1813 columnar_display_point: None,
1814 add_selections_state: None,
1815 select_next_state: None,
1816 select_prev_state: None,
1817 selection_history: SelectionHistory::default(),
1818 defer_selection_effects: false,
1819 deferred_selection_effects_state: None,
1820 autoclose_regions: Vec::new(),
1821 snippet_stack: InvalidationStack::default(),
1822 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
1823 ime_transaction: None,
1824 active_diagnostics: ActiveDiagnostic::None,
1825 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
1826 inline_diagnostics_update: Task::ready(()),
1827 inline_diagnostics: Vec::new(),
1828 soft_wrap_mode_override,
1829 diagnostics_max_severity,
1830 hard_wrap: None,
1831 completion_provider: project.clone().map(|project| Rc::new(project) as _),
1832 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
1833 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
1834 project,
1835 blink_manager: blink_manager.clone(),
1836 show_local_selections: true,
1837 show_scrollbars: ScrollbarAxes {
1838 horizontal: full_mode,
1839 vertical: full_mode,
1840 },
1841 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
1842 offset_content: !matches!(mode, EditorMode::SingleLine { .. }),
1843 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
1844 show_gutter: mode.is_full(),
1845 show_line_numbers: None,
1846 use_relative_line_numbers: None,
1847 disable_expand_excerpt_buttons: false,
1848 show_git_diff_gutter: None,
1849 show_code_actions: None,
1850 show_runnables: None,
1851 show_breakpoints: None,
1852 show_wrap_guides: None,
1853 show_indent_guides,
1854 placeholder_text: None,
1855 highlight_order: 0,
1856 highlighted_rows: HashMap::default(),
1857 background_highlights: TreeMap::default(),
1858 gutter_highlights: TreeMap::default(),
1859 scrollbar_marker_state: ScrollbarMarkerState::default(),
1860 active_indent_guides_state: ActiveIndentGuidesState::default(),
1861 nav_history: None,
1862 context_menu: RefCell::new(None),
1863 context_menu_options: None,
1864 mouse_context_menu: None,
1865 completion_tasks: Vec::new(),
1866 inline_blame_popover: None,
1867 signature_help_state: SignatureHelpState::default(),
1868 auto_signature_help: None,
1869 find_all_references_task_sources: Vec::new(),
1870 next_completion_id: 0,
1871 next_inlay_id: 0,
1872 code_action_providers,
1873 available_code_actions: None,
1874 code_actions_task: None,
1875 quick_selection_highlight_task: None,
1876 debounced_selection_highlight_task: None,
1877 document_highlights_task: None,
1878 linked_editing_range_task: None,
1879 pending_rename: None,
1880 searchable: true,
1881 cursor_shape: EditorSettings::get_global(cx)
1882 .cursor_shape
1883 .unwrap_or_default(),
1884 current_line_highlight: None,
1885 autoindent_mode: Some(AutoindentMode::EachLine),
1886 collapse_matches: false,
1887 workspace: None,
1888 input_enabled: true,
1889 use_modal_editing: mode.is_full(),
1890 read_only: mode.is_minimap(),
1891 use_autoclose: true,
1892 use_auto_surround: true,
1893 auto_replace_emoji_shortcode: false,
1894 jsx_tag_auto_close_enabled_in_any_buffer: false,
1895 leader_id: None,
1896 remote_id: None,
1897 hover_state: HoverState::default(),
1898 pending_mouse_down: None,
1899 hovered_link_state: None,
1900 edit_prediction_provider: None,
1901 active_inline_completion: None,
1902 stale_inline_completion_in_menu: None,
1903 edit_prediction_preview: EditPredictionPreview::Inactive {
1904 released_too_fast: false,
1905 },
1906 inline_diagnostics_enabled: mode.is_full(),
1907 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
1908 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
1909
1910 gutter_hovered: false,
1911 pixel_position_of_newest_cursor: None,
1912 last_bounds: None,
1913 last_position_map: None,
1914 expect_bounds_change: None,
1915 gutter_dimensions: GutterDimensions::default(),
1916 style: None,
1917 show_cursor_names: false,
1918 hovered_cursors: HashMap::default(),
1919 next_editor_action_id: EditorActionId::default(),
1920 editor_actions: Rc::default(),
1921 inline_completions_hidden_for_vim_mode: false,
1922 show_inline_completions_override: None,
1923 menu_inline_completions_policy: MenuInlineCompletionsPolicy::ByProvider,
1924 edit_prediction_settings: EditPredictionSettings::Disabled,
1925 edit_prediction_indent_conflict: false,
1926 edit_prediction_requires_modifier_in_indent_conflict: true,
1927 custom_context_menu: None,
1928 show_git_blame_gutter: false,
1929 show_git_blame_inline: false,
1930 show_selection_menu: None,
1931 show_git_blame_inline_delay_task: None,
1932 git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(),
1933 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
1934 serialize_dirty_buffers: !mode.is_minimap()
1935 && ProjectSettings::get_global(cx)
1936 .session
1937 .restore_unsaved_buffers,
1938 blame: None,
1939 blame_subscription: None,
1940 tasks: BTreeMap::default(),
1941
1942 breakpoint_store,
1943 gutter_breakpoint_indicator: (None, None),
1944 _subscriptions: vec![
1945 cx.observe(&buffer, Self::on_buffer_changed),
1946 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
1947 cx.observe_in(&display_map, window, Self::on_display_map_changed),
1948 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
1949 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
1950 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
1951 cx.observe_window_activation(window, |editor, window, cx| {
1952 let active = window.is_window_active();
1953 editor.blink_manager.update(cx, |blink_manager, cx| {
1954 if active {
1955 blink_manager.enable(cx);
1956 } else {
1957 blink_manager.disable(cx);
1958 }
1959 });
1960 if active {
1961 editor.show_mouse_cursor();
1962 }
1963 }),
1964 ],
1965 tasks_update_task: None,
1966 linked_edit_ranges: Default::default(),
1967 in_project_search: false,
1968 previous_search_ranges: None,
1969 breadcrumb_header: None,
1970 focused_block: None,
1971 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
1972 addons: HashMap::default(),
1973 registered_buffers: HashMap::default(),
1974 _scroll_cursor_center_top_bottom_task: Task::ready(()),
1975 selection_mark_mode: false,
1976 toggle_fold_multiple_buffers: Task::ready(()),
1977 serialize_selections: Task::ready(()),
1978 serialize_folds: Task::ready(()),
1979 text_style_refinement: None,
1980 load_diff_task: load_uncommitted_diff,
1981 temporary_diff_override: false,
1982 mouse_cursor_hidden: false,
1983 minimap: None,
1984 hide_mouse_mode: EditorSettings::get_global(cx)
1985 .hide_mouse
1986 .unwrap_or_default(),
1987 change_list: ChangeList::new(),
1988 mode,
1989 };
1990 if let Some(breakpoints) = this.breakpoint_store.as_ref() {
1991 this._subscriptions
1992 .push(cx.observe(breakpoints, |_, _, cx| {
1993 cx.notify();
1994 }));
1995 }
1996 this.tasks_update_task = Some(this.refresh_runnables(window, cx));
1997 this._subscriptions.extend(project_subscriptions);
1998
1999 this._subscriptions.push(cx.subscribe_in(
2000 &cx.entity(),
2001 window,
2002 |editor, _, e: &EditorEvent, window, cx| match e {
2003 EditorEvent::ScrollPositionChanged { local, .. } => {
2004 if *local {
2005 let new_anchor = editor.scroll_manager.anchor();
2006 let snapshot = editor.snapshot(window, cx);
2007 editor.update_restoration_data(cx, move |data| {
2008 data.scroll_position = (
2009 new_anchor.top_row(&snapshot.buffer_snapshot),
2010 new_anchor.offset,
2011 );
2012 });
2013 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2014 editor.inline_blame_popover.take();
2015 }
2016 }
2017 EditorEvent::Edited { .. } => {
2018 if !vim_enabled(cx) {
2019 let (map, selections) = editor.selections.all_adjusted_display(cx);
2020 let pop_state = editor
2021 .change_list
2022 .last()
2023 .map(|previous| {
2024 previous.len() == selections.len()
2025 && previous.iter().enumerate().all(|(ix, p)| {
2026 p.to_display_point(&map).row()
2027 == selections[ix].head().row()
2028 })
2029 })
2030 .unwrap_or(false);
2031 let new_positions = selections
2032 .into_iter()
2033 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
2034 .collect();
2035 editor
2036 .change_list
2037 .push_to_change_list(pop_state, new_positions);
2038 }
2039 }
2040 _ => (),
2041 },
2042 ));
2043
2044 if let Some(dap_store) = this
2045 .project
2046 .as_ref()
2047 .map(|project| project.read(cx).dap_store())
2048 {
2049 let weak_editor = cx.weak_entity();
2050
2051 this._subscriptions
2052 .push(
2053 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2054 let session_entity = cx.entity();
2055 weak_editor
2056 .update(cx, |editor, cx| {
2057 editor._subscriptions.push(
2058 cx.subscribe(&session_entity, Self::on_debug_session_event),
2059 );
2060 })
2061 .ok();
2062 }),
2063 );
2064
2065 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2066 this._subscriptions
2067 .push(cx.subscribe(&session, Self::on_debug_session_event));
2068 }
2069 }
2070
2071 this.end_selection(window, cx);
2072 this.scroll_manager.show_scrollbars(window, cx);
2073 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut this, &buffer, cx);
2074
2075 if full_mode {
2076 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2077 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2078
2079 if this.git_blame_inline_enabled {
2080 this.start_git_blame_inline(false, window, cx);
2081 }
2082
2083 this.go_to_active_debug_line(window, cx);
2084
2085 if let Some(buffer) = buffer.read(cx).as_singleton() {
2086 if let Some(project) = this.project.as_ref() {
2087 let handle = project.update(cx, |project, cx| {
2088 project.register_buffer_with_language_servers(&buffer, cx)
2089 });
2090 this.registered_buffers
2091 .insert(buffer.read(cx).remote_id(), handle);
2092 }
2093 }
2094
2095 this.minimap = this.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2096 }
2097
2098 this.report_editor_event("Editor Opened", None, cx);
2099 this
2100 }
2101
2102 pub fn deploy_mouse_context_menu(
2103 &mut self,
2104 position: gpui::Point<Pixels>,
2105 context_menu: Entity<ContextMenu>,
2106 window: &mut Window,
2107 cx: &mut Context<Self>,
2108 ) {
2109 self.mouse_context_menu = Some(MouseContextMenu::new(
2110 self,
2111 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2112 context_menu,
2113 window,
2114 cx,
2115 ));
2116 }
2117
2118 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2119 self.mouse_context_menu
2120 .as_ref()
2121 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2122 }
2123
2124 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2125 self.key_context_internal(self.has_active_inline_completion(), window, cx)
2126 }
2127
2128 fn key_context_internal(
2129 &self,
2130 has_active_edit_prediction: bool,
2131 window: &Window,
2132 cx: &App,
2133 ) -> KeyContext {
2134 let mut key_context = KeyContext::new_with_defaults();
2135 key_context.add("Editor");
2136 let mode = match self.mode {
2137 EditorMode::SingleLine { .. } => "single_line",
2138 EditorMode::AutoHeight { .. } => "auto_height",
2139 EditorMode::Minimap { .. } => "minimap",
2140 EditorMode::Full { .. } => "full",
2141 };
2142
2143 if EditorSettings::jupyter_enabled(cx) {
2144 key_context.add("jupyter");
2145 }
2146
2147 key_context.set("mode", mode);
2148 if self.pending_rename.is_some() {
2149 key_context.add("renaming");
2150 }
2151
2152 match self.context_menu.borrow().as_ref() {
2153 Some(CodeContextMenu::Completions(_)) => {
2154 key_context.add("menu");
2155 key_context.add("showing_completions");
2156 }
2157 Some(CodeContextMenu::CodeActions(_)) => {
2158 key_context.add("menu");
2159 key_context.add("showing_code_actions")
2160 }
2161 None => {}
2162 }
2163
2164 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2165 if !self.focus_handle(cx).contains_focused(window, cx)
2166 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2167 {
2168 for addon in self.addons.values() {
2169 addon.extend_key_context(&mut key_context, cx)
2170 }
2171 }
2172
2173 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2174 if let Some(extension) = singleton_buffer
2175 .read(cx)
2176 .file()
2177 .and_then(|file| file.path().extension()?.to_str())
2178 {
2179 key_context.set("extension", extension.to_string());
2180 }
2181 } else {
2182 key_context.add("multibuffer");
2183 }
2184
2185 if has_active_edit_prediction {
2186 if self.edit_prediction_in_conflict() {
2187 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2188 } else {
2189 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2190 key_context.add("copilot_suggestion");
2191 }
2192 }
2193
2194 if self.selection_mark_mode {
2195 key_context.add("selection_mode");
2196 }
2197
2198 key_context
2199 }
2200
2201 fn show_mouse_cursor(&mut self) {
2202 self.mouse_cursor_hidden = false;
2203 }
2204
2205 pub fn hide_mouse_cursor(&mut self, origin: &HideMouseCursorOrigin) {
2206 self.mouse_cursor_hidden = match origin {
2207 HideMouseCursorOrigin::TypingAction => {
2208 matches!(
2209 self.hide_mouse_mode,
2210 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2211 )
2212 }
2213 HideMouseCursorOrigin::MovementAction => {
2214 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2215 }
2216 };
2217 }
2218
2219 pub fn edit_prediction_in_conflict(&self) -> bool {
2220 if !self.show_edit_predictions_in_menu() {
2221 return false;
2222 }
2223
2224 let showing_completions = self
2225 .context_menu
2226 .borrow()
2227 .as_ref()
2228 .map_or(false, |context| {
2229 matches!(context, CodeContextMenu::Completions(_))
2230 });
2231
2232 showing_completions
2233 || self.edit_prediction_requires_modifier()
2234 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2235 // bindings to insert tab characters.
2236 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2237 }
2238
2239 pub fn accept_edit_prediction_keybind(
2240 &self,
2241 window: &Window,
2242 cx: &App,
2243 ) -> AcceptEditPredictionBinding {
2244 let key_context = self.key_context_internal(true, window, cx);
2245 let in_conflict = self.edit_prediction_in_conflict();
2246
2247 AcceptEditPredictionBinding(
2248 window
2249 .bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2250 .into_iter()
2251 .filter(|binding| {
2252 !in_conflict
2253 || binding
2254 .keystrokes()
2255 .first()
2256 .map_or(false, |keystroke| keystroke.modifiers.modified())
2257 })
2258 .rev()
2259 .min_by_key(|binding| {
2260 binding
2261 .keystrokes()
2262 .first()
2263 .map_or(u8::MAX, |k| k.modifiers.number_of_modifiers())
2264 }),
2265 )
2266 }
2267
2268 pub fn new_file(
2269 workspace: &mut Workspace,
2270 _: &workspace::NewFile,
2271 window: &mut Window,
2272 cx: &mut Context<Workspace>,
2273 ) {
2274 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2275 "Failed to create buffer",
2276 window,
2277 cx,
2278 |e, _, _| match e.error_code() {
2279 ErrorCode::RemoteUpgradeRequired => Some(format!(
2280 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2281 e.error_tag("required").unwrap_or("the latest version")
2282 )),
2283 _ => None,
2284 },
2285 );
2286 }
2287
2288 pub fn new_in_workspace(
2289 workspace: &mut Workspace,
2290 window: &mut Window,
2291 cx: &mut Context<Workspace>,
2292 ) -> Task<Result<Entity<Editor>>> {
2293 let project = workspace.project().clone();
2294 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2295
2296 cx.spawn_in(window, async move |workspace, cx| {
2297 let buffer = create.await?;
2298 workspace.update_in(cx, |workspace, window, cx| {
2299 let editor =
2300 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2301 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2302 editor
2303 })
2304 })
2305 }
2306
2307 fn new_file_vertical(
2308 workspace: &mut Workspace,
2309 _: &workspace::NewFileSplitVertical,
2310 window: &mut Window,
2311 cx: &mut Context<Workspace>,
2312 ) {
2313 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2314 }
2315
2316 fn new_file_horizontal(
2317 workspace: &mut Workspace,
2318 _: &workspace::NewFileSplitHorizontal,
2319 window: &mut Window,
2320 cx: &mut Context<Workspace>,
2321 ) {
2322 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2323 }
2324
2325 fn new_file_in_direction(
2326 workspace: &mut Workspace,
2327 direction: SplitDirection,
2328 window: &mut Window,
2329 cx: &mut Context<Workspace>,
2330 ) {
2331 let project = workspace.project().clone();
2332 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2333
2334 cx.spawn_in(window, async move |workspace, cx| {
2335 let buffer = create.await?;
2336 workspace.update_in(cx, move |workspace, window, cx| {
2337 workspace.split_item(
2338 direction,
2339 Box::new(
2340 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2341 ),
2342 window,
2343 cx,
2344 )
2345 })?;
2346 anyhow::Ok(())
2347 })
2348 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2349 match e.error_code() {
2350 ErrorCode::RemoteUpgradeRequired => Some(format!(
2351 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2352 e.error_tag("required").unwrap_or("the latest version")
2353 )),
2354 _ => None,
2355 }
2356 });
2357 }
2358
2359 pub fn leader_id(&self) -> Option<CollaboratorId> {
2360 self.leader_id
2361 }
2362
2363 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2364 &self.buffer
2365 }
2366
2367 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2368 self.workspace.as_ref()?.0.upgrade()
2369 }
2370
2371 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2372 self.buffer().read(cx).title(cx)
2373 }
2374
2375 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2376 let git_blame_gutter_max_author_length = self
2377 .render_git_blame_gutter(cx)
2378 .then(|| {
2379 if let Some(blame) = self.blame.as_ref() {
2380 let max_author_length =
2381 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2382 Some(max_author_length)
2383 } else {
2384 None
2385 }
2386 })
2387 .flatten();
2388
2389 EditorSnapshot {
2390 mode: self.mode.clone(),
2391 show_gutter: self.show_gutter,
2392 show_line_numbers: self.show_line_numbers,
2393 show_git_diff_gutter: self.show_git_diff_gutter,
2394 show_code_actions: self.show_code_actions,
2395 show_runnables: self.show_runnables,
2396 show_breakpoints: self.show_breakpoints,
2397 git_blame_gutter_max_author_length,
2398 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2399 scroll_anchor: self.scroll_manager.anchor(),
2400 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2401 placeholder_text: self.placeholder_text.clone(),
2402 is_focused: self.focus_handle.is_focused(window),
2403 current_line_highlight: self
2404 .current_line_highlight
2405 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2406 gutter_hovered: self.gutter_hovered,
2407 }
2408 }
2409
2410 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2411 self.buffer.read(cx).language_at(point, cx)
2412 }
2413
2414 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2415 self.buffer.read(cx).read(cx).file_at(point).cloned()
2416 }
2417
2418 pub fn active_excerpt(
2419 &self,
2420 cx: &App,
2421 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2422 self.buffer
2423 .read(cx)
2424 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2425 }
2426
2427 pub fn mode(&self) -> &EditorMode {
2428 &self.mode
2429 }
2430
2431 pub fn set_mode(&mut self, mode: EditorMode) {
2432 self.mode = mode;
2433 }
2434
2435 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2436 self.collaboration_hub.as_deref()
2437 }
2438
2439 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2440 self.collaboration_hub = Some(hub);
2441 }
2442
2443 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2444 self.in_project_search = in_project_search;
2445 }
2446
2447 pub fn set_custom_context_menu(
2448 &mut self,
2449 f: impl 'static
2450 + Fn(
2451 &mut Self,
2452 DisplayPoint,
2453 &mut Window,
2454 &mut Context<Self>,
2455 ) -> Option<Entity<ui::ContextMenu>>,
2456 ) {
2457 self.custom_context_menu = Some(Box::new(f))
2458 }
2459
2460 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2461 self.completion_provider = provider;
2462 }
2463
2464 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2465 self.semantics_provider.clone()
2466 }
2467
2468 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2469 self.semantics_provider = provider;
2470 }
2471
2472 pub fn set_edit_prediction_provider<T>(
2473 &mut self,
2474 provider: Option<Entity<T>>,
2475 window: &mut Window,
2476 cx: &mut Context<Self>,
2477 ) where
2478 T: EditPredictionProvider,
2479 {
2480 self.edit_prediction_provider =
2481 provider.map(|provider| RegisteredInlineCompletionProvider {
2482 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2483 if this.focus_handle.is_focused(window) {
2484 this.update_visible_inline_completion(window, cx);
2485 }
2486 }),
2487 provider: Arc::new(provider),
2488 });
2489 self.update_edit_prediction_settings(cx);
2490 self.refresh_inline_completion(false, false, window, cx);
2491 }
2492
2493 pub fn placeholder_text(&self) -> Option<&str> {
2494 self.placeholder_text.as_deref()
2495 }
2496
2497 pub fn set_placeholder_text(
2498 &mut self,
2499 placeholder_text: impl Into<Arc<str>>,
2500 cx: &mut Context<Self>,
2501 ) {
2502 let placeholder_text = Some(placeholder_text.into());
2503 if self.placeholder_text != placeholder_text {
2504 self.placeholder_text = placeholder_text;
2505 cx.notify();
2506 }
2507 }
2508
2509 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2510 self.cursor_shape = cursor_shape;
2511
2512 // Disrupt blink for immediate user feedback that the cursor shape has changed
2513 self.blink_manager.update(cx, BlinkManager::show_cursor);
2514
2515 cx.notify();
2516 }
2517
2518 pub fn set_current_line_highlight(
2519 &mut self,
2520 current_line_highlight: Option<CurrentLineHighlight>,
2521 ) {
2522 self.current_line_highlight = current_line_highlight;
2523 }
2524
2525 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2526 self.collapse_matches = collapse_matches;
2527 }
2528
2529 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2530 let buffers = self.buffer.read(cx).all_buffers();
2531 let Some(project) = self.project.as_ref() else {
2532 return;
2533 };
2534 project.update(cx, |project, cx| {
2535 for buffer in buffers {
2536 self.registered_buffers
2537 .entry(buffer.read(cx).remote_id())
2538 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2539 }
2540 })
2541 }
2542
2543 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2544 if self.collapse_matches {
2545 return range.start..range.start;
2546 }
2547 range.clone()
2548 }
2549
2550 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2551 if self.display_map.read(cx).clip_at_line_ends != clip {
2552 self.display_map
2553 .update(cx, |map, _| map.clip_at_line_ends = clip);
2554 }
2555 }
2556
2557 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2558 self.input_enabled = input_enabled;
2559 }
2560
2561 pub fn set_inline_completions_hidden_for_vim_mode(
2562 &mut self,
2563 hidden: bool,
2564 window: &mut Window,
2565 cx: &mut Context<Self>,
2566 ) {
2567 if hidden != self.inline_completions_hidden_for_vim_mode {
2568 self.inline_completions_hidden_for_vim_mode = hidden;
2569 if hidden {
2570 self.update_visible_inline_completion(window, cx);
2571 } else {
2572 self.refresh_inline_completion(true, false, window, cx);
2573 }
2574 }
2575 }
2576
2577 pub fn set_menu_inline_completions_policy(&mut self, value: MenuInlineCompletionsPolicy) {
2578 self.menu_inline_completions_policy = value;
2579 }
2580
2581 pub fn set_autoindent(&mut self, autoindent: bool) {
2582 if autoindent {
2583 self.autoindent_mode = Some(AutoindentMode::EachLine);
2584 } else {
2585 self.autoindent_mode = None;
2586 }
2587 }
2588
2589 pub fn read_only(&self, cx: &App) -> bool {
2590 self.read_only || self.buffer.read(cx).read_only()
2591 }
2592
2593 pub fn set_read_only(&mut self, read_only: bool) {
2594 self.read_only = read_only;
2595 }
2596
2597 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2598 self.use_autoclose = autoclose;
2599 }
2600
2601 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2602 self.use_auto_surround = auto_surround;
2603 }
2604
2605 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2606 self.auto_replace_emoji_shortcode = auto_replace;
2607 }
2608
2609 pub fn toggle_edit_predictions(
2610 &mut self,
2611 _: &ToggleEditPrediction,
2612 window: &mut Window,
2613 cx: &mut Context<Self>,
2614 ) {
2615 if self.show_inline_completions_override.is_some() {
2616 self.set_show_edit_predictions(None, window, cx);
2617 } else {
2618 let show_edit_predictions = !self.edit_predictions_enabled();
2619 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2620 }
2621 }
2622
2623 pub fn set_show_edit_predictions(
2624 &mut self,
2625 show_edit_predictions: Option<bool>,
2626 window: &mut Window,
2627 cx: &mut Context<Self>,
2628 ) {
2629 self.show_inline_completions_override = show_edit_predictions;
2630 self.update_edit_prediction_settings(cx);
2631
2632 if let Some(false) = show_edit_predictions {
2633 self.discard_inline_completion(false, cx);
2634 } else {
2635 self.refresh_inline_completion(false, true, window, cx);
2636 }
2637 }
2638
2639 fn inline_completions_disabled_in_scope(
2640 &self,
2641 buffer: &Entity<Buffer>,
2642 buffer_position: language::Anchor,
2643 cx: &App,
2644 ) -> bool {
2645 let snapshot = buffer.read(cx).snapshot();
2646 let settings = snapshot.settings_at(buffer_position, cx);
2647
2648 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2649 return false;
2650 };
2651
2652 scope.override_name().map_or(false, |scope_name| {
2653 settings
2654 .edit_predictions_disabled_in
2655 .iter()
2656 .any(|s| s == scope_name)
2657 })
2658 }
2659
2660 pub fn set_use_modal_editing(&mut self, to: bool) {
2661 self.use_modal_editing = to;
2662 }
2663
2664 pub fn use_modal_editing(&self) -> bool {
2665 self.use_modal_editing
2666 }
2667
2668 fn selections_did_change(
2669 &mut self,
2670 local: bool,
2671 old_cursor_position: &Anchor,
2672 should_update_completions: bool,
2673 window: &mut Window,
2674 cx: &mut Context<Self>,
2675 ) {
2676 window.invalidate_character_coordinates();
2677
2678 // Copy selections to primary selection buffer
2679 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2680 if local {
2681 let selections = self.selections.all::<usize>(cx);
2682 let buffer_handle = self.buffer.read(cx).read(cx);
2683
2684 let mut text = String::new();
2685 for (index, selection) in selections.iter().enumerate() {
2686 let text_for_selection = buffer_handle
2687 .text_for_range(selection.start..selection.end)
2688 .collect::<String>();
2689
2690 text.push_str(&text_for_selection);
2691 if index != selections.len() - 1 {
2692 text.push('\n');
2693 }
2694 }
2695
2696 if !text.is_empty() {
2697 cx.write_to_primary(ClipboardItem::new_string(text));
2698 }
2699 }
2700
2701 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
2702 self.buffer.update(cx, |buffer, cx| {
2703 buffer.set_active_selections(
2704 &self.selections.disjoint_anchors(),
2705 self.selections.line_mode,
2706 self.cursor_shape,
2707 cx,
2708 )
2709 });
2710 }
2711 let display_map = self
2712 .display_map
2713 .update(cx, |display_map, cx| display_map.snapshot(cx));
2714 let buffer = &display_map.buffer_snapshot;
2715 self.add_selections_state = None;
2716 self.select_next_state = None;
2717 self.select_prev_state = None;
2718 self.select_syntax_node_history.try_clear();
2719 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), buffer);
2720 self.snippet_stack
2721 .invalidate(&self.selections.disjoint_anchors(), buffer);
2722 self.take_rename(false, window, cx);
2723
2724 let new_cursor_position = self.selections.newest_anchor().head();
2725
2726 self.push_to_nav_history(
2727 *old_cursor_position,
2728 Some(new_cursor_position.to_point(buffer)),
2729 false,
2730 cx,
2731 );
2732
2733 if local {
2734 let new_cursor_position = self.selections.newest_anchor().head();
2735
2736 if let Some(buffer_id) = new_cursor_position.buffer_id {
2737 if !self.registered_buffers.contains_key(&buffer_id) {
2738 if let Some(project) = self.project.as_ref() {
2739 project.update(cx, |project, cx| {
2740 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
2741 return;
2742 };
2743 self.registered_buffers.insert(
2744 buffer_id,
2745 project.register_buffer_with_language_servers(&buffer, cx),
2746 );
2747 })
2748 }
2749 }
2750 }
2751
2752 let mut context_menu = self.context_menu.borrow_mut();
2753 let completion_menu = match context_menu.as_ref() {
2754 Some(CodeContextMenu::Completions(menu)) => Some(menu),
2755 Some(CodeContextMenu::CodeActions(_)) => {
2756 *context_menu = None;
2757 None
2758 }
2759 None => None,
2760 };
2761 let completion_position = completion_menu.map(|menu| menu.initial_position);
2762 drop(context_menu);
2763
2764 if should_update_completions {
2765 if let Some(completion_position) = completion_position {
2766 let new_cursor_offset = new_cursor_position.to_offset(buffer);
2767 let position_matches =
2768 new_cursor_offset == completion_position.to_offset(buffer);
2769 let continue_showing = if position_matches {
2770 let (word_range, kind) = buffer.surrounding_word(new_cursor_offset, true);
2771 if let Some(CharKind::Word) = kind {
2772 word_range.start < new_cursor_offset
2773 } else {
2774 false
2775 }
2776 } else {
2777 false
2778 };
2779
2780 if continue_showing {
2781 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
2782 } else {
2783 self.hide_context_menu(window, cx);
2784 }
2785 }
2786 }
2787
2788 hide_hover(self, cx);
2789
2790 if old_cursor_position.to_display_point(&display_map).row()
2791 != new_cursor_position.to_display_point(&display_map).row()
2792 {
2793 self.available_code_actions.take();
2794 }
2795 self.refresh_code_actions(window, cx);
2796 self.refresh_document_highlights(cx);
2797 self.refresh_selected_text_highlights(false, window, cx);
2798 refresh_matching_bracket_highlights(self, window, cx);
2799 self.update_visible_inline_completion(window, cx);
2800 self.edit_prediction_requires_modifier_in_indent_conflict = true;
2801 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
2802 self.inline_blame_popover.take();
2803 if self.git_blame_inline_enabled {
2804 self.start_inline_blame_timer(window, cx);
2805 }
2806 }
2807
2808 self.blink_manager.update(cx, BlinkManager::pause_blinking);
2809 cx.emit(EditorEvent::SelectionsChanged { local });
2810
2811 let selections = &self.selections.disjoint;
2812 if selections.len() == 1 {
2813 cx.emit(SearchEvent::ActiveMatchChanged)
2814 }
2815 if local {
2816 if let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
2817 let inmemory_selections = selections
2818 .iter()
2819 .map(|s| {
2820 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
2821 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
2822 })
2823 .collect();
2824 self.update_restoration_data(cx, |data| {
2825 data.selections = inmemory_selections;
2826 });
2827
2828 if WorkspaceSettings::get(None, cx).restore_on_startup
2829 != RestoreOnStartupBehavior::None
2830 {
2831 if let Some(workspace_id) =
2832 self.workspace.as_ref().and_then(|workspace| workspace.1)
2833 {
2834 let snapshot = self.buffer().read(cx).snapshot(cx);
2835 let selections = selections.clone();
2836 let background_executor = cx.background_executor().clone();
2837 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2838 self.serialize_selections = cx.background_spawn(async move {
2839 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2840 let db_selections = selections
2841 .iter()
2842 .map(|selection| {
2843 (
2844 selection.start.to_offset(&snapshot),
2845 selection.end.to_offset(&snapshot),
2846 )
2847 })
2848 .collect();
2849
2850 DB.save_editor_selections(editor_id, workspace_id, db_selections)
2851 .await
2852 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
2853 .log_err();
2854 });
2855 }
2856 }
2857 }
2858 }
2859
2860 cx.notify();
2861 }
2862
2863 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
2864 use text::ToOffset as _;
2865 use text::ToPoint as _;
2866
2867 if self.mode.is_minimap()
2868 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
2869 {
2870 return;
2871 }
2872
2873 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
2874 return;
2875 };
2876
2877 let snapshot = singleton.read(cx).snapshot();
2878 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
2879 let display_snapshot = display_map.snapshot(cx);
2880
2881 display_snapshot
2882 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
2883 .map(|fold| {
2884 fold.range.start.text_anchor.to_point(&snapshot)
2885 ..fold.range.end.text_anchor.to_point(&snapshot)
2886 })
2887 .collect()
2888 });
2889 self.update_restoration_data(cx, |data| {
2890 data.folds = inmemory_folds;
2891 });
2892
2893 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
2894 return;
2895 };
2896 let background_executor = cx.background_executor().clone();
2897 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2898 let db_folds = self.display_map.update(cx, |display_map, cx| {
2899 display_map
2900 .snapshot(cx)
2901 .folds_in_range(0..snapshot.len())
2902 .map(|fold| {
2903 (
2904 fold.range.start.text_anchor.to_offset(&snapshot),
2905 fold.range.end.text_anchor.to_offset(&snapshot),
2906 )
2907 })
2908 .collect()
2909 });
2910 self.serialize_folds = cx.background_spawn(async move {
2911 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2912 DB.save_editor_folds(editor_id, workspace_id, db_folds)
2913 .await
2914 .with_context(|| {
2915 format!(
2916 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
2917 )
2918 })
2919 .log_err();
2920 });
2921 }
2922
2923 pub fn sync_selections(
2924 &mut self,
2925 other: Entity<Editor>,
2926 cx: &mut Context<Self>,
2927 ) -> gpui::Subscription {
2928 let other_selections = other.read(cx).selections.disjoint.to_vec();
2929 self.selections.change_with(cx, |selections| {
2930 selections.select_anchors(other_selections);
2931 });
2932
2933 let other_subscription =
2934 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
2935 EditorEvent::SelectionsChanged { local: true } => {
2936 let other_selections = other.read(cx).selections.disjoint.to_vec();
2937 if other_selections.is_empty() {
2938 return;
2939 }
2940 this.selections.change_with(cx, |selections| {
2941 selections.select_anchors(other_selections);
2942 });
2943 }
2944 _ => {}
2945 });
2946
2947 let this_subscription =
2948 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
2949 EditorEvent::SelectionsChanged { local: true } => {
2950 let these_selections = this.selections.disjoint.to_vec();
2951 if these_selections.is_empty() {
2952 return;
2953 }
2954 other.update(cx, |other_editor, cx| {
2955 other_editor.selections.change_with(cx, |selections| {
2956 selections.select_anchors(these_selections);
2957 })
2958 });
2959 }
2960 _ => {}
2961 });
2962
2963 Subscription::join(other_subscription, this_subscription)
2964 }
2965
2966 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
2967 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
2968 /// effects of selection change occur at the end of the transaction.
2969 pub fn change_selections<R>(
2970 &mut self,
2971 autoscroll: Option<Autoscroll>,
2972 window: &mut Window,
2973 cx: &mut Context<Self>,
2974 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2975 ) -> R {
2976 self.change_selections_inner(true, autoscroll, window, cx, change)
2977 }
2978
2979 pub(crate) fn change_selections_without_updating_completions<R>(
2980 &mut self,
2981 autoscroll: Option<Autoscroll>,
2982 window: &mut Window,
2983 cx: &mut Context<Self>,
2984 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2985 ) -> R {
2986 self.change_selections_inner(false, autoscroll, window, cx, change)
2987 }
2988
2989 fn change_selections_inner<R>(
2990 &mut self,
2991 should_update_completions: bool,
2992 autoscroll: Option<Autoscroll>,
2993 window: &mut Window,
2994 cx: &mut Context<Self>,
2995 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2996 ) -> R {
2997 if let Some(state) = &mut self.deferred_selection_effects_state {
2998 state.autoscroll = autoscroll.or(state.autoscroll);
2999 state.should_update_completions = should_update_completions;
3000 let (changed, result) = self.selections.change_with(cx, change);
3001 state.changed |= changed;
3002 return result;
3003 }
3004 let mut state = DeferredSelectionEffectsState {
3005 changed: false,
3006 should_update_completions,
3007 autoscroll,
3008 old_cursor_position: self.selections.newest_anchor().head(),
3009 history_entry: SelectionHistoryEntry {
3010 selections: self.selections.disjoint_anchors(),
3011 select_next_state: self.select_next_state.clone(),
3012 select_prev_state: self.select_prev_state.clone(),
3013 add_selections_state: self.add_selections_state.clone(),
3014 },
3015 };
3016 let (changed, result) = self.selections.change_with(cx, change);
3017 state.changed = state.changed || changed;
3018 if self.defer_selection_effects {
3019 self.deferred_selection_effects_state = Some(state);
3020 } else {
3021 self.apply_selection_effects(state, window, cx);
3022 }
3023 result
3024 }
3025
3026 /// Defers the effects of selection change, so that the effects of multiple calls to
3027 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3028 /// to selection history and the state of popovers based on selection position aren't
3029 /// erroneously updated.
3030 pub fn with_selection_effects_deferred<R>(
3031 &mut self,
3032 window: &mut Window,
3033 cx: &mut Context<Self>,
3034 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3035 ) -> R {
3036 let already_deferred = self.defer_selection_effects;
3037 self.defer_selection_effects = true;
3038 let result = update(self, window, cx);
3039 if !already_deferred {
3040 self.defer_selection_effects = false;
3041 if let Some(state) = self.deferred_selection_effects_state.take() {
3042 self.apply_selection_effects(state, window, cx);
3043 }
3044 }
3045 result
3046 }
3047
3048 fn apply_selection_effects(
3049 &mut self,
3050 state: DeferredSelectionEffectsState,
3051 window: &mut Window,
3052 cx: &mut Context<Self>,
3053 ) {
3054 if state.changed {
3055 self.selection_history.push(state.history_entry);
3056
3057 if let Some(autoscroll) = state.autoscroll {
3058 self.request_autoscroll(autoscroll, cx);
3059 }
3060
3061 let old_cursor_position = &state.old_cursor_position;
3062
3063 self.selections_did_change(
3064 true,
3065 &old_cursor_position,
3066 state.should_update_completions,
3067 window,
3068 cx,
3069 );
3070
3071 if self.should_open_signature_help_automatically(&old_cursor_position, cx) {
3072 self.show_signature_help(&ShowSignatureHelp, window, cx);
3073 }
3074 }
3075 }
3076
3077 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3078 where
3079 I: IntoIterator<Item = (Range<S>, T)>,
3080 S: ToOffset,
3081 T: Into<Arc<str>>,
3082 {
3083 if self.read_only(cx) {
3084 return;
3085 }
3086
3087 self.buffer
3088 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3089 }
3090
3091 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3092 where
3093 I: IntoIterator<Item = (Range<S>, T)>,
3094 S: ToOffset,
3095 T: Into<Arc<str>>,
3096 {
3097 if self.read_only(cx) {
3098 return;
3099 }
3100
3101 self.buffer.update(cx, |buffer, cx| {
3102 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3103 });
3104 }
3105
3106 pub fn edit_with_block_indent<I, S, T>(
3107 &mut self,
3108 edits: I,
3109 original_indent_columns: Vec<Option<u32>>,
3110 cx: &mut Context<Self>,
3111 ) where
3112 I: IntoIterator<Item = (Range<S>, T)>,
3113 S: ToOffset,
3114 T: Into<Arc<str>>,
3115 {
3116 if self.read_only(cx) {
3117 return;
3118 }
3119
3120 self.buffer.update(cx, |buffer, cx| {
3121 buffer.edit(
3122 edits,
3123 Some(AutoindentMode::Block {
3124 original_indent_columns,
3125 }),
3126 cx,
3127 )
3128 });
3129 }
3130
3131 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3132 self.hide_context_menu(window, cx);
3133
3134 match phase {
3135 SelectPhase::Begin {
3136 position,
3137 add,
3138 click_count,
3139 } => self.begin_selection(position, add, click_count, window, cx),
3140 SelectPhase::BeginColumnar {
3141 position,
3142 goal_column,
3143 reset,
3144 } => self.begin_columnar_selection(position, goal_column, reset, window, cx),
3145 SelectPhase::Extend {
3146 position,
3147 click_count,
3148 } => self.extend_selection(position, click_count, window, cx),
3149 SelectPhase::Update {
3150 position,
3151 goal_column,
3152 scroll_delta,
3153 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3154 SelectPhase::End => self.end_selection(window, cx),
3155 }
3156 }
3157
3158 fn extend_selection(
3159 &mut self,
3160 position: DisplayPoint,
3161 click_count: usize,
3162 window: &mut Window,
3163 cx: &mut Context<Self>,
3164 ) {
3165 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3166 let tail = self.selections.newest::<usize>(cx).tail();
3167 self.begin_selection(position, false, click_count, window, cx);
3168
3169 let position = position.to_offset(&display_map, Bias::Left);
3170 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3171
3172 let mut pending_selection = self
3173 .selections
3174 .pending_anchor()
3175 .expect("extend_selection not called with pending selection");
3176 if position >= tail {
3177 pending_selection.start = tail_anchor;
3178 } else {
3179 pending_selection.end = tail_anchor;
3180 pending_selection.reversed = true;
3181 }
3182
3183 let mut pending_mode = self.selections.pending_mode().unwrap();
3184 match &mut pending_mode {
3185 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3186 _ => {}
3187 }
3188
3189 let auto_scroll = EditorSettings::get_global(cx).autoscroll_on_clicks;
3190
3191 self.change_selections(auto_scroll.then(Autoscroll::fit), window, cx, |s| {
3192 s.set_pending(pending_selection, pending_mode)
3193 });
3194 }
3195
3196 fn begin_selection(
3197 &mut self,
3198 position: DisplayPoint,
3199 add: bool,
3200 click_count: usize,
3201 window: &mut Window,
3202 cx: &mut Context<Self>,
3203 ) {
3204 if !self.focus_handle.is_focused(window) {
3205 self.last_focused_descendant = None;
3206 window.focus(&self.focus_handle);
3207 }
3208
3209 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3210 let buffer = &display_map.buffer_snapshot;
3211 let position = display_map.clip_point(position, Bias::Left);
3212
3213 let start;
3214 let end;
3215 let mode;
3216 let mut auto_scroll;
3217 match click_count {
3218 1 => {
3219 start = buffer.anchor_before(position.to_point(&display_map));
3220 end = start;
3221 mode = SelectMode::Character;
3222 auto_scroll = true;
3223 }
3224 2 => {
3225 let range = movement::surrounding_word(&display_map, position);
3226 start = buffer.anchor_before(range.start.to_point(&display_map));
3227 end = buffer.anchor_before(range.end.to_point(&display_map));
3228 mode = SelectMode::Word(start..end);
3229 auto_scroll = true;
3230 }
3231 3 => {
3232 let position = display_map
3233 .clip_point(position, Bias::Left)
3234 .to_point(&display_map);
3235 let line_start = display_map.prev_line_boundary(position).0;
3236 let next_line_start = buffer.clip_point(
3237 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3238 Bias::Left,
3239 );
3240 start = buffer.anchor_before(line_start);
3241 end = buffer.anchor_before(next_line_start);
3242 mode = SelectMode::Line(start..end);
3243 auto_scroll = true;
3244 }
3245 _ => {
3246 start = buffer.anchor_before(0);
3247 end = buffer.anchor_before(buffer.len());
3248 mode = SelectMode::All;
3249 auto_scroll = false;
3250 }
3251 }
3252 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3253
3254 let point_to_delete: Option<usize> = {
3255 let selected_points: Vec<Selection<Point>> =
3256 self.selections.disjoint_in_range(start..end, cx);
3257
3258 if !add || click_count > 1 {
3259 None
3260 } else if !selected_points.is_empty() {
3261 Some(selected_points[0].id)
3262 } else {
3263 let clicked_point_already_selected =
3264 self.selections.disjoint.iter().find(|selection| {
3265 selection.start.to_point(buffer) == start.to_point(buffer)
3266 || selection.end.to_point(buffer) == end.to_point(buffer)
3267 });
3268
3269 clicked_point_already_selected.map(|selection| selection.id)
3270 }
3271 };
3272
3273 let selections_count = self.selections.count();
3274
3275 self.change_selections(auto_scroll.then(Autoscroll::newest), window, cx, |s| {
3276 if let Some(point_to_delete) = point_to_delete {
3277 s.delete(point_to_delete);
3278
3279 if selections_count == 1 {
3280 s.set_pending_anchor_range(start..end, mode);
3281 }
3282 } else {
3283 if !add {
3284 s.clear_disjoint();
3285 }
3286
3287 s.set_pending_anchor_range(start..end, mode);
3288 }
3289 });
3290 }
3291
3292 fn begin_columnar_selection(
3293 &mut self,
3294 position: DisplayPoint,
3295 goal_column: u32,
3296 reset: bool,
3297 window: &mut Window,
3298 cx: &mut Context<Self>,
3299 ) {
3300 if !self.focus_handle.is_focused(window) {
3301 self.last_focused_descendant = None;
3302 window.focus(&self.focus_handle);
3303 }
3304
3305 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3306
3307 if reset {
3308 let pointer_position = display_map
3309 .buffer_snapshot
3310 .anchor_before(position.to_point(&display_map));
3311
3312 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
3313 s.clear_disjoint();
3314 s.set_pending_anchor_range(
3315 pointer_position..pointer_position,
3316 SelectMode::Character,
3317 );
3318 });
3319 if position.column() != goal_column {
3320 self.columnar_display_point = Some(DisplayPoint::new(position.row(), goal_column));
3321 } else {
3322 self.columnar_display_point = None;
3323 }
3324 }
3325
3326 let tail = self.selections.newest::<Point>(cx).tail();
3327 self.columnar_selection_tail = Some(display_map.buffer_snapshot.anchor_before(tail));
3328
3329 if !reset {
3330 self.columnar_display_point = None;
3331 self.select_columns(
3332 tail.to_display_point(&display_map),
3333 position,
3334 goal_column,
3335 &display_map,
3336 window,
3337 cx,
3338 );
3339 }
3340 }
3341
3342 fn update_selection(
3343 &mut self,
3344 position: DisplayPoint,
3345 goal_column: u32,
3346 scroll_delta: gpui::Point<f32>,
3347 window: &mut Window,
3348 cx: &mut Context<Self>,
3349 ) {
3350 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3351
3352 if let Some(tail) = self.columnar_selection_tail.as_ref() {
3353 let tail = self
3354 .columnar_display_point
3355 .unwrap_or_else(|| tail.to_display_point(&display_map));
3356 self.select_columns(tail, position, goal_column, &display_map, window, cx);
3357 } else if let Some(mut pending) = self.selections.pending_anchor() {
3358 let buffer = self.buffer.read(cx).snapshot(cx);
3359 let head;
3360 let tail;
3361 let mode = self.selections.pending_mode().unwrap();
3362 match &mode {
3363 SelectMode::Character => {
3364 head = position.to_point(&display_map);
3365 tail = pending.tail().to_point(&buffer);
3366 }
3367 SelectMode::Word(original_range) => {
3368 let original_display_range = original_range.start.to_display_point(&display_map)
3369 ..original_range.end.to_display_point(&display_map);
3370 let original_buffer_range = original_display_range.start.to_point(&display_map)
3371 ..original_display_range.end.to_point(&display_map);
3372 if movement::is_inside_word(&display_map, position)
3373 || original_display_range.contains(&position)
3374 {
3375 let word_range = movement::surrounding_word(&display_map, position);
3376 if word_range.start < original_display_range.start {
3377 head = word_range.start.to_point(&display_map);
3378 } else {
3379 head = word_range.end.to_point(&display_map);
3380 }
3381 } else {
3382 head = position.to_point(&display_map);
3383 }
3384
3385 if head <= original_buffer_range.start {
3386 tail = original_buffer_range.end;
3387 } else {
3388 tail = original_buffer_range.start;
3389 }
3390 }
3391 SelectMode::Line(original_range) => {
3392 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3393
3394 let position = display_map
3395 .clip_point(position, Bias::Left)
3396 .to_point(&display_map);
3397 let line_start = display_map.prev_line_boundary(position).0;
3398 let next_line_start = buffer.clip_point(
3399 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3400 Bias::Left,
3401 );
3402
3403 if line_start < original_range.start {
3404 head = line_start
3405 } else {
3406 head = next_line_start
3407 }
3408
3409 if head <= original_range.start {
3410 tail = original_range.end;
3411 } else {
3412 tail = original_range.start;
3413 }
3414 }
3415 SelectMode::All => {
3416 return;
3417 }
3418 };
3419
3420 if head < tail {
3421 pending.start = buffer.anchor_before(head);
3422 pending.end = buffer.anchor_before(tail);
3423 pending.reversed = true;
3424 } else {
3425 pending.start = buffer.anchor_before(tail);
3426 pending.end = buffer.anchor_before(head);
3427 pending.reversed = false;
3428 }
3429
3430 self.change_selections(None, window, cx, |s| {
3431 s.set_pending(pending, mode);
3432 });
3433 } else {
3434 log::error!("update_selection dispatched with no pending selection");
3435 return;
3436 }
3437
3438 self.apply_scroll_delta(scroll_delta, window, cx);
3439 cx.notify();
3440 }
3441
3442 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3443 self.columnar_selection_tail.take();
3444 if self.selections.pending_anchor().is_some() {
3445 let selections = self.selections.all::<usize>(cx);
3446 self.change_selections(None, window, cx, |s| {
3447 s.select(selections);
3448 s.clear_pending();
3449 });
3450 }
3451 }
3452
3453 fn select_columns(
3454 &mut self,
3455 tail: DisplayPoint,
3456 head: DisplayPoint,
3457 goal_column: u32,
3458 display_map: &DisplaySnapshot,
3459 window: &mut Window,
3460 cx: &mut Context<Self>,
3461 ) {
3462 let start_row = cmp::min(tail.row(), head.row());
3463 let end_row = cmp::max(tail.row(), head.row());
3464 let start_column = cmp::min(tail.column(), goal_column);
3465 let end_column = cmp::max(tail.column(), goal_column);
3466 let reversed = start_column < tail.column();
3467
3468 let selection_ranges = (start_row.0..=end_row.0)
3469 .map(DisplayRow)
3470 .filter_map(|row| {
3471 if !display_map.is_block_line(row) {
3472 let start = display_map
3473 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3474 .to_point(display_map);
3475 let end = display_map
3476 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3477 .to_point(display_map);
3478 if reversed {
3479 Some(end..start)
3480 } else {
3481 Some(start..end)
3482 }
3483 } else {
3484 None
3485 }
3486 })
3487 .collect::<Vec<_>>();
3488
3489 let mut non_empty_ranges = selection_ranges
3490 .iter()
3491 .filter(|selection_range| selection_range.start != selection_range.end)
3492 .peekable();
3493
3494 let ranges = if non_empty_ranges.peek().is_some() {
3495 non_empty_ranges.cloned().collect()
3496 } else {
3497 selection_ranges
3498 };
3499
3500 self.change_selections(None, window, cx, |s| {
3501 s.select_ranges(ranges);
3502 });
3503 cx.notify();
3504 }
3505
3506 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3507 self.selections
3508 .all_adjusted(cx)
3509 .iter()
3510 .any(|selection| !selection.is_empty())
3511 }
3512
3513 pub fn has_pending_nonempty_selection(&self) -> bool {
3514 let pending_nonempty_selection = match self.selections.pending_anchor() {
3515 Some(Selection { start, end, .. }) => start != end,
3516 None => false,
3517 };
3518
3519 pending_nonempty_selection
3520 || (self.columnar_selection_tail.is_some() && self.selections.disjoint.len() > 1)
3521 }
3522
3523 pub fn has_pending_selection(&self) -> bool {
3524 self.selections.pending_anchor().is_some() || self.columnar_selection_tail.is_some()
3525 }
3526
3527 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3528 self.selection_mark_mode = false;
3529
3530 if self.clear_expanded_diff_hunks(cx) {
3531 cx.notify();
3532 return;
3533 }
3534 if self.dismiss_menus_and_popups(true, window, cx) {
3535 return;
3536 }
3537
3538 if self.mode.is_full()
3539 && self.change_selections(Some(Autoscroll::fit()), window, cx, |s| s.try_cancel())
3540 {
3541 return;
3542 }
3543
3544 cx.propagate();
3545 }
3546
3547 pub fn dismiss_menus_and_popups(
3548 &mut self,
3549 is_user_requested: bool,
3550 window: &mut Window,
3551 cx: &mut Context<Self>,
3552 ) -> bool {
3553 if self.take_rename(false, window, cx).is_some() {
3554 return true;
3555 }
3556
3557 if hide_hover(self, cx) {
3558 return true;
3559 }
3560
3561 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3562 return true;
3563 }
3564
3565 if self.hide_context_menu(window, cx).is_some() {
3566 return true;
3567 }
3568
3569 if self.mouse_context_menu.take().is_some() {
3570 return true;
3571 }
3572
3573 if is_user_requested && self.discard_inline_completion(true, cx) {
3574 return true;
3575 }
3576
3577 if self.snippet_stack.pop().is_some() {
3578 return true;
3579 }
3580
3581 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3582 self.dismiss_diagnostics(cx);
3583 return true;
3584 }
3585
3586 false
3587 }
3588
3589 fn linked_editing_ranges_for(
3590 &self,
3591 selection: Range<text::Anchor>,
3592 cx: &App,
3593 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3594 if self.linked_edit_ranges.is_empty() {
3595 return None;
3596 }
3597 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3598 selection.end.buffer_id.and_then(|end_buffer_id| {
3599 if selection.start.buffer_id != Some(end_buffer_id) {
3600 return None;
3601 }
3602 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3603 let snapshot = buffer.read(cx).snapshot();
3604 self.linked_edit_ranges
3605 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3606 .map(|ranges| (ranges, snapshot, buffer))
3607 })?;
3608 use text::ToOffset as TO;
3609 // find offset from the start of current range to current cursor position
3610 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3611
3612 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3613 let start_difference = start_offset - start_byte_offset;
3614 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3615 let end_difference = end_offset - start_byte_offset;
3616 // Current range has associated linked ranges.
3617 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3618 for range in linked_ranges.iter() {
3619 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3620 let end_offset = start_offset + end_difference;
3621 let start_offset = start_offset + start_difference;
3622 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3623 continue;
3624 }
3625 if self.selections.disjoint_anchor_ranges().any(|s| {
3626 if s.start.buffer_id != selection.start.buffer_id
3627 || s.end.buffer_id != selection.end.buffer_id
3628 {
3629 return false;
3630 }
3631 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3632 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3633 }) {
3634 continue;
3635 }
3636 let start = buffer_snapshot.anchor_after(start_offset);
3637 let end = buffer_snapshot.anchor_after(end_offset);
3638 linked_edits
3639 .entry(buffer.clone())
3640 .or_default()
3641 .push(start..end);
3642 }
3643 Some(linked_edits)
3644 }
3645
3646 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3647 let text: Arc<str> = text.into();
3648
3649 if self.read_only(cx) {
3650 return;
3651 }
3652
3653 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3654
3655 let selections = self.selections.all_adjusted(cx);
3656 let mut bracket_inserted = false;
3657 let mut edits = Vec::new();
3658 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3659 let mut new_selections = Vec::with_capacity(selections.len());
3660 let mut new_autoclose_regions = Vec::new();
3661 let snapshot = self.buffer.read(cx).read(cx);
3662 let mut clear_linked_edit_ranges = false;
3663
3664 for (selection, autoclose_region) in
3665 self.selections_with_autoclose_regions(selections, &snapshot)
3666 {
3667 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3668 // Determine if the inserted text matches the opening or closing
3669 // bracket of any of this language's bracket pairs.
3670 let mut bracket_pair = None;
3671 let mut is_bracket_pair_start = false;
3672 let mut is_bracket_pair_end = false;
3673 if !text.is_empty() {
3674 let mut bracket_pair_matching_end = None;
3675 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3676 // and they are removing the character that triggered IME popup.
3677 for (pair, enabled) in scope.brackets() {
3678 if !pair.close && !pair.surround {
3679 continue;
3680 }
3681
3682 if enabled && pair.start.ends_with(text.as_ref()) {
3683 let prefix_len = pair.start.len() - text.len();
3684 let preceding_text_matches_prefix = prefix_len == 0
3685 || (selection.start.column >= (prefix_len as u32)
3686 && snapshot.contains_str_at(
3687 Point::new(
3688 selection.start.row,
3689 selection.start.column - (prefix_len as u32),
3690 ),
3691 &pair.start[..prefix_len],
3692 ));
3693 if preceding_text_matches_prefix {
3694 bracket_pair = Some(pair.clone());
3695 is_bracket_pair_start = true;
3696 break;
3697 }
3698 }
3699 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
3700 {
3701 // take first bracket pair matching end, but don't break in case a later bracket
3702 // pair matches start
3703 bracket_pair_matching_end = Some(pair.clone());
3704 }
3705 }
3706 if bracket_pair.is_none() && bracket_pair_matching_end.is_some() {
3707 bracket_pair = Some(bracket_pair_matching_end.unwrap());
3708 is_bracket_pair_end = true;
3709 }
3710 }
3711
3712 if let Some(bracket_pair) = bracket_pair {
3713 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
3714 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
3715 let auto_surround =
3716 self.use_auto_surround && snapshot_settings.use_auto_surround;
3717 if selection.is_empty() {
3718 if is_bracket_pair_start {
3719 // If the inserted text is a suffix of an opening bracket and the
3720 // selection is preceded by the rest of the opening bracket, then
3721 // insert the closing bracket.
3722 let following_text_allows_autoclose = snapshot
3723 .chars_at(selection.start)
3724 .next()
3725 .map_or(true, |c| scope.should_autoclose_before(c));
3726
3727 let preceding_text_allows_autoclose = selection.start.column == 0
3728 || snapshot.reversed_chars_at(selection.start).next().map_or(
3729 true,
3730 |c| {
3731 bracket_pair.start != bracket_pair.end
3732 || !snapshot
3733 .char_classifier_at(selection.start)
3734 .is_word(c)
3735 },
3736 );
3737
3738 let is_closing_quote = if bracket_pair.end == bracket_pair.start
3739 && bracket_pair.start.len() == 1
3740 {
3741 let target = bracket_pair.start.chars().next().unwrap();
3742 let current_line_count = snapshot
3743 .reversed_chars_at(selection.start)
3744 .take_while(|&c| c != '\n')
3745 .filter(|&c| c == target)
3746 .count();
3747 current_line_count % 2 == 1
3748 } else {
3749 false
3750 };
3751
3752 if autoclose
3753 && bracket_pair.close
3754 && following_text_allows_autoclose
3755 && preceding_text_allows_autoclose
3756 && !is_closing_quote
3757 {
3758 let anchor = snapshot.anchor_before(selection.end);
3759 new_selections.push((selection.map(|_| anchor), text.len()));
3760 new_autoclose_regions.push((
3761 anchor,
3762 text.len(),
3763 selection.id,
3764 bracket_pair.clone(),
3765 ));
3766 edits.push((
3767 selection.range(),
3768 format!("{}{}", text, bracket_pair.end).into(),
3769 ));
3770 bracket_inserted = true;
3771 continue;
3772 }
3773 }
3774
3775 if let Some(region) = autoclose_region {
3776 // If the selection is followed by an auto-inserted closing bracket,
3777 // then don't insert that closing bracket again; just move the selection
3778 // past the closing bracket.
3779 let should_skip = selection.end == region.range.end.to_point(&snapshot)
3780 && text.as_ref() == region.pair.end.as_str();
3781 if should_skip {
3782 let anchor = snapshot.anchor_after(selection.end);
3783 new_selections
3784 .push((selection.map(|_| anchor), region.pair.end.len()));
3785 continue;
3786 }
3787 }
3788
3789 let always_treat_brackets_as_autoclosed = snapshot
3790 .language_settings_at(selection.start, cx)
3791 .always_treat_brackets_as_autoclosed;
3792 if always_treat_brackets_as_autoclosed
3793 && is_bracket_pair_end
3794 && snapshot.contains_str_at(selection.end, text.as_ref())
3795 {
3796 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
3797 // and the inserted text is a closing bracket and the selection is followed
3798 // by the closing bracket then move the selection past the closing bracket.
3799 let anchor = snapshot.anchor_after(selection.end);
3800 new_selections.push((selection.map(|_| anchor), text.len()));
3801 continue;
3802 }
3803 }
3804 // If an opening bracket is 1 character long and is typed while
3805 // text is selected, then surround that text with the bracket pair.
3806 else if auto_surround
3807 && bracket_pair.surround
3808 && is_bracket_pair_start
3809 && bracket_pair.start.chars().count() == 1
3810 {
3811 edits.push((selection.start..selection.start, text.clone()));
3812 edits.push((
3813 selection.end..selection.end,
3814 bracket_pair.end.as_str().into(),
3815 ));
3816 bracket_inserted = true;
3817 new_selections.push((
3818 Selection {
3819 id: selection.id,
3820 start: snapshot.anchor_after(selection.start),
3821 end: snapshot.anchor_before(selection.end),
3822 reversed: selection.reversed,
3823 goal: selection.goal,
3824 },
3825 0,
3826 ));
3827 continue;
3828 }
3829 }
3830 }
3831
3832 if self.auto_replace_emoji_shortcode
3833 && selection.is_empty()
3834 && text.as_ref().ends_with(':')
3835 {
3836 if let Some(possible_emoji_short_code) =
3837 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
3838 {
3839 if !possible_emoji_short_code.is_empty() {
3840 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
3841 let emoji_shortcode_start = Point::new(
3842 selection.start.row,
3843 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
3844 );
3845
3846 // Remove shortcode from buffer
3847 edits.push((
3848 emoji_shortcode_start..selection.start,
3849 "".to_string().into(),
3850 ));
3851 new_selections.push((
3852 Selection {
3853 id: selection.id,
3854 start: snapshot.anchor_after(emoji_shortcode_start),
3855 end: snapshot.anchor_before(selection.start),
3856 reversed: selection.reversed,
3857 goal: selection.goal,
3858 },
3859 0,
3860 ));
3861
3862 // Insert emoji
3863 let selection_start_anchor = snapshot.anchor_after(selection.start);
3864 new_selections.push((selection.map(|_| selection_start_anchor), 0));
3865 edits.push((selection.start..selection.end, emoji.to_string().into()));
3866
3867 continue;
3868 }
3869 }
3870 }
3871 }
3872
3873 // If not handling any auto-close operation, then just replace the selected
3874 // text with the given input and move the selection to the end of the
3875 // newly inserted text.
3876 let anchor = snapshot.anchor_after(selection.end);
3877 if !self.linked_edit_ranges.is_empty() {
3878 let start_anchor = snapshot.anchor_before(selection.start);
3879
3880 let is_word_char = text.chars().next().map_or(true, |char| {
3881 let classifier = snapshot
3882 .char_classifier_at(start_anchor.to_offset(&snapshot))
3883 .ignore_punctuation(true);
3884 classifier.is_word(char)
3885 });
3886
3887 if is_word_char {
3888 if let Some(ranges) = self
3889 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
3890 {
3891 for (buffer, edits) in ranges {
3892 linked_edits
3893 .entry(buffer.clone())
3894 .or_default()
3895 .extend(edits.into_iter().map(|range| (range, text.clone())));
3896 }
3897 }
3898 } else {
3899 clear_linked_edit_ranges = true;
3900 }
3901 }
3902
3903 new_selections.push((selection.map(|_| anchor), 0));
3904 edits.push((selection.start..selection.end, text.clone()));
3905 }
3906
3907 drop(snapshot);
3908
3909 self.transact(window, cx, |this, window, cx| {
3910 if clear_linked_edit_ranges {
3911 this.linked_edit_ranges.clear();
3912 }
3913 let initial_buffer_versions =
3914 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
3915
3916 this.buffer.update(cx, |buffer, cx| {
3917 buffer.edit(edits, this.autoindent_mode.clone(), cx);
3918 });
3919 for (buffer, edits) in linked_edits {
3920 buffer.update(cx, |buffer, cx| {
3921 let snapshot = buffer.snapshot();
3922 let edits = edits
3923 .into_iter()
3924 .map(|(range, text)| {
3925 use text::ToPoint as TP;
3926 let end_point = TP::to_point(&range.end, &snapshot);
3927 let start_point = TP::to_point(&range.start, &snapshot);
3928 (start_point..end_point, text)
3929 })
3930 .sorted_by_key(|(range, _)| range.start);
3931 buffer.edit(edits, None, cx);
3932 })
3933 }
3934 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
3935 let new_selection_deltas = new_selections.iter().map(|e| e.1);
3936 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
3937 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
3938 .zip(new_selection_deltas)
3939 .map(|(selection, delta)| Selection {
3940 id: selection.id,
3941 start: selection.start + delta,
3942 end: selection.end + delta,
3943 reversed: selection.reversed,
3944 goal: SelectionGoal::None,
3945 })
3946 .collect::<Vec<_>>();
3947
3948 let mut i = 0;
3949 for (position, delta, selection_id, pair) in new_autoclose_regions {
3950 let position = position.to_offset(&map.buffer_snapshot) + delta;
3951 let start = map.buffer_snapshot.anchor_before(position);
3952 let end = map.buffer_snapshot.anchor_after(position);
3953 while let Some(existing_state) = this.autoclose_regions.get(i) {
3954 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
3955 Ordering::Less => i += 1,
3956 Ordering::Greater => break,
3957 Ordering::Equal => {
3958 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
3959 Ordering::Less => i += 1,
3960 Ordering::Equal => break,
3961 Ordering::Greater => break,
3962 }
3963 }
3964 }
3965 }
3966 this.autoclose_regions.insert(
3967 i,
3968 AutocloseRegion {
3969 selection_id,
3970 range: start..end,
3971 pair,
3972 },
3973 );
3974 }
3975
3976 let had_active_inline_completion = this.has_active_inline_completion();
3977 this.change_selections_without_updating_completions(
3978 Some(Autoscroll::fit()),
3979 window,
3980 cx,
3981 |s| s.select(new_selections),
3982 );
3983
3984 if !bracket_inserted {
3985 if let Some(on_type_format_task) =
3986 this.trigger_on_type_formatting(text.to_string(), window, cx)
3987 {
3988 on_type_format_task.detach_and_log_err(cx);
3989 }
3990 }
3991
3992 let editor_settings = EditorSettings::get_global(cx);
3993 if bracket_inserted
3994 && (editor_settings.auto_signature_help
3995 || editor_settings.show_signature_help_after_edits)
3996 {
3997 this.show_signature_help(&ShowSignatureHelp, window, cx);
3998 }
3999
4000 let trigger_in_words =
4001 this.show_edit_predictions_in_menu() || !had_active_inline_completion;
4002 if this.hard_wrap.is_some() {
4003 let latest: Range<Point> = this.selections.newest(cx).range();
4004 if latest.is_empty()
4005 && this
4006 .buffer()
4007 .read(cx)
4008 .snapshot(cx)
4009 .line_len(MultiBufferRow(latest.start.row))
4010 == latest.start.column
4011 {
4012 this.rewrap_impl(
4013 RewrapOptions {
4014 override_language_settings: true,
4015 preserve_existing_whitespace: true,
4016 },
4017 cx,
4018 )
4019 }
4020 }
4021 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4022 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
4023 this.refresh_inline_completion(true, false, window, cx);
4024 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4025 });
4026 }
4027
4028 fn find_possible_emoji_shortcode_at_position(
4029 snapshot: &MultiBufferSnapshot,
4030 position: Point,
4031 ) -> Option<String> {
4032 let mut chars = Vec::new();
4033 let mut found_colon = false;
4034 for char in snapshot.reversed_chars_at(position).take(100) {
4035 // Found a possible emoji shortcode in the middle of the buffer
4036 if found_colon {
4037 if char.is_whitespace() {
4038 chars.reverse();
4039 return Some(chars.iter().collect());
4040 }
4041 // If the previous character is not a whitespace, we are in the middle of a word
4042 // and we only want to complete the shortcode if the word is made up of other emojis
4043 let mut containing_word = String::new();
4044 for ch in snapshot
4045 .reversed_chars_at(position)
4046 .skip(chars.len() + 1)
4047 .take(100)
4048 {
4049 if ch.is_whitespace() {
4050 break;
4051 }
4052 containing_word.push(ch);
4053 }
4054 let containing_word = containing_word.chars().rev().collect::<String>();
4055 if util::word_consists_of_emojis(containing_word.as_str()) {
4056 chars.reverse();
4057 return Some(chars.iter().collect());
4058 }
4059 }
4060
4061 if char.is_whitespace() || !char.is_ascii() {
4062 return None;
4063 }
4064 if char == ':' {
4065 found_colon = true;
4066 } else {
4067 chars.push(char);
4068 }
4069 }
4070 // Found a possible emoji shortcode at the beginning of the buffer
4071 chars.reverse();
4072 Some(chars.iter().collect())
4073 }
4074
4075 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4076 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4077 self.transact(window, cx, |this, window, cx| {
4078 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4079 let selections = this.selections.all::<usize>(cx);
4080 let multi_buffer = this.buffer.read(cx);
4081 let buffer = multi_buffer.snapshot(cx);
4082 selections
4083 .iter()
4084 .map(|selection| {
4085 let start_point = selection.start.to_point(&buffer);
4086 let mut existing_indent =
4087 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4088 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4089 let start = selection.start;
4090 let end = selection.end;
4091 let selection_is_empty = start == end;
4092 let language_scope = buffer.language_scope_at(start);
4093 let (
4094 comment_delimiter,
4095 doc_delimiter,
4096 insert_extra_newline,
4097 indent_on_newline,
4098 indent_on_extra_newline,
4099 ) = if let Some(language) = &language_scope {
4100 let mut insert_extra_newline =
4101 insert_extra_newline_brackets(&buffer, start..end, language)
4102 || insert_extra_newline_tree_sitter(&buffer, start..end);
4103
4104 // Comment extension on newline is allowed only for cursor selections
4105 let comment_delimiter = maybe!({
4106 if !selection_is_empty {
4107 return None;
4108 }
4109
4110 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4111 return None;
4112 }
4113
4114 let delimiters = language.line_comment_prefixes();
4115 let max_len_of_delimiter =
4116 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4117 let (snapshot, range) =
4118 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4119
4120 let num_of_whitespaces = snapshot
4121 .chars_for_range(range.clone())
4122 .take_while(|c| c.is_whitespace())
4123 .count();
4124 let comment_candidate = snapshot
4125 .chars_for_range(range)
4126 .skip(num_of_whitespaces)
4127 .take(max_len_of_delimiter)
4128 .collect::<String>();
4129 let (delimiter, trimmed_len) = delimiters
4130 .iter()
4131 .filter_map(|delimiter| {
4132 let prefix = delimiter.trim_end();
4133 if comment_candidate.starts_with(prefix) {
4134 Some((delimiter, prefix.len()))
4135 } else {
4136 None
4137 }
4138 })
4139 .max_by_key(|(_, len)| *len)?;
4140
4141 let cursor_is_placed_after_comment_marker =
4142 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4143 if cursor_is_placed_after_comment_marker {
4144 Some(delimiter.clone())
4145 } else {
4146 None
4147 }
4148 });
4149
4150 let mut indent_on_newline = IndentSize::spaces(0);
4151 let mut indent_on_extra_newline = IndentSize::spaces(0);
4152
4153 let doc_delimiter = maybe!({
4154 if !selection_is_empty {
4155 return None;
4156 }
4157
4158 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4159 return None;
4160 }
4161
4162 let DocumentationConfig {
4163 start: start_tag,
4164 end: end_tag,
4165 prefix: delimiter,
4166 tab_size: len,
4167 } = language.documentation()?;
4168
4169 let is_within_block_comment = buffer
4170 .language_scope_at(start_point)
4171 .is_some_and(|scope| scope.override_name() == Some("comment"));
4172 if !is_within_block_comment {
4173 return None;
4174 }
4175
4176 let (snapshot, range) =
4177 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4178
4179 let num_of_whitespaces = snapshot
4180 .chars_for_range(range.clone())
4181 .take_while(|c| c.is_whitespace())
4182 .count();
4183
4184 // 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.
4185 let column = start_point.column;
4186 let cursor_is_after_start_tag = {
4187 let start_tag_len = start_tag.len();
4188 let start_tag_line = snapshot
4189 .chars_for_range(range.clone())
4190 .skip(num_of_whitespaces)
4191 .take(start_tag_len)
4192 .collect::<String>();
4193 if start_tag_line.starts_with(start_tag.as_ref()) {
4194 num_of_whitespaces + start_tag_len <= column as usize
4195 } else {
4196 false
4197 }
4198 };
4199
4200 let cursor_is_after_delimiter = {
4201 let delimiter_trim = delimiter.trim_end();
4202 let delimiter_line = snapshot
4203 .chars_for_range(range.clone())
4204 .skip(num_of_whitespaces)
4205 .take(delimiter_trim.len())
4206 .collect::<String>();
4207 if delimiter_line.starts_with(delimiter_trim) {
4208 num_of_whitespaces + delimiter_trim.len() <= column as usize
4209 } else {
4210 false
4211 }
4212 };
4213
4214 let cursor_is_before_end_tag_if_exists = {
4215 let mut char_position = 0u32;
4216 let mut end_tag_offset = None;
4217
4218 'outer: for chunk in snapshot.text_for_range(range.clone()) {
4219 if let Some(byte_pos) = chunk.find(&**end_tag) {
4220 let chars_before_match =
4221 chunk[..byte_pos].chars().count() as u32;
4222 end_tag_offset =
4223 Some(char_position + chars_before_match);
4224 break 'outer;
4225 }
4226 char_position += chunk.chars().count() as u32;
4227 }
4228
4229 if let Some(end_tag_offset) = end_tag_offset {
4230 let cursor_is_before_end_tag = column <= end_tag_offset;
4231 if cursor_is_after_start_tag {
4232 if cursor_is_before_end_tag {
4233 insert_extra_newline = true;
4234 }
4235 let cursor_is_at_start_of_end_tag =
4236 column == end_tag_offset;
4237 if cursor_is_at_start_of_end_tag {
4238 indent_on_extra_newline.len = (*len).into();
4239 }
4240 }
4241 cursor_is_before_end_tag
4242 } else {
4243 true
4244 }
4245 };
4246
4247 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4248 && cursor_is_before_end_tag_if_exists
4249 {
4250 if cursor_is_after_start_tag {
4251 indent_on_newline.len = (*len).into();
4252 }
4253 Some(delimiter.clone())
4254 } else {
4255 None
4256 }
4257 });
4258
4259 (
4260 comment_delimiter,
4261 doc_delimiter,
4262 insert_extra_newline,
4263 indent_on_newline,
4264 indent_on_extra_newline,
4265 )
4266 } else {
4267 (
4268 None,
4269 None,
4270 false,
4271 IndentSize::default(),
4272 IndentSize::default(),
4273 )
4274 };
4275
4276 let prevent_auto_indent = doc_delimiter.is_some();
4277 let delimiter = comment_delimiter.or(doc_delimiter);
4278
4279 let capacity_for_delimiter =
4280 delimiter.as_deref().map(str::len).unwrap_or_default();
4281 let mut new_text = String::with_capacity(
4282 1 + capacity_for_delimiter
4283 + existing_indent.len as usize
4284 + indent_on_newline.len as usize
4285 + indent_on_extra_newline.len as usize,
4286 );
4287 new_text.push('\n');
4288 new_text.extend(existing_indent.chars());
4289 new_text.extend(indent_on_newline.chars());
4290
4291 if let Some(delimiter) = &delimiter {
4292 new_text.push_str(delimiter);
4293 }
4294
4295 if insert_extra_newline {
4296 new_text.push('\n');
4297 new_text.extend(existing_indent.chars());
4298 new_text.extend(indent_on_extra_newline.chars());
4299 }
4300
4301 let anchor = buffer.anchor_after(end);
4302 let new_selection = selection.map(|_| anchor);
4303 (
4304 ((start..end, new_text), prevent_auto_indent),
4305 (insert_extra_newline, new_selection),
4306 )
4307 })
4308 .unzip()
4309 };
4310
4311 let mut auto_indent_edits = Vec::new();
4312 let mut edits = Vec::new();
4313 for (edit, prevent_auto_indent) in edits_with_flags {
4314 if prevent_auto_indent {
4315 edits.push(edit);
4316 } else {
4317 auto_indent_edits.push(edit);
4318 }
4319 }
4320 if !edits.is_empty() {
4321 this.edit(edits, cx);
4322 }
4323 if !auto_indent_edits.is_empty() {
4324 this.edit_with_autoindent(auto_indent_edits, cx);
4325 }
4326
4327 let buffer = this.buffer.read(cx).snapshot(cx);
4328 let new_selections = selection_info
4329 .into_iter()
4330 .map(|(extra_newline_inserted, new_selection)| {
4331 let mut cursor = new_selection.end.to_point(&buffer);
4332 if extra_newline_inserted {
4333 cursor.row -= 1;
4334 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4335 }
4336 new_selection.map(|_| cursor)
4337 })
4338 .collect();
4339
4340 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4341 s.select(new_selections)
4342 });
4343 this.refresh_inline_completion(true, false, window, cx);
4344 });
4345 }
4346
4347 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4348 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4349
4350 let buffer = self.buffer.read(cx);
4351 let snapshot = buffer.snapshot(cx);
4352
4353 let mut edits = Vec::new();
4354 let mut rows = Vec::new();
4355
4356 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4357 let cursor = selection.head();
4358 let row = cursor.row;
4359
4360 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4361
4362 let newline = "\n".to_string();
4363 edits.push((start_of_line..start_of_line, newline));
4364
4365 rows.push(row + rows_inserted as u32);
4366 }
4367
4368 self.transact(window, cx, |editor, window, cx| {
4369 editor.edit(edits, cx);
4370
4371 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4372 let mut index = 0;
4373 s.move_cursors_with(|map, _, _| {
4374 let row = rows[index];
4375 index += 1;
4376
4377 let point = Point::new(row, 0);
4378 let boundary = map.next_line_boundary(point).1;
4379 let clipped = map.clip_point(boundary, Bias::Left);
4380
4381 (clipped, SelectionGoal::None)
4382 });
4383 });
4384
4385 let mut indent_edits = Vec::new();
4386 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4387 for row in rows {
4388 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4389 for (row, indent) in indents {
4390 if indent.len == 0 {
4391 continue;
4392 }
4393
4394 let text = match indent.kind {
4395 IndentKind::Space => " ".repeat(indent.len as usize),
4396 IndentKind::Tab => "\t".repeat(indent.len as usize),
4397 };
4398 let point = Point::new(row.0, 0);
4399 indent_edits.push((point..point, text));
4400 }
4401 }
4402 editor.edit(indent_edits, cx);
4403 });
4404 }
4405
4406 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4407 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4408
4409 let buffer = self.buffer.read(cx);
4410 let snapshot = buffer.snapshot(cx);
4411
4412 let mut edits = Vec::new();
4413 let mut rows = Vec::new();
4414 let mut rows_inserted = 0;
4415
4416 for selection in self.selections.all_adjusted(cx) {
4417 let cursor = selection.head();
4418 let row = cursor.row;
4419
4420 let point = Point::new(row + 1, 0);
4421 let start_of_line = snapshot.clip_point(point, Bias::Left);
4422
4423 let newline = "\n".to_string();
4424 edits.push((start_of_line..start_of_line, newline));
4425
4426 rows_inserted += 1;
4427 rows.push(row + rows_inserted);
4428 }
4429
4430 self.transact(window, cx, |editor, window, cx| {
4431 editor.edit(edits, cx);
4432
4433 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4434 let mut index = 0;
4435 s.move_cursors_with(|map, _, _| {
4436 let row = rows[index];
4437 index += 1;
4438
4439 let point = Point::new(row, 0);
4440 let boundary = map.next_line_boundary(point).1;
4441 let clipped = map.clip_point(boundary, Bias::Left);
4442
4443 (clipped, SelectionGoal::None)
4444 });
4445 });
4446
4447 let mut indent_edits = Vec::new();
4448 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4449 for row in rows {
4450 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4451 for (row, indent) in indents {
4452 if indent.len == 0 {
4453 continue;
4454 }
4455
4456 let text = match indent.kind {
4457 IndentKind::Space => " ".repeat(indent.len as usize),
4458 IndentKind::Tab => "\t".repeat(indent.len as usize),
4459 };
4460 let point = Point::new(row.0, 0);
4461 indent_edits.push((point..point, text));
4462 }
4463 }
4464 editor.edit(indent_edits, cx);
4465 });
4466 }
4467
4468 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4469 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4470 original_indent_columns: Vec::new(),
4471 });
4472 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4473 }
4474
4475 fn insert_with_autoindent_mode(
4476 &mut self,
4477 text: &str,
4478 autoindent_mode: Option<AutoindentMode>,
4479 window: &mut Window,
4480 cx: &mut Context<Self>,
4481 ) {
4482 if self.read_only(cx) {
4483 return;
4484 }
4485
4486 let text: Arc<str> = text.into();
4487 self.transact(window, cx, |this, window, cx| {
4488 let old_selections = this.selections.all_adjusted(cx);
4489 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4490 let anchors = {
4491 let snapshot = buffer.read(cx);
4492 old_selections
4493 .iter()
4494 .map(|s| {
4495 let anchor = snapshot.anchor_after(s.head());
4496 s.map(|_| anchor)
4497 })
4498 .collect::<Vec<_>>()
4499 };
4500 buffer.edit(
4501 old_selections
4502 .iter()
4503 .map(|s| (s.start..s.end, text.clone())),
4504 autoindent_mode,
4505 cx,
4506 );
4507 anchors
4508 });
4509
4510 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4511 s.select_anchors(selection_anchors);
4512 });
4513
4514 cx.notify();
4515 });
4516 }
4517
4518 fn trigger_completion_on_input(
4519 &mut self,
4520 text: &str,
4521 trigger_in_words: bool,
4522 window: &mut Window,
4523 cx: &mut Context<Self>,
4524 ) {
4525 let completions_source = self
4526 .context_menu
4527 .borrow()
4528 .as_ref()
4529 .and_then(|menu| match menu {
4530 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4531 CodeContextMenu::CodeActions(_) => None,
4532 });
4533
4534 match completions_source {
4535 Some(CompletionsMenuSource::Words) => {
4536 self.show_word_completions(&ShowWordCompletions, window, cx)
4537 }
4538 Some(CompletionsMenuSource::Normal)
4539 | Some(CompletionsMenuSource::SnippetChoices)
4540 | None
4541 if self.is_completion_trigger(
4542 text,
4543 trigger_in_words,
4544 completions_source.is_some(),
4545 cx,
4546 ) =>
4547 {
4548 self.show_completions(
4549 &ShowCompletions {
4550 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4551 },
4552 window,
4553 cx,
4554 )
4555 }
4556 _ => {
4557 self.hide_context_menu(window, cx);
4558 }
4559 }
4560 }
4561
4562 fn is_completion_trigger(
4563 &self,
4564 text: &str,
4565 trigger_in_words: bool,
4566 menu_is_open: bool,
4567 cx: &mut Context<Self>,
4568 ) -> bool {
4569 let position = self.selections.newest_anchor().head();
4570 let multibuffer = self.buffer.read(cx);
4571 let Some(buffer) = position
4572 .buffer_id
4573 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
4574 else {
4575 return false;
4576 };
4577
4578 if let Some(completion_provider) = &self.completion_provider {
4579 completion_provider.is_completion_trigger(
4580 &buffer,
4581 position.text_anchor,
4582 text,
4583 trigger_in_words,
4584 menu_is_open,
4585 cx,
4586 )
4587 } else {
4588 false
4589 }
4590 }
4591
4592 /// If any empty selections is touching the start of its innermost containing autoclose
4593 /// region, expand it to select the brackets.
4594 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4595 let selections = self.selections.all::<usize>(cx);
4596 let buffer = self.buffer.read(cx).read(cx);
4597 let new_selections = self
4598 .selections_with_autoclose_regions(selections, &buffer)
4599 .map(|(mut selection, region)| {
4600 if !selection.is_empty() {
4601 return selection;
4602 }
4603
4604 if let Some(region) = region {
4605 let mut range = region.range.to_offset(&buffer);
4606 if selection.start == range.start && range.start >= region.pair.start.len() {
4607 range.start -= region.pair.start.len();
4608 if buffer.contains_str_at(range.start, ®ion.pair.start)
4609 && buffer.contains_str_at(range.end, ®ion.pair.end)
4610 {
4611 range.end += region.pair.end.len();
4612 selection.start = range.start;
4613 selection.end = range.end;
4614
4615 return selection;
4616 }
4617 }
4618 }
4619
4620 let always_treat_brackets_as_autoclosed = buffer
4621 .language_settings_at(selection.start, cx)
4622 .always_treat_brackets_as_autoclosed;
4623
4624 if !always_treat_brackets_as_autoclosed {
4625 return selection;
4626 }
4627
4628 if let Some(scope) = buffer.language_scope_at(selection.start) {
4629 for (pair, enabled) in scope.brackets() {
4630 if !enabled || !pair.close {
4631 continue;
4632 }
4633
4634 if buffer.contains_str_at(selection.start, &pair.end) {
4635 let pair_start_len = pair.start.len();
4636 if buffer.contains_str_at(
4637 selection.start.saturating_sub(pair_start_len),
4638 &pair.start,
4639 ) {
4640 selection.start -= pair_start_len;
4641 selection.end += pair.end.len();
4642
4643 return selection;
4644 }
4645 }
4646 }
4647 }
4648
4649 selection
4650 })
4651 .collect();
4652
4653 drop(buffer);
4654 self.change_selections(None, window, cx, |selections| {
4655 selections.select(new_selections)
4656 });
4657 }
4658
4659 /// Iterate the given selections, and for each one, find the smallest surrounding
4660 /// autoclose region. This uses the ordering of the selections and the autoclose
4661 /// regions to avoid repeated comparisons.
4662 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
4663 &'a self,
4664 selections: impl IntoIterator<Item = Selection<D>>,
4665 buffer: &'a MultiBufferSnapshot,
4666 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
4667 let mut i = 0;
4668 let mut regions = self.autoclose_regions.as_slice();
4669 selections.into_iter().map(move |selection| {
4670 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
4671
4672 let mut enclosing = None;
4673 while let Some(pair_state) = regions.get(i) {
4674 if pair_state.range.end.to_offset(buffer) < range.start {
4675 regions = ®ions[i + 1..];
4676 i = 0;
4677 } else if pair_state.range.start.to_offset(buffer) > range.end {
4678 break;
4679 } else {
4680 if pair_state.selection_id == selection.id {
4681 enclosing = Some(pair_state);
4682 }
4683 i += 1;
4684 }
4685 }
4686
4687 (selection, enclosing)
4688 })
4689 }
4690
4691 /// Remove any autoclose regions that no longer contain their selection.
4692 fn invalidate_autoclose_regions(
4693 &mut self,
4694 mut selections: &[Selection<Anchor>],
4695 buffer: &MultiBufferSnapshot,
4696 ) {
4697 self.autoclose_regions.retain(|state| {
4698 let mut i = 0;
4699 while let Some(selection) = selections.get(i) {
4700 if selection.end.cmp(&state.range.start, buffer).is_lt() {
4701 selections = &selections[1..];
4702 continue;
4703 }
4704 if selection.start.cmp(&state.range.end, buffer).is_gt() {
4705 break;
4706 }
4707 if selection.id == state.selection_id {
4708 return true;
4709 } else {
4710 i += 1;
4711 }
4712 }
4713 false
4714 });
4715 }
4716
4717 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
4718 let offset = position.to_offset(buffer);
4719 let (word_range, kind) = buffer.surrounding_word(offset, true);
4720 if offset > word_range.start && kind == Some(CharKind::Word) {
4721 Some(
4722 buffer
4723 .text_for_range(word_range.start..offset)
4724 .collect::<String>(),
4725 )
4726 } else {
4727 None
4728 }
4729 }
4730
4731 pub fn toggle_inline_values(
4732 &mut self,
4733 _: &ToggleInlineValues,
4734 _: &mut Window,
4735 cx: &mut Context<Self>,
4736 ) {
4737 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
4738
4739 self.refresh_inline_values(cx);
4740 }
4741
4742 pub fn toggle_inlay_hints(
4743 &mut self,
4744 _: &ToggleInlayHints,
4745 _: &mut Window,
4746 cx: &mut Context<Self>,
4747 ) {
4748 self.refresh_inlay_hints(
4749 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
4750 cx,
4751 );
4752 }
4753
4754 pub fn inlay_hints_enabled(&self) -> bool {
4755 self.inlay_hint_cache.enabled
4756 }
4757
4758 pub fn inline_values_enabled(&self) -> bool {
4759 self.inline_value_cache.enabled
4760 }
4761
4762 #[cfg(any(test, feature = "test-support"))]
4763 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
4764 self.display_map
4765 .read(cx)
4766 .current_inlays()
4767 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
4768 .cloned()
4769 .collect()
4770 }
4771
4772 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
4773 if self.semantics_provider.is_none() || !self.mode.is_full() {
4774 return;
4775 }
4776
4777 let reason_description = reason.description();
4778 let ignore_debounce = matches!(
4779 reason,
4780 InlayHintRefreshReason::SettingsChange(_)
4781 | InlayHintRefreshReason::Toggle(_)
4782 | InlayHintRefreshReason::ExcerptsRemoved(_)
4783 | InlayHintRefreshReason::ModifiersChanged(_)
4784 );
4785 let (invalidate_cache, required_languages) = match reason {
4786 InlayHintRefreshReason::ModifiersChanged(enabled) => {
4787 match self.inlay_hint_cache.modifiers_override(enabled) {
4788 Some(enabled) => {
4789 if enabled {
4790 (InvalidationStrategy::RefreshRequested, None)
4791 } else {
4792 self.splice_inlays(
4793 &self
4794 .visible_inlay_hints(cx)
4795 .iter()
4796 .map(|inlay| inlay.id)
4797 .collect::<Vec<InlayId>>(),
4798 Vec::new(),
4799 cx,
4800 );
4801 return;
4802 }
4803 }
4804 None => return,
4805 }
4806 }
4807 InlayHintRefreshReason::Toggle(enabled) => {
4808 if self.inlay_hint_cache.toggle(enabled) {
4809 if enabled {
4810 (InvalidationStrategy::RefreshRequested, None)
4811 } else {
4812 self.splice_inlays(
4813 &self
4814 .visible_inlay_hints(cx)
4815 .iter()
4816 .map(|inlay| inlay.id)
4817 .collect::<Vec<InlayId>>(),
4818 Vec::new(),
4819 cx,
4820 );
4821 return;
4822 }
4823 } else {
4824 return;
4825 }
4826 }
4827 InlayHintRefreshReason::SettingsChange(new_settings) => {
4828 match self.inlay_hint_cache.update_settings(
4829 &self.buffer,
4830 new_settings,
4831 self.visible_inlay_hints(cx),
4832 cx,
4833 ) {
4834 ControlFlow::Break(Some(InlaySplice {
4835 to_remove,
4836 to_insert,
4837 })) => {
4838 self.splice_inlays(&to_remove, to_insert, cx);
4839 return;
4840 }
4841 ControlFlow::Break(None) => return,
4842 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
4843 }
4844 }
4845 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
4846 if let Some(InlaySplice {
4847 to_remove,
4848 to_insert,
4849 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
4850 {
4851 self.splice_inlays(&to_remove, to_insert, cx);
4852 }
4853 self.display_map.update(cx, |display_map, _| {
4854 display_map.remove_inlays_for_excerpts(&excerpts_removed)
4855 });
4856 return;
4857 }
4858 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
4859 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
4860 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
4861 }
4862 InlayHintRefreshReason::RefreshRequested => {
4863 (InvalidationStrategy::RefreshRequested, None)
4864 }
4865 };
4866
4867 if let Some(InlaySplice {
4868 to_remove,
4869 to_insert,
4870 }) = self.inlay_hint_cache.spawn_hint_refresh(
4871 reason_description,
4872 self.excerpts_for_inlay_hints_query(required_languages.as_ref(), cx),
4873 invalidate_cache,
4874 ignore_debounce,
4875 cx,
4876 ) {
4877 self.splice_inlays(&to_remove, to_insert, cx);
4878 }
4879 }
4880
4881 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
4882 self.display_map
4883 .read(cx)
4884 .current_inlays()
4885 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
4886 .cloned()
4887 .collect()
4888 }
4889
4890 pub fn excerpts_for_inlay_hints_query(
4891 &self,
4892 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
4893 cx: &mut Context<Editor>,
4894 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
4895 let Some(project) = self.project.as_ref() else {
4896 return HashMap::default();
4897 };
4898 let project = project.read(cx);
4899 let multi_buffer = self.buffer().read(cx);
4900 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
4901 let multi_buffer_visible_start = self
4902 .scroll_manager
4903 .anchor()
4904 .anchor
4905 .to_point(&multi_buffer_snapshot);
4906 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
4907 multi_buffer_visible_start
4908 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
4909 Bias::Left,
4910 );
4911 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
4912 multi_buffer_snapshot
4913 .range_to_buffer_ranges(multi_buffer_visible_range)
4914 .into_iter()
4915 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
4916 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
4917 let buffer_file = project::File::from_dyn(buffer.file())?;
4918 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
4919 let worktree_entry = buffer_worktree
4920 .read(cx)
4921 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
4922 if worktree_entry.is_ignored {
4923 return None;
4924 }
4925
4926 let language = buffer.language()?;
4927 if let Some(restrict_to_languages) = restrict_to_languages {
4928 if !restrict_to_languages.contains(language) {
4929 return None;
4930 }
4931 }
4932 Some((
4933 excerpt_id,
4934 (
4935 multi_buffer.buffer(buffer.remote_id()).unwrap(),
4936 buffer.version().clone(),
4937 excerpt_visible_range,
4938 ),
4939 ))
4940 })
4941 .collect()
4942 }
4943
4944 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
4945 TextLayoutDetails {
4946 text_system: window.text_system().clone(),
4947 editor_style: self.style.clone().unwrap(),
4948 rem_size: window.rem_size(),
4949 scroll_anchor: self.scroll_manager.anchor(),
4950 visible_rows: self.visible_line_count(),
4951 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
4952 }
4953 }
4954
4955 pub fn splice_inlays(
4956 &self,
4957 to_remove: &[InlayId],
4958 to_insert: Vec<Inlay>,
4959 cx: &mut Context<Self>,
4960 ) {
4961 self.display_map.update(cx, |display_map, cx| {
4962 display_map.splice_inlays(to_remove, to_insert, cx)
4963 });
4964 cx.notify();
4965 }
4966
4967 fn trigger_on_type_formatting(
4968 &self,
4969 input: String,
4970 window: &mut Window,
4971 cx: &mut Context<Self>,
4972 ) -> Option<Task<Result<()>>> {
4973 if input.len() != 1 {
4974 return None;
4975 }
4976
4977 let project = self.project.as_ref()?;
4978 let position = self.selections.newest_anchor().head();
4979 let (buffer, buffer_position) = self
4980 .buffer
4981 .read(cx)
4982 .text_anchor_for_position(position, cx)?;
4983
4984 let settings = language_settings::language_settings(
4985 buffer
4986 .read(cx)
4987 .language_at(buffer_position)
4988 .map(|l| l.name()),
4989 buffer.read(cx).file(),
4990 cx,
4991 );
4992 if !settings.use_on_type_format {
4993 return None;
4994 }
4995
4996 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
4997 // hence we do LSP request & edit on host side only — add formats to host's history.
4998 let push_to_lsp_host_history = true;
4999 // If this is not the host, append its history with new edits.
5000 let push_to_client_history = project.read(cx).is_via_collab();
5001
5002 let on_type_formatting = project.update(cx, |project, cx| {
5003 project.on_type_format(
5004 buffer.clone(),
5005 buffer_position,
5006 input,
5007 push_to_lsp_host_history,
5008 cx,
5009 )
5010 });
5011 Some(cx.spawn_in(window, async move |editor, cx| {
5012 if let Some(transaction) = on_type_formatting.await? {
5013 if push_to_client_history {
5014 buffer
5015 .update(cx, |buffer, _| {
5016 buffer.push_transaction(transaction, Instant::now());
5017 buffer.finalize_last_transaction();
5018 })
5019 .ok();
5020 }
5021 editor.update(cx, |editor, cx| {
5022 editor.refresh_document_highlights(cx);
5023 })?;
5024 }
5025 Ok(())
5026 }))
5027 }
5028
5029 pub fn show_word_completions(
5030 &mut self,
5031 _: &ShowWordCompletions,
5032 window: &mut Window,
5033 cx: &mut Context<Self>,
5034 ) {
5035 self.open_or_update_completions_menu(Some(CompletionsMenuSource::Words), None, window, cx);
5036 }
5037
5038 pub fn show_completions(
5039 &mut self,
5040 options: &ShowCompletions,
5041 window: &mut Window,
5042 cx: &mut Context<Self>,
5043 ) {
5044 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5045 }
5046
5047 fn open_or_update_completions_menu(
5048 &mut self,
5049 requested_source: Option<CompletionsMenuSource>,
5050 trigger: Option<&str>,
5051 window: &mut Window,
5052 cx: &mut Context<Self>,
5053 ) {
5054 if self.pending_rename.is_some() {
5055 return;
5056 }
5057
5058 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5059
5060 let position = self
5061 .selections
5062 .newest_anchor()
5063 .head()
5064 .bias_right(&multibuffer_snapshot);
5065 if position.diff_base_anchor.is_some() {
5066 return;
5067 }
5068 let (buffer, buffer_position) =
5069 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
5070 output
5071 } else {
5072 return;
5073 };
5074 let buffer_snapshot = buffer.read(cx).snapshot();
5075
5076 let query: Option<Arc<String>> =
5077 Self::completion_query(&multibuffer_snapshot, position).map(|query| query.into());
5078
5079 drop(multibuffer_snapshot);
5080
5081 let provider = match requested_source {
5082 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5083 Some(CompletionsMenuSource::Words) => None,
5084 Some(CompletionsMenuSource::SnippetChoices) => {
5085 log::error!("bug: SnippetChoices requested_source is not handled");
5086 None
5087 }
5088 };
5089
5090 let sort_completions = provider
5091 .as_ref()
5092 .map_or(false, |provider| provider.sort_completions());
5093
5094 let filter_completions = provider
5095 .as_ref()
5096 .map_or(true, |provider| provider.filter_completions());
5097
5098 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5099 if filter_completions {
5100 menu.filter(query.clone(), provider.clone(), window, cx);
5101 }
5102 // When `is_incomplete` is false, no need to re-query completions when the current query
5103 // is a suffix of the initial query.
5104 if !menu.is_incomplete {
5105 // If the new query is a suffix of the old query (typing more characters) and
5106 // the previous result was complete, the existing completions can be filtered.
5107 //
5108 // Note that this is always true for snippet completions.
5109 let query_matches = match (&menu.initial_query, &query) {
5110 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5111 (None, _) => true,
5112 _ => false,
5113 };
5114 if query_matches {
5115 let position_matches = if menu.initial_position == position {
5116 true
5117 } else {
5118 let snapshot = self.buffer.read(cx).read(cx);
5119 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5120 };
5121 if position_matches {
5122 return;
5123 }
5124 }
5125 }
5126 };
5127
5128 let trigger_kind = match trigger {
5129 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5130 CompletionTriggerKind::TRIGGER_CHARACTER
5131 }
5132 _ => CompletionTriggerKind::INVOKED,
5133 };
5134 let completion_context = CompletionContext {
5135 trigger_character: trigger.and_then(|trigger| {
5136 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5137 Some(String::from(trigger))
5138 } else {
5139 None
5140 }
5141 }),
5142 trigger_kind,
5143 };
5144
5145 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5146 buffer_snapshot.surrounding_word(buffer_position)
5147 {
5148 let word_to_exclude = buffer_snapshot
5149 .text_for_range(word_range.clone())
5150 .collect::<String>();
5151 (
5152 buffer_snapshot.anchor_before(word_range.start)
5153 ..buffer_snapshot.anchor_after(buffer_position),
5154 Some(word_to_exclude),
5155 )
5156 } else {
5157 (buffer_position..buffer_position, None)
5158 };
5159
5160 let language = buffer_snapshot
5161 .language_at(buffer_position)
5162 .map(|language| language.name());
5163
5164 let completion_settings =
5165 language_settings(language.clone(), buffer_snapshot.file(), cx).completions;
5166
5167 let show_completion_documentation = buffer_snapshot
5168 .settings_at(buffer_position, cx)
5169 .show_completion_documentation;
5170
5171 // The document can be large, so stay in reasonable bounds when searching for words,
5172 // otherwise completion pop-up might be slow to appear.
5173 const WORD_LOOKUP_ROWS: u32 = 5_000;
5174 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5175 let min_word_search = buffer_snapshot.clip_point(
5176 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5177 Bias::Left,
5178 );
5179 let max_word_search = buffer_snapshot.clip_point(
5180 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5181 Bias::Right,
5182 );
5183 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5184 ..buffer_snapshot.point_to_offset(max_word_search);
5185
5186 let skip_digits = query
5187 .as_ref()
5188 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
5189
5190 let (mut words, provider_responses) = match &provider {
5191 Some(provider) => {
5192 let provider_responses = provider.completions(
5193 position.excerpt_id,
5194 &buffer,
5195 buffer_position,
5196 completion_context,
5197 window,
5198 cx,
5199 );
5200
5201 let words = match completion_settings.words {
5202 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
5203 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
5204 .background_spawn(async move {
5205 buffer_snapshot.words_in_range(WordsQuery {
5206 fuzzy_contents: None,
5207 range: word_search_range,
5208 skip_digits,
5209 })
5210 }),
5211 };
5212
5213 (words, provider_responses)
5214 }
5215 None => (
5216 cx.background_spawn(async move {
5217 buffer_snapshot.words_in_range(WordsQuery {
5218 fuzzy_contents: None,
5219 range: word_search_range,
5220 skip_digits,
5221 })
5222 }),
5223 Task::ready(Ok(Vec::new())),
5224 ),
5225 };
5226
5227 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5228
5229 let id = post_inc(&mut self.next_completion_id);
5230 let task = cx.spawn_in(window, async move |editor, cx| {
5231 let Ok(()) = editor.update(cx, |this, _| {
5232 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5233 }) else {
5234 return;
5235 };
5236
5237 // TODO: Ideally completions from different sources would be selectively re-queried, so
5238 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5239 let mut completions = Vec::new();
5240 let mut is_incomplete = false;
5241 if let Some(provider_responses) = provider_responses.await.log_err() {
5242 if !provider_responses.is_empty() {
5243 for response in provider_responses {
5244 completions.extend(response.completions);
5245 is_incomplete = is_incomplete || response.is_incomplete;
5246 }
5247 if completion_settings.words == WordsCompletionMode::Fallback {
5248 words = Task::ready(BTreeMap::default());
5249 }
5250 }
5251 }
5252
5253 let mut words = words.await;
5254 if let Some(word_to_exclude) = &word_to_exclude {
5255 words.remove(word_to_exclude);
5256 }
5257 for lsp_completion in &completions {
5258 words.remove(&lsp_completion.new_text);
5259 }
5260 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5261 replace_range: word_replace_range.clone(),
5262 new_text: word.clone(),
5263 label: CodeLabel::plain(word, None),
5264 icon_path: None,
5265 documentation: None,
5266 source: CompletionSource::BufferWord {
5267 word_range,
5268 resolved: false,
5269 },
5270 insert_text_mode: Some(InsertTextMode::AS_IS),
5271 confirm: None,
5272 }));
5273
5274 let menu = if completions.is_empty() {
5275 None
5276 } else {
5277 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5278 let languages = editor
5279 .workspace
5280 .as_ref()
5281 .and_then(|(workspace, _)| workspace.upgrade())
5282 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5283 let menu = CompletionsMenu::new(
5284 id,
5285 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5286 sort_completions,
5287 show_completion_documentation,
5288 position,
5289 query.clone(),
5290 is_incomplete,
5291 buffer.clone(),
5292 completions.into(),
5293 snippet_sort_order,
5294 languages,
5295 language,
5296 cx,
5297 );
5298
5299 let query = if filter_completions { query } else { None };
5300 let matches_task = if let Some(query) = query {
5301 menu.do_async_filtering(query, cx)
5302 } else {
5303 Task::ready(menu.unfiltered_matches())
5304 };
5305 (menu, matches_task)
5306 }) else {
5307 return;
5308 };
5309
5310 let matches = matches_task.await;
5311
5312 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5313 // Newer menu already set, so exit.
5314 match editor.context_menu.borrow().as_ref() {
5315 Some(CodeContextMenu::Completions(prev_menu)) => {
5316 if prev_menu.id > id {
5317 return;
5318 }
5319 }
5320 _ => {}
5321 };
5322
5323 // Only valid to take prev_menu because it the new menu is immediately set
5324 // below, or the menu is hidden.
5325 match editor.context_menu.borrow_mut().take() {
5326 Some(CodeContextMenu::Completions(prev_menu)) => {
5327 let position_matches =
5328 if prev_menu.initial_position == menu.initial_position {
5329 true
5330 } else {
5331 let snapshot = editor.buffer.read(cx).read(cx);
5332 prev_menu.initial_position.to_offset(&snapshot)
5333 == menu.initial_position.to_offset(&snapshot)
5334 };
5335 if position_matches {
5336 // Preserve markdown cache before `set_filter_results` because it will
5337 // try to populate the documentation cache.
5338 menu.preserve_markdown_cache(prev_menu);
5339 }
5340 }
5341 _ => {}
5342 };
5343
5344 menu.set_filter_results(matches, provider, window, cx);
5345 }) else {
5346 return;
5347 };
5348
5349 menu.visible().then_some(menu)
5350 };
5351
5352 editor
5353 .update_in(cx, |editor, window, cx| {
5354 if editor.focus_handle.is_focused(window) {
5355 if let Some(menu) = menu {
5356 *editor.context_menu.borrow_mut() =
5357 Some(CodeContextMenu::Completions(menu));
5358
5359 crate::hover_popover::hide_hover(editor, cx);
5360 if editor.show_edit_predictions_in_menu() {
5361 editor.update_visible_inline_completion(window, cx);
5362 } else {
5363 editor.discard_inline_completion(false, cx);
5364 }
5365
5366 cx.notify();
5367 return;
5368 }
5369 }
5370
5371 if editor.completion_tasks.len() <= 1 {
5372 // If there are no more completion tasks and the last menu was empty, we should hide it.
5373 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5374 // If it was already hidden and we don't show inline completions in the menu, we should
5375 // also show the inline-completion when available.
5376 if was_hidden && editor.show_edit_predictions_in_menu() {
5377 editor.update_visible_inline_completion(window, cx);
5378 }
5379 }
5380 })
5381 .ok();
5382 });
5383
5384 self.completion_tasks.push((id, task));
5385 }
5386
5387 #[cfg(feature = "test-support")]
5388 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5389 let menu = self.context_menu.borrow();
5390 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5391 let completions = menu.completions.borrow();
5392 Some(completions.to_vec())
5393 } else {
5394 None
5395 }
5396 }
5397
5398 pub fn with_completions_menu_matching_id<R>(
5399 &self,
5400 id: CompletionId,
5401 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5402 ) -> R {
5403 let mut context_menu = self.context_menu.borrow_mut();
5404 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5405 return f(None);
5406 };
5407 if completions_menu.id != id {
5408 return f(None);
5409 }
5410 f(Some(completions_menu))
5411 }
5412
5413 pub fn confirm_completion(
5414 &mut self,
5415 action: &ConfirmCompletion,
5416 window: &mut Window,
5417 cx: &mut Context<Self>,
5418 ) -> Option<Task<Result<()>>> {
5419 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5420 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5421 }
5422
5423 pub fn confirm_completion_insert(
5424 &mut self,
5425 _: &ConfirmCompletionInsert,
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::CompleteWithInsert, window, cx)
5431 }
5432
5433 pub fn confirm_completion_replace(
5434 &mut self,
5435 _: &ConfirmCompletionReplace,
5436 window: &mut Window,
5437 cx: &mut Context<Self>,
5438 ) -> Option<Task<Result<()>>> {
5439 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5440 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5441 }
5442
5443 pub fn compose_completion(
5444 &mut self,
5445 action: &ComposeCompletion,
5446 window: &mut Window,
5447 cx: &mut Context<Self>,
5448 ) -> Option<Task<Result<()>>> {
5449 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5450 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5451 }
5452
5453 fn do_completion(
5454 &mut self,
5455 item_ix: Option<usize>,
5456 intent: CompletionIntent,
5457 window: &mut Window,
5458 cx: &mut Context<Editor>,
5459 ) -> Option<Task<Result<()>>> {
5460 use language::ToOffset as _;
5461
5462 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5463 else {
5464 return None;
5465 };
5466
5467 let candidate_id = {
5468 let entries = completions_menu.entries.borrow();
5469 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5470 if self.show_edit_predictions_in_menu() {
5471 self.discard_inline_completion(true, cx);
5472 }
5473 mat.candidate_id
5474 };
5475
5476 let completion = completions_menu
5477 .completions
5478 .borrow()
5479 .get(candidate_id)?
5480 .clone();
5481 cx.stop_propagation();
5482
5483 let buffer_handle = completions_menu.buffer.clone();
5484
5485 let CompletionEdit {
5486 new_text,
5487 snippet,
5488 replace_range,
5489 } = process_completion_for_edit(
5490 &completion,
5491 intent,
5492 &buffer_handle,
5493 &completions_menu.initial_position.text_anchor,
5494 cx,
5495 );
5496
5497 let buffer = buffer_handle.read(cx);
5498 let snapshot = self.buffer.read(cx).snapshot(cx);
5499 let newest_anchor = self.selections.newest_anchor();
5500 let replace_range_multibuffer = {
5501 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5502 let multibuffer_anchor = snapshot
5503 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5504 .unwrap()
5505 ..snapshot
5506 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5507 .unwrap();
5508 multibuffer_anchor.start.to_offset(&snapshot)
5509 ..multibuffer_anchor.end.to_offset(&snapshot)
5510 };
5511 if newest_anchor.head().buffer_id != Some(buffer.remote_id()) {
5512 return None;
5513 }
5514
5515 let old_text = buffer
5516 .text_for_range(replace_range.clone())
5517 .collect::<String>();
5518 let lookbehind = newest_anchor
5519 .start
5520 .text_anchor
5521 .to_offset(buffer)
5522 .saturating_sub(replace_range.start);
5523 let lookahead = replace_range
5524 .end
5525 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5526 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5527 let suffix = &old_text[lookbehind.min(old_text.len())..];
5528
5529 let selections = self.selections.all::<usize>(cx);
5530 let mut ranges = Vec::new();
5531 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5532
5533 for selection in &selections {
5534 let range = if selection.id == newest_anchor.id {
5535 replace_range_multibuffer.clone()
5536 } else {
5537 let mut range = selection.range();
5538
5539 // if prefix is present, don't duplicate it
5540 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5541 range.start = range.start.saturating_sub(lookbehind);
5542
5543 // if suffix is also present, mimic the newest cursor and replace it
5544 if selection.id != newest_anchor.id
5545 && snapshot.contains_str_at(range.end, suffix)
5546 {
5547 range.end += lookahead;
5548 }
5549 }
5550 range
5551 };
5552
5553 ranges.push(range.clone());
5554
5555 if !self.linked_edit_ranges.is_empty() {
5556 let start_anchor = snapshot.anchor_before(range.start);
5557 let end_anchor = snapshot.anchor_after(range.end);
5558 if let Some(ranges) = self
5559 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5560 {
5561 for (buffer, edits) in ranges {
5562 linked_edits
5563 .entry(buffer.clone())
5564 .or_default()
5565 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5566 }
5567 }
5568 }
5569 }
5570
5571 let common_prefix_len = old_text
5572 .chars()
5573 .zip(new_text.chars())
5574 .take_while(|(a, b)| a == b)
5575 .map(|(a, _)| a.len_utf8())
5576 .sum::<usize>();
5577
5578 cx.emit(EditorEvent::InputHandled {
5579 utf16_range_to_replace: None,
5580 text: new_text[common_prefix_len..].into(),
5581 });
5582
5583 self.transact(window, cx, |this, window, cx| {
5584 if let Some(mut snippet) = snippet {
5585 snippet.text = new_text.to_string();
5586 this.insert_snippet(&ranges, snippet, window, cx).log_err();
5587 } else {
5588 this.buffer.update(cx, |buffer, cx| {
5589 let auto_indent = match completion.insert_text_mode {
5590 Some(InsertTextMode::AS_IS) => None,
5591 _ => this.autoindent_mode.clone(),
5592 };
5593 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5594 buffer.edit(edits, auto_indent, cx);
5595 });
5596 }
5597 for (buffer, edits) in linked_edits {
5598 buffer.update(cx, |buffer, cx| {
5599 let snapshot = buffer.snapshot();
5600 let edits = edits
5601 .into_iter()
5602 .map(|(range, text)| {
5603 use text::ToPoint as TP;
5604 let end_point = TP::to_point(&range.end, &snapshot);
5605 let start_point = TP::to_point(&range.start, &snapshot);
5606 (start_point..end_point, text)
5607 })
5608 .sorted_by_key(|(range, _)| range.start);
5609 buffer.edit(edits, None, cx);
5610 })
5611 }
5612
5613 this.refresh_inline_completion(true, false, window, cx);
5614 });
5615
5616 let show_new_completions_on_confirm = completion
5617 .confirm
5618 .as_ref()
5619 .map_or(false, |confirm| confirm(intent, window, cx));
5620 if show_new_completions_on_confirm {
5621 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5622 }
5623
5624 let provider = self.completion_provider.as_ref()?;
5625 drop(completion);
5626 let apply_edits = provider.apply_additional_edits_for_completion(
5627 buffer_handle,
5628 completions_menu.completions.clone(),
5629 candidate_id,
5630 true,
5631 cx,
5632 );
5633
5634 let editor_settings = EditorSettings::get_global(cx);
5635 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5636 // After the code completion is finished, users often want to know what signatures are needed.
5637 // so we should automatically call signature_help
5638 self.show_signature_help(&ShowSignatureHelp, window, cx);
5639 }
5640
5641 Some(cx.foreground_executor().spawn(async move {
5642 apply_edits.await?;
5643 Ok(())
5644 }))
5645 }
5646
5647 pub fn toggle_code_actions(
5648 &mut self,
5649 action: &ToggleCodeActions,
5650 window: &mut Window,
5651 cx: &mut Context<Self>,
5652 ) {
5653 let quick_launch = action.quick_launch;
5654 let mut context_menu = self.context_menu.borrow_mut();
5655 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5656 if code_actions.deployed_from == action.deployed_from {
5657 // Toggle if we're selecting the same one
5658 *context_menu = None;
5659 cx.notify();
5660 return;
5661 } else {
5662 // Otherwise, clear it and start a new one
5663 *context_menu = None;
5664 cx.notify();
5665 }
5666 }
5667 drop(context_menu);
5668 let snapshot = self.snapshot(window, cx);
5669 let deployed_from = action.deployed_from.clone();
5670 let mut task = self.code_actions_task.take();
5671 let action = action.clone();
5672 cx.spawn_in(window, async move |editor, cx| {
5673 while let Some(prev_task) = task {
5674 prev_task.await.log_err();
5675 task = editor.update(cx, |this, _| this.code_actions_task.take())?;
5676 }
5677
5678 let spawned_test_task = editor.update_in(cx, |editor, window, cx| {
5679 if editor.focus_handle.is_focused(window) {
5680 let multibuffer_point = match &action.deployed_from {
5681 Some(CodeActionSource::Indicator(row)) => {
5682 DisplayPoint::new(*row, 0).to_point(&snapshot)
5683 }
5684 _ => editor.selections.newest::<Point>(cx).head(),
5685 };
5686 let (buffer, buffer_row) = snapshot
5687 .buffer_snapshot
5688 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
5689 .and_then(|(buffer_snapshot, range)| {
5690 editor
5691 .buffer
5692 .read(cx)
5693 .buffer(buffer_snapshot.remote_id())
5694 .map(|buffer| (buffer, range.start.row))
5695 })?;
5696 let (_, code_actions) = editor
5697 .available_code_actions
5698 .clone()
5699 .and_then(|(location, code_actions)| {
5700 let snapshot = location.buffer.read(cx).snapshot();
5701 let point_range = location.range.to_point(&snapshot);
5702 let point_range = point_range.start.row..=point_range.end.row;
5703 if point_range.contains(&buffer_row) {
5704 Some((location, code_actions))
5705 } else {
5706 None
5707 }
5708 })
5709 .unzip();
5710 let buffer_id = buffer.read(cx).remote_id();
5711 let tasks = editor
5712 .tasks
5713 .get(&(buffer_id, buffer_row))
5714 .map(|t| Arc::new(t.to_owned()));
5715 if tasks.is_none() && code_actions.is_none() {
5716 return None;
5717 }
5718
5719 editor.completion_tasks.clear();
5720 editor.discard_inline_completion(false, cx);
5721 let task_context =
5722 tasks
5723 .as_ref()
5724 .zip(editor.project.clone())
5725 .map(|(tasks, project)| {
5726 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx)
5727 });
5728
5729 Some(cx.spawn_in(window, async move |editor, cx| {
5730 let task_context = match task_context {
5731 Some(task_context) => task_context.await,
5732 None => None,
5733 };
5734 let resolved_tasks =
5735 tasks
5736 .zip(task_context.clone())
5737 .map(|(tasks, task_context)| ResolvedTasks {
5738 templates: tasks.resolve(&task_context).collect(),
5739 position: snapshot.buffer_snapshot.anchor_before(Point::new(
5740 multibuffer_point.row,
5741 tasks.column,
5742 )),
5743 });
5744 let debug_scenarios = editor.update(cx, |editor, cx| {
5745 if cx.has_flag::<DebuggerFeatureFlag>() {
5746 maybe!({
5747 let project = editor.project.as_ref()?;
5748 let dap_store = project.read(cx).dap_store();
5749 let mut scenarios = vec![];
5750 let resolved_tasks = resolved_tasks.as_ref()?;
5751 let buffer = buffer.read(cx);
5752 let language = buffer.language()?;
5753 let file = buffer.file();
5754 let debug_adapter =
5755 language_settings(language.name().into(), file, cx)
5756 .debuggers
5757 .first()
5758 .map(SharedString::from)
5759 .or_else(|| {
5760 language
5761 .config()
5762 .debuggers
5763 .first()
5764 .map(SharedString::from)
5765 })?;
5766
5767 dap_store.update(cx, |dap_store, cx| {
5768 for (_, task) in &resolved_tasks.templates {
5769 if let Some(scenario) = dap_store
5770 .debug_scenario_for_build_task(
5771 task.original_task().clone(),
5772 debug_adapter.clone().into(),
5773 task.display_label().to_owned().into(),
5774 cx,
5775 )
5776 {
5777 scenarios.push(scenario);
5778 }
5779 }
5780 });
5781 Some(scenarios)
5782 })
5783 .unwrap_or_default()
5784 } else {
5785 vec![]
5786 }
5787 })?;
5788 let spawn_straight_away = quick_launch
5789 && resolved_tasks
5790 .as_ref()
5791 .map_or(false, |tasks| tasks.templates.len() == 1)
5792 && code_actions
5793 .as_ref()
5794 .map_or(true, |actions| actions.is_empty())
5795 && debug_scenarios.is_empty();
5796 if let Ok(task) = editor.update_in(cx, |editor, window, cx| {
5797 crate::hover_popover::hide_hover(editor, cx);
5798 *editor.context_menu.borrow_mut() =
5799 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
5800 buffer,
5801 actions: CodeActionContents::new(
5802 resolved_tasks,
5803 code_actions,
5804 debug_scenarios,
5805 task_context.unwrap_or_default(),
5806 ),
5807 selected_item: Default::default(),
5808 scroll_handle: UniformListScrollHandle::default(),
5809 deployed_from,
5810 }));
5811 if spawn_straight_away {
5812 if let Some(task) = editor.confirm_code_action(
5813 &ConfirmCodeAction { item_ix: Some(0) },
5814 window,
5815 cx,
5816 ) {
5817 cx.notify();
5818 return task;
5819 }
5820 }
5821 cx.notify();
5822 Task::ready(Ok(()))
5823 }) {
5824 task.await
5825 } else {
5826 Ok(())
5827 }
5828 }))
5829 } else {
5830 Some(Task::ready(Ok(())))
5831 }
5832 })?;
5833 if let Some(task) = spawned_test_task {
5834 task.await?;
5835 }
5836
5837 anyhow::Ok(())
5838 })
5839 .detach_and_log_err(cx);
5840 }
5841
5842 pub fn confirm_code_action(
5843 &mut self,
5844 action: &ConfirmCodeAction,
5845 window: &mut Window,
5846 cx: &mut Context<Self>,
5847 ) -> Option<Task<Result<()>>> {
5848 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5849
5850 let actions_menu =
5851 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
5852 menu
5853 } else {
5854 return None;
5855 };
5856
5857 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
5858 let action = actions_menu.actions.get(action_ix)?;
5859 let title = action.label();
5860 let buffer = actions_menu.buffer;
5861 let workspace = self.workspace()?;
5862
5863 match action {
5864 CodeActionsItem::Task(task_source_kind, resolved_task) => {
5865 workspace.update(cx, |workspace, cx| {
5866 workspace.schedule_resolved_task(
5867 task_source_kind,
5868 resolved_task,
5869 false,
5870 window,
5871 cx,
5872 );
5873
5874 Some(Task::ready(Ok(())))
5875 })
5876 }
5877 CodeActionsItem::CodeAction {
5878 excerpt_id,
5879 action,
5880 provider,
5881 } => {
5882 let apply_code_action =
5883 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
5884 let workspace = workspace.downgrade();
5885 Some(cx.spawn_in(window, async move |editor, cx| {
5886 let project_transaction = apply_code_action.await?;
5887 Self::open_project_transaction(
5888 &editor,
5889 workspace,
5890 project_transaction,
5891 title,
5892 cx,
5893 )
5894 .await
5895 }))
5896 }
5897 CodeActionsItem::DebugScenario(scenario) => {
5898 let context = actions_menu.actions.context.clone();
5899
5900 workspace.update(cx, |workspace, cx| {
5901 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
5902 workspace.start_debug_session(scenario, context, Some(buffer), window, cx);
5903 });
5904 Some(Task::ready(Ok(())))
5905 }
5906 }
5907 }
5908
5909 pub async fn open_project_transaction(
5910 this: &WeakEntity<Editor>,
5911 workspace: WeakEntity<Workspace>,
5912 transaction: ProjectTransaction,
5913 title: String,
5914 cx: &mut AsyncWindowContext,
5915 ) -> Result<()> {
5916 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
5917 cx.update(|_, cx| {
5918 entries.sort_unstable_by_key(|(buffer, _)| {
5919 buffer.read(cx).file().map(|f| f.path().clone())
5920 });
5921 })?;
5922
5923 // If the project transaction's edits are all contained within this editor, then
5924 // avoid opening a new editor to display them.
5925
5926 if let Some((buffer, transaction)) = entries.first() {
5927 if entries.len() == 1 {
5928 let excerpt = this.update(cx, |editor, cx| {
5929 editor
5930 .buffer()
5931 .read(cx)
5932 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
5933 })?;
5934 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
5935 if excerpted_buffer == *buffer {
5936 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
5937 let excerpt_range = excerpt_range.to_offset(buffer);
5938 buffer
5939 .edited_ranges_for_transaction::<usize>(transaction)
5940 .all(|range| {
5941 excerpt_range.start <= range.start
5942 && excerpt_range.end >= range.end
5943 })
5944 })?;
5945
5946 if all_edits_within_excerpt {
5947 return Ok(());
5948 }
5949 }
5950 }
5951 }
5952 } else {
5953 return Ok(());
5954 }
5955
5956 let mut ranges_to_highlight = Vec::new();
5957 let excerpt_buffer = cx.new(|cx| {
5958 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
5959 for (buffer_handle, transaction) in &entries {
5960 let edited_ranges = buffer_handle
5961 .read(cx)
5962 .edited_ranges_for_transaction::<Point>(transaction)
5963 .collect::<Vec<_>>();
5964 let (ranges, _) = multibuffer.set_excerpts_for_path(
5965 PathKey::for_buffer(buffer_handle, cx),
5966 buffer_handle.clone(),
5967 edited_ranges,
5968 DEFAULT_MULTIBUFFER_CONTEXT,
5969 cx,
5970 );
5971
5972 ranges_to_highlight.extend(ranges);
5973 }
5974 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
5975 multibuffer
5976 })?;
5977
5978 workspace.update_in(cx, |workspace, window, cx| {
5979 let project = workspace.project().clone();
5980 let editor =
5981 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
5982 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
5983 editor.update(cx, |editor, cx| {
5984 editor.highlight_background::<Self>(
5985 &ranges_to_highlight,
5986 |theme| theme.editor_highlighted_line_background,
5987 cx,
5988 );
5989 });
5990 })?;
5991
5992 Ok(())
5993 }
5994
5995 pub fn clear_code_action_providers(&mut self) {
5996 self.code_action_providers.clear();
5997 self.available_code_actions.take();
5998 }
5999
6000 pub fn add_code_action_provider(
6001 &mut self,
6002 provider: Rc<dyn CodeActionProvider>,
6003 window: &mut Window,
6004 cx: &mut Context<Self>,
6005 ) {
6006 if self
6007 .code_action_providers
6008 .iter()
6009 .any(|existing_provider| existing_provider.id() == provider.id())
6010 {
6011 return;
6012 }
6013
6014 self.code_action_providers.push(provider);
6015 self.refresh_code_actions(window, cx);
6016 }
6017
6018 pub fn remove_code_action_provider(
6019 &mut self,
6020 id: Arc<str>,
6021 window: &mut Window,
6022 cx: &mut Context<Self>,
6023 ) {
6024 self.code_action_providers
6025 .retain(|provider| provider.id() != id);
6026 self.refresh_code_actions(window, cx);
6027 }
6028
6029 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6030 !self.code_action_providers.is_empty()
6031 && EditorSettings::get_global(cx).toolbar.code_actions
6032 }
6033
6034 pub fn has_available_code_actions(&self) -> bool {
6035 self.available_code_actions
6036 .as_ref()
6037 .is_some_and(|(_, actions)| !actions.is_empty())
6038 }
6039
6040 fn render_inline_code_actions(
6041 &self,
6042 icon_size: ui::IconSize,
6043 display_row: DisplayRow,
6044 is_active: bool,
6045 cx: &mut Context<Self>,
6046 ) -> AnyElement {
6047 let show_tooltip = !self.context_menu_visible();
6048 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6049 .icon_size(icon_size)
6050 .shape(ui::IconButtonShape::Square)
6051 .style(ButtonStyle::Transparent)
6052 .icon_color(ui::Color::Hidden)
6053 .toggle_state(is_active)
6054 .when(show_tooltip, |this| {
6055 this.tooltip({
6056 let focus_handle = self.focus_handle.clone();
6057 move |window, cx| {
6058 Tooltip::for_action_in(
6059 "Toggle Code Actions",
6060 &ToggleCodeActions {
6061 deployed_from: None,
6062 quick_launch: false,
6063 },
6064 &focus_handle,
6065 window,
6066 cx,
6067 )
6068 }
6069 })
6070 })
6071 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6072 window.focus(&editor.focus_handle(cx));
6073 editor.toggle_code_actions(
6074 &crate::actions::ToggleCodeActions {
6075 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6076 display_row,
6077 )),
6078 quick_launch: false,
6079 },
6080 window,
6081 cx,
6082 );
6083 }))
6084 .into_any_element()
6085 }
6086
6087 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6088 &self.context_menu
6089 }
6090
6091 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
6092 let newest_selection = self.selections.newest_anchor().clone();
6093 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
6094 let buffer = self.buffer.read(cx);
6095 if newest_selection.head().diff_base_anchor.is_some() {
6096 return None;
6097 }
6098 let (start_buffer, start) =
6099 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6100 let (end_buffer, end) =
6101 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6102 if start_buffer != end_buffer {
6103 return None;
6104 }
6105
6106 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6107 cx.background_executor()
6108 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6109 .await;
6110
6111 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6112 let providers = this.code_action_providers.clone();
6113 let tasks = this
6114 .code_action_providers
6115 .iter()
6116 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6117 .collect::<Vec<_>>();
6118 (providers, tasks)
6119 })?;
6120
6121 let mut actions = Vec::new();
6122 for (provider, provider_actions) in
6123 providers.into_iter().zip(future::join_all(tasks).await)
6124 {
6125 if let Some(provider_actions) = provider_actions.log_err() {
6126 actions.extend(provider_actions.into_iter().map(|action| {
6127 AvailableCodeAction {
6128 excerpt_id: newest_selection.start.excerpt_id,
6129 action,
6130 provider: provider.clone(),
6131 }
6132 }));
6133 }
6134 }
6135
6136 this.update(cx, |this, cx| {
6137 this.available_code_actions = if actions.is_empty() {
6138 None
6139 } else {
6140 Some((
6141 Location {
6142 buffer: start_buffer,
6143 range: start..end,
6144 },
6145 actions.into(),
6146 ))
6147 };
6148 cx.notify();
6149 })
6150 }));
6151 None
6152 }
6153
6154 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6155 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6156 self.show_git_blame_inline = false;
6157
6158 self.show_git_blame_inline_delay_task =
6159 Some(cx.spawn_in(window, async move |this, cx| {
6160 cx.background_executor().timer(delay).await;
6161
6162 this.update(cx, |this, cx| {
6163 this.show_git_blame_inline = true;
6164 cx.notify();
6165 })
6166 .log_err();
6167 }));
6168 }
6169 }
6170
6171 fn show_blame_popover(
6172 &mut self,
6173 blame_entry: &BlameEntry,
6174 position: gpui::Point<Pixels>,
6175 cx: &mut Context<Self>,
6176 ) {
6177 if let Some(state) = &mut self.inline_blame_popover {
6178 state.hide_task.take();
6179 cx.notify();
6180 } else {
6181 let delay = EditorSettings::get_global(cx).hover_popover_delay;
6182 let show_task = cx.spawn(async move |editor, cx| {
6183 cx.background_executor()
6184 .timer(std::time::Duration::from_millis(delay))
6185 .await;
6186 editor
6187 .update(cx, |editor, cx| {
6188 if let Some(state) = &mut editor.inline_blame_popover {
6189 state.show_task = None;
6190 cx.notify();
6191 }
6192 })
6193 .ok();
6194 });
6195 let Some(blame) = self.blame.as_ref() else {
6196 return;
6197 };
6198 let blame = blame.read(cx);
6199 let details = blame.details_for_entry(&blame_entry);
6200 let markdown = cx.new(|cx| {
6201 Markdown::new(
6202 details
6203 .as_ref()
6204 .map(|message| message.message.clone())
6205 .unwrap_or_default(),
6206 None,
6207 None,
6208 cx,
6209 )
6210 });
6211 self.inline_blame_popover = Some(InlineBlamePopover {
6212 position,
6213 show_task: Some(show_task),
6214 hide_task: None,
6215 popover_bounds: None,
6216 popover_state: InlineBlamePopoverState {
6217 scroll_handle: ScrollHandle::new(),
6218 commit_message: details,
6219 markdown,
6220 },
6221 });
6222 }
6223 }
6224
6225 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6226 if let Some(state) = &mut self.inline_blame_popover {
6227 if state.show_task.is_some() {
6228 self.inline_blame_popover.take();
6229 cx.notify();
6230 } else {
6231 let hide_task = cx.spawn(async move |editor, cx| {
6232 cx.background_executor()
6233 .timer(std::time::Duration::from_millis(100))
6234 .await;
6235 editor
6236 .update(cx, |editor, cx| {
6237 editor.inline_blame_popover.take();
6238 cx.notify();
6239 })
6240 .ok();
6241 });
6242 state.hide_task = Some(hide_task);
6243 }
6244 }
6245 }
6246
6247 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6248 if self.pending_rename.is_some() {
6249 return None;
6250 }
6251
6252 let provider = self.semantics_provider.clone()?;
6253 let buffer = self.buffer.read(cx);
6254 let newest_selection = self.selections.newest_anchor().clone();
6255 let cursor_position = newest_selection.head();
6256 let (cursor_buffer, cursor_buffer_position) =
6257 buffer.text_anchor_for_position(cursor_position, cx)?;
6258 let (tail_buffer, tail_buffer_position) =
6259 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6260 if cursor_buffer != tail_buffer {
6261 return None;
6262 }
6263
6264 let snapshot = cursor_buffer.read(cx).snapshot();
6265 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position);
6266 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position);
6267 if start_word_range != end_word_range {
6268 self.document_highlights_task.take();
6269 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6270 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6271 return None;
6272 }
6273
6274 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6275 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6276 cx.background_executor()
6277 .timer(Duration::from_millis(debounce))
6278 .await;
6279
6280 let highlights = if let Some(highlights) = cx
6281 .update(|cx| {
6282 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6283 })
6284 .ok()
6285 .flatten()
6286 {
6287 highlights.await.log_err()
6288 } else {
6289 None
6290 };
6291
6292 if let Some(highlights) = highlights {
6293 this.update(cx, |this, cx| {
6294 if this.pending_rename.is_some() {
6295 return;
6296 }
6297
6298 let buffer_id = cursor_position.buffer_id;
6299 let buffer = this.buffer.read(cx);
6300 if !buffer
6301 .text_anchor_for_position(cursor_position, cx)
6302 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
6303 {
6304 return;
6305 }
6306
6307 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6308 let mut write_ranges = Vec::new();
6309 let mut read_ranges = Vec::new();
6310 for highlight in highlights {
6311 for (excerpt_id, excerpt_range) in
6312 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
6313 {
6314 let start = highlight
6315 .range
6316 .start
6317 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6318 let end = highlight
6319 .range
6320 .end
6321 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6322 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6323 continue;
6324 }
6325
6326 let range = Anchor {
6327 buffer_id,
6328 excerpt_id,
6329 text_anchor: start,
6330 diff_base_anchor: None,
6331 }..Anchor {
6332 buffer_id,
6333 excerpt_id,
6334 text_anchor: end,
6335 diff_base_anchor: None,
6336 };
6337 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6338 write_ranges.push(range);
6339 } else {
6340 read_ranges.push(range);
6341 }
6342 }
6343 }
6344
6345 this.highlight_background::<DocumentHighlightRead>(
6346 &read_ranges,
6347 |theme| theme.editor_document_highlight_read_background,
6348 cx,
6349 );
6350 this.highlight_background::<DocumentHighlightWrite>(
6351 &write_ranges,
6352 |theme| theme.editor_document_highlight_write_background,
6353 cx,
6354 );
6355 cx.notify();
6356 })
6357 .log_err();
6358 }
6359 }));
6360 None
6361 }
6362
6363 fn prepare_highlight_query_from_selection(
6364 &mut self,
6365 cx: &mut Context<Editor>,
6366 ) -> Option<(String, Range<Anchor>)> {
6367 if matches!(self.mode, EditorMode::SingleLine { .. }) {
6368 return None;
6369 }
6370 if !EditorSettings::get_global(cx).selection_highlight {
6371 return None;
6372 }
6373 if self.selections.count() != 1 || self.selections.line_mode {
6374 return None;
6375 }
6376 let selection = self.selections.newest::<Point>(cx);
6377 if selection.is_empty() || selection.start.row != selection.end.row {
6378 return None;
6379 }
6380 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6381 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6382 let query = multi_buffer_snapshot
6383 .text_for_range(selection_anchor_range.clone())
6384 .collect::<String>();
6385 if query.trim().is_empty() {
6386 return None;
6387 }
6388 Some((query, selection_anchor_range))
6389 }
6390
6391 fn update_selection_occurrence_highlights(
6392 &mut self,
6393 query_text: String,
6394 query_range: Range<Anchor>,
6395 multi_buffer_range_to_query: Range<Point>,
6396 use_debounce: bool,
6397 window: &mut Window,
6398 cx: &mut Context<Editor>,
6399 ) -> Task<()> {
6400 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6401 cx.spawn_in(window, async move |editor, cx| {
6402 if use_debounce {
6403 cx.background_executor()
6404 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6405 .await;
6406 }
6407 let match_task = cx.background_spawn(async move {
6408 let buffer_ranges = multi_buffer_snapshot
6409 .range_to_buffer_ranges(multi_buffer_range_to_query)
6410 .into_iter()
6411 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6412 let mut match_ranges = Vec::new();
6413 let Ok(regex) = project::search::SearchQuery::text(
6414 query_text.clone(),
6415 false,
6416 false,
6417 false,
6418 Default::default(),
6419 Default::default(),
6420 false,
6421 None,
6422 ) else {
6423 return Vec::default();
6424 };
6425 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6426 match_ranges.extend(
6427 regex
6428 .search(&buffer_snapshot, Some(search_range.clone()))
6429 .await
6430 .into_iter()
6431 .filter_map(|match_range| {
6432 let match_start = buffer_snapshot
6433 .anchor_after(search_range.start + match_range.start);
6434 let match_end = buffer_snapshot
6435 .anchor_before(search_range.start + match_range.end);
6436 let match_anchor_range = Anchor::range_in_buffer(
6437 excerpt_id,
6438 buffer_snapshot.remote_id(),
6439 match_start..match_end,
6440 );
6441 (match_anchor_range != query_range).then_some(match_anchor_range)
6442 }),
6443 );
6444 }
6445 match_ranges
6446 });
6447 let match_ranges = match_task.await;
6448 editor
6449 .update_in(cx, |editor, _, cx| {
6450 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6451 if !match_ranges.is_empty() {
6452 editor.highlight_background::<SelectedTextHighlight>(
6453 &match_ranges,
6454 |theme| theme.editor_document_highlight_bracket_background,
6455 cx,
6456 )
6457 }
6458 })
6459 .log_err();
6460 })
6461 }
6462
6463 fn refresh_selected_text_highlights(
6464 &mut self,
6465 on_buffer_edit: bool,
6466 window: &mut Window,
6467 cx: &mut Context<Editor>,
6468 ) {
6469 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
6470 else {
6471 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6472 self.quick_selection_highlight_task.take();
6473 self.debounced_selection_highlight_task.take();
6474 return;
6475 };
6476 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6477 if on_buffer_edit
6478 || self
6479 .quick_selection_highlight_task
6480 .as_ref()
6481 .map_or(true, |(prev_anchor_range, _)| {
6482 prev_anchor_range != &query_range
6483 })
6484 {
6485 let multi_buffer_visible_start = self
6486 .scroll_manager
6487 .anchor()
6488 .anchor
6489 .to_point(&multi_buffer_snapshot);
6490 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
6491 multi_buffer_visible_start
6492 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
6493 Bias::Left,
6494 );
6495 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
6496 self.quick_selection_highlight_task = Some((
6497 query_range.clone(),
6498 self.update_selection_occurrence_highlights(
6499 query_text.clone(),
6500 query_range.clone(),
6501 multi_buffer_visible_range,
6502 false,
6503 window,
6504 cx,
6505 ),
6506 ));
6507 }
6508 if on_buffer_edit
6509 || self
6510 .debounced_selection_highlight_task
6511 .as_ref()
6512 .map_or(true, |(prev_anchor_range, _)| {
6513 prev_anchor_range != &query_range
6514 })
6515 {
6516 let multi_buffer_start = multi_buffer_snapshot
6517 .anchor_before(0)
6518 .to_point(&multi_buffer_snapshot);
6519 let multi_buffer_end = multi_buffer_snapshot
6520 .anchor_after(multi_buffer_snapshot.len())
6521 .to_point(&multi_buffer_snapshot);
6522 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
6523 self.debounced_selection_highlight_task = Some((
6524 query_range.clone(),
6525 self.update_selection_occurrence_highlights(
6526 query_text,
6527 query_range,
6528 multi_buffer_full_range,
6529 true,
6530 window,
6531 cx,
6532 ),
6533 ));
6534 }
6535 }
6536
6537 pub fn refresh_inline_completion(
6538 &mut self,
6539 debounce: bool,
6540 user_requested: bool,
6541 window: &mut Window,
6542 cx: &mut Context<Self>,
6543 ) -> Option<()> {
6544 let provider = self.edit_prediction_provider()?;
6545 let cursor = self.selections.newest_anchor().head();
6546 let (buffer, cursor_buffer_position) =
6547 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6548
6549 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
6550 self.discard_inline_completion(false, cx);
6551 return None;
6552 }
6553
6554 if !user_requested
6555 && (!self.should_show_edit_predictions()
6556 || !self.is_focused(window)
6557 || buffer.read(cx).is_empty())
6558 {
6559 self.discard_inline_completion(false, cx);
6560 return None;
6561 }
6562
6563 self.update_visible_inline_completion(window, cx);
6564 provider.refresh(
6565 self.project.clone(),
6566 buffer,
6567 cursor_buffer_position,
6568 debounce,
6569 cx,
6570 );
6571 Some(())
6572 }
6573
6574 fn show_edit_predictions_in_menu(&self) -> bool {
6575 match self.edit_prediction_settings {
6576 EditPredictionSettings::Disabled => false,
6577 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
6578 }
6579 }
6580
6581 pub fn edit_predictions_enabled(&self) -> bool {
6582 match self.edit_prediction_settings {
6583 EditPredictionSettings::Disabled => false,
6584 EditPredictionSettings::Enabled { .. } => true,
6585 }
6586 }
6587
6588 fn edit_prediction_requires_modifier(&self) -> bool {
6589 match self.edit_prediction_settings {
6590 EditPredictionSettings::Disabled => false,
6591 EditPredictionSettings::Enabled {
6592 preview_requires_modifier,
6593 ..
6594 } => preview_requires_modifier,
6595 }
6596 }
6597
6598 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
6599 if self.edit_prediction_provider.is_none() {
6600 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6601 } else {
6602 let selection = self.selections.newest_anchor();
6603 let cursor = selection.head();
6604
6605 if let Some((buffer, cursor_buffer_position)) =
6606 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6607 {
6608 self.edit_prediction_settings =
6609 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6610 }
6611 }
6612 }
6613
6614 fn edit_prediction_settings_at_position(
6615 &self,
6616 buffer: &Entity<Buffer>,
6617 buffer_position: language::Anchor,
6618 cx: &App,
6619 ) -> EditPredictionSettings {
6620 if !self.mode.is_full()
6621 || !self.show_inline_completions_override.unwrap_or(true)
6622 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
6623 {
6624 return EditPredictionSettings::Disabled;
6625 }
6626
6627 let buffer = buffer.read(cx);
6628
6629 let file = buffer.file();
6630
6631 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
6632 return EditPredictionSettings::Disabled;
6633 };
6634
6635 let by_provider = matches!(
6636 self.menu_inline_completions_policy,
6637 MenuInlineCompletionsPolicy::ByProvider
6638 );
6639
6640 let show_in_menu = by_provider
6641 && self
6642 .edit_prediction_provider
6643 .as_ref()
6644 .map_or(false, |provider| {
6645 provider.provider.show_completions_in_menu()
6646 });
6647
6648 let preview_requires_modifier =
6649 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
6650
6651 EditPredictionSettings::Enabled {
6652 show_in_menu,
6653 preview_requires_modifier,
6654 }
6655 }
6656
6657 fn should_show_edit_predictions(&self) -> bool {
6658 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
6659 }
6660
6661 pub fn edit_prediction_preview_is_active(&self) -> bool {
6662 matches!(
6663 self.edit_prediction_preview,
6664 EditPredictionPreview::Active { .. }
6665 )
6666 }
6667
6668 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
6669 let cursor = self.selections.newest_anchor().head();
6670 if let Some((buffer, cursor_position)) =
6671 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6672 {
6673 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
6674 } else {
6675 false
6676 }
6677 }
6678
6679 pub fn supports_minimap(&self, cx: &App) -> bool {
6680 !self.minimap_visibility.disabled() && self.is_singleton(cx)
6681 }
6682
6683 fn edit_predictions_enabled_in_buffer(
6684 &self,
6685 buffer: &Entity<Buffer>,
6686 buffer_position: language::Anchor,
6687 cx: &App,
6688 ) -> bool {
6689 maybe!({
6690 if self.read_only(cx) {
6691 return Some(false);
6692 }
6693 let provider = self.edit_prediction_provider()?;
6694 if !provider.is_enabled(&buffer, buffer_position, cx) {
6695 return Some(false);
6696 }
6697 let buffer = buffer.read(cx);
6698 let Some(file) = buffer.file() else {
6699 return Some(true);
6700 };
6701 let settings = all_language_settings(Some(file), cx);
6702 Some(settings.edit_predictions_enabled_for_file(file, cx))
6703 })
6704 .unwrap_or(false)
6705 }
6706
6707 fn cycle_inline_completion(
6708 &mut self,
6709 direction: Direction,
6710 window: &mut Window,
6711 cx: &mut Context<Self>,
6712 ) -> Option<()> {
6713 let provider = self.edit_prediction_provider()?;
6714 let cursor = self.selections.newest_anchor().head();
6715 let (buffer, cursor_buffer_position) =
6716 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6717 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
6718 return None;
6719 }
6720
6721 provider.cycle(buffer, cursor_buffer_position, direction, cx);
6722 self.update_visible_inline_completion(window, cx);
6723
6724 Some(())
6725 }
6726
6727 pub fn show_inline_completion(
6728 &mut self,
6729 _: &ShowEditPrediction,
6730 window: &mut Window,
6731 cx: &mut Context<Self>,
6732 ) {
6733 if !self.has_active_inline_completion() {
6734 self.refresh_inline_completion(false, true, window, cx);
6735 return;
6736 }
6737
6738 self.update_visible_inline_completion(window, cx);
6739 }
6740
6741 pub fn display_cursor_names(
6742 &mut self,
6743 _: &DisplayCursorNames,
6744 window: &mut Window,
6745 cx: &mut Context<Self>,
6746 ) {
6747 self.show_cursor_names(window, cx);
6748 }
6749
6750 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6751 self.show_cursor_names = true;
6752 cx.notify();
6753 cx.spawn_in(window, async move |this, cx| {
6754 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
6755 this.update(cx, |this, cx| {
6756 this.show_cursor_names = false;
6757 cx.notify()
6758 })
6759 .ok()
6760 })
6761 .detach();
6762 }
6763
6764 pub fn next_edit_prediction(
6765 &mut self,
6766 _: &NextEditPrediction,
6767 window: &mut Window,
6768 cx: &mut Context<Self>,
6769 ) {
6770 if self.has_active_inline_completion() {
6771 self.cycle_inline_completion(Direction::Next, window, cx);
6772 } else {
6773 let is_copilot_disabled = self
6774 .refresh_inline_completion(false, true, window, cx)
6775 .is_none();
6776 if is_copilot_disabled {
6777 cx.propagate();
6778 }
6779 }
6780 }
6781
6782 pub fn previous_edit_prediction(
6783 &mut self,
6784 _: &PreviousEditPrediction,
6785 window: &mut Window,
6786 cx: &mut Context<Self>,
6787 ) {
6788 if self.has_active_inline_completion() {
6789 self.cycle_inline_completion(Direction::Prev, window, cx);
6790 } else {
6791 let is_copilot_disabled = self
6792 .refresh_inline_completion(false, true, window, cx)
6793 .is_none();
6794 if is_copilot_disabled {
6795 cx.propagate();
6796 }
6797 }
6798 }
6799
6800 pub fn accept_edit_prediction(
6801 &mut self,
6802 _: &AcceptEditPrediction,
6803 window: &mut Window,
6804 cx: &mut Context<Self>,
6805 ) {
6806 if self.show_edit_predictions_in_menu() {
6807 self.hide_context_menu(window, cx);
6808 }
6809
6810 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6811 return;
6812 };
6813
6814 self.report_inline_completion_event(
6815 active_inline_completion.completion_id.clone(),
6816 true,
6817 cx,
6818 );
6819
6820 match &active_inline_completion.completion {
6821 InlineCompletion::Move { target, .. } => {
6822 let target = *target;
6823
6824 if let Some(position_map) = &self.last_position_map {
6825 if position_map
6826 .visible_row_range
6827 .contains(&target.to_display_point(&position_map.snapshot).row())
6828 || !self.edit_prediction_requires_modifier()
6829 {
6830 self.unfold_ranges(&[target..target], true, false, cx);
6831 // Note that this is also done in vim's handler of the Tab action.
6832 self.change_selections(
6833 Some(Autoscroll::newest()),
6834 window,
6835 cx,
6836 |selections| {
6837 selections.select_anchor_ranges([target..target]);
6838 },
6839 );
6840 self.clear_row_highlights::<EditPredictionPreview>();
6841
6842 self.edit_prediction_preview
6843 .set_previous_scroll_position(None);
6844 } else {
6845 self.edit_prediction_preview
6846 .set_previous_scroll_position(Some(
6847 position_map.snapshot.scroll_anchor,
6848 ));
6849
6850 self.highlight_rows::<EditPredictionPreview>(
6851 target..target,
6852 cx.theme().colors().editor_highlighted_line_background,
6853 RowHighlightOptions {
6854 autoscroll: true,
6855 ..Default::default()
6856 },
6857 cx,
6858 );
6859 self.request_autoscroll(Autoscroll::fit(), cx);
6860 }
6861 }
6862 }
6863 InlineCompletion::Edit { edits, .. } => {
6864 if let Some(provider) = self.edit_prediction_provider() {
6865 provider.accept(cx);
6866 }
6867
6868 // Store the transaction ID and selections before applying the edit
6869 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
6870
6871 let snapshot = self.buffer.read(cx).snapshot(cx);
6872 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
6873
6874 self.buffer.update(cx, |buffer, cx| {
6875 buffer.edit(edits.iter().cloned(), None, cx)
6876 });
6877
6878 self.change_selections(None, window, cx, |s| {
6879 s.select_anchor_ranges([last_edit_end..last_edit_end]);
6880 });
6881
6882 let selections = self.selections.disjoint_anchors();
6883 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
6884 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
6885 if has_new_transaction {
6886 self.selection_history
6887 .insert_transaction(transaction_id_now, selections);
6888 }
6889 }
6890
6891 self.update_visible_inline_completion(window, cx);
6892 if self.active_inline_completion.is_none() {
6893 self.refresh_inline_completion(true, true, window, cx);
6894 }
6895
6896 cx.notify();
6897 }
6898 }
6899
6900 self.edit_prediction_requires_modifier_in_indent_conflict = false;
6901 }
6902
6903 pub fn accept_partial_inline_completion(
6904 &mut self,
6905 _: &AcceptPartialEditPrediction,
6906 window: &mut Window,
6907 cx: &mut Context<Self>,
6908 ) {
6909 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6910 return;
6911 };
6912 if self.selections.count() != 1 {
6913 return;
6914 }
6915
6916 self.report_inline_completion_event(
6917 active_inline_completion.completion_id.clone(),
6918 true,
6919 cx,
6920 );
6921
6922 match &active_inline_completion.completion {
6923 InlineCompletion::Move { target, .. } => {
6924 let target = *target;
6925 self.change_selections(Some(Autoscroll::newest()), window, cx, |selections| {
6926 selections.select_anchor_ranges([target..target]);
6927 });
6928 }
6929 InlineCompletion::Edit { edits, .. } => {
6930 // Find an insertion that starts at the cursor position.
6931 let snapshot = self.buffer.read(cx).snapshot(cx);
6932 let cursor_offset = self.selections.newest::<usize>(cx).head();
6933 let insertion = edits.iter().find_map(|(range, text)| {
6934 let range = range.to_offset(&snapshot);
6935 if range.is_empty() && range.start == cursor_offset {
6936 Some(text)
6937 } else {
6938 None
6939 }
6940 });
6941
6942 if let Some(text) = insertion {
6943 let mut partial_completion = text
6944 .chars()
6945 .by_ref()
6946 .take_while(|c| c.is_alphabetic())
6947 .collect::<String>();
6948 if partial_completion.is_empty() {
6949 partial_completion = text
6950 .chars()
6951 .by_ref()
6952 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
6953 .collect::<String>();
6954 }
6955
6956 cx.emit(EditorEvent::InputHandled {
6957 utf16_range_to_replace: None,
6958 text: partial_completion.clone().into(),
6959 });
6960
6961 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
6962
6963 self.refresh_inline_completion(true, true, window, cx);
6964 cx.notify();
6965 } else {
6966 self.accept_edit_prediction(&Default::default(), window, cx);
6967 }
6968 }
6969 }
6970 }
6971
6972 fn discard_inline_completion(
6973 &mut self,
6974 should_report_inline_completion_event: bool,
6975 cx: &mut Context<Self>,
6976 ) -> bool {
6977 if should_report_inline_completion_event {
6978 let completion_id = self
6979 .active_inline_completion
6980 .as_ref()
6981 .and_then(|active_completion| active_completion.completion_id.clone());
6982
6983 self.report_inline_completion_event(completion_id, false, cx);
6984 }
6985
6986 if let Some(provider) = self.edit_prediction_provider() {
6987 provider.discard(cx);
6988 }
6989
6990 self.take_active_inline_completion(cx)
6991 }
6992
6993 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
6994 let Some(provider) = self.edit_prediction_provider() else {
6995 return;
6996 };
6997
6998 let Some((_, buffer, _)) = self
6999 .buffer
7000 .read(cx)
7001 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7002 else {
7003 return;
7004 };
7005
7006 let extension = buffer
7007 .read(cx)
7008 .file()
7009 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
7010
7011 let event_type = match accepted {
7012 true => "Edit Prediction Accepted",
7013 false => "Edit Prediction Discarded",
7014 };
7015 telemetry::event!(
7016 event_type,
7017 provider = provider.name(),
7018 prediction_id = id,
7019 suggestion_accepted = accepted,
7020 file_extension = extension,
7021 );
7022 }
7023
7024 pub fn has_active_inline_completion(&self) -> bool {
7025 self.active_inline_completion.is_some()
7026 }
7027
7028 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
7029 let Some(active_inline_completion) = self.active_inline_completion.take() else {
7030 return false;
7031 };
7032
7033 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
7034 self.clear_highlights::<InlineCompletionHighlight>(cx);
7035 self.stale_inline_completion_in_menu = Some(active_inline_completion);
7036 true
7037 }
7038
7039 /// Returns true when we're displaying the edit prediction popover below the cursor
7040 /// like we are not previewing and the LSP autocomplete menu is visible
7041 /// or we are in `when_holding_modifier` mode.
7042 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7043 if self.edit_prediction_preview_is_active()
7044 || !self.show_edit_predictions_in_menu()
7045 || !self.edit_predictions_enabled()
7046 {
7047 return false;
7048 }
7049
7050 if self.has_visible_completions_menu() {
7051 return true;
7052 }
7053
7054 has_completion && self.edit_prediction_requires_modifier()
7055 }
7056
7057 fn handle_modifiers_changed(
7058 &mut self,
7059 modifiers: Modifiers,
7060 position_map: &PositionMap,
7061 window: &mut Window,
7062 cx: &mut Context<Self>,
7063 ) {
7064 if self.show_edit_predictions_in_menu() {
7065 self.update_edit_prediction_preview(&modifiers, window, cx);
7066 }
7067
7068 self.update_selection_mode(&modifiers, position_map, window, cx);
7069
7070 let mouse_position = window.mouse_position();
7071 if !position_map.text_hitbox.is_hovered(window) {
7072 return;
7073 }
7074
7075 self.update_hovered_link(
7076 position_map.point_for_position(mouse_position),
7077 &position_map.snapshot,
7078 modifiers,
7079 window,
7080 cx,
7081 )
7082 }
7083
7084 fn update_selection_mode(
7085 &mut self,
7086 modifiers: &Modifiers,
7087 position_map: &PositionMap,
7088 window: &mut Window,
7089 cx: &mut Context<Self>,
7090 ) {
7091 if modifiers != &COLUMNAR_SELECTION_MODIFIERS || self.selections.pending.is_none() {
7092 return;
7093 }
7094
7095 let mouse_position = window.mouse_position();
7096 let point_for_position = position_map.point_for_position(mouse_position);
7097 let position = point_for_position.previous_valid;
7098
7099 self.select(
7100 SelectPhase::BeginColumnar {
7101 position,
7102 reset: false,
7103 goal_column: point_for_position.exact_unclipped.column(),
7104 },
7105 window,
7106 cx,
7107 );
7108 }
7109
7110 fn update_edit_prediction_preview(
7111 &mut self,
7112 modifiers: &Modifiers,
7113 window: &mut Window,
7114 cx: &mut Context<Self>,
7115 ) {
7116 let accept_keybind = self.accept_edit_prediction_keybind(window, cx);
7117 let Some(accept_keystroke) = accept_keybind.keystroke() else {
7118 return;
7119 };
7120
7121 if &accept_keystroke.modifiers == modifiers && accept_keystroke.modifiers.modified() {
7122 if matches!(
7123 self.edit_prediction_preview,
7124 EditPredictionPreview::Inactive { .. }
7125 ) {
7126 self.edit_prediction_preview = EditPredictionPreview::Active {
7127 previous_scroll_position: None,
7128 since: Instant::now(),
7129 };
7130
7131 self.update_visible_inline_completion(window, cx);
7132 cx.notify();
7133 }
7134 } else if let EditPredictionPreview::Active {
7135 previous_scroll_position,
7136 since,
7137 } = self.edit_prediction_preview
7138 {
7139 if let (Some(previous_scroll_position), Some(position_map)) =
7140 (previous_scroll_position, self.last_position_map.as_ref())
7141 {
7142 self.set_scroll_position(
7143 previous_scroll_position
7144 .scroll_position(&position_map.snapshot.display_snapshot),
7145 window,
7146 cx,
7147 );
7148 }
7149
7150 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7151 released_too_fast: since.elapsed() < Duration::from_millis(200),
7152 };
7153 self.clear_row_highlights::<EditPredictionPreview>();
7154 self.update_visible_inline_completion(window, cx);
7155 cx.notify();
7156 }
7157 }
7158
7159 fn update_visible_inline_completion(
7160 &mut self,
7161 _window: &mut Window,
7162 cx: &mut Context<Self>,
7163 ) -> Option<()> {
7164 let selection = self.selections.newest_anchor();
7165 let cursor = selection.head();
7166 let multibuffer = self.buffer.read(cx).snapshot(cx);
7167 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7168 let excerpt_id = cursor.excerpt_id;
7169
7170 let show_in_menu = self.show_edit_predictions_in_menu();
7171 let completions_menu_has_precedence = !show_in_menu
7172 && (self.context_menu.borrow().is_some()
7173 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
7174
7175 if completions_menu_has_precedence
7176 || !offset_selection.is_empty()
7177 || self
7178 .active_inline_completion
7179 .as_ref()
7180 .map_or(false, |completion| {
7181 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
7182 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7183 !invalidation_range.contains(&offset_selection.head())
7184 })
7185 {
7186 self.discard_inline_completion(false, cx);
7187 return None;
7188 }
7189
7190 self.take_active_inline_completion(cx);
7191 let Some(provider) = self.edit_prediction_provider() else {
7192 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7193 return None;
7194 };
7195
7196 let (buffer, cursor_buffer_position) =
7197 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7198
7199 self.edit_prediction_settings =
7200 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7201
7202 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7203
7204 if self.edit_prediction_indent_conflict {
7205 let cursor_point = cursor.to_point(&multibuffer);
7206
7207 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7208
7209 if let Some((_, indent)) = indents.iter().next() {
7210 if indent.len == cursor_point.column {
7211 self.edit_prediction_indent_conflict = false;
7212 }
7213 }
7214 }
7215
7216 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7217 let edits = inline_completion
7218 .edits
7219 .into_iter()
7220 .flat_map(|(range, new_text)| {
7221 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7222 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7223 Some((start..end, new_text))
7224 })
7225 .collect::<Vec<_>>();
7226 if edits.is_empty() {
7227 return None;
7228 }
7229
7230 let first_edit_start = edits.first().unwrap().0.start;
7231 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7232 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7233
7234 let last_edit_end = edits.last().unwrap().0.end;
7235 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7236 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7237
7238 let cursor_row = cursor.to_point(&multibuffer).row;
7239
7240 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7241
7242 let mut inlay_ids = Vec::new();
7243 let invalidation_row_range;
7244 let move_invalidation_row_range = if cursor_row < edit_start_row {
7245 Some(cursor_row..edit_end_row)
7246 } else if cursor_row > edit_end_row {
7247 Some(edit_start_row..cursor_row)
7248 } else {
7249 None
7250 };
7251 let is_move =
7252 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
7253 let completion = if is_move {
7254 invalidation_row_range =
7255 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7256 let target = first_edit_start;
7257 InlineCompletion::Move { target, snapshot }
7258 } else {
7259 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7260 && !self.inline_completions_hidden_for_vim_mode;
7261
7262 if show_completions_in_buffer {
7263 if edits
7264 .iter()
7265 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7266 {
7267 let mut inlays = Vec::new();
7268 for (range, new_text) in &edits {
7269 let inlay = Inlay::inline_completion(
7270 post_inc(&mut self.next_inlay_id),
7271 range.start,
7272 new_text.as_str(),
7273 );
7274 inlay_ids.push(inlay.id);
7275 inlays.push(inlay);
7276 }
7277
7278 self.splice_inlays(&[], inlays, cx);
7279 } else {
7280 let background_color = cx.theme().status().deleted_background;
7281 self.highlight_text::<InlineCompletionHighlight>(
7282 edits.iter().map(|(range, _)| range.clone()).collect(),
7283 HighlightStyle {
7284 background_color: Some(background_color),
7285 ..Default::default()
7286 },
7287 cx,
7288 );
7289 }
7290 }
7291
7292 invalidation_row_range = edit_start_row..edit_end_row;
7293
7294 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7295 if provider.show_tab_accept_marker() {
7296 EditDisplayMode::TabAccept
7297 } else {
7298 EditDisplayMode::Inline
7299 }
7300 } else {
7301 EditDisplayMode::DiffPopover
7302 };
7303
7304 InlineCompletion::Edit {
7305 edits,
7306 edit_preview: inline_completion.edit_preview,
7307 display_mode,
7308 snapshot,
7309 }
7310 };
7311
7312 let invalidation_range = multibuffer
7313 .anchor_before(Point::new(invalidation_row_range.start, 0))
7314 ..multibuffer.anchor_after(Point::new(
7315 invalidation_row_range.end,
7316 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7317 ));
7318
7319 self.stale_inline_completion_in_menu = None;
7320 self.active_inline_completion = Some(InlineCompletionState {
7321 inlay_ids,
7322 completion,
7323 completion_id: inline_completion.id,
7324 invalidation_range,
7325 });
7326
7327 cx.notify();
7328
7329 Some(())
7330 }
7331
7332 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
7333 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7334 }
7335
7336 fn clear_tasks(&mut self) {
7337 self.tasks.clear()
7338 }
7339
7340 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7341 if self.tasks.insert(key, value).is_some() {
7342 // This case should hopefully be rare, but just in case...
7343 log::error!(
7344 "multiple different run targets found on a single line, only the last target will be rendered"
7345 )
7346 }
7347 }
7348
7349 /// Get all display points of breakpoints that will be rendered within editor
7350 ///
7351 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7352 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7353 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7354 fn active_breakpoints(
7355 &self,
7356 range: Range<DisplayRow>,
7357 window: &mut Window,
7358 cx: &mut Context<Self>,
7359 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7360 let mut breakpoint_display_points = HashMap::default();
7361
7362 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7363 return breakpoint_display_points;
7364 };
7365
7366 let snapshot = self.snapshot(window, cx);
7367
7368 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
7369 let Some(project) = self.project.as_ref() else {
7370 return breakpoint_display_points;
7371 };
7372
7373 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7374 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7375
7376 for (buffer_snapshot, range, excerpt_id) in
7377 multi_buffer_snapshot.range_to_buffer_ranges(range)
7378 {
7379 let Some(buffer) = project
7380 .read(cx)
7381 .buffer_for_id(buffer_snapshot.remote_id(), cx)
7382 else {
7383 continue;
7384 };
7385 let breakpoints = breakpoint_store.read(cx).breakpoints(
7386 &buffer,
7387 Some(
7388 buffer_snapshot.anchor_before(range.start)
7389 ..buffer_snapshot.anchor_after(range.end),
7390 ),
7391 buffer_snapshot,
7392 cx,
7393 );
7394 for (breakpoint, state) in breakpoints {
7395 let multi_buffer_anchor =
7396 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
7397 let position = multi_buffer_anchor
7398 .to_point(&multi_buffer_snapshot)
7399 .to_display_point(&snapshot);
7400
7401 breakpoint_display_points.insert(
7402 position.row(),
7403 (multi_buffer_anchor, breakpoint.bp.clone(), state),
7404 );
7405 }
7406 }
7407
7408 breakpoint_display_points
7409 }
7410
7411 fn breakpoint_context_menu(
7412 &self,
7413 anchor: Anchor,
7414 window: &mut Window,
7415 cx: &mut Context<Self>,
7416 ) -> Entity<ui::ContextMenu> {
7417 let weak_editor = cx.weak_entity();
7418 let focus_handle = self.focus_handle(cx);
7419
7420 let row = self
7421 .buffer
7422 .read(cx)
7423 .snapshot(cx)
7424 .summary_for_anchor::<Point>(&anchor)
7425 .row;
7426
7427 let breakpoint = self
7428 .breakpoint_at_row(row, window, cx)
7429 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
7430
7431 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
7432 "Edit Log Breakpoint"
7433 } else {
7434 "Set Log Breakpoint"
7435 };
7436
7437 let condition_breakpoint_msg = if breakpoint
7438 .as_ref()
7439 .is_some_and(|bp| bp.1.condition.is_some())
7440 {
7441 "Edit Condition Breakpoint"
7442 } else {
7443 "Set Condition Breakpoint"
7444 };
7445
7446 let hit_condition_breakpoint_msg = if breakpoint
7447 .as_ref()
7448 .is_some_and(|bp| bp.1.hit_condition.is_some())
7449 {
7450 "Edit Hit Condition Breakpoint"
7451 } else {
7452 "Set Hit Condition Breakpoint"
7453 };
7454
7455 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
7456 "Unset Breakpoint"
7457 } else {
7458 "Set Breakpoint"
7459 };
7460
7461 let run_to_cursor = command_palette_hooks::CommandPaletteFilter::try_global(cx)
7462 .map_or(false, |filter| !filter.is_hidden(&DebuggerRunToCursor));
7463
7464 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
7465 BreakpointState::Enabled => Some("Disable"),
7466 BreakpointState::Disabled => Some("Enable"),
7467 });
7468
7469 let (anchor, breakpoint) =
7470 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
7471
7472 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
7473 menu.on_blur_subscription(Subscription::new(|| {}))
7474 .context(focus_handle)
7475 .when(run_to_cursor, |this| {
7476 let weak_editor = weak_editor.clone();
7477 this.entry("Run to cursor", None, move |window, cx| {
7478 weak_editor
7479 .update(cx, |editor, cx| {
7480 editor.change_selections(None, window, cx, |s| {
7481 s.select_ranges([Point::new(row, 0)..Point::new(row, 0)])
7482 });
7483 })
7484 .ok();
7485
7486 window.dispatch_action(Box::new(DebuggerRunToCursor), cx);
7487 })
7488 .separator()
7489 })
7490 .when_some(toggle_state_msg, |this, msg| {
7491 this.entry(msg, None, {
7492 let weak_editor = weak_editor.clone();
7493 let breakpoint = breakpoint.clone();
7494 move |_window, cx| {
7495 weak_editor
7496 .update(cx, |this, cx| {
7497 this.edit_breakpoint_at_anchor(
7498 anchor,
7499 breakpoint.as_ref().clone(),
7500 BreakpointEditAction::InvertState,
7501 cx,
7502 );
7503 })
7504 .log_err();
7505 }
7506 })
7507 })
7508 .entry(set_breakpoint_msg, None, {
7509 let weak_editor = weak_editor.clone();
7510 let breakpoint = breakpoint.clone();
7511 move |_window, cx| {
7512 weak_editor
7513 .update(cx, |this, cx| {
7514 this.edit_breakpoint_at_anchor(
7515 anchor,
7516 breakpoint.as_ref().clone(),
7517 BreakpointEditAction::Toggle,
7518 cx,
7519 );
7520 })
7521 .log_err();
7522 }
7523 })
7524 .entry(log_breakpoint_msg, None, {
7525 let breakpoint = breakpoint.clone();
7526 let weak_editor = weak_editor.clone();
7527 move |window, cx| {
7528 weak_editor
7529 .update(cx, |this, cx| {
7530 this.add_edit_breakpoint_block(
7531 anchor,
7532 breakpoint.as_ref(),
7533 BreakpointPromptEditAction::Log,
7534 window,
7535 cx,
7536 );
7537 })
7538 .log_err();
7539 }
7540 })
7541 .entry(condition_breakpoint_msg, None, {
7542 let breakpoint = breakpoint.clone();
7543 let weak_editor = weak_editor.clone();
7544 move |window, cx| {
7545 weak_editor
7546 .update(cx, |this, cx| {
7547 this.add_edit_breakpoint_block(
7548 anchor,
7549 breakpoint.as_ref(),
7550 BreakpointPromptEditAction::Condition,
7551 window,
7552 cx,
7553 );
7554 })
7555 .log_err();
7556 }
7557 })
7558 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
7559 weak_editor
7560 .update(cx, |this, cx| {
7561 this.add_edit_breakpoint_block(
7562 anchor,
7563 breakpoint.as_ref(),
7564 BreakpointPromptEditAction::HitCondition,
7565 window,
7566 cx,
7567 );
7568 })
7569 .log_err();
7570 })
7571 })
7572 }
7573
7574 fn render_breakpoint(
7575 &self,
7576 position: Anchor,
7577 row: DisplayRow,
7578 breakpoint: &Breakpoint,
7579 state: Option<BreakpointSessionState>,
7580 cx: &mut Context<Self>,
7581 ) -> IconButton {
7582 let is_rejected = state.is_some_and(|s| !s.verified);
7583 // Is it a breakpoint that shows up when hovering over gutter?
7584 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
7585 (false, false),
7586 |PhantomBreakpointIndicator {
7587 is_active,
7588 display_row,
7589 collides_with_existing_breakpoint,
7590 }| {
7591 (
7592 is_active && display_row == row,
7593 collides_with_existing_breakpoint,
7594 )
7595 },
7596 );
7597
7598 let (color, icon) = {
7599 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
7600 (false, false) => ui::IconName::DebugBreakpoint,
7601 (true, false) => ui::IconName::DebugLogBreakpoint,
7602 (false, true) => ui::IconName::DebugDisabledBreakpoint,
7603 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
7604 };
7605
7606 let color = if is_phantom {
7607 Color::Hint
7608 } else if is_rejected {
7609 Color::Disabled
7610 } else {
7611 Color::Debugger
7612 };
7613
7614 (color, icon)
7615 };
7616
7617 let breakpoint = Arc::from(breakpoint.clone());
7618
7619 let alt_as_text = gpui::Keystroke {
7620 modifiers: Modifiers::secondary_key(),
7621 ..Default::default()
7622 };
7623 let primary_action_text = if breakpoint.is_disabled() {
7624 "Enable breakpoint"
7625 } else if is_phantom && !collides_with_existing {
7626 "Set breakpoint"
7627 } else {
7628 "Unset breakpoint"
7629 };
7630 let focus_handle = self.focus_handle.clone();
7631
7632 let meta = if is_rejected {
7633 SharedString::from("No executable code is associated with this line.")
7634 } else if collides_with_existing && !breakpoint.is_disabled() {
7635 SharedString::from(format!(
7636 "{alt_as_text}-click to disable,\nright-click for more options."
7637 ))
7638 } else {
7639 SharedString::from("Right-click for more options.")
7640 };
7641 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
7642 .icon_size(IconSize::XSmall)
7643 .size(ui::ButtonSize::None)
7644 .when(is_rejected, |this| {
7645 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
7646 })
7647 .icon_color(color)
7648 .style(ButtonStyle::Transparent)
7649 .on_click(cx.listener({
7650 let breakpoint = breakpoint.clone();
7651
7652 move |editor, event: &ClickEvent, window, cx| {
7653 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
7654 BreakpointEditAction::InvertState
7655 } else {
7656 BreakpointEditAction::Toggle
7657 };
7658
7659 window.focus(&editor.focus_handle(cx));
7660 editor.edit_breakpoint_at_anchor(
7661 position,
7662 breakpoint.as_ref().clone(),
7663 edit_action,
7664 cx,
7665 );
7666 }
7667 }))
7668 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7669 editor.set_breakpoint_context_menu(
7670 row,
7671 Some(position),
7672 event.down.position,
7673 window,
7674 cx,
7675 );
7676 }))
7677 .tooltip(move |window, cx| {
7678 Tooltip::with_meta_in(
7679 primary_action_text,
7680 Some(&ToggleBreakpoint),
7681 meta.clone(),
7682 &focus_handle,
7683 window,
7684 cx,
7685 )
7686 })
7687 }
7688
7689 fn build_tasks_context(
7690 project: &Entity<Project>,
7691 buffer: &Entity<Buffer>,
7692 buffer_row: u32,
7693 tasks: &Arc<RunnableTasks>,
7694 cx: &mut Context<Self>,
7695 ) -> Task<Option<task::TaskContext>> {
7696 let position = Point::new(buffer_row, tasks.column);
7697 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
7698 let location = Location {
7699 buffer: buffer.clone(),
7700 range: range_start..range_start,
7701 };
7702 // Fill in the environmental variables from the tree-sitter captures
7703 let mut captured_task_variables = TaskVariables::default();
7704 for (capture_name, value) in tasks.extra_variables.clone() {
7705 captured_task_variables.insert(
7706 task::VariableName::Custom(capture_name.into()),
7707 value.clone(),
7708 );
7709 }
7710 project.update(cx, |project, cx| {
7711 project.task_store().update(cx, |task_store, cx| {
7712 task_store.task_context_for_location(captured_task_variables, location, cx)
7713 })
7714 })
7715 }
7716
7717 pub fn spawn_nearest_task(
7718 &mut self,
7719 action: &SpawnNearestTask,
7720 window: &mut Window,
7721 cx: &mut Context<Self>,
7722 ) {
7723 let Some((workspace, _)) = self.workspace.clone() else {
7724 return;
7725 };
7726 let Some(project) = self.project.clone() else {
7727 return;
7728 };
7729
7730 // Try to find a closest, enclosing node using tree-sitter that has a
7731 // task
7732 let Some((buffer, buffer_row, tasks)) = self
7733 .find_enclosing_node_task(cx)
7734 // Or find the task that's closest in row-distance.
7735 .or_else(|| self.find_closest_task(cx))
7736 else {
7737 return;
7738 };
7739
7740 let reveal_strategy = action.reveal;
7741 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
7742 cx.spawn_in(window, async move |_, cx| {
7743 let context = task_context.await?;
7744 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
7745
7746 let resolved = &mut resolved_task.resolved;
7747 resolved.reveal = reveal_strategy;
7748
7749 workspace
7750 .update_in(cx, |workspace, window, cx| {
7751 workspace.schedule_resolved_task(
7752 task_source_kind,
7753 resolved_task,
7754 false,
7755 window,
7756 cx,
7757 );
7758 })
7759 .ok()
7760 })
7761 .detach();
7762 }
7763
7764 fn find_closest_task(
7765 &mut self,
7766 cx: &mut Context<Self>,
7767 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7768 let cursor_row = self.selections.newest_adjusted(cx).head().row;
7769
7770 let ((buffer_id, row), tasks) = self
7771 .tasks
7772 .iter()
7773 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
7774
7775 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
7776 let tasks = Arc::new(tasks.to_owned());
7777 Some((buffer, *row, tasks))
7778 }
7779
7780 fn find_enclosing_node_task(
7781 &mut self,
7782 cx: &mut Context<Self>,
7783 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7784 let snapshot = self.buffer.read(cx).snapshot(cx);
7785 let offset = self.selections.newest::<usize>(cx).head();
7786 let excerpt = snapshot.excerpt_containing(offset..offset)?;
7787 let buffer_id = excerpt.buffer().remote_id();
7788
7789 let layer = excerpt.buffer().syntax_layer_at(offset)?;
7790 let mut cursor = layer.node().walk();
7791
7792 while cursor.goto_first_child_for_byte(offset).is_some() {
7793 if cursor.node().end_byte() == offset {
7794 cursor.goto_next_sibling();
7795 }
7796 }
7797
7798 // Ascend to the smallest ancestor that contains the range and has a task.
7799 loop {
7800 let node = cursor.node();
7801 let node_range = node.byte_range();
7802 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
7803
7804 // Check if this node contains our offset
7805 if node_range.start <= offset && node_range.end >= offset {
7806 // If it contains offset, check for task
7807 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
7808 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
7809 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
7810 }
7811 }
7812
7813 if !cursor.goto_parent() {
7814 break;
7815 }
7816 }
7817 None
7818 }
7819
7820 fn render_run_indicator(
7821 &self,
7822 _style: &EditorStyle,
7823 is_active: bool,
7824 row: DisplayRow,
7825 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
7826 cx: &mut Context<Self>,
7827 ) -> IconButton {
7828 let color = Color::Muted;
7829 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
7830
7831 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
7832 .shape(ui::IconButtonShape::Square)
7833 .icon_size(IconSize::XSmall)
7834 .icon_color(color)
7835 .toggle_state(is_active)
7836 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
7837 let quick_launch = e.down.button == MouseButton::Left;
7838 window.focus(&editor.focus_handle(cx));
7839 editor.toggle_code_actions(
7840 &ToggleCodeActions {
7841 deployed_from: Some(CodeActionSource::Indicator(row)),
7842 quick_launch,
7843 },
7844 window,
7845 cx,
7846 );
7847 }))
7848 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7849 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
7850 }))
7851 }
7852
7853 pub fn context_menu_visible(&self) -> bool {
7854 !self.edit_prediction_preview_is_active()
7855 && self
7856 .context_menu
7857 .borrow()
7858 .as_ref()
7859 .map_or(false, |menu| menu.visible())
7860 }
7861
7862 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
7863 self.context_menu
7864 .borrow()
7865 .as_ref()
7866 .map(|menu| menu.origin())
7867 }
7868
7869 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
7870 self.context_menu_options = Some(options);
7871 }
7872
7873 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
7874 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
7875
7876 fn render_edit_prediction_popover(
7877 &mut self,
7878 text_bounds: &Bounds<Pixels>,
7879 content_origin: gpui::Point<Pixels>,
7880 right_margin: Pixels,
7881 editor_snapshot: &EditorSnapshot,
7882 visible_row_range: Range<DisplayRow>,
7883 scroll_top: f32,
7884 scroll_bottom: f32,
7885 line_layouts: &[LineWithInvisibles],
7886 line_height: Pixels,
7887 scroll_pixel_position: gpui::Point<Pixels>,
7888 newest_selection_head: Option<DisplayPoint>,
7889 editor_width: Pixels,
7890 style: &EditorStyle,
7891 window: &mut Window,
7892 cx: &mut App,
7893 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7894 if self.mode().is_minimap() {
7895 return None;
7896 }
7897 let active_inline_completion = self.active_inline_completion.as_ref()?;
7898
7899 if self.edit_prediction_visible_in_cursor_popover(true) {
7900 return None;
7901 }
7902
7903 match &active_inline_completion.completion {
7904 InlineCompletion::Move { target, .. } => {
7905 let target_display_point = target.to_display_point(editor_snapshot);
7906
7907 if self.edit_prediction_requires_modifier() {
7908 if !self.edit_prediction_preview_is_active() {
7909 return None;
7910 }
7911
7912 self.render_edit_prediction_modifier_jump_popover(
7913 text_bounds,
7914 content_origin,
7915 visible_row_range,
7916 line_layouts,
7917 line_height,
7918 scroll_pixel_position,
7919 newest_selection_head,
7920 target_display_point,
7921 window,
7922 cx,
7923 )
7924 } else {
7925 self.render_edit_prediction_eager_jump_popover(
7926 text_bounds,
7927 content_origin,
7928 editor_snapshot,
7929 visible_row_range,
7930 scroll_top,
7931 scroll_bottom,
7932 line_height,
7933 scroll_pixel_position,
7934 target_display_point,
7935 editor_width,
7936 window,
7937 cx,
7938 )
7939 }
7940 }
7941 InlineCompletion::Edit {
7942 display_mode: EditDisplayMode::Inline,
7943 ..
7944 } => None,
7945 InlineCompletion::Edit {
7946 display_mode: EditDisplayMode::TabAccept,
7947 edits,
7948 ..
7949 } => {
7950 let range = &edits.first()?.0;
7951 let target_display_point = range.end.to_display_point(editor_snapshot);
7952
7953 self.render_edit_prediction_end_of_line_popover(
7954 "Accept",
7955 editor_snapshot,
7956 visible_row_range,
7957 target_display_point,
7958 line_height,
7959 scroll_pixel_position,
7960 content_origin,
7961 editor_width,
7962 window,
7963 cx,
7964 )
7965 }
7966 InlineCompletion::Edit {
7967 edits,
7968 edit_preview,
7969 display_mode: EditDisplayMode::DiffPopover,
7970 snapshot,
7971 } => self.render_edit_prediction_diff_popover(
7972 text_bounds,
7973 content_origin,
7974 right_margin,
7975 editor_snapshot,
7976 visible_row_range,
7977 line_layouts,
7978 line_height,
7979 scroll_pixel_position,
7980 newest_selection_head,
7981 editor_width,
7982 style,
7983 edits,
7984 edit_preview,
7985 snapshot,
7986 window,
7987 cx,
7988 ),
7989 }
7990 }
7991
7992 fn render_edit_prediction_modifier_jump_popover(
7993 &mut self,
7994 text_bounds: &Bounds<Pixels>,
7995 content_origin: gpui::Point<Pixels>,
7996 visible_row_range: Range<DisplayRow>,
7997 line_layouts: &[LineWithInvisibles],
7998 line_height: Pixels,
7999 scroll_pixel_position: gpui::Point<Pixels>,
8000 newest_selection_head: Option<DisplayPoint>,
8001 target_display_point: DisplayPoint,
8002 window: &mut Window,
8003 cx: &mut App,
8004 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8005 let scrolled_content_origin =
8006 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
8007
8008 const SCROLL_PADDING_Y: Pixels = px(12.);
8009
8010 if target_display_point.row() < visible_row_range.start {
8011 return self.render_edit_prediction_scroll_popover(
8012 |_| SCROLL_PADDING_Y,
8013 IconName::ArrowUp,
8014 visible_row_range,
8015 line_layouts,
8016 newest_selection_head,
8017 scrolled_content_origin,
8018 window,
8019 cx,
8020 );
8021 } else if target_display_point.row() >= visible_row_range.end {
8022 return self.render_edit_prediction_scroll_popover(
8023 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8024 IconName::ArrowDown,
8025 visible_row_range,
8026 line_layouts,
8027 newest_selection_head,
8028 scrolled_content_origin,
8029 window,
8030 cx,
8031 );
8032 }
8033
8034 const POLE_WIDTH: Pixels = px(2.);
8035
8036 let line_layout =
8037 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8038 let target_column = target_display_point.column() as usize;
8039
8040 let target_x = line_layout.x_for_index(target_column);
8041 let target_y =
8042 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
8043
8044 let flag_on_right = target_x < text_bounds.size.width / 2.;
8045
8046 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8047 border_color.l += 0.001;
8048
8049 let mut element = v_flex()
8050 .items_end()
8051 .when(flag_on_right, |el| el.items_start())
8052 .child(if flag_on_right {
8053 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8054 .rounded_bl(px(0.))
8055 .rounded_tl(px(0.))
8056 .border_l_2()
8057 .border_color(border_color)
8058 } else {
8059 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8060 .rounded_br(px(0.))
8061 .rounded_tr(px(0.))
8062 .border_r_2()
8063 .border_color(border_color)
8064 })
8065 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8066 .into_any();
8067
8068 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8069
8070 let mut origin = scrolled_content_origin + point(target_x, target_y)
8071 - point(
8072 if flag_on_right {
8073 POLE_WIDTH
8074 } else {
8075 size.width - POLE_WIDTH
8076 },
8077 size.height - line_height,
8078 );
8079
8080 origin.x = origin.x.max(content_origin.x);
8081
8082 element.prepaint_at(origin, window, cx);
8083
8084 Some((element, origin))
8085 }
8086
8087 fn render_edit_prediction_scroll_popover(
8088 &mut self,
8089 to_y: impl Fn(Size<Pixels>) -> Pixels,
8090 scroll_icon: IconName,
8091 visible_row_range: Range<DisplayRow>,
8092 line_layouts: &[LineWithInvisibles],
8093 newest_selection_head: Option<DisplayPoint>,
8094 scrolled_content_origin: gpui::Point<Pixels>,
8095 window: &mut Window,
8096 cx: &mut App,
8097 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8098 let mut element = self
8099 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
8100 .into_any();
8101
8102 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8103
8104 let cursor = newest_selection_head?;
8105 let cursor_row_layout =
8106 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8107 let cursor_column = cursor.column() as usize;
8108
8109 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8110
8111 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8112
8113 element.prepaint_at(origin, window, cx);
8114 Some((element, origin))
8115 }
8116
8117 fn render_edit_prediction_eager_jump_popover(
8118 &mut self,
8119 text_bounds: &Bounds<Pixels>,
8120 content_origin: gpui::Point<Pixels>,
8121 editor_snapshot: &EditorSnapshot,
8122 visible_row_range: Range<DisplayRow>,
8123 scroll_top: f32,
8124 scroll_bottom: f32,
8125 line_height: Pixels,
8126 scroll_pixel_position: gpui::Point<Pixels>,
8127 target_display_point: DisplayPoint,
8128 editor_width: Pixels,
8129 window: &mut Window,
8130 cx: &mut App,
8131 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8132 if target_display_point.row().as_f32() < scroll_top {
8133 let mut element = self
8134 .render_edit_prediction_line_popover(
8135 "Jump to Edit",
8136 Some(IconName::ArrowUp),
8137 window,
8138 cx,
8139 )?
8140 .into_any();
8141
8142 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8143 let offset = point(
8144 (text_bounds.size.width - size.width) / 2.,
8145 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8146 );
8147
8148 let origin = text_bounds.origin + offset;
8149 element.prepaint_at(origin, window, cx);
8150 Some((element, origin))
8151 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
8152 let mut element = self
8153 .render_edit_prediction_line_popover(
8154 "Jump to Edit",
8155 Some(IconName::ArrowDown),
8156 window,
8157 cx,
8158 )?
8159 .into_any();
8160
8161 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8162 let offset = point(
8163 (text_bounds.size.width - size.width) / 2.,
8164 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8165 );
8166
8167 let origin = text_bounds.origin + offset;
8168 element.prepaint_at(origin, window, cx);
8169 Some((element, origin))
8170 } else {
8171 self.render_edit_prediction_end_of_line_popover(
8172 "Jump to Edit",
8173 editor_snapshot,
8174 visible_row_range,
8175 target_display_point,
8176 line_height,
8177 scroll_pixel_position,
8178 content_origin,
8179 editor_width,
8180 window,
8181 cx,
8182 )
8183 }
8184 }
8185
8186 fn render_edit_prediction_end_of_line_popover(
8187 self: &mut Editor,
8188 label: &'static str,
8189 editor_snapshot: &EditorSnapshot,
8190 visible_row_range: Range<DisplayRow>,
8191 target_display_point: DisplayPoint,
8192 line_height: Pixels,
8193 scroll_pixel_position: gpui::Point<Pixels>,
8194 content_origin: gpui::Point<Pixels>,
8195 editor_width: Pixels,
8196 window: &mut Window,
8197 cx: &mut App,
8198 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8199 let target_line_end = DisplayPoint::new(
8200 target_display_point.row(),
8201 editor_snapshot.line_len(target_display_point.row()),
8202 );
8203
8204 let mut element = self
8205 .render_edit_prediction_line_popover(label, None, window, cx)?
8206 .into_any();
8207
8208 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8209
8210 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8211
8212 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
8213 let mut origin = start_point
8214 + line_origin
8215 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8216 origin.x = origin.x.max(content_origin.x);
8217
8218 let max_x = content_origin.x + editor_width - size.width;
8219
8220 if origin.x > max_x {
8221 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8222
8223 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8224 origin.y += offset;
8225 IconName::ArrowUp
8226 } else {
8227 origin.y -= offset;
8228 IconName::ArrowDown
8229 };
8230
8231 element = self
8232 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
8233 .into_any();
8234
8235 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8236
8237 origin.x = content_origin.x + editor_width - size.width - px(2.);
8238 }
8239
8240 element.prepaint_at(origin, window, cx);
8241 Some((element, origin))
8242 }
8243
8244 fn render_edit_prediction_diff_popover(
8245 self: &Editor,
8246 text_bounds: &Bounds<Pixels>,
8247 content_origin: gpui::Point<Pixels>,
8248 right_margin: Pixels,
8249 editor_snapshot: &EditorSnapshot,
8250 visible_row_range: Range<DisplayRow>,
8251 line_layouts: &[LineWithInvisibles],
8252 line_height: Pixels,
8253 scroll_pixel_position: gpui::Point<Pixels>,
8254 newest_selection_head: Option<DisplayPoint>,
8255 editor_width: Pixels,
8256 style: &EditorStyle,
8257 edits: &Vec<(Range<Anchor>, String)>,
8258 edit_preview: &Option<language::EditPreview>,
8259 snapshot: &language::BufferSnapshot,
8260 window: &mut Window,
8261 cx: &mut App,
8262 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8263 let edit_start = edits
8264 .first()
8265 .unwrap()
8266 .0
8267 .start
8268 .to_display_point(editor_snapshot);
8269 let edit_end = edits
8270 .last()
8271 .unwrap()
8272 .0
8273 .end
8274 .to_display_point(editor_snapshot);
8275
8276 let is_visible = visible_row_range.contains(&edit_start.row())
8277 || visible_row_range.contains(&edit_end.row());
8278 if !is_visible {
8279 return None;
8280 }
8281
8282 let highlighted_edits =
8283 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
8284
8285 let styled_text = highlighted_edits.to_styled_text(&style.text);
8286 let line_count = highlighted_edits.text.lines().count();
8287
8288 const BORDER_WIDTH: Pixels = px(1.);
8289
8290 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8291 let has_keybind = keybind.is_some();
8292
8293 let mut element = h_flex()
8294 .items_start()
8295 .child(
8296 h_flex()
8297 .bg(cx.theme().colors().editor_background)
8298 .border(BORDER_WIDTH)
8299 .shadow_sm()
8300 .border_color(cx.theme().colors().border)
8301 .rounded_l_lg()
8302 .when(line_count > 1, |el| el.rounded_br_lg())
8303 .pr_1()
8304 .child(styled_text),
8305 )
8306 .child(
8307 h_flex()
8308 .h(line_height + BORDER_WIDTH * 2.)
8309 .px_1p5()
8310 .gap_1()
8311 // Workaround: For some reason, there's a gap if we don't do this
8312 .ml(-BORDER_WIDTH)
8313 .shadow(vec![gpui::BoxShadow {
8314 color: gpui::black().opacity(0.05),
8315 offset: point(px(1.), px(1.)),
8316 blur_radius: px(2.),
8317 spread_radius: px(0.),
8318 }])
8319 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8320 .border(BORDER_WIDTH)
8321 .border_color(cx.theme().colors().border)
8322 .rounded_r_lg()
8323 .id("edit_prediction_diff_popover_keybind")
8324 .when(!has_keybind, |el| {
8325 let status_colors = cx.theme().status();
8326
8327 el.bg(status_colors.error_background)
8328 .border_color(status_colors.error.opacity(0.6))
8329 .child(Icon::new(IconName::Info).color(Color::Error))
8330 .cursor_default()
8331 .hoverable_tooltip(move |_window, cx| {
8332 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8333 })
8334 })
8335 .children(keybind),
8336 )
8337 .into_any();
8338
8339 let longest_row =
8340 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8341 let longest_line_width = if visible_row_range.contains(&longest_row) {
8342 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8343 } else {
8344 layout_line(
8345 longest_row,
8346 editor_snapshot,
8347 style,
8348 editor_width,
8349 |_| false,
8350 window,
8351 cx,
8352 )
8353 .width
8354 };
8355
8356 let viewport_bounds =
8357 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8358 right: -right_margin,
8359 ..Default::default()
8360 });
8361
8362 let x_after_longest =
8363 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
8364 - scroll_pixel_position.x;
8365
8366 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8367
8368 // Fully visible if it can be displayed within the window (allow overlapping other
8369 // panes). However, this is only allowed if the popover starts within text_bounds.
8370 let can_position_to_the_right = x_after_longest < text_bounds.right()
8371 && x_after_longest + element_bounds.width < viewport_bounds.right();
8372
8373 let mut origin = if can_position_to_the_right {
8374 point(
8375 x_after_longest,
8376 text_bounds.origin.y + edit_start.row().as_f32() * line_height
8377 - scroll_pixel_position.y,
8378 )
8379 } else {
8380 let cursor_row = newest_selection_head.map(|head| head.row());
8381 let above_edit = edit_start
8382 .row()
8383 .0
8384 .checked_sub(line_count as u32)
8385 .map(DisplayRow);
8386 let below_edit = Some(edit_end.row() + 1);
8387 let above_cursor =
8388 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
8389 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
8390
8391 // Place the edit popover adjacent to the edit if there is a location
8392 // available that is onscreen and does not obscure the cursor. Otherwise,
8393 // place it adjacent to the cursor.
8394 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
8395 .into_iter()
8396 .flatten()
8397 .find(|&start_row| {
8398 let end_row = start_row + line_count as u32;
8399 visible_row_range.contains(&start_row)
8400 && visible_row_range.contains(&end_row)
8401 && cursor_row.map_or(true, |cursor_row| {
8402 !((start_row..end_row).contains(&cursor_row))
8403 })
8404 })?;
8405
8406 content_origin
8407 + point(
8408 -scroll_pixel_position.x,
8409 row_target.as_f32() * line_height - scroll_pixel_position.y,
8410 )
8411 };
8412
8413 origin.x -= BORDER_WIDTH;
8414
8415 window.defer_draw(element, origin, 1);
8416
8417 // Do not return an element, since it will already be drawn due to defer_draw.
8418 None
8419 }
8420
8421 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
8422 px(30.)
8423 }
8424
8425 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
8426 if self.read_only(cx) {
8427 cx.theme().players().read_only()
8428 } else {
8429 self.style.as_ref().unwrap().local_player
8430 }
8431 }
8432
8433 fn render_edit_prediction_accept_keybind(
8434 &self,
8435 window: &mut Window,
8436 cx: &App,
8437 ) -> Option<AnyElement> {
8438 let accept_binding = self.accept_edit_prediction_keybind(window, cx);
8439 let accept_keystroke = accept_binding.keystroke()?;
8440
8441 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8442
8443 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
8444 Color::Accent
8445 } else {
8446 Color::Muted
8447 };
8448
8449 h_flex()
8450 .px_0p5()
8451 .when(is_platform_style_mac, |parent| parent.gap_0p5())
8452 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8453 .text_size(TextSize::XSmall.rems(cx))
8454 .child(h_flex().children(ui::render_modifiers(
8455 &accept_keystroke.modifiers,
8456 PlatformStyle::platform(),
8457 Some(modifiers_color),
8458 Some(IconSize::XSmall.rems().into()),
8459 true,
8460 )))
8461 .when(is_platform_style_mac, |parent| {
8462 parent.child(accept_keystroke.key.clone())
8463 })
8464 .when(!is_platform_style_mac, |parent| {
8465 parent.child(
8466 Key::new(
8467 util::capitalize(&accept_keystroke.key),
8468 Some(Color::Default),
8469 )
8470 .size(Some(IconSize::XSmall.rems().into())),
8471 )
8472 })
8473 .into_any()
8474 .into()
8475 }
8476
8477 fn render_edit_prediction_line_popover(
8478 &self,
8479 label: impl Into<SharedString>,
8480 icon: Option<IconName>,
8481 window: &mut Window,
8482 cx: &App,
8483 ) -> Option<Stateful<Div>> {
8484 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
8485
8486 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8487 let has_keybind = keybind.is_some();
8488
8489 let result = h_flex()
8490 .id("ep-line-popover")
8491 .py_0p5()
8492 .pl_1()
8493 .pr(padding_right)
8494 .gap_1()
8495 .rounded_md()
8496 .border_1()
8497 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8498 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
8499 .shadow_sm()
8500 .when(!has_keybind, |el| {
8501 let status_colors = cx.theme().status();
8502
8503 el.bg(status_colors.error_background)
8504 .border_color(status_colors.error.opacity(0.6))
8505 .pl_2()
8506 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
8507 .cursor_default()
8508 .hoverable_tooltip(move |_window, cx| {
8509 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8510 })
8511 })
8512 .children(keybind)
8513 .child(
8514 Label::new(label)
8515 .size(LabelSize::Small)
8516 .when(!has_keybind, |el| {
8517 el.color(cx.theme().status().error.into()).strikethrough()
8518 }),
8519 )
8520 .when(!has_keybind, |el| {
8521 el.child(
8522 h_flex().ml_1().child(
8523 Icon::new(IconName::Info)
8524 .size(IconSize::Small)
8525 .color(cx.theme().status().error.into()),
8526 ),
8527 )
8528 })
8529 .when_some(icon, |element, icon| {
8530 element.child(
8531 div()
8532 .mt(px(1.5))
8533 .child(Icon::new(icon).size(IconSize::Small)),
8534 )
8535 });
8536
8537 Some(result)
8538 }
8539
8540 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
8541 let accent_color = cx.theme().colors().text_accent;
8542 let editor_bg_color = cx.theme().colors().editor_background;
8543 editor_bg_color.blend(accent_color.opacity(0.1))
8544 }
8545
8546 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
8547 let accent_color = cx.theme().colors().text_accent;
8548 let editor_bg_color = cx.theme().colors().editor_background;
8549 editor_bg_color.blend(accent_color.opacity(0.6))
8550 }
8551
8552 fn render_edit_prediction_cursor_popover(
8553 &self,
8554 min_width: Pixels,
8555 max_width: Pixels,
8556 cursor_point: Point,
8557 style: &EditorStyle,
8558 accept_keystroke: Option<&gpui::Keystroke>,
8559 _window: &Window,
8560 cx: &mut Context<Editor>,
8561 ) -> Option<AnyElement> {
8562 let provider = self.edit_prediction_provider.as_ref()?;
8563
8564 if provider.provider.needs_terms_acceptance(cx) {
8565 return Some(
8566 h_flex()
8567 .min_w(min_width)
8568 .flex_1()
8569 .px_2()
8570 .py_1()
8571 .gap_3()
8572 .elevation_2(cx)
8573 .hover(|style| style.bg(cx.theme().colors().element_hover))
8574 .id("accept-terms")
8575 .cursor_pointer()
8576 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
8577 .on_click(cx.listener(|this, _event, window, cx| {
8578 cx.stop_propagation();
8579 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
8580 window.dispatch_action(
8581 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
8582 cx,
8583 );
8584 }))
8585 .child(
8586 h_flex()
8587 .flex_1()
8588 .gap_2()
8589 .child(Icon::new(IconName::ZedPredict))
8590 .child(Label::new("Accept Terms of Service"))
8591 .child(div().w_full())
8592 .child(
8593 Icon::new(IconName::ArrowUpRight)
8594 .color(Color::Muted)
8595 .size(IconSize::Small),
8596 )
8597 .into_any_element(),
8598 )
8599 .into_any(),
8600 );
8601 }
8602
8603 let is_refreshing = provider.provider.is_refreshing(cx);
8604
8605 fn pending_completion_container() -> Div {
8606 h_flex()
8607 .h_full()
8608 .flex_1()
8609 .gap_2()
8610 .child(Icon::new(IconName::ZedPredict))
8611 }
8612
8613 let completion = match &self.active_inline_completion {
8614 Some(prediction) => {
8615 if !self.has_visible_completions_menu() {
8616 const RADIUS: Pixels = px(6.);
8617 const BORDER_WIDTH: Pixels = px(1.);
8618
8619 return Some(
8620 h_flex()
8621 .elevation_2(cx)
8622 .border(BORDER_WIDTH)
8623 .border_color(cx.theme().colors().border)
8624 .when(accept_keystroke.is_none(), |el| {
8625 el.border_color(cx.theme().status().error)
8626 })
8627 .rounded(RADIUS)
8628 .rounded_tl(px(0.))
8629 .overflow_hidden()
8630 .child(div().px_1p5().child(match &prediction.completion {
8631 InlineCompletion::Move { target, snapshot } => {
8632 use text::ToPoint as _;
8633 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
8634 {
8635 Icon::new(IconName::ZedPredictDown)
8636 } else {
8637 Icon::new(IconName::ZedPredictUp)
8638 }
8639 }
8640 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
8641 }))
8642 .child(
8643 h_flex()
8644 .gap_1()
8645 .py_1()
8646 .px_2()
8647 .rounded_r(RADIUS - BORDER_WIDTH)
8648 .border_l_1()
8649 .border_color(cx.theme().colors().border)
8650 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8651 .when(self.edit_prediction_preview.released_too_fast(), |el| {
8652 el.child(
8653 Label::new("Hold")
8654 .size(LabelSize::Small)
8655 .when(accept_keystroke.is_none(), |el| {
8656 el.strikethrough()
8657 })
8658 .line_height_style(LineHeightStyle::UiLabel),
8659 )
8660 })
8661 .id("edit_prediction_cursor_popover_keybind")
8662 .when(accept_keystroke.is_none(), |el| {
8663 let status_colors = cx.theme().status();
8664
8665 el.bg(status_colors.error_background)
8666 .border_color(status_colors.error.opacity(0.6))
8667 .child(Icon::new(IconName::Info).color(Color::Error))
8668 .cursor_default()
8669 .hoverable_tooltip(move |_window, cx| {
8670 cx.new(|_| MissingEditPredictionKeybindingTooltip)
8671 .into()
8672 })
8673 })
8674 .when_some(
8675 accept_keystroke.as_ref(),
8676 |el, accept_keystroke| {
8677 el.child(h_flex().children(ui::render_modifiers(
8678 &accept_keystroke.modifiers,
8679 PlatformStyle::platform(),
8680 Some(Color::Default),
8681 Some(IconSize::XSmall.rems().into()),
8682 false,
8683 )))
8684 },
8685 ),
8686 )
8687 .into_any(),
8688 );
8689 }
8690
8691 self.render_edit_prediction_cursor_popover_preview(
8692 prediction,
8693 cursor_point,
8694 style,
8695 cx,
8696 )?
8697 }
8698
8699 None if is_refreshing => match &self.stale_inline_completion_in_menu {
8700 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
8701 stale_completion,
8702 cursor_point,
8703 style,
8704 cx,
8705 )?,
8706
8707 None => {
8708 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
8709 }
8710 },
8711
8712 None => pending_completion_container().child(Label::new("No Prediction")),
8713 };
8714
8715 let completion = if is_refreshing {
8716 completion
8717 .with_animation(
8718 "loading-completion",
8719 Animation::new(Duration::from_secs(2))
8720 .repeat()
8721 .with_easing(pulsating_between(0.4, 0.8)),
8722 |label, delta| label.opacity(delta),
8723 )
8724 .into_any_element()
8725 } else {
8726 completion.into_any_element()
8727 };
8728
8729 let has_completion = self.active_inline_completion.is_some();
8730
8731 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8732 Some(
8733 h_flex()
8734 .min_w(min_width)
8735 .max_w(max_width)
8736 .flex_1()
8737 .elevation_2(cx)
8738 .border_color(cx.theme().colors().border)
8739 .child(
8740 div()
8741 .flex_1()
8742 .py_1()
8743 .px_2()
8744 .overflow_hidden()
8745 .child(completion),
8746 )
8747 .when_some(accept_keystroke, |el, accept_keystroke| {
8748 if !accept_keystroke.modifiers.modified() {
8749 return el;
8750 }
8751
8752 el.child(
8753 h_flex()
8754 .h_full()
8755 .border_l_1()
8756 .rounded_r_lg()
8757 .border_color(cx.theme().colors().border)
8758 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8759 .gap_1()
8760 .py_1()
8761 .px_2()
8762 .child(
8763 h_flex()
8764 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8765 .when(is_platform_style_mac, |parent| parent.gap_1())
8766 .child(h_flex().children(ui::render_modifiers(
8767 &accept_keystroke.modifiers,
8768 PlatformStyle::platform(),
8769 Some(if !has_completion {
8770 Color::Muted
8771 } else {
8772 Color::Default
8773 }),
8774 None,
8775 false,
8776 ))),
8777 )
8778 .child(Label::new("Preview").into_any_element())
8779 .opacity(if has_completion { 1.0 } else { 0.4 }),
8780 )
8781 })
8782 .into_any(),
8783 )
8784 }
8785
8786 fn render_edit_prediction_cursor_popover_preview(
8787 &self,
8788 completion: &InlineCompletionState,
8789 cursor_point: Point,
8790 style: &EditorStyle,
8791 cx: &mut Context<Editor>,
8792 ) -> Option<Div> {
8793 use text::ToPoint as _;
8794
8795 fn render_relative_row_jump(
8796 prefix: impl Into<String>,
8797 current_row: u32,
8798 target_row: u32,
8799 ) -> Div {
8800 let (row_diff, arrow) = if target_row < current_row {
8801 (current_row - target_row, IconName::ArrowUp)
8802 } else {
8803 (target_row - current_row, IconName::ArrowDown)
8804 };
8805
8806 h_flex()
8807 .child(
8808 Label::new(format!("{}{}", prefix.into(), row_diff))
8809 .color(Color::Muted)
8810 .size(LabelSize::Small),
8811 )
8812 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
8813 }
8814
8815 match &completion.completion {
8816 InlineCompletion::Move {
8817 target, snapshot, ..
8818 } => Some(
8819 h_flex()
8820 .px_2()
8821 .gap_2()
8822 .flex_1()
8823 .child(
8824 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
8825 Icon::new(IconName::ZedPredictDown)
8826 } else {
8827 Icon::new(IconName::ZedPredictUp)
8828 },
8829 )
8830 .child(Label::new("Jump to Edit")),
8831 ),
8832
8833 InlineCompletion::Edit {
8834 edits,
8835 edit_preview,
8836 snapshot,
8837 display_mode: _,
8838 } => {
8839 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
8840
8841 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
8842 &snapshot,
8843 &edits,
8844 edit_preview.as_ref()?,
8845 true,
8846 cx,
8847 )
8848 .first_line_preview();
8849
8850 let styled_text = gpui::StyledText::new(highlighted_edits.text)
8851 .with_default_highlights(&style.text, highlighted_edits.highlights);
8852
8853 let preview = h_flex()
8854 .gap_1()
8855 .min_w_16()
8856 .child(styled_text)
8857 .when(has_more_lines, |parent| parent.child("…"));
8858
8859 let left = if first_edit_row != cursor_point.row {
8860 render_relative_row_jump("", cursor_point.row, first_edit_row)
8861 .into_any_element()
8862 } else {
8863 Icon::new(IconName::ZedPredict).into_any_element()
8864 };
8865
8866 Some(
8867 h_flex()
8868 .h_full()
8869 .flex_1()
8870 .gap_2()
8871 .pr_1()
8872 .overflow_x_hidden()
8873 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8874 .child(left)
8875 .child(preview),
8876 )
8877 }
8878 }
8879 }
8880
8881 pub fn render_context_menu(
8882 &self,
8883 style: &EditorStyle,
8884 max_height_in_lines: u32,
8885 window: &mut Window,
8886 cx: &mut Context<Editor>,
8887 ) -> Option<AnyElement> {
8888 let menu = self.context_menu.borrow();
8889 let menu = menu.as_ref()?;
8890 if !menu.visible() {
8891 return None;
8892 };
8893 Some(menu.render(style, max_height_in_lines, window, cx))
8894 }
8895
8896 fn render_context_menu_aside(
8897 &mut self,
8898 max_size: Size<Pixels>,
8899 window: &mut Window,
8900 cx: &mut Context<Editor>,
8901 ) -> Option<AnyElement> {
8902 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
8903 if menu.visible() {
8904 menu.render_aside(max_size, window, cx)
8905 } else {
8906 None
8907 }
8908 })
8909 }
8910
8911 fn hide_context_menu(
8912 &mut self,
8913 window: &mut Window,
8914 cx: &mut Context<Self>,
8915 ) -> Option<CodeContextMenu> {
8916 cx.notify();
8917 self.completion_tasks.clear();
8918 let context_menu = self.context_menu.borrow_mut().take();
8919 self.stale_inline_completion_in_menu.take();
8920 self.update_visible_inline_completion(window, cx);
8921 if let Some(CodeContextMenu::Completions(_)) = &context_menu {
8922 if let Some(completion_provider) = &self.completion_provider {
8923 completion_provider.selection_changed(None, window, cx);
8924 }
8925 }
8926 context_menu
8927 }
8928
8929 fn show_snippet_choices(
8930 &mut self,
8931 choices: &Vec<String>,
8932 selection: Range<Anchor>,
8933 cx: &mut Context<Self>,
8934 ) {
8935 if selection.start.buffer_id.is_none() {
8936 return;
8937 }
8938 let buffer_id = selection.start.buffer_id.unwrap();
8939 let buffer = self.buffer().read(cx).buffer(buffer_id);
8940 let id = post_inc(&mut self.next_completion_id);
8941 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
8942
8943 if let Some(buffer) = buffer {
8944 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
8945 CompletionsMenu::new_snippet_choices(
8946 id,
8947 true,
8948 choices,
8949 selection,
8950 buffer,
8951 snippet_sort_order,
8952 ),
8953 ));
8954 }
8955 }
8956
8957 pub fn insert_snippet(
8958 &mut self,
8959 insertion_ranges: &[Range<usize>],
8960 snippet: Snippet,
8961 window: &mut Window,
8962 cx: &mut Context<Self>,
8963 ) -> Result<()> {
8964 struct Tabstop<T> {
8965 is_end_tabstop: bool,
8966 ranges: Vec<Range<T>>,
8967 choices: Option<Vec<String>>,
8968 }
8969
8970 let tabstops = self.buffer.update(cx, |buffer, cx| {
8971 let snippet_text: Arc<str> = snippet.text.clone().into();
8972 let edits = insertion_ranges
8973 .iter()
8974 .cloned()
8975 .map(|range| (range, snippet_text.clone()));
8976 let autoindent_mode = AutoindentMode::Block {
8977 original_indent_columns: Vec::new(),
8978 };
8979 buffer.edit(edits, Some(autoindent_mode), cx);
8980
8981 let snapshot = &*buffer.read(cx);
8982 let snippet = &snippet;
8983 snippet
8984 .tabstops
8985 .iter()
8986 .map(|tabstop| {
8987 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
8988 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
8989 });
8990 let mut tabstop_ranges = tabstop
8991 .ranges
8992 .iter()
8993 .flat_map(|tabstop_range| {
8994 let mut delta = 0_isize;
8995 insertion_ranges.iter().map(move |insertion_range| {
8996 let insertion_start = insertion_range.start as isize + delta;
8997 delta +=
8998 snippet.text.len() as isize - insertion_range.len() as isize;
8999
9000 let start = ((insertion_start + tabstop_range.start) as usize)
9001 .min(snapshot.len());
9002 let end = ((insertion_start + tabstop_range.end) as usize)
9003 .min(snapshot.len());
9004 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9005 })
9006 })
9007 .collect::<Vec<_>>();
9008 // Sort in reverse order so that the first range is the newest created
9009 // selection. Completions will use it and autoscroll will prioritize it.
9010 tabstop_ranges.sort_unstable_by(|a, b| b.start.cmp(&a.start, snapshot));
9011
9012 Tabstop {
9013 is_end_tabstop,
9014 ranges: tabstop_ranges,
9015 choices: tabstop.choices.clone(),
9016 }
9017 })
9018 .collect::<Vec<_>>()
9019 });
9020 if let Some(tabstop) = tabstops.first() {
9021 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9022 s.select_ranges(tabstop.ranges.iter().cloned());
9023 });
9024
9025 if let Some(choices) = &tabstop.choices {
9026 if let Some(selection) = tabstop.ranges.first() {
9027 self.show_snippet_choices(choices, selection.clone(), cx)
9028 }
9029 }
9030
9031 // If we're already at the last tabstop and it's at the end of the snippet,
9032 // we're done, we don't need to keep the state around.
9033 if !tabstop.is_end_tabstop {
9034 let choices = tabstops
9035 .iter()
9036 .map(|tabstop| tabstop.choices.clone())
9037 .collect();
9038
9039 let ranges = tabstops
9040 .into_iter()
9041 .map(|tabstop| tabstop.ranges)
9042 .collect::<Vec<_>>();
9043
9044 self.snippet_stack.push(SnippetState {
9045 active_index: 0,
9046 ranges,
9047 choices,
9048 });
9049 }
9050
9051 // Check whether the just-entered snippet ends with an auto-closable bracket.
9052 if self.autoclose_regions.is_empty() {
9053 let snapshot = self.buffer.read(cx).snapshot(cx);
9054 for selection in &mut self.selections.all::<Point>(cx) {
9055 let selection_head = selection.head();
9056 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9057 continue;
9058 };
9059
9060 let mut bracket_pair = None;
9061 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
9062 let prev_chars = snapshot
9063 .reversed_chars_at(selection_head)
9064 .collect::<String>();
9065 for (pair, enabled) in scope.brackets() {
9066 if enabled
9067 && pair.close
9068 && prev_chars.starts_with(pair.start.as_str())
9069 && next_chars.starts_with(pair.end.as_str())
9070 {
9071 bracket_pair = Some(pair.clone());
9072 break;
9073 }
9074 }
9075 if let Some(pair) = bracket_pair {
9076 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9077 let autoclose_enabled =
9078 self.use_autoclose && snapshot_settings.use_autoclose;
9079 if autoclose_enabled {
9080 let start = snapshot.anchor_after(selection_head);
9081 let end = snapshot.anchor_after(selection_head);
9082 self.autoclose_regions.push(AutocloseRegion {
9083 selection_id: selection.id,
9084 range: start..end,
9085 pair,
9086 });
9087 }
9088 }
9089 }
9090 }
9091 }
9092 Ok(())
9093 }
9094
9095 pub fn move_to_next_snippet_tabstop(
9096 &mut self,
9097 window: &mut Window,
9098 cx: &mut Context<Self>,
9099 ) -> bool {
9100 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9101 }
9102
9103 pub fn move_to_prev_snippet_tabstop(
9104 &mut self,
9105 window: &mut Window,
9106 cx: &mut Context<Self>,
9107 ) -> bool {
9108 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9109 }
9110
9111 pub fn move_to_snippet_tabstop(
9112 &mut self,
9113 bias: Bias,
9114 window: &mut Window,
9115 cx: &mut Context<Self>,
9116 ) -> bool {
9117 if let Some(mut snippet) = self.snippet_stack.pop() {
9118 match bias {
9119 Bias::Left => {
9120 if snippet.active_index > 0 {
9121 snippet.active_index -= 1;
9122 } else {
9123 self.snippet_stack.push(snippet);
9124 return false;
9125 }
9126 }
9127 Bias::Right => {
9128 if snippet.active_index + 1 < snippet.ranges.len() {
9129 snippet.active_index += 1;
9130 } else {
9131 self.snippet_stack.push(snippet);
9132 return false;
9133 }
9134 }
9135 }
9136 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9137 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9138 s.select_ranges(current_ranges.iter().cloned())
9139 });
9140
9141 if let Some(choices) = &snippet.choices[snippet.active_index] {
9142 if let Some(selection) = current_ranges.first() {
9143 self.show_snippet_choices(&choices, selection.clone(), cx);
9144 }
9145 }
9146
9147 // If snippet state is not at the last tabstop, push it back on the stack
9148 if snippet.active_index + 1 < snippet.ranges.len() {
9149 self.snippet_stack.push(snippet);
9150 }
9151 return true;
9152 }
9153 }
9154
9155 false
9156 }
9157
9158 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9159 self.transact(window, cx, |this, window, cx| {
9160 this.select_all(&SelectAll, window, cx);
9161 this.insert("", window, cx);
9162 });
9163 }
9164
9165 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9166 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9167 self.transact(window, cx, |this, window, cx| {
9168 this.select_autoclose_pair(window, cx);
9169 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9170 if !this.linked_edit_ranges.is_empty() {
9171 let selections = this.selections.all::<MultiBufferPoint>(cx);
9172 let snapshot = this.buffer.read(cx).snapshot(cx);
9173
9174 for selection in selections.iter() {
9175 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9176 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9177 if selection_start.buffer_id != selection_end.buffer_id {
9178 continue;
9179 }
9180 if let Some(ranges) =
9181 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9182 {
9183 for (buffer, entries) in ranges {
9184 linked_ranges.entry(buffer).or_default().extend(entries);
9185 }
9186 }
9187 }
9188 }
9189
9190 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
9191 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9192 for selection in &mut selections {
9193 if selection.is_empty() {
9194 let old_head = selection.head();
9195 let mut new_head =
9196 movement::left(&display_map, old_head.to_display_point(&display_map))
9197 .to_point(&display_map);
9198 if let Some((buffer, line_buffer_range)) = display_map
9199 .buffer_snapshot
9200 .buffer_line_for_row(MultiBufferRow(old_head.row))
9201 {
9202 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9203 let indent_len = match indent_size.kind {
9204 IndentKind::Space => {
9205 buffer.settings_at(line_buffer_range.start, cx).tab_size
9206 }
9207 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9208 };
9209 if old_head.column <= indent_size.len && old_head.column > 0 {
9210 let indent_len = indent_len.get();
9211 new_head = cmp::min(
9212 new_head,
9213 MultiBufferPoint::new(
9214 old_head.row,
9215 ((old_head.column - 1) / indent_len) * indent_len,
9216 ),
9217 );
9218 }
9219 }
9220
9221 selection.set_head(new_head, SelectionGoal::None);
9222 }
9223 }
9224
9225 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9226 s.select(selections)
9227 });
9228 this.insert("", window, cx);
9229 let empty_str: Arc<str> = Arc::from("");
9230 for (buffer, edits) in linked_ranges {
9231 let snapshot = buffer.read(cx).snapshot();
9232 use text::ToPoint as TP;
9233
9234 let edits = edits
9235 .into_iter()
9236 .map(|range| {
9237 let end_point = TP::to_point(&range.end, &snapshot);
9238 let mut start_point = TP::to_point(&range.start, &snapshot);
9239
9240 if end_point == start_point {
9241 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9242 .saturating_sub(1);
9243 start_point =
9244 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9245 };
9246
9247 (start_point..end_point, empty_str.clone())
9248 })
9249 .sorted_by_key(|(range, _)| range.start)
9250 .collect::<Vec<_>>();
9251 buffer.update(cx, |this, cx| {
9252 this.edit(edits, None, cx);
9253 })
9254 }
9255 this.refresh_inline_completion(true, false, window, cx);
9256 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
9257 });
9258 }
9259
9260 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9261 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9262 self.transact(window, cx, |this, window, cx| {
9263 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9264 s.move_with(|map, selection| {
9265 if selection.is_empty() {
9266 let cursor = movement::right(map, selection.head());
9267 selection.end = cursor;
9268 selection.reversed = true;
9269 selection.goal = SelectionGoal::None;
9270 }
9271 })
9272 });
9273 this.insert("", window, cx);
9274 this.refresh_inline_completion(true, false, window, cx);
9275 });
9276 }
9277
9278 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9279 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9280 if self.move_to_prev_snippet_tabstop(window, cx) {
9281 return;
9282 }
9283 self.outdent(&Outdent, window, cx);
9284 }
9285
9286 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9287 if self.move_to_next_snippet_tabstop(window, cx) {
9288 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9289 return;
9290 }
9291 if self.read_only(cx) {
9292 return;
9293 }
9294 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9295 let mut selections = self.selections.all_adjusted(cx);
9296 let buffer = self.buffer.read(cx);
9297 let snapshot = buffer.snapshot(cx);
9298 let rows_iter = selections.iter().map(|s| s.head().row);
9299 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9300
9301 let has_some_cursor_in_whitespace = selections
9302 .iter()
9303 .filter(|selection| selection.is_empty())
9304 .any(|selection| {
9305 let cursor = selection.head();
9306 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9307 cursor.column < current_indent.len
9308 });
9309
9310 let mut edits = Vec::new();
9311 let mut prev_edited_row = 0;
9312 let mut row_delta = 0;
9313 for selection in &mut selections {
9314 if selection.start.row != prev_edited_row {
9315 row_delta = 0;
9316 }
9317 prev_edited_row = selection.end.row;
9318
9319 // If the selection is non-empty, then increase the indentation of the selected lines.
9320 if !selection.is_empty() {
9321 row_delta =
9322 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9323 continue;
9324 }
9325
9326 let cursor = selection.head();
9327 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9328 if let Some(suggested_indent) =
9329 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
9330 {
9331 // Don't do anything if already at suggested indent
9332 // and there is any other cursor which is not
9333 if has_some_cursor_in_whitespace
9334 && cursor.column == current_indent.len
9335 && current_indent.len == suggested_indent.len
9336 {
9337 continue;
9338 }
9339
9340 // Adjust line and move cursor to suggested indent
9341 // if cursor is not at suggested indent
9342 if cursor.column < suggested_indent.len
9343 && cursor.column <= current_indent.len
9344 && current_indent.len <= suggested_indent.len
9345 {
9346 selection.start = Point::new(cursor.row, suggested_indent.len);
9347 selection.end = selection.start;
9348 if row_delta == 0 {
9349 edits.extend(Buffer::edit_for_indent_size_adjustment(
9350 cursor.row,
9351 current_indent,
9352 suggested_indent,
9353 ));
9354 row_delta = suggested_indent.len - current_indent.len;
9355 }
9356 continue;
9357 }
9358
9359 // If current indent is more than suggested indent
9360 // only move cursor to current indent and skip indent
9361 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
9362 selection.start = Point::new(cursor.row, current_indent.len);
9363 selection.end = selection.start;
9364 continue;
9365 }
9366 }
9367
9368 // Otherwise, insert a hard or soft tab.
9369 let settings = buffer.language_settings_at(cursor, cx);
9370 let tab_size = if settings.hard_tabs {
9371 IndentSize::tab()
9372 } else {
9373 let tab_size = settings.tab_size.get();
9374 let indent_remainder = snapshot
9375 .text_for_range(Point::new(cursor.row, 0)..cursor)
9376 .flat_map(str::chars)
9377 .fold(row_delta % tab_size, |counter: u32, c| {
9378 if c == '\t' {
9379 0
9380 } else {
9381 (counter + 1) % tab_size
9382 }
9383 });
9384
9385 let chars_to_next_tab_stop = tab_size - indent_remainder;
9386 IndentSize::spaces(chars_to_next_tab_stop)
9387 };
9388 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
9389 selection.end = selection.start;
9390 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
9391 row_delta += tab_size.len;
9392 }
9393
9394 self.transact(window, cx, |this, window, cx| {
9395 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9396 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9397 s.select(selections)
9398 });
9399 this.refresh_inline_completion(true, false, window, cx);
9400 });
9401 }
9402
9403 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
9404 if self.read_only(cx) {
9405 return;
9406 }
9407 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9408 let mut selections = self.selections.all::<Point>(cx);
9409 let mut prev_edited_row = 0;
9410 let mut row_delta = 0;
9411 let mut edits = Vec::new();
9412 let buffer = self.buffer.read(cx);
9413 let snapshot = buffer.snapshot(cx);
9414 for selection in &mut selections {
9415 if selection.start.row != prev_edited_row {
9416 row_delta = 0;
9417 }
9418 prev_edited_row = selection.end.row;
9419
9420 row_delta =
9421 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9422 }
9423
9424 self.transact(window, cx, |this, window, cx| {
9425 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9426 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9427 s.select(selections)
9428 });
9429 });
9430 }
9431
9432 fn indent_selection(
9433 buffer: &MultiBuffer,
9434 snapshot: &MultiBufferSnapshot,
9435 selection: &mut Selection<Point>,
9436 edits: &mut Vec<(Range<Point>, String)>,
9437 delta_for_start_row: u32,
9438 cx: &App,
9439 ) -> u32 {
9440 let settings = buffer.language_settings_at(selection.start, cx);
9441 let tab_size = settings.tab_size.get();
9442 let indent_kind = if settings.hard_tabs {
9443 IndentKind::Tab
9444 } else {
9445 IndentKind::Space
9446 };
9447 let mut start_row = selection.start.row;
9448 let mut end_row = selection.end.row + 1;
9449
9450 // If a selection ends at the beginning of a line, don't indent
9451 // that last line.
9452 if selection.end.column == 0 && selection.end.row > selection.start.row {
9453 end_row -= 1;
9454 }
9455
9456 // Avoid re-indenting a row that has already been indented by a
9457 // previous selection, but still update this selection's column
9458 // to reflect that indentation.
9459 if delta_for_start_row > 0 {
9460 start_row += 1;
9461 selection.start.column += delta_for_start_row;
9462 if selection.end.row == selection.start.row {
9463 selection.end.column += delta_for_start_row;
9464 }
9465 }
9466
9467 let mut delta_for_end_row = 0;
9468 let has_multiple_rows = start_row + 1 != end_row;
9469 for row in start_row..end_row {
9470 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
9471 let indent_delta = match (current_indent.kind, indent_kind) {
9472 (IndentKind::Space, IndentKind::Space) => {
9473 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
9474 IndentSize::spaces(columns_to_next_tab_stop)
9475 }
9476 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
9477 (_, IndentKind::Tab) => IndentSize::tab(),
9478 };
9479
9480 let start = if has_multiple_rows || current_indent.len < selection.start.column {
9481 0
9482 } else {
9483 selection.start.column
9484 };
9485 let row_start = Point::new(row, start);
9486 edits.push((
9487 row_start..row_start,
9488 indent_delta.chars().collect::<String>(),
9489 ));
9490
9491 // Update this selection's endpoints to reflect the indentation.
9492 if row == selection.start.row {
9493 selection.start.column += indent_delta.len;
9494 }
9495 if row == selection.end.row {
9496 selection.end.column += indent_delta.len;
9497 delta_for_end_row = indent_delta.len;
9498 }
9499 }
9500
9501 if selection.start.row == selection.end.row {
9502 delta_for_start_row + delta_for_end_row
9503 } else {
9504 delta_for_end_row
9505 }
9506 }
9507
9508 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
9509 if self.read_only(cx) {
9510 return;
9511 }
9512 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9513 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9514 let selections = self.selections.all::<Point>(cx);
9515 let mut deletion_ranges = Vec::new();
9516 let mut last_outdent = None;
9517 {
9518 let buffer = self.buffer.read(cx);
9519 let snapshot = buffer.snapshot(cx);
9520 for selection in &selections {
9521 let settings = buffer.language_settings_at(selection.start, cx);
9522 let tab_size = settings.tab_size.get();
9523 let mut rows = selection.spanned_rows(false, &display_map);
9524
9525 // Avoid re-outdenting a row that has already been outdented by a
9526 // previous selection.
9527 if let Some(last_row) = last_outdent {
9528 if last_row == rows.start {
9529 rows.start = rows.start.next_row();
9530 }
9531 }
9532 let has_multiple_rows = rows.len() > 1;
9533 for row in rows.iter_rows() {
9534 let indent_size = snapshot.indent_size_for_line(row);
9535 if indent_size.len > 0 {
9536 let deletion_len = match indent_size.kind {
9537 IndentKind::Space => {
9538 let columns_to_prev_tab_stop = indent_size.len % tab_size;
9539 if columns_to_prev_tab_stop == 0 {
9540 tab_size
9541 } else {
9542 columns_to_prev_tab_stop
9543 }
9544 }
9545 IndentKind::Tab => 1,
9546 };
9547 let start = if has_multiple_rows
9548 || deletion_len > selection.start.column
9549 || indent_size.len < selection.start.column
9550 {
9551 0
9552 } else {
9553 selection.start.column - deletion_len
9554 };
9555 deletion_ranges.push(
9556 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
9557 );
9558 last_outdent = Some(row);
9559 }
9560 }
9561 }
9562 }
9563
9564 self.transact(window, cx, |this, window, cx| {
9565 this.buffer.update(cx, |buffer, cx| {
9566 let empty_str: Arc<str> = Arc::default();
9567 buffer.edit(
9568 deletion_ranges
9569 .into_iter()
9570 .map(|range| (range, empty_str.clone())),
9571 None,
9572 cx,
9573 );
9574 });
9575 let selections = this.selections.all::<usize>(cx);
9576 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9577 s.select(selections)
9578 });
9579 });
9580 }
9581
9582 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
9583 if self.read_only(cx) {
9584 return;
9585 }
9586 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9587 let selections = self
9588 .selections
9589 .all::<usize>(cx)
9590 .into_iter()
9591 .map(|s| s.range());
9592
9593 self.transact(window, cx, |this, window, cx| {
9594 this.buffer.update(cx, |buffer, cx| {
9595 buffer.autoindent_ranges(selections, cx);
9596 });
9597 let selections = this.selections.all::<usize>(cx);
9598 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9599 s.select(selections)
9600 });
9601 });
9602 }
9603
9604 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
9605 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9606 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9607 let selections = self.selections.all::<Point>(cx);
9608
9609 let mut new_cursors = Vec::new();
9610 let mut edit_ranges = Vec::new();
9611 let mut selections = selections.iter().peekable();
9612 while let Some(selection) = selections.next() {
9613 let mut rows = selection.spanned_rows(false, &display_map);
9614 let goal_display_column = selection.head().to_display_point(&display_map).column();
9615
9616 // Accumulate contiguous regions of rows that we want to delete.
9617 while let Some(next_selection) = selections.peek() {
9618 let next_rows = next_selection.spanned_rows(false, &display_map);
9619 if next_rows.start <= rows.end {
9620 rows.end = next_rows.end;
9621 selections.next().unwrap();
9622 } else {
9623 break;
9624 }
9625 }
9626
9627 let buffer = &display_map.buffer_snapshot;
9628 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
9629 let edit_end;
9630 let cursor_buffer_row;
9631 if buffer.max_point().row >= rows.end.0 {
9632 // If there's a line after the range, delete the \n from the end of the row range
9633 // and position the cursor on the next line.
9634 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
9635 cursor_buffer_row = rows.end;
9636 } else {
9637 // If there isn't a line after the range, delete the \n from the line before the
9638 // start of the row range and position the cursor there.
9639 edit_start = edit_start.saturating_sub(1);
9640 edit_end = buffer.len();
9641 cursor_buffer_row = rows.start.previous_row();
9642 }
9643
9644 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
9645 *cursor.column_mut() =
9646 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
9647
9648 new_cursors.push((
9649 selection.id,
9650 buffer.anchor_after(cursor.to_point(&display_map)),
9651 ));
9652 edit_ranges.push(edit_start..edit_end);
9653 }
9654
9655 self.transact(window, cx, |this, window, cx| {
9656 let buffer = this.buffer.update(cx, |buffer, cx| {
9657 let empty_str: Arc<str> = Arc::default();
9658 buffer.edit(
9659 edit_ranges
9660 .into_iter()
9661 .map(|range| (range, empty_str.clone())),
9662 None,
9663 cx,
9664 );
9665 buffer.snapshot(cx)
9666 });
9667 let new_selections = new_cursors
9668 .into_iter()
9669 .map(|(id, cursor)| {
9670 let cursor = cursor.to_point(&buffer);
9671 Selection {
9672 id,
9673 start: cursor,
9674 end: cursor,
9675 reversed: false,
9676 goal: SelectionGoal::None,
9677 }
9678 })
9679 .collect();
9680
9681 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9682 s.select(new_selections);
9683 });
9684 });
9685 }
9686
9687 pub fn join_lines_impl(
9688 &mut self,
9689 insert_whitespace: bool,
9690 window: &mut Window,
9691 cx: &mut Context<Self>,
9692 ) {
9693 if self.read_only(cx) {
9694 return;
9695 }
9696 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
9697 for selection in self.selections.all::<Point>(cx) {
9698 let start = MultiBufferRow(selection.start.row);
9699 // Treat single line selections as if they include the next line. Otherwise this action
9700 // would do nothing for single line selections individual cursors.
9701 let end = if selection.start.row == selection.end.row {
9702 MultiBufferRow(selection.start.row + 1)
9703 } else {
9704 MultiBufferRow(selection.end.row)
9705 };
9706
9707 if let Some(last_row_range) = row_ranges.last_mut() {
9708 if start <= last_row_range.end {
9709 last_row_range.end = end;
9710 continue;
9711 }
9712 }
9713 row_ranges.push(start..end);
9714 }
9715
9716 let snapshot = self.buffer.read(cx).snapshot(cx);
9717 let mut cursor_positions = Vec::new();
9718 for row_range in &row_ranges {
9719 let anchor = snapshot.anchor_before(Point::new(
9720 row_range.end.previous_row().0,
9721 snapshot.line_len(row_range.end.previous_row()),
9722 ));
9723 cursor_positions.push(anchor..anchor);
9724 }
9725
9726 self.transact(window, cx, |this, window, cx| {
9727 for row_range in row_ranges.into_iter().rev() {
9728 for row in row_range.iter_rows().rev() {
9729 let end_of_line = Point::new(row.0, snapshot.line_len(row));
9730 let next_line_row = row.next_row();
9731 let indent = snapshot.indent_size_for_line(next_line_row);
9732 let start_of_next_line = Point::new(next_line_row.0, indent.len);
9733
9734 let replace =
9735 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
9736 " "
9737 } else {
9738 ""
9739 };
9740
9741 this.buffer.update(cx, |buffer, cx| {
9742 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
9743 });
9744 }
9745 }
9746
9747 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9748 s.select_anchor_ranges(cursor_positions)
9749 });
9750 });
9751 }
9752
9753 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
9754 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9755 self.join_lines_impl(true, window, cx);
9756 }
9757
9758 pub fn sort_lines_case_sensitive(
9759 &mut self,
9760 _: &SortLinesCaseSensitive,
9761 window: &mut Window,
9762 cx: &mut Context<Self>,
9763 ) {
9764 self.manipulate_lines(window, cx, |lines| lines.sort())
9765 }
9766
9767 pub fn sort_lines_case_insensitive(
9768 &mut self,
9769 _: &SortLinesCaseInsensitive,
9770 window: &mut Window,
9771 cx: &mut Context<Self>,
9772 ) {
9773 self.manipulate_lines(window, cx, |lines| {
9774 lines.sort_by_key(|line| line.to_lowercase())
9775 })
9776 }
9777
9778 pub fn unique_lines_case_insensitive(
9779 &mut self,
9780 _: &UniqueLinesCaseInsensitive,
9781 window: &mut Window,
9782 cx: &mut Context<Self>,
9783 ) {
9784 self.manipulate_lines(window, cx, |lines| {
9785 let mut seen = HashSet::default();
9786 lines.retain(|line| seen.insert(line.to_lowercase()));
9787 })
9788 }
9789
9790 pub fn unique_lines_case_sensitive(
9791 &mut self,
9792 _: &UniqueLinesCaseSensitive,
9793 window: &mut Window,
9794 cx: &mut Context<Self>,
9795 ) {
9796 self.manipulate_lines(window, cx, |lines| {
9797 let mut seen = HashSet::default();
9798 lines.retain(|line| seen.insert(*line));
9799 })
9800 }
9801
9802 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
9803 let Some(project) = self.project.clone() else {
9804 return;
9805 };
9806 self.reload(project, window, cx)
9807 .detach_and_notify_err(window, cx);
9808 }
9809
9810 pub fn restore_file(
9811 &mut self,
9812 _: &::git::RestoreFile,
9813 window: &mut Window,
9814 cx: &mut Context<Self>,
9815 ) {
9816 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9817 let mut buffer_ids = HashSet::default();
9818 let snapshot = self.buffer().read(cx).snapshot(cx);
9819 for selection in self.selections.all::<usize>(cx) {
9820 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
9821 }
9822
9823 let buffer = self.buffer().read(cx);
9824 let ranges = buffer_ids
9825 .into_iter()
9826 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
9827 .collect::<Vec<_>>();
9828
9829 self.restore_hunks_in_ranges(ranges, window, cx);
9830 }
9831
9832 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
9833 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9834 let selections = self
9835 .selections
9836 .all(cx)
9837 .into_iter()
9838 .map(|s| s.range())
9839 .collect();
9840 self.restore_hunks_in_ranges(selections, window, cx);
9841 }
9842
9843 pub fn restore_hunks_in_ranges(
9844 &mut self,
9845 ranges: Vec<Range<Point>>,
9846 window: &mut Window,
9847 cx: &mut Context<Editor>,
9848 ) {
9849 let mut revert_changes = HashMap::default();
9850 let chunk_by = self
9851 .snapshot(window, cx)
9852 .hunks_for_ranges(ranges)
9853 .into_iter()
9854 .chunk_by(|hunk| hunk.buffer_id);
9855 for (buffer_id, hunks) in &chunk_by {
9856 let hunks = hunks.collect::<Vec<_>>();
9857 for hunk in &hunks {
9858 self.prepare_restore_change(&mut revert_changes, hunk, cx);
9859 }
9860 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
9861 }
9862 drop(chunk_by);
9863 if !revert_changes.is_empty() {
9864 self.transact(window, cx, |editor, window, cx| {
9865 editor.restore(revert_changes, window, cx);
9866 });
9867 }
9868 }
9869
9870 pub fn open_active_item_in_terminal(
9871 &mut self,
9872 _: &OpenInTerminal,
9873 window: &mut Window,
9874 cx: &mut Context<Self>,
9875 ) {
9876 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
9877 let project_path = buffer.read(cx).project_path(cx)?;
9878 let project = self.project.as_ref()?.read(cx);
9879 let entry = project.entry_for_path(&project_path, cx)?;
9880 let parent = match &entry.canonical_path {
9881 Some(canonical_path) => canonical_path.to_path_buf(),
9882 None => project.absolute_path(&project_path, cx)?,
9883 }
9884 .parent()?
9885 .to_path_buf();
9886 Some(parent)
9887 }) {
9888 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
9889 }
9890 }
9891
9892 fn set_breakpoint_context_menu(
9893 &mut self,
9894 display_row: DisplayRow,
9895 position: Option<Anchor>,
9896 clicked_point: gpui::Point<Pixels>,
9897 window: &mut Window,
9898 cx: &mut Context<Self>,
9899 ) {
9900 if !cx.has_flag::<DebuggerFeatureFlag>() {
9901 return;
9902 }
9903 let source = self
9904 .buffer
9905 .read(cx)
9906 .snapshot(cx)
9907 .anchor_before(Point::new(display_row.0, 0u32));
9908
9909 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
9910
9911 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
9912 self,
9913 source,
9914 clicked_point,
9915 context_menu,
9916 window,
9917 cx,
9918 );
9919 }
9920
9921 fn add_edit_breakpoint_block(
9922 &mut self,
9923 anchor: Anchor,
9924 breakpoint: &Breakpoint,
9925 edit_action: BreakpointPromptEditAction,
9926 window: &mut Window,
9927 cx: &mut Context<Self>,
9928 ) {
9929 let weak_editor = cx.weak_entity();
9930 let bp_prompt = cx.new(|cx| {
9931 BreakpointPromptEditor::new(
9932 weak_editor,
9933 anchor,
9934 breakpoint.clone(),
9935 edit_action,
9936 window,
9937 cx,
9938 )
9939 });
9940
9941 let height = bp_prompt.update(cx, |this, cx| {
9942 this.prompt
9943 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
9944 });
9945 let cloned_prompt = bp_prompt.clone();
9946 let blocks = vec![BlockProperties {
9947 style: BlockStyle::Sticky,
9948 placement: BlockPlacement::Above(anchor),
9949 height: Some(height),
9950 render: Arc::new(move |cx| {
9951 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
9952 cloned_prompt.clone().into_any_element()
9953 }),
9954 priority: 0,
9955 render_in_minimap: true,
9956 }];
9957
9958 let focus_handle = bp_prompt.focus_handle(cx);
9959 window.focus(&focus_handle);
9960
9961 let block_ids = self.insert_blocks(blocks, None, cx);
9962 bp_prompt.update(cx, |prompt, _| {
9963 prompt.add_block_ids(block_ids);
9964 });
9965 }
9966
9967 pub(crate) fn breakpoint_at_row(
9968 &self,
9969 row: u32,
9970 window: &mut Window,
9971 cx: &mut Context<Self>,
9972 ) -> Option<(Anchor, Breakpoint)> {
9973 let snapshot = self.snapshot(window, cx);
9974 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
9975
9976 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
9977 }
9978
9979 pub(crate) fn breakpoint_at_anchor(
9980 &self,
9981 breakpoint_position: Anchor,
9982 snapshot: &EditorSnapshot,
9983 cx: &mut Context<Self>,
9984 ) -> Option<(Anchor, Breakpoint)> {
9985 let project = self.project.clone()?;
9986
9987 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
9988 snapshot
9989 .buffer_snapshot
9990 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
9991 })?;
9992
9993 let enclosing_excerpt = breakpoint_position.excerpt_id;
9994 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
9995 let buffer_snapshot = buffer.read(cx).snapshot();
9996
9997 let row = buffer_snapshot
9998 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
9999 .row;
10000
10001 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
10002 let anchor_end = snapshot
10003 .buffer_snapshot
10004 .anchor_after(Point::new(row, line_len));
10005
10006 let bp = self
10007 .breakpoint_store
10008 .as_ref()?
10009 .read_with(cx, |breakpoint_store, cx| {
10010 breakpoint_store
10011 .breakpoints(
10012 &buffer,
10013 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10014 &buffer_snapshot,
10015 cx,
10016 )
10017 .next()
10018 .and_then(|(bp, _)| {
10019 let breakpoint_row = buffer_snapshot
10020 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10021 .row;
10022
10023 if breakpoint_row == row {
10024 snapshot
10025 .buffer_snapshot
10026 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10027 .map(|position| (position, bp.bp.clone()))
10028 } else {
10029 None
10030 }
10031 })
10032 });
10033 bp
10034 }
10035
10036 pub fn edit_log_breakpoint(
10037 &mut self,
10038 _: &EditLogBreakpoint,
10039 window: &mut Window,
10040 cx: &mut Context<Self>,
10041 ) {
10042 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10043 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10044 message: None,
10045 state: BreakpointState::Enabled,
10046 condition: None,
10047 hit_condition: None,
10048 });
10049
10050 self.add_edit_breakpoint_block(
10051 anchor,
10052 &breakpoint,
10053 BreakpointPromptEditAction::Log,
10054 window,
10055 cx,
10056 );
10057 }
10058 }
10059
10060 fn breakpoints_at_cursors(
10061 &self,
10062 window: &mut Window,
10063 cx: &mut Context<Self>,
10064 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10065 let snapshot = self.snapshot(window, cx);
10066 let cursors = self
10067 .selections
10068 .disjoint_anchors()
10069 .into_iter()
10070 .map(|selection| {
10071 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
10072
10073 let breakpoint_position = self
10074 .breakpoint_at_row(cursor_position.row, window, cx)
10075 .map(|bp| bp.0)
10076 .unwrap_or_else(|| {
10077 snapshot
10078 .display_snapshot
10079 .buffer_snapshot
10080 .anchor_after(Point::new(cursor_position.row, 0))
10081 });
10082
10083 let breakpoint = self
10084 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10085 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10086
10087 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10088 })
10089 // 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.
10090 .collect::<HashMap<Anchor, _>>();
10091
10092 cursors.into_iter().collect()
10093 }
10094
10095 pub fn enable_breakpoint(
10096 &mut self,
10097 _: &crate::actions::EnableBreakpoint,
10098 window: &mut Window,
10099 cx: &mut Context<Self>,
10100 ) {
10101 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10102 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10103 continue;
10104 };
10105 self.edit_breakpoint_at_anchor(
10106 anchor,
10107 breakpoint,
10108 BreakpointEditAction::InvertState,
10109 cx,
10110 );
10111 }
10112 }
10113
10114 pub fn disable_breakpoint(
10115 &mut self,
10116 _: &crate::actions::DisableBreakpoint,
10117 window: &mut Window,
10118 cx: &mut Context<Self>,
10119 ) {
10120 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10121 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10122 continue;
10123 };
10124 self.edit_breakpoint_at_anchor(
10125 anchor,
10126 breakpoint,
10127 BreakpointEditAction::InvertState,
10128 cx,
10129 );
10130 }
10131 }
10132
10133 pub fn toggle_breakpoint(
10134 &mut self,
10135 _: &crate::actions::ToggleBreakpoint,
10136 window: &mut Window,
10137 cx: &mut Context<Self>,
10138 ) {
10139 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10140 if let Some(breakpoint) = breakpoint {
10141 self.edit_breakpoint_at_anchor(
10142 anchor,
10143 breakpoint,
10144 BreakpointEditAction::Toggle,
10145 cx,
10146 );
10147 } else {
10148 self.edit_breakpoint_at_anchor(
10149 anchor,
10150 Breakpoint::new_standard(),
10151 BreakpointEditAction::Toggle,
10152 cx,
10153 );
10154 }
10155 }
10156 }
10157
10158 pub fn edit_breakpoint_at_anchor(
10159 &mut self,
10160 breakpoint_position: Anchor,
10161 breakpoint: Breakpoint,
10162 edit_action: BreakpointEditAction,
10163 cx: &mut Context<Self>,
10164 ) {
10165 let Some(breakpoint_store) = &self.breakpoint_store else {
10166 return;
10167 };
10168
10169 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
10170 if breakpoint_position == Anchor::min() {
10171 self.buffer()
10172 .read(cx)
10173 .excerpt_buffer_ids()
10174 .into_iter()
10175 .next()
10176 } else {
10177 None
10178 }
10179 }) else {
10180 return;
10181 };
10182
10183 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
10184 return;
10185 };
10186
10187 breakpoint_store.update(cx, |breakpoint_store, cx| {
10188 breakpoint_store.toggle_breakpoint(
10189 buffer,
10190 BreakpointWithPosition {
10191 position: breakpoint_position.text_anchor,
10192 bp: breakpoint,
10193 },
10194 edit_action,
10195 cx,
10196 );
10197 });
10198
10199 cx.notify();
10200 }
10201
10202 #[cfg(any(test, feature = "test-support"))]
10203 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10204 self.breakpoint_store.clone()
10205 }
10206
10207 pub fn prepare_restore_change(
10208 &self,
10209 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10210 hunk: &MultiBufferDiffHunk,
10211 cx: &mut App,
10212 ) -> Option<()> {
10213 if hunk.is_created_file() {
10214 return None;
10215 }
10216 let buffer = self.buffer.read(cx);
10217 let diff = buffer.diff_for(hunk.buffer_id)?;
10218 let buffer = buffer.buffer(hunk.buffer_id)?;
10219 let buffer = buffer.read(cx);
10220 let original_text = diff
10221 .read(cx)
10222 .base_text()
10223 .as_rope()
10224 .slice(hunk.diff_base_byte_range.clone());
10225 let buffer_snapshot = buffer.snapshot();
10226 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
10227 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
10228 probe
10229 .0
10230 .start
10231 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
10232 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
10233 }) {
10234 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
10235 Some(())
10236 } else {
10237 None
10238 }
10239 }
10240
10241 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
10242 self.manipulate_lines(window, cx, |lines| lines.reverse())
10243 }
10244
10245 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
10246 self.manipulate_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
10247 }
10248
10249 fn manipulate_lines<Fn>(
10250 &mut self,
10251 window: &mut Window,
10252 cx: &mut Context<Self>,
10253 mut callback: Fn,
10254 ) where
10255 Fn: FnMut(&mut Vec<&str>),
10256 {
10257 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10258
10259 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10260 let buffer = self.buffer.read(cx).snapshot(cx);
10261
10262 let mut edits = Vec::new();
10263
10264 let selections = self.selections.all::<Point>(cx);
10265 let mut selections = selections.iter().peekable();
10266 let mut contiguous_row_selections = Vec::new();
10267 let mut new_selections = Vec::new();
10268 let mut added_lines = 0;
10269 let mut removed_lines = 0;
10270
10271 while let Some(selection) = selections.next() {
10272 let (start_row, end_row) = consume_contiguous_rows(
10273 &mut contiguous_row_selections,
10274 selection,
10275 &display_map,
10276 &mut selections,
10277 );
10278
10279 let start_point = Point::new(start_row.0, 0);
10280 let end_point = Point::new(
10281 end_row.previous_row().0,
10282 buffer.line_len(end_row.previous_row()),
10283 );
10284 let text = buffer
10285 .text_for_range(start_point..end_point)
10286 .collect::<String>();
10287
10288 let mut lines = text.split('\n').collect_vec();
10289
10290 let lines_before = lines.len();
10291 callback(&mut lines);
10292 let lines_after = lines.len();
10293
10294 edits.push((start_point..end_point, lines.join("\n")));
10295
10296 // Selections must change based on added and removed line count
10297 let start_row =
10298 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
10299 let end_row = MultiBufferRow(start_row.0 + lines_after.saturating_sub(1) as u32);
10300 new_selections.push(Selection {
10301 id: selection.id,
10302 start: start_row,
10303 end: end_row,
10304 goal: SelectionGoal::None,
10305 reversed: selection.reversed,
10306 });
10307
10308 if lines_after > lines_before {
10309 added_lines += lines_after - lines_before;
10310 } else if lines_before > lines_after {
10311 removed_lines += lines_before - lines_after;
10312 }
10313 }
10314
10315 self.transact(window, cx, |this, window, cx| {
10316 let buffer = this.buffer.update(cx, |buffer, cx| {
10317 buffer.edit(edits, None, cx);
10318 buffer.snapshot(cx)
10319 });
10320
10321 // Recalculate offsets on newly edited buffer
10322 let new_selections = new_selections
10323 .iter()
10324 .map(|s| {
10325 let start_point = Point::new(s.start.0, 0);
10326 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
10327 Selection {
10328 id: s.id,
10329 start: buffer.point_to_offset(start_point),
10330 end: buffer.point_to_offset(end_point),
10331 goal: s.goal,
10332 reversed: s.reversed,
10333 }
10334 })
10335 .collect();
10336
10337 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10338 s.select(new_selections);
10339 });
10340
10341 this.request_autoscroll(Autoscroll::fit(), cx);
10342 });
10343 }
10344
10345 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
10346 self.manipulate_text(window, cx, |text| {
10347 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
10348 if has_upper_case_characters {
10349 text.to_lowercase()
10350 } else {
10351 text.to_uppercase()
10352 }
10353 })
10354 }
10355
10356 pub fn convert_to_upper_case(
10357 &mut self,
10358 _: &ConvertToUpperCase,
10359 window: &mut Window,
10360 cx: &mut Context<Self>,
10361 ) {
10362 self.manipulate_text(window, cx, |text| text.to_uppercase())
10363 }
10364
10365 pub fn convert_to_lower_case(
10366 &mut self,
10367 _: &ConvertToLowerCase,
10368 window: &mut Window,
10369 cx: &mut Context<Self>,
10370 ) {
10371 self.manipulate_text(window, cx, |text| text.to_lowercase())
10372 }
10373
10374 pub fn convert_to_title_case(
10375 &mut self,
10376 _: &ConvertToTitleCase,
10377 window: &mut Window,
10378 cx: &mut Context<Self>,
10379 ) {
10380 self.manipulate_text(window, cx, |text| {
10381 text.split('\n')
10382 .map(|line| line.to_case(Case::Title))
10383 .join("\n")
10384 })
10385 }
10386
10387 pub fn convert_to_snake_case(
10388 &mut self,
10389 _: &ConvertToSnakeCase,
10390 window: &mut Window,
10391 cx: &mut Context<Self>,
10392 ) {
10393 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
10394 }
10395
10396 pub fn convert_to_kebab_case(
10397 &mut self,
10398 _: &ConvertToKebabCase,
10399 window: &mut Window,
10400 cx: &mut Context<Self>,
10401 ) {
10402 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
10403 }
10404
10405 pub fn convert_to_upper_camel_case(
10406 &mut self,
10407 _: &ConvertToUpperCamelCase,
10408 window: &mut Window,
10409 cx: &mut Context<Self>,
10410 ) {
10411 self.manipulate_text(window, cx, |text| {
10412 text.split('\n')
10413 .map(|line| line.to_case(Case::UpperCamel))
10414 .join("\n")
10415 })
10416 }
10417
10418 pub fn convert_to_lower_camel_case(
10419 &mut self,
10420 _: &ConvertToLowerCamelCase,
10421 window: &mut Window,
10422 cx: &mut Context<Self>,
10423 ) {
10424 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
10425 }
10426
10427 pub fn convert_to_opposite_case(
10428 &mut self,
10429 _: &ConvertToOppositeCase,
10430 window: &mut Window,
10431 cx: &mut Context<Self>,
10432 ) {
10433 self.manipulate_text(window, cx, |text| {
10434 text.chars()
10435 .fold(String::with_capacity(text.len()), |mut t, c| {
10436 if c.is_uppercase() {
10437 t.extend(c.to_lowercase());
10438 } else {
10439 t.extend(c.to_uppercase());
10440 }
10441 t
10442 })
10443 })
10444 }
10445
10446 pub fn convert_to_rot13(
10447 &mut self,
10448 _: &ConvertToRot13,
10449 window: &mut Window,
10450 cx: &mut Context<Self>,
10451 ) {
10452 self.manipulate_text(window, cx, |text| {
10453 text.chars()
10454 .map(|c| match c {
10455 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
10456 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
10457 _ => c,
10458 })
10459 .collect()
10460 })
10461 }
10462
10463 pub fn convert_to_rot47(
10464 &mut self,
10465 _: &ConvertToRot47,
10466 window: &mut Window,
10467 cx: &mut Context<Self>,
10468 ) {
10469 self.manipulate_text(window, cx, |text| {
10470 text.chars()
10471 .map(|c| {
10472 let code_point = c as u32;
10473 if code_point >= 33 && code_point <= 126 {
10474 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
10475 }
10476 c
10477 })
10478 .collect()
10479 })
10480 }
10481
10482 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
10483 where
10484 Fn: FnMut(&str) -> String,
10485 {
10486 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10487 let buffer = self.buffer.read(cx).snapshot(cx);
10488
10489 let mut new_selections = Vec::new();
10490 let mut edits = Vec::new();
10491 let mut selection_adjustment = 0i32;
10492
10493 for selection in self.selections.all::<usize>(cx) {
10494 let selection_is_empty = selection.is_empty();
10495
10496 let (start, end) = if selection_is_empty {
10497 let word_range = movement::surrounding_word(
10498 &display_map,
10499 selection.start.to_display_point(&display_map),
10500 );
10501 let start = word_range.start.to_offset(&display_map, Bias::Left);
10502 let end = word_range.end.to_offset(&display_map, Bias::Left);
10503 (start, end)
10504 } else {
10505 (selection.start, selection.end)
10506 };
10507
10508 let text = buffer.text_for_range(start..end).collect::<String>();
10509 let old_length = text.len() as i32;
10510 let text = callback(&text);
10511
10512 new_selections.push(Selection {
10513 start: (start as i32 - selection_adjustment) as usize,
10514 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
10515 goal: SelectionGoal::None,
10516 ..selection
10517 });
10518
10519 selection_adjustment += old_length - text.len() as i32;
10520
10521 edits.push((start..end, text));
10522 }
10523
10524 self.transact(window, cx, |this, window, cx| {
10525 this.buffer.update(cx, |buffer, cx| {
10526 buffer.edit(edits, None, cx);
10527 });
10528
10529 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10530 s.select(new_selections);
10531 });
10532
10533 this.request_autoscroll(Autoscroll::fit(), cx);
10534 });
10535 }
10536
10537 pub fn duplicate(
10538 &mut self,
10539 upwards: bool,
10540 whole_lines: bool,
10541 window: &mut Window,
10542 cx: &mut Context<Self>,
10543 ) {
10544 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10545
10546 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10547 let buffer = &display_map.buffer_snapshot;
10548 let selections = self.selections.all::<Point>(cx);
10549
10550 let mut edits = Vec::new();
10551 let mut selections_iter = selections.iter().peekable();
10552 while let Some(selection) = selections_iter.next() {
10553 let mut rows = selection.spanned_rows(false, &display_map);
10554 // duplicate line-wise
10555 if whole_lines || selection.start == selection.end {
10556 // Avoid duplicating the same lines twice.
10557 while let Some(next_selection) = selections_iter.peek() {
10558 let next_rows = next_selection.spanned_rows(false, &display_map);
10559 if next_rows.start < rows.end {
10560 rows.end = next_rows.end;
10561 selections_iter.next().unwrap();
10562 } else {
10563 break;
10564 }
10565 }
10566
10567 // Copy the text from the selected row region and splice it either at the start
10568 // or end of the region.
10569 let start = Point::new(rows.start.0, 0);
10570 let end = Point::new(
10571 rows.end.previous_row().0,
10572 buffer.line_len(rows.end.previous_row()),
10573 );
10574 let text = buffer
10575 .text_for_range(start..end)
10576 .chain(Some("\n"))
10577 .collect::<String>();
10578 let insert_location = if upwards {
10579 Point::new(rows.end.0, 0)
10580 } else {
10581 start
10582 };
10583 edits.push((insert_location..insert_location, text));
10584 } else {
10585 // duplicate character-wise
10586 let start = selection.start;
10587 let end = selection.end;
10588 let text = buffer.text_for_range(start..end).collect::<String>();
10589 edits.push((selection.end..selection.end, text));
10590 }
10591 }
10592
10593 self.transact(window, cx, |this, _, cx| {
10594 this.buffer.update(cx, |buffer, cx| {
10595 buffer.edit(edits, None, cx);
10596 });
10597
10598 this.request_autoscroll(Autoscroll::fit(), cx);
10599 });
10600 }
10601
10602 pub fn duplicate_line_up(
10603 &mut self,
10604 _: &DuplicateLineUp,
10605 window: &mut Window,
10606 cx: &mut Context<Self>,
10607 ) {
10608 self.duplicate(true, true, window, cx);
10609 }
10610
10611 pub fn duplicate_line_down(
10612 &mut self,
10613 _: &DuplicateLineDown,
10614 window: &mut Window,
10615 cx: &mut Context<Self>,
10616 ) {
10617 self.duplicate(false, true, window, cx);
10618 }
10619
10620 pub fn duplicate_selection(
10621 &mut self,
10622 _: &DuplicateSelection,
10623 window: &mut Window,
10624 cx: &mut Context<Self>,
10625 ) {
10626 self.duplicate(false, false, window, cx);
10627 }
10628
10629 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
10630 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10631
10632 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10633 let buffer = self.buffer.read(cx).snapshot(cx);
10634
10635 let mut edits = Vec::new();
10636 let mut unfold_ranges = Vec::new();
10637 let mut refold_creases = Vec::new();
10638
10639 let selections = self.selections.all::<Point>(cx);
10640 let mut selections = selections.iter().peekable();
10641 let mut contiguous_row_selections = Vec::new();
10642 let mut new_selections = Vec::new();
10643
10644 while let Some(selection) = selections.next() {
10645 // Find all the selections that span a contiguous row range
10646 let (start_row, end_row) = consume_contiguous_rows(
10647 &mut contiguous_row_selections,
10648 selection,
10649 &display_map,
10650 &mut selections,
10651 );
10652
10653 // Move the text spanned by the row range to be before the line preceding the row range
10654 if start_row.0 > 0 {
10655 let range_to_move = Point::new(
10656 start_row.previous_row().0,
10657 buffer.line_len(start_row.previous_row()),
10658 )
10659 ..Point::new(
10660 end_row.previous_row().0,
10661 buffer.line_len(end_row.previous_row()),
10662 );
10663 let insertion_point = display_map
10664 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
10665 .0;
10666
10667 // Don't move lines across excerpts
10668 if buffer
10669 .excerpt_containing(insertion_point..range_to_move.end)
10670 .is_some()
10671 {
10672 let text = buffer
10673 .text_for_range(range_to_move.clone())
10674 .flat_map(|s| s.chars())
10675 .skip(1)
10676 .chain(['\n'])
10677 .collect::<String>();
10678
10679 edits.push((
10680 buffer.anchor_after(range_to_move.start)
10681 ..buffer.anchor_before(range_to_move.end),
10682 String::new(),
10683 ));
10684 let insertion_anchor = buffer.anchor_after(insertion_point);
10685 edits.push((insertion_anchor..insertion_anchor, text));
10686
10687 let row_delta = range_to_move.start.row - insertion_point.row + 1;
10688
10689 // Move selections up
10690 new_selections.extend(contiguous_row_selections.drain(..).map(
10691 |mut selection| {
10692 selection.start.row -= row_delta;
10693 selection.end.row -= row_delta;
10694 selection
10695 },
10696 ));
10697
10698 // Move folds up
10699 unfold_ranges.push(range_to_move.clone());
10700 for fold in display_map.folds_in_range(
10701 buffer.anchor_before(range_to_move.start)
10702 ..buffer.anchor_after(range_to_move.end),
10703 ) {
10704 let mut start = fold.range.start.to_point(&buffer);
10705 let mut end = fold.range.end.to_point(&buffer);
10706 start.row -= row_delta;
10707 end.row -= row_delta;
10708 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10709 }
10710 }
10711 }
10712
10713 // If we didn't move line(s), preserve the existing selections
10714 new_selections.append(&mut contiguous_row_selections);
10715 }
10716
10717 self.transact(window, cx, |this, window, cx| {
10718 this.unfold_ranges(&unfold_ranges, true, true, cx);
10719 this.buffer.update(cx, |buffer, cx| {
10720 for (range, text) in edits {
10721 buffer.edit([(range, text)], None, cx);
10722 }
10723 });
10724 this.fold_creases(refold_creases, true, window, cx);
10725 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10726 s.select(new_selections);
10727 })
10728 });
10729 }
10730
10731 pub fn move_line_down(
10732 &mut self,
10733 _: &MoveLineDown,
10734 window: &mut Window,
10735 cx: &mut Context<Self>,
10736 ) {
10737 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10738
10739 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10740 let buffer = self.buffer.read(cx).snapshot(cx);
10741
10742 let mut edits = Vec::new();
10743 let mut unfold_ranges = Vec::new();
10744 let mut refold_creases = Vec::new();
10745
10746 let selections = self.selections.all::<Point>(cx);
10747 let mut selections = selections.iter().peekable();
10748 let mut contiguous_row_selections = Vec::new();
10749 let mut new_selections = Vec::new();
10750
10751 while let Some(selection) = selections.next() {
10752 // Find all the selections that span a contiguous row range
10753 let (start_row, end_row) = consume_contiguous_rows(
10754 &mut contiguous_row_selections,
10755 selection,
10756 &display_map,
10757 &mut selections,
10758 );
10759
10760 // Move the text spanned by the row range to be after the last line of the row range
10761 if end_row.0 <= buffer.max_point().row {
10762 let range_to_move =
10763 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
10764 let insertion_point = display_map
10765 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
10766 .0;
10767
10768 // Don't move lines across excerpt boundaries
10769 if buffer
10770 .excerpt_containing(range_to_move.start..insertion_point)
10771 .is_some()
10772 {
10773 let mut text = String::from("\n");
10774 text.extend(buffer.text_for_range(range_to_move.clone()));
10775 text.pop(); // Drop trailing newline
10776 edits.push((
10777 buffer.anchor_after(range_to_move.start)
10778 ..buffer.anchor_before(range_to_move.end),
10779 String::new(),
10780 ));
10781 let insertion_anchor = buffer.anchor_after(insertion_point);
10782 edits.push((insertion_anchor..insertion_anchor, text));
10783
10784 let row_delta = insertion_point.row - range_to_move.end.row + 1;
10785
10786 // Move selections down
10787 new_selections.extend(contiguous_row_selections.drain(..).map(
10788 |mut selection| {
10789 selection.start.row += row_delta;
10790 selection.end.row += row_delta;
10791 selection
10792 },
10793 ));
10794
10795 // Move folds down
10796 unfold_ranges.push(range_to_move.clone());
10797 for fold in display_map.folds_in_range(
10798 buffer.anchor_before(range_to_move.start)
10799 ..buffer.anchor_after(range_to_move.end),
10800 ) {
10801 let mut start = fold.range.start.to_point(&buffer);
10802 let mut end = fold.range.end.to_point(&buffer);
10803 start.row += row_delta;
10804 end.row += row_delta;
10805 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10806 }
10807 }
10808 }
10809
10810 // If we didn't move line(s), preserve the existing selections
10811 new_selections.append(&mut contiguous_row_selections);
10812 }
10813
10814 self.transact(window, cx, |this, window, cx| {
10815 this.unfold_ranges(&unfold_ranges, true, true, cx);
10816 this.buffer.update(cx, |buffer, cx| {
10817 for (range, text) in edits {
10818 buffer.edit([(range, text)], None, cx);
10819 }
10820 });
10821 this.fold_creases(refold_creases, true, window, cx);
10822 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10823 s.select(new_selections)
10824 });
10825 });
10826 }
10827
10828 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
10829 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10830 let text_layout_details = &self.text_layout_details(window);
10831 self.transact(window, cx, |this, window, cx| {
10832 let edits = this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10833 let mut edits: Vec<(Range<usize>, String)> = Default::default();
10834 s.move_with(|display_map, selection| {
10835 if !selection.is_empty() {
10836 return;
10837 }
10838
10839 let mut head = selection.head();
10840 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
10841 if head.column() == display_map.line_len(head.row()) {
10842 transpose_offset = display_map
10843 .buffer_snapshot
10844 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
10845 }
10846
10847 if transpose_offset == 0 {
10848 return;
10849 }
10850
10851 *head.column_mut() += 1;
10852 head = display_map.clip_point(head, Bias::Right);
10853 let goal = SelectionGoal::HorizontalPosition(
10854 display_map
10855 .x_for_display_point(head, text_layout_details)
10856 .into(),
10857 );
10858 selection.collapse_to(head, goal);
10859
10860 let transpose_start = display_map
10861 .buffer_snapshot
10862 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
10863 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
10864 let transpose_end = display_map
10865 .buffer_snapshot
10866 .clip_offset(transpose_offset + 1, Bias::Right);
10867 if let Some(ch) =
10868 display_map.buffer_snapshot.chars_at(transpose_start).next()
10869 {
10870 edits.push((transpose_start..transpose_offset, String::new()));
10871 edits.push((transpose_end..transpose_end, ch.to_string()));
10872 }
10873 }
10874 });
10875 edits
10876 });
10877 this.buffer
10878 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
10879 let selections = this.selections.all::<usize>(cx);
10880 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10881 s.select(selections);
10882 });
10883 });
10884 }
10885
10886 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
10887 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10888 self.rewrap_impl(RewrapOptions::default(), cx)
10889 }
10890
10891 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
10892 let buffer = self.buffer.read(cx).snapshot(cx);
10893 let selections = self.selections.all::<Point>(cx);
10894 let mut selections = selections.iter().peekable();
10895
10896 let mut edits = Vec::new();
10897 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
10898
10899 while let Some(selection) = selections.next() {
10900 let mut start_row = selection.start.row;
10901 let mut end_row = selection.end.row;
10902
10903 // Skip selections that overlap with a range that has already been rewrapped.
10904 let selection_range = start_row..end_row;
10905 if rewrapped_row_ranges
10906 .iter()
10907 .any(|range| range.overlaps(&selection_range))
10908 {
10909 continue;
10910 }
10911
10912 let tab_size = buffer.language_settings_at(selection.head(), cx).tab_size;
10913
10914 // Since not all lines in the selection may be at the same indent
10915 // level, choose the indent size that is the most common between all
10916 // of the lines.
10917 //
10918 // If there is a tie, we use the deepest indent.
10919 let (indent_size, indent_end) = {
10920 let mut indent_size_occurrences = HashMap::default();
10921 let mut rows_by_indent_size = HashMap::<IndentSize, Vec<u32>>::default();
10922
10923 for row in start_row..=end_row {
10924 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
10925 rows_by_indent_size.entry(indent).or_default().push(row);
10926 *indent_size_occurrences.entry(indent).or_insert(0) += 1;
10927 }
10928
10929 let indent_size = indent_size_occurrences
10930 .into_iter()
10931 .max_by_key(|(indent, count)| (*count, indent.len_with_expanded_tabs(tab_size)))
10932 .map(|(indent, _)| indent)
10933 .unwrap_or_default();
10934 let row = rows_by_indent_size[&indent_size][0];
10935 let indent_end = Point::new(row, indent_size.len);
10936
10937 (indent_size, indent_end)
10938 };
10939
10940 let mut line_prefix = indent_size.chars().collect::<String>();
10941
10942 let mut inside_comment = false;
10943 if let Some(comment_prefix) =
10944 buffer
10945 .language_scope_at(selection.head())
10946 .and_then(|language| {
10947 language
10948 .line_comment_prefixes()
10949 .iter()
10950 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
10951 .cloned()
10952 })
10953 {
10954 line_prefix.push_str(&comment_prefix);
10955 inside_comment = true;
10956 }
10957
10958 let language_settings = buffer.language_settings_at(selection.head(), cx);
10959 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
10960 RewrapBehavior::InComments => inside_comment,
10961 RewrapBehavior::InSelections => !selection.is_empty(),
10962 RewrapBehavior::Anywhere => true,
10963 };
10964
10965 let should_rewrap = options.override_language_settings
10966 || allow_rewrap_based_on_language
10967 || self.hard_wrap.is_some();
10968 if !should_rewrap {
10969 continue;
10970 }
10971
10972 if selection.is_empty() {
10973 'expand_upwards: while start_row > 0 {
10974 let prev_row = start_row - 1;
10975 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
10976 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
10977 {
10978 start_row = prev_row;
10979 } else {
10980 break 'expand_upwards;
10981 }
10982 }
10983
10984 'expand_downwards: while end_row < buffer.max_point().row {
10985 let next_row = end_row + 1;
10986 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
10987 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
10988 {
10989 end_row = next_row;
10990 } else {
10991 break 'expand_downwards;
10992 }
10993 }
10994 }
10995
10996 let start = Point::new(start_row, 0);
10997 let start_offset = start.to_offset(&buffer);
10998 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
10999 let selection_text = buffer.text_for_range(start..end).collect::<String>();
11000 let Some(lines_without_prefixes) = selection_text
11001 .lines()
11002 .map(|line| {
11003 line.strip_prefix(&line_prefix)
11004 .or_else(|| line.trim_start().strip_prefix(&line_prefix.trim_start()))
11005 .with_context(|| {
11006 format!("line did not start with prefix {line_prefix:?}: {line:?}")
11007 })
11008 })
11009 .collect::<Result<Vec<_>, _>>()
11010 .log_err()
11011 else {
11012 continue;
11013 };
11014
11015 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
11016 buffer
11017 .language_settings_at(Point::new(start_row, 0), cx)
11018 .preferred_line_length as usize
11019 });
11020 let wrapped_text = wrap_with_prefix(
11021 line_prefix,
11022 lines_without_prefixes.join("\n"),
11023 wrap_column,
11024 tab_size,
11025 options.preserve_existing_whitespace,
11026 );
11027
11028 // TODO: should always use char-based diff while still supporting cursor behavior that
11029 // matches vim.
11030 let mut diff_options = DiffOptions::default();
11031 if options.override_language_settings {
11032 diff_options.max_word_diff_len = 0;
11033 diff_options.max_word_diff_line_count = 0;
11034 } else {
11035 diff_options.max_word_diff_len = usize::MAX;
11036 diff_options.max_word_diff_line_count = usize::MAX;
11037 }
11038
11039 for (old_range, new_text) in
11040 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
11041 {
11042 let edit_start = buffer.anchor_after(start_offset + old_range.start);
11043 let edit_end = buffer.anchor_after(start_offset + old_range.end);
11044 edits.push((edit_start..edit_end, new_text));
11045 }
11046
11047 rewrapped_row_ranges.push(start_row..=end_row);
11048 }
11049
11050 self.buffer
11051 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11052 }
11053
11054 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
11055 let mut text = String::new();
11056 let buffer = self.buffer.read(cx).snapshot(cx);
11057 let mut selections = self.selections.all::<Point>(cx);
11058 let mut clipboard_selections = Vec::with_capacity(selections.len());
11059 {
11060 let max_point = buffer.max_point();
11061 let mut is_first = true;
11062 for selection in &mut selections {
11063 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11064 if is_entire_line {
11065 selection.start = Point::new(selection.start.row, 0);
11066 if !selection.is_empty() && selection.end.column == 0 {
11067 selection.end = cmp::min(max_point, selection.end);
11068 } else {
11069 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
11070 }
11071 selection.goal = SelectionGoal::None;
11072 }
11073 if is_first {
11074 is_first = false;
11075 } else {
11076 text += "\n";
11077 }
11078 let mut len = 0;
11079 for chunk in buffer.text_for_range(selection.start..selection.end) {
11080 text.push_str(chunk);
11081 len += chunk.len();
11082 }
11083 clipboard_selections.push(ClipboardSelection {
11084 len,
11085 is_entire_line,
11086 first_line_indent: buffer
11087 .indent_size_for_line(MultiBufferRow(selection.start.row))
11088 .len,
11089 });
11090 }
11091 }
11092
11093 self.transact(window, cx, |this, window, cx| {
11094 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11095 s.select(selections);
11096 });
11097 this.insert("", window, cx);
11098 });
11099 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
11100 }
11101
11102 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
11103 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11104 let item = self.cut_common(window, cx);
11105 cx.write_to_clipboard(item);
11106 }
11107
11108 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
11109 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11110 self.change_selections(None, window, cx, |s| {
11111 s.move_with(|snapshot, sel| {
11112 if sel.is_empty() {
11113 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
11114 }
11115 });
11116 });
11117 let item = self.cut_common(window, cx);
11118 cx.set_global(KillRing(item))
11119 }
11120
11121 pub fn kill_ring_yank(
11122 &mut self,
11123 _: &KillRingYank,
11124 window: &mut Window,
11125 cx: &mut Context<Self>,
11126 ) {
11127 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11128 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
11129 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
11130 (kill_ring.text().to_string(), kill_ring.metadata_json())
11131 } else {
11132 return;
11133 }
11134 } else {
11135 return;
11136 };
11137 self.do_paste(&text, metadata, false, window, cx);
11138 }
11139
11140 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
11141 self.do_copy(true, cx);
11142 }
11143
11144 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
11145 self.do_copy(false, cx);
11146 }
11147
11148 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
11149 let selections = self.selections.all::<Point>(cx);
11150 let buffer = self.buffer.read(cx).read(cx);
11151 let mut text = String::new();
11152
11153 let mut clipboard_selections = Vec::with_capacity(selections.len());
11154 {
11155 let max_point = buffer.max_point();
11156 let mut is_first = true;
11157 for selection in &selections {
11158 let mut start = selection.start;
11159 let mut end = selection.end;
11160 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11161 if is_entire_line {
11162 start = Point::new(start.row, 0);
11163 end = cmp::min(max_point, Point::new(end.row + 1, 0));
11164 }
11165
11166 let mut trimmed_selections = Vec::new();
11167 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
11168 let row = MultiBufferRow(start.row);
11169 let first_indent = buffer.indent_size_for_line(row);
11170 if first_indent.len == 0 || start.column > first_indent.len {
11171 trimmed_selections.push(start..end);
11172 } else {
11173 trimmed_selections.push(
11174 Point::new(row.0, first_indent.len)
11175 ..Point::new(row.0, buffer.line_len(row)),
11176 );
11177 for row in start.row + 1..=end.row {
11178 let mut line_len = buffer.line_len(MultiBufferRow(row));
11179 if row == end.row {
11180 line_len = end.column;
11181 }
11182 if line_len == 0 {
11183 trimmed_selections
11184 .push(Point::new(row, 0)..Point::new(row, line_len));
11185 continue;
11186 }
11187 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
11188 if row_indent_size.len >= first_indent.len {
11189 trimmed_selections.push(
11190 Point::new(row, first_indent.len)..Point::new(row, line_len),
11191 );
11192 } else {
11193 trimmed_selections.clear();
11194 trimmed_selections.push(start..end);
11195 break;
11196 }
11197 }
11198 }
11199 } else {
11200 trimmed_selections.push(start..end);
11201 }
11202
11203 for trimmed_range in trimmed_selections {
11204 if is_first {
11205 is_first = false;
11206 } else {
11207 text += "\n";
11208 }
11209 let mut len = 0;
11210 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
11211 text.push_str(chunk);
11212 len += chunk.len();
11213 }
11214 clipboard_selections.push(ClipboardSelection {
11215 len,
11216 is_entire_line,
11217 first_line_indent: buffer
11218 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
11219 .len,
11220 });
11221 }
11222 }
11223 }
11224
11225 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
11226 text,
11227 clipboard_selections,
11228 ));
11229 }
11230
11231 pub fn do_paste(
11232 &mut self,
11233 text: &String,
11234 clipboard_selections: Option<Vec<ClipboardSelection>>,
11235 handle_entire_lines: bool,
11236 window: &mut Window,
11237 cx: &mut Context<Self>,
11238 ) {
11239 if self.read_only(cx) {
11240 return;
11241 }
11242
11243 let clipboard_text = Cow::Borrowed(text);
11244
11245 self.transact(window, cx, |this, window, cx| {
11246 if let Some(mut clipboard_selections) = clipboard_selections {
11247 let old_selections = this.selections.all::<usize>(cx);
11248 let all_selections_were_entire_line =
11249 clipboard_selections.iter().all(|s| s.is_entire_line);
11250 let first_selection_indent_column =
11251 clipboard_selections.first().map(|s| s.first_line_indent);
11252 if clipboard_selections.len() != old_selections.len() {
11253 clipboard_selections.drain(..);
11254 }
11255 let cursor_offset = this.selections.last::<usize>(cx).head();
11256 let mut auto_indent_on_paste = true;
11257
11258 this.buffer.update(cx, |buffer, cx| {
11259 let snapshot = buffer.read(cx);
11260 auto_indent_on_paste = snapshot
11261 .language_settings_at(cursor_offset, cx)
11262 .auto_indent_on_paste;
11263
11264 let mut start_offset = 0;
11265 let mut edits = Vec::new();
11266 let mut original_indent_columns = Vec::new();
11267 for (ix, selection) in old_selections.iter().enumerate() {
11268 let to_insert;
11269 let entire_line;
11270 let original_indent_column;
11271 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
11272 let end_offset = start_offset + clipboard_selection.len;
11273 to_insert = &clipboard_text[start_offset..end_offset];
11274 entire_line = clipboard_selection.is_entire_line;
11275 start_offset = end_offset + 1;
11276 original_indent_column = Some(clipboard_selection.first_line_indent);
11277 } else {
11278 to_insert = clipboard_text.as_str();
11279 entire_line = all_selections_were_entire_line;
11280 original_indent_column = first_selection_indent_column
11281 }
11282
11283 // If the corresponding selection was empty when this slice of the
11284 // clipboard text was written, then the entire line containing the
11285 // selection was copied. If this selection is also currently empty,
11286 // then paste the line before the current line of the buffer.
11287 let range = if selection.is_empty() && handle_entire_lines && entire_line {
11288 let column = selection.start.to_point(&snapshot).column as usize;
11289 let line_start = selection.start - column;
11290 line_start..line_start
11291 } else {
11292 selection.range()
11293 };
11294
11295 edits.push((range, to_insert));
11296 original_indent_columns.push(original_indent_column);
11297 }
11298 drop(snapshot);
11299
11300 buffer.edit(
11301 edits,
11302 if auto_indent_on_paste {
11303 Some(AutoindentMode::Block {
11304 original_indent_columns,
11305 })
11306 } else {
11307 None
11308 },
11309 cx,
11310 );
11311 });
11312
11313 let selections = this.selections.all::<usize>(cx);
11314 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11315 s.select(selections)
11316 });
11317 } else {
11318 this.insert(&clipboard_text, window, cx);
11319 }
11320 });
11321 }
11322
11323 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
11324 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11325 if let Some(item) = cx.read_from_clipboard() {
11326 let entries = item.entries();
11327
11328 match entries.first() {
11329 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
11330 // of all the pasted entries.
11331 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
11332 .do_paste(
11333 clipboard_string.text(),
11334 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
11335 true,
11336 window,
11337 cx,
11338 ),
11339 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
11340 }
11341 }
11342 }
11343
11344 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
11345 if self.read_only(cx) {
11346 return;
11347 }
11348
11349 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11350
11351 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
11352 if let Some((selections, _)) =
11353 self.selection_history.transaction(transaction_id).cloned()
11354 {
11355 self.change_selections(None, window, cx, |s| {
11356 s.select_anchors(selections.to_vec());
11357 });
11358 } else {
11359 log::error!(
11360 "No entry in selection_history found for undo. \
11361 This may correspond to a bug where undo does not update the selection. \
11362 If this is occurring, please add details to \
11363 https://github.com/zed-industries/zed/issues/22692"
11364 );
11365 }
11366 self.request_autoscroll(Autoscroll::fit(), cx);
11367 self.unmark_text(window, cx);
11368 self.refresh_inline_completion(true, false, window, cx);
11369 cx.emit(EditorEvent::Edited { transaction_id });
11370 cx.emit(EditorEvent::TransactionUndone { transaction_id });
11371 }
11372 }
11373
11374 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
11375 if self.read_only(cx) {
11376 return;
11377 }
11378
11379 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11380
11381 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
11382 if let Some((_, Some(selections))) =
11383 self.selection_history.transaction(transaction_id).cloned()
11384 {
11385 self.change_selections(None, window, cx, |s| {
11386 s.select_anchors(selections.to_vec());
11387 });
11388 } else {
11389 log::error!(
11390 "No entry in selection_history found for redo. \
11391 This may correspond to a bug where undo does not update the selection. \
11392 If this is occurring, please add details to \
11393 https://github.com/zed-industries/zed/issues/22692"
11394 );
11395 }
11396 self.request_autoscroll(Autoscroll::fit(), cx);
11397 self.unmark_text(window, cx);
11398 self.refresh_inline_completion(true, false, window, cx);
11399 cx.emit(EditorEvent::Edited { transaction_id });
11400 }
11401 }
11402
11403 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
11404 self.buffer
11405 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
11406 }
11407
11408 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
11409 self.buffer
11410 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
11411 }
11412
11413 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
11414 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11415 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11416 s.move_with(|map, selection| {
11417 let cursor = if selection.is_empty() {
11418 movement::left(map, selection.start)
11419 } else {
11420 selection.start
11421 };
11422 selection.collapse_to(cursor, SelectionGoal::None);
11423 });
11424 })
11425 }
11426
11427 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
11428 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11429 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11430 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
11431 })
11432 }
11433
11434 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
11435 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11436 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11437 s.move_with(|map, selection| {
11438 let cursor = if selection.is_empty() {
11439 movement::right(map, selection.end)
11440 } else {
11441 selection.end
11442 };
11443 selection.collapse_to(cursor, SelectionGoal::None)
11444 });
11445 })
11446 }
11447
11448 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
11449 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11450 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11451 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
11452 })
11453 }
11454
11455 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
11456 if self.take_rename(true, window, cx).is_some() {
11457 return;
11458 }
11459
11460 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11461 cx.propagate();
11462 return;
11463 }
11464
11465 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11466
11467 let text_layout_details = &self.text_layout_details(window);
11468 let selection_count = self.selections.count();
11469 let first_selection = self.selections.first_anchor();
11470
11471 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11472 s.move_with(|map, selection| {
11473 if !selection.is_empty() {
11474 selection.goal = SelectionGoal::None;
11475 }
11476 let (cursor, goal) = movement::up(
11477 map,
11478 selection.start,
11479 selection.goal,
11480 false,
11481 text_layout_details,
11482 );
11483 selection.collapse_to(cursor, goal);
11484 });
11485 });
11486
11487 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11488 {
11489 cx.propagate();
11490 }
11491 }
11492
11493 pub fn move_up_by_lines(
11494 &mut self,
11495 action: &MoveUpByLines,
11496 window: &mut Window,
11497 cx: &mut Context<Self>,
11498 ) {
11499 if self.take_rename(true, window, cx).is_some() {
11500 return;
11501 }
11502
11503 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11504 cx.propagate();
11505 return;
11506 }
11507
11508 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11509
11510 let text_layout_details = &self.text_layout_details(window);
11511
11512 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11513 s.move_with(|map, selection| {
11514 if !selection.is_empty() {
11515 selection.goal = SelectionGoal::None;
11516 }
11517 let (cursor, goal) = movement::up_by_rows(
11518 map,
11519 selection.start,
11520 action.lines,
11521 selection.goal,
11522 false,
11523 text_layout_details,
11524 );
11525 selection.collapse_to(cursor, goal);
11526 });
11527 })
11528 }
11529
11530 pub fn move_down_by_lines(
11531 &mut self,
11532 action: &MoveDownByLines,
11533 window: &mut Window,
11534 cx: &mut Context<Self>,
11535 ) {
11536 if self.take_rename(true, window, cx).is_some() {
11537 return;
11538 }
11539
11540 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11541 cx.propagate();
11542 return;
11543 }
11544
11545 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11546
11547 let text_layout_details = &self.text_layout_details(window);
11548
11549 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11550 s.move_with(|map, selection| {
11551 if !selection.is_empty() {
11552 selection.goal = SelectionGoal::None;
11553 }
11554 let (cursor, goal) = movement::down_by_rows(
11555 map,
11556 selection.start,
11557 action.lines,
11558 selection.goal,
11559 false,
11560 text_layout_details,
11561 );
11562 selection.collapse_to(cursor, goal);
11563 });
11564 })
11565 }
11566
11567 pub fn select_down_by_lines(
11568 &mut self,
11569 action: &SelectDownByLines,
11570 window: &mut Window,
11571 cx: &mut Context<Self>,
11572 ) {
11573 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11574 let text_layout_details = &self.text_layout_details(window);
11575 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11576 s.move_heads_with(|map, head, goal| {
11577 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
11578 })
11579 })
11580 }
11581
11582 pub fn select_up_by_lines(
11583 &mut self,
11584 action: &SelectUpByLines,
11585 window: &mut Window,
11586 cx: &mut Context<Self>,
11587 ) {
11588 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11589 let text_layout_details = &self.text_layout_details(window);
11590 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11591 s.move_heads_with(|map, head, goal| {
11592 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
11593 })
11594 })
11595 }
11596
11597 pub fn select_page_up(
11598 &mut self,
11599 _: &SelectPageUp,
11600 window: &mut Window,
11601 cx: &mut Context<Self>,
11602 ) {
11603 let Some(row_count) = self.visible_row_count() else {
11604 return;
11605 };
11606
11607 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11608
11609 let text_layout_details = &self.text_layout_details(window);
11610
11611 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11612 s.move_heads_with(|map, head, goal| {
11613 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
11614 })
11615 })
11616 }
11617
11618 pub fn move_page_up(
11619 &mut self,
11620 action: &MovePageUp,
11621 window: &mut Window,
11622 cx: &mut Context<Self>,
11623 ) {
11624 if self.take_rename(true, window, cx).is_some() {
11625 return;
11626 }
11627
11628 if self
11629 .context_menu
11630 .borrow_mut()
11631 .as_mut()
11632 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
11633 .unwrap_or(false)
11634 {
11635 return;
11636 }
11637
11638 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11639 cx.propagate();
11640 return;
11641 }
11642
11643 let Some(row_count) = self.visible_row_count() else {
11644 return;
11645 };
11646
11647 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11648
11649 let autoscroll = if action.center_cursor {
11650 Autoscroll::center()
11651 } else {
11652 Autoscroll::fit()
11653 };
11654
11655 let text_layout_details = &self.text_layout_details(window);
11656
11657 self.change_selections(Some(autoscroll), window, cx, |s| {
11658 s.move_with(|map, selection| {
11659 if !selection.is_empty() {
11660 selection.goal = SelectionGoal::None;
11661 }
11662 let (cursor, goal) = movement::up_by_rows(
11663 map,
11664 selection.end,
11665 row_count,
11666 selection.goal,
11667 false,
11668 text_layout_details,
11669 );
11670 selection.collapse_to(cursor, goal);
11671 });
11672 });
11673 }
11674
11675 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
11676 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11677 let text_layout_details = &self.text_layout_details(window);
11678 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11679 s.move_heads_with(|map, head, goal| {
11680 movement::up(map, head, goal, false, text_layout_details)
11681 })
11682 })
11683 }
11684
11685 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
11686 self.take_rename(true, window, cx);
11687
11688 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11689 cx.propagate();
11690 return;
11691 }
11692
11693 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11694
11695 let text_layout_details = &self.text_layout_details(window);
11696 let selection_count = self.selections.count();
11697 let first_selection = self.selections.first_anchor();
11698
11699 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11700 s.move_with(|map, selection| {
11701 if !selection.is_empty() {
11702 selection.goal = SelectionGoal::None;
11703 }
11704 let (cursor, goal) = movement::down(
11705 map,
11706 selection.end,
11707 selection.goal,
11708 false,
11709 text_layout_details,
11710 );
11711 selection.collapse_to(cursor, goal);
11712 });
11713 });
11714
11715 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11716 {
11717 cx.propagate();
11718 }
11719 }
11720
11721 pub fn select_page_down(
11722 &mut self,
11723 _: &SelectPageDown,
11724 window: &mut Window,
11725 cx: &mut Context<Self>,
11726 ) {
11727 let Some(row_count) = self.visible_row_count() else {
11728 return;
11729 };
11730
11731 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11732
11733 let text_layout_details = &self.text_layout_details(window);
11734
11735 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11736 s.move_heads_with(|map, head, goal| {
11737 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
11738 })
11739 })
11740 }
11741
11742 pub fn move_page_down(
11743 &mut self,
11744 action: &MovePageDown,
11745 window: &mut Window,
11746 cx: &mut Context<Self>,
11747 ) {
11748 if self.take_rename(true, window, cx).is_some() {
11749 return;
11750 }
11751
11752 if self
11753 .context_menu
11754 .borrow_mut()
11755 .as_mut()
11756 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
11757 .unwrap_or(false)
11758 {
11759 return;
11760 }
11761
11762 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11763 cx.propagate();
11764 return;
11765 }
11766
11767 let Some(row_count) = self.visible_row_count() else {
11768 return;
11769 };
11770
11771 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11772
11773 let autoscroll = if action.center_cursor {
11774 Autoscroll::center()
11775 } else {
11776 Autoscroll::fit()
11777 };
11778
11779 let text_layout_details = &self.text_layout_details(window);
11780 self.change_selections(Some(autoscroll), window, cx, |s| {
11781 s.move_with(|map, selection| {
11782 if !selection.is_empty() {
11783 selection.goal = SelectionGoal::None;
11784 }
11785 let (cursor, goal) = movement::down_by_rows(
11786 map,
11787 selection.end,
11788 row_count,
11789 selection.goal,
11790 false,
11791 text_layout_details,
11792 );
11793 selection.collapse_to(cursor, goal);
11794 });
11795 });
11796 }
11797
11798 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
11799 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11800 let text_layout_details = &self.text_layout_details(window);
11801 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11802 s.move_heads_with(|map, head, goal| {
11803 movement::down(map, head, goal, false, text_layout_details)
11804 })
11805 });
11806 }
11807
11808 pub fn context_menu_first(
11809 &mut self,
11810 _: &ContextMenuFirst,
11811 window: &mut Window,
11812 cx: &mut Context<Self>,
11813 ) {
11814 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11815 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
11816 }
11817 }
11818
11819 pub fn context_menu_prev(
11820 &mut self,
11821 _: &ContextMenuPrevious,
11822 window: &mut Window,
11823 cx: &mut Context<Self>,
11824 ) {
11825 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11826 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
11827 }
11828 }
11829
11830 pub fn context_menu_next(
11831 &mut self,
11832 _: &ContextMenuNext,
11833 window: &mut Window,
11834 cx: &mut Context<Self>,
11835 ) {
11836 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11837 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
11838 }
11839 }
11840
11841 pub fn context_menu_last(
11842 &mut self,
11843 _: &ContextMenuLast,
11844 window: &mut Window,
11845 cx: &mut Context<Self>,
11846 ) {
11847 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11848 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
11849 }
11850 }
11851
11852 pub fn move_to_previous_word_start(
11853 &mut self,
11854 _: &MoveToPreviousWordStart,
11855 window: &mut Window,
11856 cx: &mut Context<Self>,
11857 ) {
11858 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11859 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11860 s.move_cursors_with(|map, head, _| {
11861 (
11862 movement::previous_word_start(map, head),
11863 SelectionGoal::None,
11864 )
11865 });
11866 })
11867 }
11868
11869 pub fn move_to_previous_subword_start(
11870 &mut self,
11871 _: &MoveToPreviousSubwordStart,
11872 window: &mut Window,
11873 cx: &mut Context<Self>,
11874 ) {
11875 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11876 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11877 s.move_cursors_with(|map, head, _| {
11878 (
11879 movement::previous_subword_start(map, head),
11880 SelectionGoal::None,
11881 )
11882 });
11883 })
11884 }
11885
11886 pub fn select_to_previous_word_start(
11887 &mut self,
11888 _: &SelectToPreviousWordStart,
11889 window: &mut Window,
11890 cx: &mut Context<Self>,
11891 ) {
11892 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11893 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11894 s.move_heads_with(|map, head, _| {
11895 (
11896 movement::previous_word_start(map, head),
11897 SelectionGoal::None,
11898 )
11899 });
11900 })
11901 }
11902
11903 pub fn select_to_previous_subword_start(
11904 &mut self,
11905 _: &SelectToPreviousSubwordStart,
11906 window: &mut Window,
11907 cx: &mut Context<Self>,
11908 ) {
11909 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11910 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11911 s.move_heads_with(|map, head, _| {
11912 (
11913 movement::previous_subword_start(map, head),
11914 SelectionGoal::None,
11915 )
11916 });
11917 })
11918 }
11919
11920 pub fn delete_to_previous_word_start(
11921 &mut self,
11922 action: &DeleteToPreviousWordStart,
11923 window: &mut Window,
11924 cx: &mut Context<Self>,
11925 ) {
11926 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11927 self.transact(window, cx, |this, window, cx| {
11928 this.select_autoclose_pair(window, cx);
11929 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11930 s.move_with(|map, selection| {
11931 if selection.is_empty() {
11932 let cursor = if action.ignore_newlines {
11933 movement::previous_word_start(map, selection.head())
11934 } else {
11935 movement::previous_word_start_or_newline(map, selection.head())
11936 };
11937 selection.set_head(cursor, SelectionGoal::None);
11938 }
11939 });
11940 });
11941 this.insert("", window, cx);
11942 });
11943 }
11944
11945 pub fn delete_to_previous_subword_start(
11946 &mut self,
11947 _: &DeleteToPreviousSubwordStart,
11948 window: &mut Window,
11949 cx: &mut Context<Self>,
11950 ) {
11951 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11952 self.transact(window, cx, |this, window, cx| {
11953 this.select_autoclose_pair(window, cx);
11954 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11955 s.move_with(|map, selection| {
11956 if selection.is_empty() {
11957 let cursor = movement::previous_subword_start(map, selection.head());
11958 selection.set_head(cursor, SelectionGoal::None);
11959 }
11960 });
11961 });
11962 this.insert("", window, cx);
11963 });
11964 }
11965
11966 pub fn move_to_next_word_end(
11967 &mut self,
11968 _: &MoveToNextWordEnd,
11969 window: &mut Window,
11970 cx: &mut Context<Self>,
11971 ) {
11972 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11973 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11974 s.move_cursors_with(|map, head, _| {
11975 (movement::next_word_end(map, head), SelectionGoal::None)
11976 });
11977 })
11978 }
11979
11980 pub fn move_to_next_subword_end(
11981 &mut self,
11982 _: &MoveToNextSubwordEnd,
11983 window: &mut Window,
11984 cx: &mut Context<Self>,
11985 ) {
11986 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11987 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11988 s.move_cursors_with(|map, head, _| {
11989 (movement::next_subword_end(map, head), SelectionGoal::None)
11990 });
11991 })
11992 }
11993
11994 pub fn select_to_next_word_end(
11995 &mut self,
11996 _: &SelectToNextWordEnd,
11997 window: &mut Window,
11998 cx: &mut Context<Self>,
11999 ) {
12000 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12001 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12002 s.move_heads_with(|map, head, _| {
12003 (movement::next_word_end(map, head), SelectionGoal::None)
12004 });
12005 })
12006 }
12007
12008 pub fn select_to_next_subword_end(
12009 &mut self,
12010 _: &SelectToNextSubwordEnd,
12011 window: &mut Window,
12012 cx: &mut Context<Self>,
12013 ) {
12014 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12015 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12016 s.move_heads_with(|map, head, _| {
12017 (movement::next_subword_end(map, head), SelectionGoal::None)
12018 });
12019 })
12020 }
12021
12022 pub fn delete_to_next_word_end(
12023 &mut self,
12024 action: &DeleteToNextWordEnd,
12025 window: &mut Window,
12026 cx: &mut Context<Self>,
12027 ) {
12028 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12029 self.transact(window, cx, |this, window, cx| {
12030 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12031 s.move_with(|map, selection| {
12032 if selection.is_empty() {
12033 let cursor = if action.ignore_newlines {
12034 movement::next_word_end(map, selection.head())
12035 } else {
12036 movement::next_word_end_or_newline(map, selection.head())
12037 };
12038 selection.set_head(cursor, SelectionGoal::None);
12039 }
12040 });
12041 });
12042 this.insert("", window, cx);
12043 });
12044 }
12045
12046 pub fn delete_to_next_subword_end(
12047 &mut self,
12048 _: &DeleteToNextSubwordEnd,
12049 window: &mut Window,
12050 cx: &mut Context<Self>,
12051 ) {
12052 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12053 self.transact(window, cx, |this, window, cx| {
12054 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12055 s.move_with(|map, selection| {
12056 if selection.is_empty() {
12057 let cursor = movement::next_subword_end(map, selection.head());
12058 selection.set_head(cursor, SelectionGoal::None);
12059 }
12060 });
12061 });
12062 this.insert("", window, cx);
12063 });
12064 }
12065
12066 pub fn move_to_beginning_of_line(
12067 &mut self,
12068 action: &MoveToBeginningOfLine,
12069 window: &mut Window,
12070 cx: &mut Context<Self>,
12071 ) {
12072 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12073 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12074 s.move_cursors_with(|map, head, _| {
12075 (
12076 movement::indented_line_beginning(
12077 map,
12078 head,
12079 action.stop_at_soft_wraps,
12080 action.stop_at_indent,
12081 ),
12082 SelectionGoal::None,
12083 )
12084 });
12085 })
12086 }
12087
12088 pub fn select_to_beginning_of_line(
12089 &mut self,
12090 action: &SelectToBeginningOfLine,
12091 window: &mut Window,
12092 cx: &mut Context<Self>,
12093 ) {
12094 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12095 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12096 s.move_heads_with(|map, head, _| {
12097 (
12098 movement::indented_line_beginning(
12099 map,
12100 head,
12101 action.stop_at_soft_wraps,
12102 action.stop_at_indent,
12103 ),
12104 SelectionGoal::None,
12105 )
12106 });
12107 });
12108 }
12109
12110 pub fn delete_to_beginning_of_line(
12111 &mut self,
12112 action: &DeleteToBeginningOfLine,
12113 window: &mut Window,
12114 cx: &mut Context<Self>,
12115 ) {
12116 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12117 self.transact(window, cx, |this, window, cx| {
12118 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12119 s.move_with(|_, selection| {
12120 selection.reversed = true;
12121 });
12122 });
12123
12124 this.select_to_beginning_of_line(
12125 &SelectToBeginningOfLine {
12126 stop_at_soft_wraps: false,
12127 stop_at_indent: action.stop_at_indent,
12128 },
12129 window,
12130 cx,
12131 );
12132 this.backspace(&Backspace, window, cx);
12133 });
12134 }
12135
12136 pub fn move_to_end_of_line(
12137 &mut self,
12138 action: &MoveToEndOfLine,
12139 window: &mut Window,
12140 cx: &mut Context<Self>,
12141 ) {
12142 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12143 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12144 s.move_cursors_with(|map, head, _| {
12145 (
12146 movement::line_end(map, head, action.stop_at_soft_wraps),
12147 SelectionGoal::None,
12148 )
12149 });
12150 })
12151 }
12152
12153 pub fn select_to_end_of_line(
12154 &mut self,
12155 action: &SelectToEndOfLine,
12156 window: &mut Window,
12157 cx: &mut Context<Self>,
12158 ) {
12159 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12160 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12161 s.move_heads_with(|map, head, _| {
12162 (
12163 movement::line_end(map, head, action.stop_at_soft_wraps),
12164 SelectionGoal::None,
12165 )
12166 });
12167 })
12168 }
12169
12170 pub fn delete_to_end_of_line(
12171 &mut self,
12172 _: &DeleteToEndOfLine,
12173 window: &mut Window,
12174 cx: &mut Context<Self>,
12175 ) {
12176 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12177 self.transact(window, cx, |this, window, cx| {
12178 this.select_to_end_of_line(
12179 &SelectToEndOfLine {
12180 stop_at_soft_wraps: false,
12181 },
12182 window,
12183 cx,
12184 );
12185 this.delete(&Delete, window, cx);
12186 });
12187 }
12188
12189 pub fn cut_to_end_of_line(
12190 &mut self,
12191 _: &CutToEndOfLine,
12192 window: &mut Window,
12193 cx: &mut Context<Self>,
12194 ) {
12195 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12196 self.transact(window, cx, |this, window, cx| {
12197 this.select_to_end_of_line(
12198 &SelectToEndOfLine {
12199 stop_at_soft_wraps: false,
12200 },
12201 window,
12202 cx,
12203 );
12204 this.cut(&Cut, window, cx);
12205 });
12206 }
12207
12208 pub fn move_to_start_of_paragraph(
12209 &mut self,
12210 _: &MoveToStartOfParagraph,
12211 window: &mut Window,
12212 cx: &mut Context<Self>,
12213 ) {
12214 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12215 cx.propagate();
12216 return;
12217 }
12218 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12219 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12220 s.move_with(|map, selection| {
12221 selection.collapse_to(
12222 movement::start_of_paragraph(map, selection.head(), 1),
12223 SelectionGoal::None,
12224 )
12225 });
12226 })
12227 }
12228
12229 pub fn move_to_end_of_paragraph(
12230 &mut self,
12231 _: &MoveToEndOfParagraph,
12232 window: &mut Window,
12233 cx: &mut Context<Self>,
12234 ) {
12235 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12236 cx.propagate();
12237 return;
12238 }
12239 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12240 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12241 s.move_with(|map, selection| {
12242 selection.collapse_to(
12243 movement::end_of_paragraph(map, selection.head(), 1),
12244 SelectionGoal::None,
12245 )
12246 });
12247 })
12248 }
12249
12250 pub fn select_to_start_of_paragraph(
12251 &mut self,
12252 _: &SelectToStartOfParagraph,
12253 window: &mut Window,
12254 cx: &mut Context<Self>,
12255 ) {
12256 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12257 cx.propagate();
12258 return;
12259 }
12260 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12261 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12262 s.move_heads_with(|map, head, _| {
12263 (
12264 movement::start_of_paragraph(map, head, 1),
12265 SelectionGoal::None,
12266 )
12267 });
12268 })
12269 }
12270
12271 pub fn select_to_end_of_paragraph(
12272 &mut self,
12273 _: &SelectToEndOfParagraph,
12274 window: &mut Window,
12275 cx: &mut Context<Self>,
12276 ) {
12277 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12278 cx.propagate();
12279 return;
12280 }
12281 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12282 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12283 s.move_heads_with(|map, head, _| {
12284 (
12285 movement::end_of_paragraph(map, head, 1),
12286 SelectionGoal::None,
12287 )
12288 });
12289 })
12290 }
12291
12292 pub fn move_to_start_of_excerpt(
12293 &mut self,
12294 _: &MoveToStartOfExcerpt,
12295 window: &mut Window,
12296 cx: &mut Context<Self>,
12297 ) {
12298 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12299 cx.propagate();
12300 return;
12301 }
12302 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12303 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12304 s.move_with(|map, selection| {
12305 selection.collapse_to(
12306 movement::start_of_excerpt(
12307 map,
12308 selection.head(),
12309 workspace::searchable::Direction::Prev,
12310 ),
12311 SelectionGoal::None,
12312 )
12313 });
12314 })
12315 }
12316
12317 pub fn move_to_start_of_next_excerpt(
12318 &mut self,
12319 _: &MoveToStartOfNextExcerpt,
12320 window: &mut Window,
12321 cx: &mut Context<Self>,
12322 ) {
12323 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12324 cx.propagate();
12325 return;
12326 }
12327
12328 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12329 s.move_with(|map, selection| {
12330 selection.collapse_to(
12331 movement::start_of_excerpt(
12332 map,
12333 selection.head(),
12334 workspace::searchable::Direction::Next,
12335 ),
12336 SelectionGoal::None,
12337 )
12338 });
12339 })
12340 }
12341
12342 pub fn move_to_end_of_excerpt(
12343 &mut self,
12344 _: &MoveToEndOfExcerpt,
12345 window: &mut Window,
12346 cx: &mut Context<Self>,
12347 ) {
12348 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12349 cx.propagate();
12350 return;
12351 }
12352 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12353 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12354 s.move_with(|map, selection| {
12355 selection.collapse_to(
12356 movement::end_of_excerpt(
12357 map,
12358 selection.head(),
12359 workspace::searchable::Direction::Next,
12360 ),
12361 SelectionGoal::None,
12362 )
12363 });
12364 })
12365 }
12366
12367 pub fn move_to_end_of_previous_excerpt(
12368 &mut self,
12369 _: &MoveToEndOfPreviousExcerpt,
12370 window: &mut Window,
12371 cx: &mut Context<Self>,
12372 ) {
12373 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12374 cx.propagate();
12375 return;
12376 }
12377 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12378 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12379 s.move_with(|map, selection| {
12380 selection.collapse_to(
12381 movement::end_of_excerpt(
12382 map,
12383 selection.head(),
12384 workspace::searchable::Direction::Prev,
12385 ),
12386 SelectionGoal::None,
12387 )
12388 });
12389 })
12390 }
12391
12392 pub fn select_to_start_of_excerpt(
12393 &mut self,
12394 _: &SelectToStartOfExcerpt,
12395 window: &mut Window,
12396 cx: &mut Context<Self>,
12397 ) {
12398 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12399 cx.propagate();
12400 return;
12401 }
12402 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12403 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12404 s.move_heads_with(|map, head, _| {
12405 (
12406 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12407 SelectionGoal::None,
12408 )
12409 });
12410 })
12411 }
12412
12413 pub fn select_to_start_of_next_excerpt(
12414 &mut self,
12415 _: &SelectToStartOfNextExcerpt,
12416 window: &mut Window,
12417 cx: &mut Context<Self>,
12418 ) {
12419 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12420 cx.propagate();
12421 return;
12422 }
12423 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12424 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12425 s.move_heads_with(|map, head, _| {
12426 (
12427 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
12428 SelectionGoal::None,
12429 )
12430 });
12431 })
12432 }
12433
12434 pub fn select_to_end_of_excerpt(
12435 &mut self,
12436 _: &SelectToEndOfExcerpt,
12437 window: &mut Window,
12438 cx: &mut Context<Self>,
12439 ) {
12440 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12441 cx.propagate();
12442 return;
12443 }
12444 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12445 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12446 s.move_heads_with(|map, head, _| {
12447 (
12448 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
12449 SelectionGoal::None,
12450 )
12451 });
12452 })
12453 }
12454
12455 pub fn select_to_end_of_previous_excerpt(
12456 &mut self,
12457 _: &SelectToEndOfPreviousExcerpt,
12458 window: &mut Window,
12459 cx: &mut Context<Self>,
12460 ) {
12461 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12462 cx.propagate();
12463 return;
12464 }
12465 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12466 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12467 s.move_heads_with(|map, head, _| {
12468 (
12469 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12470 SelectionGoal::None,
12471 )
12472 });
12473 })
12474 }
12475
12476 pub fn move_to_beginning(
12477 &mut self,
12478 _: &MoveToBeginning,
12479 window: &mut Window,
12480 cx: &mut Context<Self>,
12481 ) {
12482 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12483 cx.propagate();
12484 return;
12485 }
12486 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12487 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12488 s.select_ranges(vec![0..0]);
12489 });
12490 }
12491
12492 pub fn select_to_beginning(
12493 &mut self,
12494 _: &SelectToBeginning,
12495 window: &mut Window,
12496 cx: &mut Context<Self>,
12497 ) {
12498 let mut selection = self.selections.last::<Point>(cx);
12499 selection.set_head(Point::zero(), SelectionGoal::None);
12500 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12501 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12502 s.select(vec![selection]);
12503 });
12504 }
12505
12506 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
12507 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12508 cx.propagate();
12509 return;
12510 }
12511 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12512 let cursor = self.buffer.read(cx).read(cx).len();
12513 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12514 s.select_ranges(vec![cursor..cursor])
12515 });
12516 }
12517
12518 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
12519 self.nav_history = nav_history;
12520 }
12521
12522 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
12523 self.nav_history.as_ref()
12524 }
12525
12526 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
12527 self.push_to_nav_history(self.selections.newest_anchor().head(), None, false, cx);
12528 }
12529
12530 fn push_to_nav_history(
12531 &mut self,
12532 cursor_anchor: Anchor,
12533 new_position: Option<Point>,
12534 is_deactivate: bool,
12535 cx: &mut Context<Self>,
12536 ) {
12537 if let Some(nav_history) = self.nav_history.as_mut() {
12538 let buffer = self.buffer.read(cx).read(cx);
12539 let cursor_position = cursor_anchor.to_point(&buffer);
12540 let scroll_state = self.scroll_manager.anchor();
12541 let scroll_top_row = scroll_state.top_row(&buffer);
12542 drop(buffer);
12543
12544 if let Some(new_position) = new_position {
12545 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
12546 if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA {
12547 return;
12548 }
12549 }
12550
12551 nav_history.push(
12552 Some(NavigationData {
12553 cursor_anchor,
12554 cursor_position,
12555 scroll_anchor: scroll_state,
12556 scroll_top_row,
12557 }),
12558 cx,
12559 );
12560 cx.emit(EditorEvent::PushedToNavHistory {
12561 anchor: cursor_anchor,
12562 is_deactivate,
12563 })
12564 }
12565 }
12566
12567 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
12568 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12569 let buffer = self.buffer.read(cx).snapshot(cx);
12570 let mut selection = self.selections.first::<usize>(cx);
12571 selection.set_head(buffer.len(), SelectionGoal::None);
12572 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12573 s.select(vec![selection]);
12574 });
12575 }
12576
12577 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
12578 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12579 let end = self.buffer.read(cx).read(cx).len();
12580 self.change_selections(None, window, cx, |s| {
12581 s.select_ranges(vec![0..end]);
12582 });
12583 }
12584
12585 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
12586 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12587 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12588 let mut selections = self.selections.all::<Point>(cx);
12589 let max_point = display_map.buffer_snapshot.max_point();
12590 for selection in &mut selections {
12591 let rows = selection.spanned_rows(true, &display_map);
12592 selection.start = Point::new(rows.start.0, 0);
12593 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
12594 selection.reversed = false;
12595 }
12596 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12597 s.select(selections);
12598 });
12599 }
12600
12601 pub fn split_selection_into_lines(
12602 &mut self,
12603 _: &SplitSelectionIntoLines,
12604 window: &mut Window,
12605 cx: &mut Context<Self>,
12606 ) {
12607 let selections = self
12608 .selections
12609 .all::<Point>(cx)
12610 .into_iter()
12611 .map(|selection| selection.start..selection.end)
12612 .collect::<Vec<_>>();
12613 self.unfold_ranges(&selections, true, true, cx);
12614
12615 let mut new_selection_ranges = Vec::new();
12616 {
12617 let buffer = self.buffer.read(cx).read(cx);
12618 for selection in selections {
12619 for row in selection.start.row..selection.end.row {
12620 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12621 new_selection_ranges.push(cursor..cursor);
12622 }
12623
12624 let is_multiline_selection = selection.start.row != selection.end.row;
12625 // Don't insert last one if it's a multi-line selection ending at the start of a line,
12626 // so this action feels more ergonomic when paired with other selection operations
12627 let should_skip_last = is_multiline_selection && selection.end.column == 0;
12628 if !should_skip_last {
12629 new_selection_ranges.push(selection.end..selection.end);
12630 }
12631 }
12632 }
12633 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12634 s.select_ranges(new_selection_ranges);
12635 });
12636 }
12637
12638 pub fn add_selection_above(
12639 &mut self,
12640 _: &AddSelectionAbove,
12641 window: &mut Window,
12642 cx: &mut Context<Self>,
12643 ) {
12644 self.add_selection(true, window, cx);
12645 }
12646
12647 pub fn add_selection_below(
12648 &mut self,
12649 _: &AddSelectionBelow,
12650 window: &mut Window,
12651 cx: &mut Context<Self>,
12652 ) {
12653 self.add_selection(false, window, cx);
12654 }
12655
12656 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
12657 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12658
12659 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12660 let mut selections = self.selections.all::<Point>(cx);
12661 let text_layout_details = self.text_layout_details(window);
12662 let mut state = self.add_selections_state.take().unwrap_or_else(|| {
12663 let oldest_selection = selections.iter().min_by_key(|s| s.id).unwrap().clone();
12664 let range = oldest_selection.display_range(&display_map).sorted();
12665
12666 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
12667 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
12668 let positions = start_x.min(end_x)..start_x.max(end_x);
12669
12670 selections.clear();
12671 let mut stack = Vec::new();
12672 for row in range.start.row().0..=range.end.row().0 {
12673 if let Some(selection) = self.selections.build_columnar_selection(
12674 &display_map,
12675 DisplayRow(row),
12676 &positions,
12677 oldest_selection.reversed,
12678 &text_layout_details,
12679 ) {
12680 stack.push(selection.id);
12681 selections.push(selection);
12682 }
12683 }
12684
12685 if above {
12686 stack.reverse();
12687 }
12688
12689 AddSelectionsState { above, stack }
12690 });
12691
12692 let last_added_selection = *state.stack.last().unwrap();
12693 let mut new_selections = Vec::new();
12694 if above == state.above {
12695 let end_row = if above {
12696 DisplayRow(0)
12697 } else {
12698 display_map.max_point().row()
12699 };
12700
12701 'outer: for selection in selections {
12702 if selection.id == last_added_selection {
12703 let range = selection.display_range(&display_map).sorted();
12704 debug_assert_eq!(range.start.row(), range.end.row());
12705 let mut row = range.start.row();
12706 let positions =
12707 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
12708 px(start)..px(end)
12709 } else {
12710 let start_x =
12711 display_map.x_for_display_point(range.start, &text_layout_details);
12712 let end_x =
12713 display_map.x_for_display_point(range.end, &text_layout_details);
12714 start_x.min(end_x)..start_x.max(end_x)
12715 };
12716
12717 while row != end_row {
12718 if above {
12719 row.0 -= 1;
12720 } else {
12721 row.0 += 1;
12722 }
12723
12724 if let Some(new_selection) = self.selections.build_columnar_selection(
12725 &display_map,
12726 row,
12727 &positions,
12728 selection.reversed,
12729 &text_layout_details,
12730 ) {
12731 state.stack.push(new_selection.id);
12732 if above {
12733 new_selections.push(new_selection);
12734 new_selections.push(selection);
12735 } else {
12736 new_selections.push(selection);
12737 new_selections.push(new_selection);
12738 }
12739
12740 continue 'outer;
12741 }
12742 }
12743 }
12744
12745 new_selections.push(selection);
12746 }
12747 } else {
12748 new_selections = selections;
12749 new_selections.retain(|s| s.id != last_added_selection);
12750 state.stack.pop();
12751 }
12752
12753 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12754 s.select(new_selections);
12755 });
12756 if state.stack.len() > 1 {
12757 self.add_selections_state = Some(state);
12758 }
12759 }
12760
12761 fn select_match_ranges(
12762 &mut self,
12763 range: Range<usize>,
12764 reversed: bool,
12765 replace_newest: bool,
12766 auto_scroll: Option<Autoscroll>,
12767 window: &mut Window,
12768 cx: &mut Context<Editor>,
12769 ) {
12770 self.unfold_ranges(&[range.clone()], false, auto_scroll.is_some(), cx);
12771 self.change_selections(auto_scroll, window, cx, |s| {
12772 if replace_newest {
12773 s.delete(s.newest_anchor().id);
12774 }
12775 if reversed {
12776 s.insert_range(range.end..range.start);
12777 } else {
12778 s.insert_range(range);
12779 }
12780 });
12781 }
12782
12783 pub fn select_next_match_internal(
12784 &mut self,
12785 display_map: &DisplaySnapshot,
12786 replace_newest: bool,
12787 autoscroll: Option<Autoscroll>,
12788 window: &mut Window,
12789 cx: &mut Context<Self>,
12790 ) -> Result<()> {
12791 let buffer = &display_map.buffer_snapshot;
12792 let mut selections = self.selections.all::<usize>(cx);
12793 if let Some(mut select_next_state) = self.select_next_state.take() {
12794 let query = &select_next_state.query;
12795 if !select_next_state.done {
12796 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
12797 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
12798 let mut next_selected_range = None;
12799
12800 let bytes_after_last_selection =
12801 buffer.bytes_in_range(last_selection.end..buffer.len());
12802 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
12803 let query_matches = query
12804 .stream_find_iter(bytes_after_last_selection)
12805 .map(|result| (last_selection.end, result))
12806 .chain(
12807 query
12808 .stream_find_iter(bytes_before_first_selection)
12809 .map(|result| (0, result)),
12810 );
12811
12812 for (start_offset, query_match) in query_matches {
12813 let query_match = query_match.unwrap(); // can only fail due to I/O
12814 let offset_range =
12815 start_offset + query_match.start()..start_offset + query_match.end();
12816 let display_range = offset_range.start.to_display_point(display_map)
12817 ..offset_range.end.to_display_point(display_map);
12818
12819 if !select_next_state.wordwise
12820 || (!movement::is_inside_word(display_map, display_range.start)
12821 && !movement::is_inside_word(display_map, display_range.end))
12822 {
12823 // TODO: This is n^2, because we might check all the selections
12824 if !selections
12825 .iter()
12826 .any(|selection| selection.range().overlaps(&offset_range))
12827 {
12828 next_selected_range = Some(offset_range);
12829 break;
12830 }
12831 }
12832 }
12833
12834 if let Some(next_selected_range) = next_selected_range {
12835 self.select_match_ranges(
12836 next_selected_range,
12837 last_selection.reversed,
12838 replace_newest,
12839 autoscroll,
12840 window,
12841 cx,
12842 );
12843 } else {
12844 select_next_state.done = true;
12845 }
12846 }
12847
12848 self.select_next_state = Some(select_next_state);
12849 } else {
12850 let mut only_carets = true;
12851 let mut same_text_selected = true;
12852 let mut selected_text = None;
12853
12854 let mut selections_iter = selections.iter().peekable();
12855 while let Some(selection) = selections_iter.next() {
12856 if selection.start != selection.end {
12857 only_carets = false;
12858 }
12859
12860 if same_text_selected {
12861 if selected_text.is_none() {
12862 selected_text =
12863 Some(buffer.text_for_range(selection.range()).collect::<String>());
12864 }
12865
12866 if let Some(next_selection) = selections_iter.peek() {
12867 if next_selection.range().len() == selection.range().len() {
12868 let next_selected_text = buffer
12869 .text_for_range(next_selection.range())
12870 .collect::<String>();
12871 if Some(next_selected_text) != selected_text {
12872 same_text_selected = false;
12873 selected_text = None;
12874 }
12875 } else {
12876 same_text_selected = false;
12877 selected_text = None;
12878 }
12879 }
12880 }
12881 }
12882
12883 if only_carets {
12884 for selection in &mut selections {
12885 let word_range = movement::surrounding_word(
12886 display_map,
12887 selection.start.to_display_point(display_map),
12888 );
12889 selection.start = word_range.start.to_offset(display_map, Bias::Left);
12890 selection.end = word_range.end.to_offset(display_map, Bias::Left);
12891 selection.goal = SelectionGoal::None;
12892 selection.reversed = false;
12893 self.select_match_ranges(
12894 selection.start..selection.end,
12895 selection.reversed,
12896 replace_newest,
12897 autoscroll,
12898 window,
12899 cx,
12900 );
12901 }
12902
12903 if selections.len() == 1 {
12904 let selection = selections
12905 .last()
12906 .expect("ensured that there's only one selection");
12907 let query = buffer
12908 .text_for_range(selection.start..selection.end)
12909 .collect::<String>();
12910 let is_empty = query.is_empty();
12911 let select_state = SelectNextState {
12912 query: AhoCorasick::new(&[query])?,
12913 wordwise: true,
12914 done: is_empty,
12915 };
12916 self.select_next_state = Some(select_state);
12917 } else {
12918 self.select_next_state = None;
12919 }
12920 } else if let Some(selected_text) = selected_text {
12921 self.select_next_state = Some(SelectNextState {
12922 query: AhoCorasick::new(&[selected_text])?,
12923 wordwise: false,
12924 done: false,
12925 });
12926 self.select_next_match_internal(
12927 display_map,
12928 replace_newest,
12929 autoscroll,
12930 window,
12931 cx,
12932 )?;
12933 }
12934 }
12935 Ok(())
12936 }
12937
12938 pub fn select_all_matches(
12939 &mut self,
12940 _action: &SelectAllMatches,
12941 window: &mut Window,
12942 cx: &mut Context<Self>,
12943 ) -> Result<()> {
12944 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12945
12946 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12947
12948 self.select_next_match_internal(&display_map, false, None, window, cx)?;
12949 let Some(select_next_state) = self.select_next_state.as_mut() else {
12950 return Ok(());
12951 };
12952 if select_next_state.done {
12953 return Ok(());
12954 }
12955
12956 let mut new_selections = Vec::new();
12957
12958 let reversed = self.selections.oldest::<usize>(cx).reversed;
12959 let buffer = &display_map.buffer_snapshot;
12960 let query_matches = select_next_state
12961 .query
12962 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
12963
12964 for query_match in query_matches.into_iter() {
12965 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
12966 let offset_range = if reversed {
12967 query_match.end()..query_match.start()
12968 } else {
12969 query_match.start()..query_match.end()
12970 };
12971 let display_range = offset_range.start.to_display_point(&display_map)
12972 ..offset_range.end.to_display_point(&display_map);
12973
12974 if !select_next_state.wordwise
12975 || (!movement::is_inside_word(&display_map, display_range.start)
12976 && !movement::is_inside_word(&display_map, display_range.end))
12977 {
12978 new_selections.push(offset_range.start..offset_range.end);
12979 }
12980 }
12981
12982 select_next_state.done = true;
12983 self.unfold_ranges(&new_selections.clone(), false, false, cx);
12984 self.change_selections(None, window, cx, |selections| {
12985 selections.select_ranges(new_selections)
12986 });
12987
12988 Ok(())
12989 }
12990
12991 pub fn select_next(
12992 &mut self,
12993 action: &SelectNext,
12994 window: &mut Window,
12995 cx: &mut Context<Self>,
12996 ) -> Result<()> {
12997 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12998 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12999 self.select_next_match_internal(
13000 &display_map,
13001 action.replace_newest,
13002 Some(Autoscroll::newest()),
13003 window,
13004 cx,
13005 )?;
13006 Ok(())
13007 }
13008
13009 pub fn select_previous(
13010 &mut self,
13011 action: &SelectPrevious,
13012 window: &mut Window,
13013 cx: &mut Context<Self>,
13014 ) -> Result<()> {
13015 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13016 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13017 let buffer = &display_map.buffer_snapshot;
13018 let mut selections = self.selections.all::<usize>(cx);
13019 if let Some(mut select_prev_state) = self.select_prev_state.take() {
13020 let query = &select_prev_state.query;
13021 if !select_prev_state.done {
13022 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13023 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13024 let mut next_selected_range = None;
13025 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
13026 let bytes_before_last_selection =
13027 buffer.reversed_bytes_in_range(0..last_selection.start);
13028 let bytes_after_first_selection =
13029 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
13030 let query_matches = query
13031 .stream_find_iter(bytes_before_last_selection)
13032 .map(|result| (last_selection.start, result))
13033 .chain(
13034 query
13035 .stream_find_iter(bytes_after_first_selection)
13036 .map(|result| (buffer.len(), result)),
13037 );
13038 for (end_offset, query_match) in query_matches {
13039 let query_match = query_match.unwrap(); // can only fail due to I/O
13040 let offset_range =
13041 end_offset - query_match.end()..end_offset - query_match.start();
13042 let display_range = offset_range.start.to_display_point(&display_map)
13043 ..offset_range.end.to_display_point(&display_map);
13044
13045 if !select_prev_state.wordwise
13046 || (!movement::is_inside_word(&display_map, display_range.start)
13047 && !movement::is_inside_word(&display_map, display_range.end))
13048 {
13049 next_selected_range = Some(offset_range);
13050 break;
13051 }
13052 }
13053
13054 if let Some(next_selected_range) = next_selected_range {
13055 self.select_match_ranges(
13056 next_selected_range,
13057 last_selection.reversed,
13058 action.replace_newest,
13059 Some(Autoscroll::newest()),
13060 window,
13061 cx,
13062 );
13063 } else {
13064 select_prev_state.done = true;
13065 }
13066 }
13067
13068 self.select_prev_state = Some(select_prev_state);
13069 } else {
13070 let mut only_carets = true;
13071 let mut same_text_selected = true;
13072 let mut selected_text = None;
13073
13074 let mut selections_iter = selections.iter().peekable();
13075 while let Some(selection) = selections_iter.next() {
13076 if selection.start != selection.end {
13077 only_carets = false;
13078 }
13079
13080 if same_text_selected {
13081 if selected_text.is_none() {
13082 selected_text =
13083 Some(buffer.text_for_range(selection.range()).collect::<String>());
13084 }
13085
13086 if let Some(next_selection) = selections_iter.peek() {
13087 if next_selection.range().len() == selection.range().len() {
13088 let next_selected_text = buffer
13089 .text_for_range(next_selection.range())
13090 .collect::<String>();
13091 if Some(next_selected_text) != selected_text {
13092 same_text_selected = false;
13093 selected_text = None;
13094 }
13095 } else {
13096 same_text_selected = false;
13097 selected_text = None;
13098 }
13099 }
13100 }
13101 }
13102
13103 if only_carets {
13104 for selection in &mut selections {
13105 let word_range = movement::surrounding_word(
13106 &display_map,
13107 selection.start.to_display_point(&display_map),
13108 );
13109 selection.start = word_range.start.to_offset(&display_map, Bias::Left);
13110 selection.end = word_range.end.to_offset(&display_map, Bias::Left);
13111 selection.goal = SelectionGoal::None;
13112 selection.reversed = false;
13113 self.select_match_ranges(
13114 selection.start..selection.end,
13115 selection.reversed,
13116 action.replace_newest,
13117 Some(Autoscroll::newest()),
13118 window,
13119 cx,
13120 );
13121 }
13122 if selections.len() == 1 {
13123 let selection = selections
13124 .last()
13125 .expect("ensured that there's only one selection");
13126 let query = buffer
13127 .text_for_range(selection.start..selection.end)
13128 .collect::<String>();
13129 let is_empty = query.is_empty();
13130 let select_state = SelectNextState {
13131 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
13132 wordwise: true,
13133 done: is_empty,
13134 };
13135 self.select_prev_state = Some(select_state);
13136 } else {
13137 self.select_prev_state = None;
13138 }
13139 } else if let Some(selected_text) = selected_text {
13140 self.select_prev_state = Some(SelectNextState {
13141 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
13142 wordwise: false,
13143 done: false,
13144 });
13145 self.select_previous(action, window, cx)?;
13146 }
13147 }
13148 Ok(())
13149 }
13150
13151 pub fn find_next_match(
13152 &mut self,
13153 _: &FindNextMatch,
13154 window: &mut Window,
13155 cx: &mut Context<Self>,
13156 ) -> Result<()> {
13157 let selections = self.selections.disjoint_anchors();
13158 match selections.first() {
13159 Some(first) if selections.len() >= 2 => {
13160 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13161 s.select_ranges([first.range()]);
13162 });
13163 }
13164 _ => self.select_next(
13165 &SelectNext {
13166 replace_newest: true,
13167 },
13168 window,
13169 cx,
13170 )?,
13171 }
13172 Ok(())
13173 }
13174
13175 pub fn find_previous_match(
13176 &mut self,
13177 _: &FindPreviousMatch,
13178 window: &mut Window,
13179 cx: &mut Context<Self>,
13180 ) -> Result<()> {
13181 let selections = self.selections.disjoint_anchors();
13182 match selections.last() {
13183 Some(last) if selections.len() >= 2 => {
13184 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13185 s.select_ranges([last.range()]);
13186 });
13187 }
13188 _ => self.select_previous(
13189 &SelectPrevious {
13190 replace_newest: true,
13191 },
13192 window,
13193 cx,
13194 )?,
13195 }
13196 Ok(())
13197 }
13198
13199 pub fn toggle_comments(
13200 &mut self,
13201 action: &ToggleComments,
13202 window: &mut Window,
13203 cx: &mut Context<Self>,
13204 ) {
13205 if self.read_only(cx) {
13206 return;
13207 }
13208 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
13209 let text_layout_details = &self.text_layout_details(window);
13210 self.transact(window, cx, |this, window, cx| {
13211 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
13212 let mut edits = Vec::new();
13213 let mut selection_edit_ranges = Vec::new();
13214 let mut last_toggled_row = None;
13215 let snapshot = this.buffer.read(cx).read(cx);
13216 let empty_str: Arc<str> = Arc::default();
13217 let mut suffixes_inserted = Vec::new();
13218 let ignore_indent = action.ignore_indent;
13219
13220 fn comment_prefix_range(
13221 snapshot: &MultiBufferSnapshot,
13222 row: MultiBufferRow,
13223 comment_prefix: &str,
13224 comment_prefix_whitespace: &str,
13225 ignore_indent: bool,
13226 ) -> Range<Point> {
13227 let indent_size = if ignore_indent {
13228 0
13229 } else {
13230 snapshot.indent_size_for_line(row).len
13231 };
13232
13233 let start = Point::new(row.0, indent_size);
13234
13235 let mut line_bytes = snapshot
13236 .bytes_in_range(start..snapshot.max_point())
13237 .flatten()
13238 .copied();
13239
13240 // If this line currently begins with the line comment prefix, then record
13241 // the range containing the prefix.
13242 if line_bytes
13243 .by_ref()
13244 .take(comment_prefix.len())
13245 .eq(comment_prefix.bytes())
13246 {
13247 // Include any whitespace that matches the comment prefix.
13248 let matching_whitespace_len = line_bytes
13249 .zip(comment_prefix_whitespace.bytes())
13250 .take_while(|(a, b)| a == b)
13251 .count() as u32;
13252 let end = Point::new(
13253 start.row,
13254 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
13255 );
13256 start..end
13257 } else {
13258 start..start
13259 }
13260 }
13261
13262 fn comment_suffix_range(
13263 snapshot: &MultiBufferSnapshot,
13264 row: MultiBufferRow,
13265 comment_suffix: &str,
13266 comment_suffix_has_leading_space: bool,
13267 ) -> Range<Point> {
13268 let end = Point::new(row.0, snapshot.line_len(row));
13269 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
13270
13271 let mut line_end_bytes = snapshot
13272 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
13273 .flatten()
13274 .copied();
13275
13276 let leading_space_len = if suffix_start_column > 0
13277 && line_end_bytes.next() == Some(b' ')
13278 && comment_suffix_has_leading_space
13279 {
13280 1
13281 } else {
13282 0
13283 };
13284
13285 // If this line currently begins with the line comment prefix, then record
13286 // the range containing the prefix.
13287 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
13288 let start = Point::new(end.row, suffix_start_column - leading_space_len);
13289 start..end
13290 } else {
13291 end..end
13292 }
13293 }
13294
13295 // TODO: Handle selections that cross excerpts
13296 for selection in &mut selections {
13297 let start_column = snapshot
13298 .indent_size_for_line(MultiBufferRow(selection.start.row))
13299 .len;
13300 let language = if let Some(language) =
13301 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
13302 {
13303 language
13304 } else {
13305 continue;
13306 };
13307
13308 selection_edit_ranges.clear();
13309
13310 // If multiple selections contain a given row, avoid processing that
13311 // row more than once.
13312 let mut start_row = MultiBufferRow(selection.start.row);
13313 if last_toggled_row == Some(start_row) {
13314 start_row = start_row.next_row();
13315 }
13316 let end_row =
13317 if selection.end.row > selection.start.row && selection.end.column == 0 {
13318 MultiBufferRow(selection.end.row - 1)
13319 } else {
13320 MultiBufferRow(selection.end.row)
13321 };
13322 last_toggled_row = Some(end_row);
13323
13324 if start_row > end_row {
13325 continue;
13326 }
13327
13328 // If the language has line comments, toggle those.
13329 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
13330
13331 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
13332 if ignore_indent {
13333 full_comment_prefixes = full_comment_prefixes
13334 .into_iter()
13335 .map(|s| Arc::from(s.trim_end()))
13336 .collect();
13337 }
13338
13339 if !full_comment_prefixes.is_empty() {
13340 let first_prefix = full_comment_prefixes
13341 .first()
13342 .expect("prefixes is non-empty");
13343 let prefix_trimmed_lengths = full_comment_prefixes
13344 .iter()
13345 .map(|p| p.trim_end_matches(' ').len())
13346 .collect::<SmallVec<[usize; 4]>>();
13347
13348 let mut all_selection_lines_are_comments = true;
13349
13350 for row in start_row.0..=end_row.0 {
13351 let row = MultiBufferRow(row);
13352 if start_row < end_row && snapshot.is_line_blank(row) {
13353 continue;
13354 }
13355
13356 let prefix_range = full_comment_prefixes
13357 .iter()
13358 .zip(prefix_trimmed_lengths.iter().copied())
13359 .map(|(prefix, trimmed_prefix_len)| {
13360 comment_prefix_range(
13361 snapshot.deref(),
13362 row,
13363 &prefix[..trimmed_prefix_len],
13364 &prefix[trimmed_prefix_len..],
13365 ignore_indent,
13366 )
13367 })
13368 .max_by_key(|range| range.end.column - range.start.column)
13369 .expect("prefixes is non-empty");
13370
13371 if prefix_range.is_empty() {
13372 all_selection_lines_are_comments = false;
13373 }
13374
13375 selection_edit_ranges.push(prefix_range);
13376 }
13377
13378 if all_selection_lines_are_comments {
13379 edits.extend(
13380 selection_edit_ranges
13381 .iter()
13382 .cloned()
13383 .map(|range| (range, empty_str.clone())),
13384 );
13385 } else {
13386 let min_column = selection_edit_ranges
13387 .iter()
13388 .map(|range| range.start.column)
13389 .min()
13390 .unwrap_or(0);
13391 edits.extend(selection_edit_ranges.iter().map(|range| {
13392 let position = Point::new(range.start.row, min_column);
13393 (position..position, first_prefix.clone())
13394 }));
13395 }
13396 } else if let Some((full_comment_prefix, comment_suffix)) =
13397 language.block_comment_delimiters()
13398 {
13399 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
13400 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
13401 let prefix_range = comment_prefix_range(
13402 snapshot.deref(),
13403 start_row,
13404 comment_prefix,
13405 comment_prefix_whitespace,
13406 ignore_indent,
13407 );
13408 let suffix_range = comment_suffix_range(
13409 snapshot.deref(),
13410 end_row,
13411 comment_suffix.trim_start_matches(' '),
13412 comment_suffix.starts_with(' '),
13413 );
13414
13415 if prefix_range.is_empty() || suffix_range.is_empty() {
13416 edits.push((
13417 prefix_range.start..prefix_range.start,
13418 full_comment_prefix.clone(),
13419 ));
13420 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
13421 suffixes_inserted.push((end_row, comment_suffix.len()));
13422 } else {
13423 edits.push((prefix_range, empty_str.clone()));
13424 edits.push((suffix_range, empty_str.clone()));
13425 }
13426 } else {
13427 continue;
13428 }
13429 }
13430
13431 drop(snapshot);
13432 this.buffer.update(cx, |buffer, cx| {
13433 buffer.edit(edits, None, cx);
13434 });
13435
13436 // Adjust selections so that they end before any comment suffixes that
13437 // were inserted.
13438 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
13439 let mut selections = this.selections.all::<Point>(cx);
13440 let snapshot = this.buffer.read(cx).read(cx);
13441 for selection in &mut selections {
13442 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
13443 match row.cmp(&MultiBufferRow(selection.end.row)) {
13444 Ordering::Less => {
13445 suffixes_inserted.next();
13446 continue;
13447 }
13448 Ordering::Greater => break,
13449 Ordering::Equal => {
13450 if selection.end.column == snapshot.line_len(row) {
13451 if selection.is_empty() {
13452 selection.start.column -= suffix_len as u32;
13453 }
13454 selection.end.column -= suffix_len as u32;
13455 }
13456 break;
13457 }
13458 }
13459 }
13460 }
13461
13462 drop(snapshot);
13463 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13464 s.select(selections)
13465 });
13466
13467 let selections = this.selections.all::<Point>(cx);
13468 let selections_on_single_row = selections.windows(2).all(|selections| {
13469 selections[0].start.row == selections[1].start.row
13470 && selections[0].end.row == selections[1].end.row
13471 && selections[0].start.row == selections[0].end.row
13472 });
13473 let selections_selecting = selections
13474 .iter()
13475 .any(|selection| selection.start != selection.end);
13476 let advance_downwards = action.advance_downwards
13477 && selections_on_single_row
13478 && !selections_selecting
13479 && !matches!(this.mode, EditorMode::SingleLine { .. });
13480
13481 if advance_downwards {
13482 let snapshot = this.buffer.read(cx).snapshot(cx);
13483
13484 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13485 s.move_cursors_with(|display_snapshot, display_point, _| {
13486 let mut point = display_point.to_point(display_snapshot);
13487 point.row += 1;
13488 point = snapshot.clip_point(point, Bias::Left);
13489 let display_point = point.to_display_point(display_snapshot);
13490 let goal = SelectionGoal::HorizontalPosition(
13491 display_snapshot
13492 .x_for_display_point(display_point, text_layout_details)
13493 .into(),
13494 );
13495 (display_point, goal)
13496 })
13497 });
13498 }
13499 });
13500 }
13501
13502 pub fn select_enclosing_symbol(
13503 &mut self,
13504 _: &SelectEnclosingSymbol,
13505 window: &mut Window,
13506 cx: &mut Context<Self>,
13507 ) {
13508 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13509
13510 let buffer = self.buffer.read(cx).snapshot(cx);
13511 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
13512
13513 fn update_selection(
13514 selection: &Selection<usize>,
13515 buffer_snap: &MultiBufferSnapshot,
13516 ) -> Option<Selection<usize>> {
13517 let cursor = selection.head();
13518 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
13519 for symbol in symbols.iter().rev() {
13520 let start = symbol.range.start.to_offset(buffer_snap);
13521 let end = symbol.range.end.to_offset(buffer_snap);
13522 let new_range = start..end;
13523 if start < selection.start || end > selection.end {
13524 return Some(Selection {
13525 id: selection.id,
13526 start: new_range.start,
13527 end: new_range.end,
13528 goal: SelectionGoal::None,
13529 reversed: selection.reversed,
13530 });
13531 }
13532 }
13533 None
13534 }
13535
13536 let mut selected_larger_symbol = false;
13537 let new_selections = old_selections
13538 .iter()
13539 .map(|selection| match update_selection(selection, &buffer) {
13540 Some(new_selection) => {
13541 if new_selection.range() != selection.range() {
13542 selected_larger_symbol = true;
13543 }
13544 new_selection
13545 }
13546 None => selection.clone(),
13547 })
13548 .collect::<Vec<_>>();
13549
13550 if selected_larger_symbol {
13551 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13552 s.select(new_selections);
13553 });
13554 }
13555 }
13556
13557 pub fn select_larger_syntax_node(
13558 &mut self,
13559 _: &SelectLargerSyntaxNode,
13560 window: &mut Window,
13561 cx: &mut Context<Self>,
13562 ) {
13563 let Some(visible_row_count) = self.visible_row_count() else {
13564 return;
13565 };
13566 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
13567 if old_selections.is_empty() {
13568 return;
13569 }
13570
13571 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13572
13573 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13574 let buffer = self.buffer.read(cx).snapshot(cx);
13575
13576 let mut selected_larger_node = false;
13577 let mut new_selections = old_selections
13578 .iter()
13579 .map(|selection| {
13580 let old_range = selection.start..selection.end;
13581
13582 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
13583 // manually select word at selection
13584 if ["string_content", "inline"].contains(&node.kind()) {
13585 let word_range = {
13586 let display_point = buffer
13587 .offset_to_point(old_range.start)
13588 .to_display_point(&display_map);
13589 let Range { start, end } =
13590 movement::surrounding_word(&display_map, display_point);
13591 start.to_point(&display_map).to_offset(&buffer)
13592 ..end.to_point(&display_map).to_offset(&buffer)
13593 };
13594 // ignore if word is already selected
13595 if !word_range.is_empty() && old_range != word_range {
13596 let last_word_range = {
13597 let display_point = buffer
13598 .offset_to_point(old_range.end)
13599 .to_display_point(&display_map);
13600 let Range { start, end } =
13601 movement::surrounding_word(&display_map, display_point);
13602 start.to_point(&display_map).to_offset(&buffer)
13603 ..end.to_point(&display_map).to_offset(&buffer)
13604 };
13605 // only select word if start and end point belongs to same word
13606 if word_range == last_word_range {
13607 selected_larger_node = true;
13608 return Selection {
13609 id: selection.id,
13610 start: word_range.start,
13611 end: word_range.end,
13612 goal: SelectionGoal::None,
13613 reversed: selection.reversed,
13614 };
13615 }
13616 }
13617 }
13618 }
13619
13620 let mut new_range = old_range.clone();
13621 while let Some((_node, containing_range)) =
13622 buffer.syntax_ancestor(new_range.clone())
13623 {
13624 new_range = match containing_range {
13625 MultiOrSingleBufferOffsetRange::Single(_) => break,
13626 MultiOrSingleBufferOffsetRange::Multi(range) => range,
13627 };
13628 if !display_map.intersects_fold(new_range.start)
13629 && !display_map.intersects_fold(new_range.end)
13630 {
13631 break;
13632 }
13633 }
13634
13635 selected_larger_node |= new_range != old_range;
13636 Selection {
13637 id: selection.id,
13638 start: new_range.start,
13639 end: new_range.end,
13640 goal: SelectionGoal::None,
13641 reversed: selection.reversed,
13642 }
13643 })
13644 .collect::<Vec<_>>();
13645
13646 if !selected_larger_node {
13647 return; // don't put this call in the history
13648 }
13649
13650 // scroll based on transformation done to the last selection created by the user
13651 let (last_old, last_new) = old_selections
13652 .last()
13653 .zip(new_selections.last().cloned())
13654 .expect("old_selections isn't empty");
13655
13656 // revert selection
13657 let is_selection_reversed = {
13658 let should_newest_selection_be_reversed = last_old.start != last_new.start;
13659 new_selections.last_mut().expect("checked above").reversed =
13660 should_newest_selection_be_reversed;
13661 should_newest_selection_be_reversed
13662 };
13663
13664 if selected_larger_node {
13665 self.select_syntax_node_history.disable_clearing = true;
13666 self.change_selections(None, window, cx, |s| {
13667 s.select(new_selections.clone());
13668 });
13669 self.select_syntax_node_history.disable_clearing = false;
13670 }
13671
13672 let start_row = last_new.start.to_display_point(&display_map).row().0;
13673 let end_row = last_new.end.to_display_point(&display_map).row().0;
13674 let selection_height = end_row - start_row + 1;
13675 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
13676
13677 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
13678 let scroll_behavior = if fits_on_the_screen {
13679 self.request_autoscroll(Autoscroll::fit(), cx);
13680 SelectSyntaxNodeScrollBehavior::FitSelection
13681 } else if is_selection_reversed {
13682 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13683 SelectSyntaxNodeScrollBehavior::CursorTop
13684 } else {
13685 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13686 SelectSyntaxNodeScrollBehavior::CursorBottom
13687 };
13688
13689 self.select_syntax_node_history.push((
13690 old_selections,
13691 scroll_behavior,
13692 is_selection_reversed,
13693 ));
13694 }
13695
13696 pub fn select_smaller_syntax_node(
13697 &mut self,
13698 _: &SelectSmallerSyntaxNode,
13699 window: &mut Window,
13700 cx: &mut Context<Self>,
13701 ) {
13702 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13703
13704 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
13705 self.select_syntax_node_history.pop()
13706 {
13707 if let Some(selection) = selections.last_mut() {
13708 selection.reversed = is_selection_reversed;
13709 }
13710
13711 self.select_syntax_node_history.disable_clearing = true;
13712 self.change_selections(None, window, cx, |s| {
13713 s.select(selections.to_vec());
13714 });
13715 self.select_syntax_node_history.disable_clearing = false;
13716
13717 match scroll_behavior {
13718 SelectSyntaxNodeScrollBehavior::CursorTop => {
13719 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13720 }
13721 SelectSyntaxNodeScrollBehavior::FitSelection => {
13722 self.request_autoscroll(Autoscroll::fit(), cx);
13723 }
13724 SelectSyntaxNodeScrollBehavior::CursorBottom => {
13725 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13726 }
13727 }
13728 }
13729 }
13730
13731 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
13732 if !EditorSettings::get_global(cx).gutter.runnables {
13733 self.clear_tasks();
13734 return Task::ready(());
13735 }
13736 let project = self.project.as_ref().map(Entity::downgrade);
13737 let task_sources = self.lsp_task_sources(cx);
13738 let multi_buffer = self.buffer.downgrade();
13739 cx.spawn_in(window, async move |editor, cx| {
13740 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
13741 let Some(project) = project.and_then(|p| p.upgrade()) else {
13742 return;
13743 };
13744 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
13745 this.display_map.update(cx, |map, cx| map.snapshot(cx))
13746 }) else {
13747 return;
13748 };
13749
13750 let hide_runnables = project
13751 .update(cx, |project, cx| {
13752 // Do not display any test indicators in non-dev server remote projects.
13753 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
13754 })
13755 .unwrap_or(true);
13756 if hide_runnables {
13757 return;
13758 }
13759 let new_rows =
13760 cx.background_spawn({
13761 let snapshot = display_snapshot.clone();
13762 async move {
13763 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
13764 }
13765 })
13766 .await;
13767 let Ok(lsp_tasks) =
13768 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
13769 else {
13770 return;
13771 };
13772 let lsp_tasks = lsp_tasks.await;
13773
13774 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
13775 lsp_tasks
13776 .into_iter()
13777 .flat_map(|(kind, tasks)| {
13778 tasks.into_iter().filter_map(move |(location, task)| {
13779 Some((kind.clone(), location?, task))
13780 })
13781 })
13782 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
13783 let buffer = location.target.buffer;
13784 let buffer_snapshot = buffer.read(cx).snapshot();
13785 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
13786 |(excerpt_id, snapshot, _)| {
13787 if snapshot.remote_id() == buffer_snapshot.remote_id() {
13788 display_snapshot
13789 .buffer_snapshot
13790 .anchor_in_excerpt(excerpt_id, location.target.range.start)
13791 } else {
13792 None
13793 }
13794 },
13795 );
13796 if let Some(offset) = offset {
13797 let task_buffer_range =
13798 location.target.range.to_point(&buffer_snapshot);
13799 let context_buffer_range =
13800 task_buffer_range.to_offset(&buffer_snapshot);
13801 let context_range = BufferOffset(context_buffer_range.start)
13802 ..BufferOffset(context_buffer_range.end);
13803
13804 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
13805 .or_insert_with(|| RunnableTasks {
13806 templates: Vec::new(),
13807 offset,
13808 column: task_buffer_range.start.column,
13809 extra_variables: HashMap::default(),
13810 context_range,
13811 })
13812 .templates
13813 .push((kind, task.original_task().clone()));
13814 }
13815
13816 acc
13817 })
13818 }) else {
13819 return;
13820 };
13821
13822 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
13823 buffer.language_settings(cx).tasks.prefer_lsp
13824 }) else {
13825 return;
13826 };
13827
13828 let rows = Self::runnable_rows(
13829 project,
13830 display_snapshot,
13831 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
13832 new_rows,
13833 cx.clone(),
13834 );
13835 editor
13836 .update(cx, |editor, _| {
13837 editor.clear_tasks();
13838 for (key, mut value) in rows {
13839 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
13840 value.templates.extend(lsp_tasks.templates);
13841 }
13842
13843 editor.insert_tasks(key, value);
13844 }
13845 for (key, value) in lsp_tasks_by_rows {
13846 editor.insert_tasks(key, value);
13847 }
13848 })
13849 .ok();
13850 })
13851 }
13852 fn fetch_runnable_ranges(
13853 snapshot: &DisplaySnapshot,
13854 range: Range<Anchor>,
13855 ) -> Vec<language::RunnableRange> {
13856 snapshot.buffer_snapshot.runnable_ranges(range).collect()
13857 }
13858
13859 fn runnable_rows(
13860 project: Entity<Project>,
13861 snapshot: DisplaySnapshot,
13862 prefer_lsp: bool,
13863 runnable_ranges: Vec<RunnableRange>,
13864 mut cx: AsyncWindowContext,
13865 ) -> Vec<((BufferId, BufferRow), RunnableTasks)> {
13866 runnable_ranges
13867 .into_iter()
13868 .filter_map(|mut runnable| {
13869 let mut tasks = cx
13870 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
13871 .ok()?;
13872 if prefer_lsp {
13873 tasks.retain(|(task_kind, _)| {
13874 !matches!(task_kind, TaskSourceKind::Language { .. })
13875 });
13876 }
13877 if tasks.is_empty() {
13878 return None;
13879 }
13880
13881 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
13882
13883 let row = snapshot
13884 .buffer_snapshot
13885 .buffer_line_for_row(MultiBufferRow(point.row))?
13886 .1
13887 .start
13888 .row;
13889
13890 let context_range =
13891 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
13892 Some((
13893 (runnable.buffer_id, row),
13894 RunnableTasks {
13895 templates: tasks,
13896 offset: snapshot
13897 .buffer_snapshot
13898 .anchor_before(runnable.run_range.start),
13899 context_range,
13900 column: point.column,
13901 extra_variables: runnable.extra_captures,
13902 },
13903 ))
13904 })
13905 .collect()
13906 }
13907
13908 fn templates_with_tags(
13909 project: &Entity<Project>,
13910 runnable: &mut Runnable,
13911 cx: &mut App,
13912 ) -> Vec<(TaskSourceKind, TaskTemplate)> {
13913 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
13914 let (worktree_id, file) = project
13915 .buffer_for_id(runnable.buffer, cx)
13916 .and_then(|buffer| buffer.read(cx).file())
13917 .map(|file| (file.worktree_id(cx), file.clone()))
13918 .unzip();
13919
13920 (
13921 project.task_store().read(cx).task_inventory().cloned(),
13922 worktree_id,
13923 file,
13924 )
13925 });
13926
13927 let mut templates_with_tags = mem::take(&mut runnable.tags)
13928 .into_iter()
13929 .flat_map(|RunnableTag(tag)| {
13930 inventory
13931 .as_ref()
13932 .into_iter()
13933 .flat_map(|inventory| {
13934 inventory.read(cx).list_tasks(
13935 file.clone(),
13936 Some(runnable.language.clone()),
13937 worktree_id,
13938 cx,
13939 )
13940 })
13941 .filter(move |(_, template)| {
13942 template.tags.iter().any(|source_tag| source_tag == &tag)
13943 })
13944 })
13945 .sorted_by_key(|(kind, _)| kind.to_owned())
13946 .collect::<Vec<_>>();
13947 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
13948 // Strongest source wins; if we have worktree tag binding, prefer that to
13949 // global and language bindings;
13950 // if we have a global binding, prefer that to language binding.
13951 let first_mismatch = templates_with_tags
13952 .iter()
13953 .position(|(tag_source, _)| tag_source != leading_tag_source);
13954 if let Some(index) = first_mismatch {
13955 templates_with_tags.truncate(index);
13956 }
13957 }
13958
13959 templates_with_tags
13960 }
13961
13962 pub fn move_to_enclosing_bracket(
13963 &mut self,
13964 _: &MoveToEnclosingBracket,
13965 window: &mut Window,
13966 cx: &mut Context<Self>,
13967 ) {
13968 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13969 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13970 s.move_offsets_with(|snapshot, selection| {
13971 let Some(enclosing_bracket_ranges) =
13972 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
13973 else {
13974 return;
13975 };
13976
13977 let mut best_length = usize::MAX;
13978 let mut best_inside = false;
13979 let mut best_in_bracket_range = false;
13980 let mut best_destination = None;
13981 for (open, close) in enclosing_bracket_ranges {
13982 let close = close.to_inclusive();
13983 let length = close.end() - open.start;
13984 let inside = selection.start >= open.end && selection.end <= *close.start();
13985 let in_bracket_range = open.to_inclusive().contains(&selection.head())
13986 || close.contains(&selection.head());
13987
13988 // If best is next to a bracket and current isn't, skip
13989 if !in_bracket_range && best_in_bracket_range {
13990 continue;
13991 }
13992
13993 // Prefer smaller lengths unless best is inside and current isn't
13994 if length > best_length && (best_inside || !inside) {
13995 continue;
13996 }
13997
13998 best_length = length;
13999 best_inside = inside;
14000 best_in_bracket_range = in_bracket_range;
14001 best_destination = Some(
14002 if close.contains(&selection.start) && close.contains(&selection.end) {
14003 if inside { open.end } else { open.start }
14004 } else if inside {
14005 *close.start()
14006 } else {
14007 *close.end()
14008 },
14009 );
14010 }
14011
14012 if let Some(destination) = best_destination {
14013 selection.collapse_to(destination, SelectionGoal::None);
14014 }
14015 })
14016 });
14017 }
14018
14019 pub fn undo_selection(
14020 &mut self,
14021 _: &UndoSelection,
14022 window: &mut Window,
14023 cx: &mut Context<Self>,
14024 ) {
14025 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14026 self.end_selection(window, cx);
14027 self.selection_history.mode = SelectionHistoryMode::Undoing;
14028 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
14029 self.change_selections(None, window, cx, |s| {
14030 s.select_anchors(entry.selections.to_vec())
14031 });
14032 self.select_next_state = entry.select_next_state;
14033 self.select_prev_state = entry.select_prev_state;
14034 self.add_selections_state = entry.add_selections_state;
14035 self.request_autoscroll(Autoscroll::newest(), cx);
14036 }
14037 self.selection_history.mode = SelectionHistoryMode::Normal;
14038 }
14039
14040 pub fn redo_selection(
14041 &mut self,
14042 _: &RedoSelection,
14043 window: &mut Window,
14044 cx: &mut Context<Self>,
14045 ) {
14046 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14047 self.end_selection(window, cx);
14048 self.selection_history.mode = SelectionHistoryMode::Redoing;
14049 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
14050 self.change_selections(None, window, cx, |s| {
14051 s.select_anchors(entry.selections.to_vec())
14052 });
14053 self.select_next_state = entry.select_next_state;
14054 self.select_prev_state = entry.select_prev_state;
14055 self.add_selections_state = entry.add_selections_state;
14056 self.request_autoscroll(Autoscroll::newest(), cx);
14057 }
14058 self.selection_history.mode = SelectionHistoryMode::Normal;
14059 }
14060
14061 pub fn expand_excerpts(
14062 &mut self,
14063 action: &ExpandExcerpts,
14064 _: &mut Window,
14065 cx: &mut Context<Self>,
14066 ) {
14067 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
14068 }
14069
14070 pub fn expand_excerpts_down(
14071 &mut self,
14072 action: &ExpandExcerptsDown,
14073 _: &mut Window,
14074 cx: &mut Context<Self>,
14075 ) {
14076 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
14077 }
14078
14079 pub fn expand_excerpts_up(
14080 &mut self,
14081 action: &ExpandExcerptsUp,
14082 _: &mut Window,
14083 cx: &mut Context<Self>,
14084 ) {
14085 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
14086 }
14087
14088 pub fn expand_excerpts_for_direction(
14089 &mut self,
14090 lines: u32,
14091 direction: ExpandExcerptDirection,
14092
14093 cx: &mut Context<Self>,
14094 ) {
14095 let selections = self.selections.disjoint_anchors();
14096
14097 let lines = if lines == 0 {
14098 EditorSettings::get_global(cx).expand_excerpt_lines
14099 } else {
14100 lines
14101 };
14102
14103 self.buffer.update(cx, |buffer, cx| {
14104 let snapshot = buffer.snapshot(cx);
14105 let mut excerpt_ids = selections
14106 .iter()
14107 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
14108 .collect::<Vec<_>>();
14109 excerpt_ids.sort();
14110 excerpt_ids.dedup();
14111 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
14112 })
14113 }
14114
14115 pub fn expand_excerpt(
14116 &mut self,
14117 excerpt: ExcerptId,
14118 direction: ExpandExcerptDirection,
14119 window: &mut Window,
14120 cx: &mut Context<Self>,
14121 ) {
14122 let current_scroll_position = self.scroll_position(cx);
14123 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
14124 let mut should_scroll_up = false;
14125
14126 if direction == ExpandExcerptDirection::Down {
14127 let multi_buffer = self.buffer.read(cx);
14128 let snapshot = multi_buffer.snapshot(cx);
14129 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
14130 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
14131 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
14132 let buffer_snapshot = buffer.read(cx).snapshot();
14133 let excerpt_end_row =
14134 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
14135 let last_row = buffer_snapshot.max_point().row;
14136 let lines_below = last_row.saturating_sub(excerpt_end_row);
14137 should_scroll_up = lines_below >= lines_to_expand;
14138 }
14139 }
14140 }
14141 }
14142
14143 self.buffer.update(cx, |buffer, cx| {
14144 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
14145 });
14146
14147 if should_scroll_up {
14148 let new_scroll_position =
14149 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
14150 self.set_scroll_position(new_scroll_position, window, cx);
14151 }
14152 }
14153
14154 pub fn go_to_singleton_buffer_point(
14155 &mut self,
14156 point: Point,
14157 window: &mut Window,
14158 cx: &mut Context<Self>,
14159 ) {
14160 self.go_to_singleton_buffer_range(point..point, window, cx);
14161 }
14162
14163 pub fn go_to_singleton_buffer_range(
14164 &mut self,
14165 range: Range<Point>,
14166 window: &mut Window,
14167 cx: &mut Context<Self>,
14168 ) {
14169 let multibuffer = self.buffer().read(cx);
14170 let Some(buffer) = multibuffer.as_singleton() else {
14171 return;
14172 };
14173 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
14174 return;
14175 };
14176 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
14177 return;
14178 };
14179 self.change_selections(Some(Autoscroll::center()), window, cx, |s| {
14180 s.select_anchor_ranges([start..end])
14181 });
14182 }
14183
14184 pub fn go_to_diagnostic(
14185 &mut self,
14186 _: &GoToDiagnostic,
14187 window: &mut Window,
14188 cx: &mut Context<Self>,
14189 ) {
14190 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14191 self.go_to_diagnostic_impl(Direction::Next, window, cx)
14192 }
14193
14194 pub fn go_to_prev_diagnostic(
14195 &mut self,
14196 _: &GoToPreviousDiagnostic,
14197 window: &mut Window,
14198 cx: &mut Context<Self>,
14199 ) {
14200 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14201 self.go_to_diagnostic_impl(Direction::Prev, window, cx)
14202 }
14203
14204 pub fn go_to_diagnostic_impl(
14205 &mut self,
14206 direction: Direction,
14207 window: &mut Window,
14208 cx: &mut Context<Self>,
14209 ) {
14210 let buffer = self.buffer.read(cx).snapshot(cx);
14211 let selection = self.selections.newest::<usize>(cx);
14212
14213 let mut active_group_id = None;
14214 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
14215 if active_group.active_range.start.to_offset(&buffer) == selection.start {
14216 active_group_id = Some(active_group.group_id);
14217 }
14218 }
14219
14220 fn filtered(
14221 snapshot: EditorSnapshot,
14222 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
14223 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
14224 diagnostics
14225 .filter(|entry| entry.range.start != entry.range.end)
14226 .filter(|entry| !entry.diagnostic.is_unnecessary)
14227 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
14228 }
14229
14230 let snapshot = self.snapshot(window, cx);
14231 let before = filtered(
14232 snapshot.clone(),
14233 buffer
14234 .diagnostics_in_range(0..selection.start)
14235 .filter(|entry| entry.range.start <= selection.start),
14236 );
14237 let after = filtered(
14238 snapshot,
14239 buffer
14240 .diagnostics_in_range(selection.start..buffer.len())
14241 .filter(|entry| entry.range.start >= selection.start),
14242 );
14243
14244 let mut found: Option<DiagnosticEntry<usize>> = None;
14245 if direction == Direction::Prev {
14246 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
14247 {
14248 for diagnostic in prev_diagnostics.into_iter().rev() {
14249 if diagnostic.range.start != selection.start
14250 || active_group_id
14251 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
14252 {
14253 found = Some(diagnostic);
14254 break 'outer;
14255 }
14256 }
14257 }
14258 } else {
14259 for diagnostic in after.chain(before) {
14260 if diagnostic.range.start != selection.start
14261 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
14262 {
14263 found = Some(diagnostic);
14264 break;
14265 }
14266 }
14267 }
14268 let Some(next_diagnostic) = found else {
14269 return;
14270 };
14271
14272 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
14273 return;
14274 };
14275 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14276 s.select_ranges(vec![
14277 next_diagnostic.range.start..next_diagnostic.range.start,
14278 ])
14279 });
14280 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
14281 self.refresh_inline_completion(false, true, window, cx);
14282 }
14283
14284 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
14285 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14286 let snapshot = self.snapshot(window, cx);
14287 let selection = self.selections.newest::<Point>(cx);
14288 self.go_to_hunk_before_or_after_position(
14289 &snapshot,
14290 selection.head(),
14291 Direction::Next,
14292 window,
14293 cx,
14294 );
14295 }
14296
14297 pub fn go_to_hunk_before_or_after_position(
14298 &mut self,
14299 snapshot: &EditorSnapshot,
14300 position: Point,
14301 direction: Direction,
14302 window: &mut Window,
14303 cx: &mut Context<Editor>,
14304 ) {
14305 let row = if direction == Direction::Next {
14306 self.hunk_after_position(snapshot, position)
14307 .map(|hunk| hunk.row_range.start)
14308 } else {
14309 self.hunk_before_position(snapshot, position)
14310 };
14311
14312 if let Some(row) = row {
14313 let destination = Point::new(row.0, 0);
14314 let autoscroll = Autoscroll::center();
14315
14316 self.unfold_ranges(&[destination..destination], false, false, cx);
14317 self.change_selections(Some(autoscroll), window, cx, |s| {
14318 s.select_ranges([destination..destination]);
14319 });
14320 }
14321 }
14322
14323 fn hunk_after_position(
14324 &mut self,
14325 snapshot: &EditorSnapshot,
14326 position: Point,
14327 ) -> Option<MultiBufferDiffHunk> {
14328 snapshot
14329 .buffer_snapshot
14330 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
14331 .find(|hunk| hunk.row_range.start.0 > position.row)
14332 .or_else(|| {
14333 snapshot
14334 .buffer_snapshot
14335 .diff_hunks_in_range(Point::zero()..position)
14336 .find(|hunk| hunk.row_range.end.0 < position.row)
14337 })
14338 }
14339
14340 fn go_to_prev_hunk(
14341 &mut self,
14342 _: &GoToPreviousHunk,
14343 window: &mut Window,
14344 cx: &mut Context<Self>,
14345 ) {
14346 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14347 let snapshot = self.snapshot(window, cx);
14348 let selection = self.selections.newest::<Point>(cx);
14349 self.go_to_hunk_before_or_after_position(
14350 &snapshot,
14351 selection.head(),
14352 Direction::Prev,
14353 window,
14354 cx,
14355 );
14356 }
14357
14358 fn hunk_before_position(
14359 &mut self,
14360 snapshot: &EditorSnapshot,
14361 position: Point,
14362 ) -> Option<MultiBufferRow> {
14363 snapshot
14364 .buffer_snapshot
14365 .diff_hunk_before(position)
14366 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
14367 }
14368
14369 fn go_to_next_change(
14370 &mut self,
14371 _: &GoToNextChange,
14372 window: &mut Window,
14373 cx: &mut Context<Self>,
14374 ) {
14375 if let Some(selections) = self
14376 .change_list
14377 .next_change(1, Direction::Next)
14378 .map(|s| s.to_vec())
14379 {
14380 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14381 let map = s.display_map();
14382 s.select_display_ranges(selections.iter().map(|a| {
14383 let point = a.to_display_point(&map);
14384 point..point
14385 }))
14386 })
14387 }
14388 }
14389
14390 fn go_to_previous_change(
14391 &mut self,
14392 _: &GoToPreviousChange,
14393 window: &mut Window,
14394 cx: &mut Context<Self>,
14395 ) {
14396 if let Some(selections) = self
14397 .change_list
14398 .next_change(1, Direction::Prev)
14399 .map(|s| s.to_vec())
14400 {
14401 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14402 let map = s.display_map();
14403 s.select_display_ranges(selections.iter().map(|a| {
14404 let point = a.to_display_point(&map);
14405 point..point
14406 }))
14407 })
14408 }
14409 }
14410
14411 fn go_to_line<T: 'static>(
14412 &mut self,
14413 position: Anchor,
14414 highlight_color: Option<Hsla>,
14415 window: &mut Window,
14416 cx: &mut Context<Self>,
14417 ) {
14418 let snapshot = self.snapshot(window, cx).display_snapshot;
14419 let position = position.to_point(&snapshot.buffer_snapshot);
14420 let start = snapshot
14421 .buffer_snapshot
14422 .clip_point(Point::new(position.row, 0), Bias::Left);
14423 let end = start + Point::new(1, 0);
14424 let start = snapshot.buffer_snapshot.anchor_before(start);
14425 let end = snapshot.buffer_snapshot.anchor_before(end);
14426
14427 self.highlight_rows::<T>(
14428 start..end,
14429 highlight_color
14430 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
14431 Default::default(),
14432 cx,
14433 );
14434
14435 if self.buffer.read(cx).is_singleton() {
14436 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
14437 }
14438 }
14439
14440 pub fn go_to_definition(
14441 &mut self,
14442 _: &GoToDefinition,
14443 window: &mut Window,
14444 cx: &mut Context<Self>,
14445 ) -> Task<Result<Navigated>> {
14446 let definition =
14447 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
14448 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
14449 cx.spawn_in(window, async move |editor, cx| {
14450 if definition.await? == Navigated::Yes {
14451 return Ok(Navigated::Yes);
14452 }
14453 match fallback_strategy {
14454 GoToDefinitionFallback::None => Ok(Navigated::No),
14455 GoToDefinitionFallback::FindAllReferences => {
14456 match editor.update_in(cx, |editor, window, cx| {
14457 editor.find_all_references(&FindAllReferences, window, cx)
14458 })? {
14459 Some(references) => references.await,
14460 None => Ok(Navigated::No),
14461 }
14462 }
14463 }
14464 })
14465 }
14466
14467 pub fn go_to_declaration(
14468 &mut self,
14469 _: &GoToDeclaration,
14470 window: &mut Window,
14471 cx: &mut Context<Self>,
14472 ) -> Task<Result<Navigated>> {
14473 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
14474 }
14475
14476 pub fn go_to_declaration_split(
14477 &mut self,
14478 _: &GoToDeclaration,
14479 window: &mut Window,
14480 cx: &mut Context<Self>,
14481 ) -> Task<Result<Navigated>> {
14482 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
14483 }
14484
14485 pub fn go_to_implementation(
14486 &mut self,
14487 _: &GoToImplementation,
14488 window: &mut Window,
14489 cx: &mut Context<Self>,
14490 ) -> Task<Result<Navigated>> {
14491 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
14492 }
14493
14494 pub fn go_to_implementation_split(
14495 &mut self,
14496 _: &GoToImplementationSplit,
14497 window: &mut Window,
14498 cx: &mut Context<Self>,
14499 ) -> Task<Result<Navigated>> {
14500 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
14501 }
14502
14503 pub fn go_to_type_definition(
14504 &mut self,
14505 _: &GoToTypeDefinition,
14506 window: &mut Window,
14507 cx: &mut Context<Self>,
14508 ) -> Task<Result<Navigated>> {
14509 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
14510 }
14511
14512 pub fn go_to_definition_split(
14513 &mut self,
14514 _: &GoToDefinitionSplit,
14515 window: &mut Window,
14516 cx: &mut Context<Self>,
14517 ) -> Task<Result<Navigated>> {
14518 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
14519 }
14520
14521 pub fn go_to_type_definition_split(
14522 &mut self,
14523 _: &GoToTypeDefinitionSplit,
14524 window: &mut Window,
14525 cx: &mut Context<Self>,
14526 ) -> Task<Result<Navigated>> {
14527 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
14528 }
14529
14530 fn go_to_definition_of_kind(
14531 &mut self,
14532 kind: GotoDefinitionKind,
14533 split: bool,
14534 window: &mut Window,
14535 cx: &mut Context<Self>,
14536 ) -> Task<Result<Navigated>> {
14537 let Some(provider) = self.semantics_provider.clone() else {
14538 return Task::ready(Ok(Navigated::No));
14539 };
14540 let head = self.selections.newest::<usize>(cx).head();
14541 let buffer = self.buffer.read(cx);
14542 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
14543 text_anchor
14544 } else {
14545 return Task::ready(Ok(Navigated::No));
14546 };
14547
14548 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
14549 return Task::ready(Ok(Navigated::No));
14550 };
14551
14552 cx.spawn_in(window, async move |editor, cx| {
14553 let definitions = definitions.await?;
14554 let navigated = editor
14555 .update_in(cx, |editor, window, cx| {
14556 editor.navigate_to_hover_links(
14557 Some(kind),
14558 definitions
14559 .into_iter()
14560 .filter(|location| {
14561 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
14562 })
14563 .map(HoverLink::Text)
14564 .collect::<Vec<_>>(),
14565 split,
14566 window,
14567 cx,
14568 )
14569 })?
14570 .await?;
14571 anyhow::Ok(navigated)
14572 })
14573 }
14574
14575 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
14576 let selection = self.selections.newest_anchor();
14577 let head = selection.head();
14578 let tail = selection.tail();
14579
14580 let Some((buffer, start_position)) =
14581 self.buffer.read(cx).text_anchor_for_position(head, cx)
14582 else {
14583 return;
14584 };
14585
14586 let end_position = if head != tail {
14587 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
14588 return;
14589 };
14590 Some(pos)
14591 } else {
14592 None
14593 };
14594
14595 let url_finder = cx.spawn_in(window, async move |editor, cx| {
14596 let url = if let Some(end_pos) = end_position {
14597 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
14598 } else {
14599 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
14600 };
14601
14602 if let Some(url) = url {
14603 editor.update(cx, |_, cx| {
14604 cx.open_url(&url);
14605 })
14606 } else {
14607 Ok(())
14608 }
14609 });
14610
14611 url_finder.detach();
14612 }
14613
14614 pub fn open_selected_filename(
14615 &mut self,
14616 _: &OpenSelectedFilename,
14617 window: &mut Window,
14618 cx: &mut Context<Self>,
14619 ) {
14620 let Some(workspace) = self.workspace() else {
14621 return;
14622 };
14623
14624 let position = self.selections.newest_anchor().head();
14625
14626 let Some((buffer, buffer_position)) =
14627 self.buffer.read(cx).text_anchor_for_position(position, cx)
14628 else {
14629 return;
14630 };
14631
14632 let project = self.project.clone();
14633
14634 cx.spawn_in(window, async move |_, cx| {
14635 let result = find_file(&buffer, project, buffer_position, cx).await;
14636
14637 if let Some((_, path)) = result {
14638 workspace
14639 .update_in(cx, |workspace, window, cx| {
14640 workspace.open_resolved_path(path, window, cx)
14641 })?
14642 .await?;
14643 }
14644 anyhow::Ok(())
14645 })
14646 .detach();
14647 }
14648
14649 pub(crate) fn navigate_to_hover_links(
14650 &mut self,
14651 kind: Option<GotoDefinitionKind>,
14652 mut definitions: Vec<HoverLink>,
14653 split: bool,
14654 window: &mut Window,
14655 cx: &mut Context<Editor>,
14656 ) -> Task<Result<Navigated>> {
14657 // If there is one definition, just open it directly
14658 if definitions.len() == 1 {
14659 let definition = definitions.pop().unwrap();
14660
14661 enum TargetTaskResult {
14662 Location(Option<Location>),
14663 AlreadyNavigated,
14664 }
14665
14666 let target_task = match definition {
14667 HoverLink::Text(link) => {
14668 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
14669 }
14670 HoverLink::InlayHint(lsp_location, server_id) => {
14671 let computation =
14672 self.compute_target_location(lsp_location, server_id, window, cx);
14673 cx.background_spawn(async move {
14674 let location = computation.await?;
14675 Ok(TargetTaskResult::Location(location))
14676 })
14677 }
14678 HoverLink::Url(url) => {
14679 cx.open_url(&url);
14680 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
14681 }
14682 HoverLink::File(path) => {
14683 if let Some(workspace) = self.workspace() {
14684 cx.spawn_in(window, async move |_, cx| {
14685 workspace
14686 .update_in(cx, |workspace, window, cx| {
14687 workspace.open_resolved_path(path, window, cx)
14688 })?
14689 .await
14690 .map(|_| TargetTaskResult::AlreadyNavigated)
14691 })
14692 } else {
14693 Task::ready(Ok(TargetTaskResult::Location(None)))
14694 }
14695 }
14696 };
14697 cx.spawn_in(window, async move |editor, cx| {
14698 let target = match target_task.await.context("target resolution task")? {
14699 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
14700 TargetTaskResult::Location(None) => return Ok(Navigated::No),
14701 TargetTaskResult::Location(Some(target)) => target,
14702 };
14703
14704 editor.update_in(cx, |editor, window, cx| {
14705 let Some(workspace) = editor.workspace() else {
14706 return Navigated::No;
14707 };
14708 let pane = workspace.read(cx).active_pane().clone();
14709
14710 let range = target.range.to_point(target.buffer.read(cx));
14711 let range = editor.range_for_match(&range);
14712 let range = collapse_multiline_range(range);
14713
14714 if !split
14715 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
14716 {
14717 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
14718 } else {
14719 window.defer(cx, move |window, cx| {
14720 let target_editor: Entity<Self> =
14721 workspace.update(cx, |workspace, cx| {
14722 let pane = if split {
14723 workspace.adjacent_pane(window, cx)
14724 } else {
14725 workspace.active_pane().clone()
14726 };
14727
14728 workspace.open_project_item(
14729 pane,
14730 target.buffer.clone(),
14731 true,
14732 true,
14733 window,
14734 cx,
14735 )
14736 });
14737 target_editor.update(cx, |target_editor, cx| {
14738 // When selecting a definition in a different buffer, disable the nav history
14739 // to avoid creating a history entry at the previous cursor location.
14740 pane.update(cx, |pane, _| pane.disable_history());
14741 target_editor.go_to_singleton_buffer_range(range, window, cx);
14742 pane.update(cx, |pane, _| pane.enable_history());
14743 });
14744 });
14745 }
14746 Navigated::Yes
14747 })
14748 })
14749 } else if !definitions.is_empty() {
14750 cx.spawn_in(window, async move |editor, cx| {
14751 let (title, location_tasks, workspace) = editor
14752 .update_in(cx, |editor, window, cx| {
14753 let tab_kind = match kind {
14754 Some(GotoDefinitionKind::Implementation) => "Implementations",
14755 _ => "Definitions",
14756 };
14757 let title = definitions
14758 .iter()
14759 .find_map(|definition| match definition {
14760 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
14761 let buffer = origin.buffer.read(cx);
14762 format!(
14763 "{} for {}",
14764 tab_kind,
14765 buffer
14766 .text_for_range(origin.range.clone())
14767 .collect::<String>()
14768 )
14769 }),
14770 HoverLink::InlayHint(_, _) => None,
14771 HoverLink::Url(_) => None,
14772 HoverLink::File(_) => None,
14773 })
14774 .unwrap_or(tab_kind.to_string());
14775 let location_tasks = definitions
14776 .into_iter()
14777 .map(|definition| match definition {
14778 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
14779 HoverLink::InlayHint(lsp_location, server_id) => editor
14780 .compute_target_location(lsp_location, server_id, window, cx),
14781 HoverLink::Url(_) => Task::ready(Ok(None)),
14782 HoverLink::File(_) => Task::ready(Ok(None)),
14783 })
14784 .collect::<Vec<_>>();
14785 (title, location_tasks, editor.workspace().clone())
14786 })
14787 .context("location tasks preparation")?;
14788
14789 let locations = future::join_all(location_tasks)
14790 .await
14791 .into_iter()
14792 .filter_map(|location| location.transpose())
14793 .collect::<Result<_>>()
14794 .context("location tasks")?;
14795
14796 let Some(workspace) = workspace else {
14797 return Ok(Navigated::No);
14798 };
14799 let opened = workspace
14800 .update_in(cx, |workspace, window, cx| {
14801 Self::open_locations_in_multibuffer(
14802 workspace,
14803 locations,
14804 title,
14805 split,
14806 MultibufferSelectionMode::First,
14807 window,
14808 cx,
14809 )
14810 })
14811 .ok();
14812
14813 anyhow::Ok(Navigated::from_bool(opened.is_some()))
14814 })
14815 } else {
14816 Task::ready(Ok(Navigated::No))
14817 }
14818 }
14819
14820 fn compute_target_location(
14821 &self,
14822 lsp_location: lsp::Location,
14823 server_id: LanguageServerId,
14824 window: &mut Window,
14825 cx: &mut Context<Self>,
14826 ) -> Task<anyhow::Result<Option<Location>>> {
14827 let Some(project) = self.project.clone() else {
14828 return Task::ready(Ok(None));
14829 };
14830
14831 cx.spawn_in(window, async move |editor, cx| {
14832 let location_task = editor.update(cx, |_, cx| {
14833 project.update(cx, |project, cx| {
14834 let language_server_name = project
14835 .language_server_statuses(cx)
14836 .find(|(id, _)| server_id == *id)
14837 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
14838 language_server_name.map(|language_server_name| {
14839 project.open_local_buffer_via_lsp(
14840 lsp_location.uri.clone(),
14841 server_id,
14842 language_server_name,
14843 cx,
14844 )
14845 })
14846 })
14847 })?;
14848 let location = match location_task {
14849 Some(task) => Some({
14850 let target_buffer_handle = task.await.context("open local buffer")?;
14851 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
14852 let target_start = target_buffer
14853 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
14854 let target_end = target_buffer
14855 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
14856 target_buffer.anchor_after(target_start)
14857 ..target_buffer.anchor_before(target_end)
14858 })?;
14859 Location {
14860 buffer: target_buffer_handle,
14861 range,
14862 }
14863 }),
14864 None => None,
14865 };
14866 Ok(location)
14867 })
14868 }
14869
14870 pub fn find_all_references(
14871 &mut self,
14872 _: &FindAllReferences,
14873 window: &mut Window,
14874 cx: &mut Context<Self>,
14875 ) -> Option<Task<Result<Navigated>>> {
14876 let selection = self.selections.newest::<usize>(cx);
14877 let multi_buffer = self.buffer.read(cx);
14878 let head = selection.head();
14879
14880 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
14881 let head_anchor = multi_buffer_snapshot.anchor_at(
14882 head,
14883 if head < selection.tail() {
14884 Bias::Right
14885 } else {
14886 Bias::Left
14887 },
14888 );
14889
14890 match self
14891 .find_all_references_task_sources
14892 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
14893 {
14894 Ok(_) => {
14895 log::info!(
14896 "Ignoring repeated FindAllReferences invocation with the position of already running task"
14897 );
14898 return None;
14899 }
14900 Err(i) => {
14901 self.find_all_references_task_sources.insert(i, head_anchor);
14902 }
14903 }
14904
14905 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
14906 let workspace = self.workspace()?;
14907 let project = workspace.read(cx).project().clone();
14908 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
14909 Some(cx.spawn_in(window, async move |editor, cx| {
14910 let _cleanup = cx.on_drop(&editor, move |editor, _| {
14911 if let Ok(i) = editor
14912 .find_all_references_task_sources
14913 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
14914 {
14915 editor.find_all_references_task_sources.remove(i);
14916 }
14917 });
14918
14919 let locations = references.await?;
14920 if locations.is_empty() {
14921 return anyhow::Ok(Navigated::No);
14922 }
14923
14924 workspace.update_in(cx, |workspace, window, cx| {
14925 let title = locations
14926 .first()
14927 .as_ref()
14928 .map(|location| {
14929 let buffer = location.buffer.read(cx);
14930 format!(
14931 "References to `{}`",
14932 buffer
14933 .text_for_range(location.range.clone())
14934 .collect::<String>()
14935 )
14936 })
14937 .unwrap();
14938 Self::open_locations_in_multibuffer(
14939 workspace,
14940 locations,
14941 title,
14942 false,
14943 MultibufferSelectionMode::First,
14944 window,
14945 cx,
14946 );
14947 Navigated::Yes
14948 })
14949 }))
14950 }
14951
14952 /// Opens a multibuffer with the given project locations in it
14953 pub fn open_locations_in_multibuffer(
14954 workspace: &mut Workspace,
14955 mut locations: Vec<Location>,
14956 title: String,
14957 split: bool,
14958 multibuffer_selection_mode: MultibufferSelectionMode,
14959 window: &mut Window,
14960 cx: &mut Context<Workspace>,
14961 ) {
14962 // If there are multiple definitions, open them in a multibuffer
14963 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
14964 let mut locations = locations.into_iter().peekable();
14965 let mut ranges: Vec<Range<Anchor>> = Vec::new();
14966 let capability = workspace.project().read(cx).capability();
14967
14968 let excerpt_buffer = cx.new(|cx| {
14969 let mut multibuffer = MultiBuffer::new(capability);
14970 while let Some(location) = locations.next() {
14971 let buffer = location.buffer.read(cx);
14972 let mut ranges_for_buffer = Vec::new();
14973 let range = location.range.to_point(buffer);
14974 ranges_for_buffer.push(range.clone());
14975
14976 while let Some(next_location) = locations.peek() {
14977 if next_location.buffer == location.buffer {
14978 ranges_for_buffer.push(next_location.range.to_point(buffer));
14979 locations.next();
14980 } else {
14981 break;
14982 }
14983 }
14984
14985 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
14986 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
14987 PathKey::for_buffer(&location.buffer, cx),
14988 location.buffer.clone(),
14989 ranges_for_buffer,
14990 DEFAULT_MULTIBUFFER_CONTEXT,
14991 cx,
14992 );
14993 ranges.extend(new_ranges)
14994 }
14995
14996 multibuffer.with_title(title)
14997 });
14998
14999 let editor = cx.new(|cx| {
15000 Editor::for_multibuffer(
15001 excerpt_buffer,
15002 Some(workspace.project().clone()),
15003 window,
15004 cx,
15005 )
15006 });
15007 editor.update(cx, |editor, cx| {
15008 match multibuffer_selection_mode {
15009 MultibufferSelectionMode::First => {
15010 if let Some(first_range) = ranges.first() {
15011 editor.change_selections(None, window, cx, |selections| {
15012 selections.clear_disjoint();
15013 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
15014 });
15015 }
15016 editor.highlight_background::<Self>(
15017 &ranges,
15018 |theme| theme.editor_highlighted_line_background,
15019 cx,
15020 );
15021 }
15022 MultibufferSelectionMode::All => {
15023 editor.change_selections(None, window, cx, |selections| {
15024 selections.clear_disjoint();
15025 selections.select_anchor_ranges(ranges);
15026 });
15027 }
15028 }
15029 editor.register_buffers_with_language_servers(cx);
15030 });
15031
15032 let item = Box::new(editor);
15033 let item_id = item.item_id();
15034
15035 if split {
15036 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
15037 } else {
15038 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
15039 let (preview_item_id, preview_item_idx) =
15040 workspace.active_pane().read_with(cx, |pane, _| {
15041 (pane.preview_item_id(), pane.preview_item_idx())
15042 });
15043
15044 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
15045
15046 if let Some(preview_item_id) = preview_item_id {
15047 workspace.active_pane().update(cx, |pane, cx| {
15048 pane.remove_item(preview_item_id, false, false, window, cx);
15049 });
15050 }
15051 } else {
15052 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
15053 }
15054 }
15055 workspace.active_pane().update(cx, |pane, cx| {
15056 pane.set_preview_item_id(Some(item_id), cx);
15057 });
15058 }
15059
15060 pub fn rename(
15061 &mut self,
15062 _: &Rename,
15063 window: &mut Window,
15064 cx: &mut Context<Self>,
15065 ) -> Option<Task<Result<()>>> {
15066 use language::ToOffset as _;
15067
15068 let provider = self.semantics_provider.clone()?;
15069 let selection = self.selections.newest_anchor().clone();
15070 let (cursor_buffer, cursor_buffer_position) = self
15071 .buffer
15072 .read(cx)
15073 .text_anchor_for_position(selection.head(), cx)?;
15074 let (tail_buffer, cursor_buffer_position_end) = self
15075 .buffer
15076 .read(cx)
15077 .text_anchor_for_position(selection.tail(), cx)?;
15078 if tail_buffer != cursor_buffer {
15079 return None;
15080 }
15081
15082 let snapshot = cursor_buffer.read(cx).snapshot();
15083 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
15084 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
15085 let prepare_rename = provider
15086 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
15087 .unwrap_or_else(|| Task::ready(Ok(None)));
15088 drop(snapshot);
15089
15090 Some(cx.spawn_in(window, async move |this, cx| {
15091 let rename_range = if let Some(range) = prepare_rename.await? {
15092 Some(range)
15093 } else {
15094 this.update(cx, |this, cx| {
15095 let buffer = this.buffer.read(cx).snapshot(cx);
15096 let mut buffer_highlights = this
15097 .document_highlights_for_position(selection.head(), &buffer)
15098 .filter(|highlight| {
15099 highlight.start.excerpt_id == selection.head().excerpt_id
15100 && highlight.end.excerpt_id == selection.head().excerpt_id
15101 });
15102 buffer_highlights
15103 .next()
15104 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
15105 })?
15106 };
15107 if let Some(rename_range) = rename_range {
15108 this.update_in(cx, |this, window, cx| {
15109 let snapshot = cursor_buffer.read(cx).snapshot();
15110 let rename_buffer_range = rename_range.to_offset(&snapshot);
15111 let cursor_offset_in_rename_range =
15112 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
15113 let cursor_offset_in_rename_range_end =
15114 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
15115
15116 this.take_rename(false, window, cx);
15117 let buffer = this.buffer.read(cx).read(cx);
15118 let cursor_offset = selection.head().to_offset(&buffer);
15119 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
15120 let rename_end = rename_start + rename_buffer_range.len();
15121 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
15122 let mut old_highlight_id = None;
15123 let old_name: Arc<str> = buffer
15124 .chunks(rename_start..rename_end, true)
15125 .map(|chunk| {
15126 if old_highlight_id.is_none() {
15127 old_highlight_id = chunk.syntax_highlight_id;
15128 }
15129 chunk.text
15130 })
15131 .collect::<String>()
15132 .into();
15133
15134 drop(buffer);
15135
15136 // Position the selection in the rename editor so that it matches the current selection.
15137 this.show_local_selections = false;
15138 let rename_editor = cx.new(|cx| {
15139 let mut editor = Editor::single_line(window, cx);
15140 editor.buffer.update(cx, |buffer, cx| {
15141 buffer.edit([(0..0, old_name.clone())], None, cx)
15142 });
15143 let rename_selection_range = match cursor_offset_in_rename_range
15144 .cmp(&cursor_offset_in_rename_range_end)
15145 {
15146 Ordering::Equal => {
15147 editor.select_all(&SelectAll, window, cx);
15148 return editor;
15149 }
15150 Ordering::Less => {
15151 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
15152 }
15153 Ordering::Greater => {
15154 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
15155 }
15156 };
15157 if rename_selection_range.end > old_name.len() {
15158 editor.select_all(&SelectAll, window, cx);
15159 } else {
15160 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
15161 s.select_ranges([rename_selection_range]);
15162 });
15163 }
15164 editor
15165 });
15166 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
15167 if e == &EditorEvent::Focused {
15168 cx.emit(EditorEvent::FocusedIn)
15169 }
15170 })
15171 .detach();
15172
15173 let write_highlights =
15174 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
15175 let read_highlights =
15176 this.clear_background_highlights::<DocumentHighlightRead>(cx);
15177 let ranges = write_highlights
15178 .iter()
15179 .flat_map(|(_, ranges)| ranges.iter())
15180 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
15181 .cloned()
15182 .collect();
15183
15184 this.highlight_text::<Rename>(
15185 ranges,
15186 HighlightStyle {
15187 fade_out: Some(0.6),
15188 ..Default::default()
15189 },
15190 cx,
15191 );
15192 let rename_focus_handle = rename_editor.focus_handle(cx);
15193 window.focus(&rename_focus_handle);
15194 let block_id = this.insert_blocks(
15195 [BlockProperties {
15196 style: BlockStyle::Flex,
15197 placement: BlockPlacement::Below(range.start),
15198 height: Some(1),
15199 render: Arc::new({
15200 let rename_editor = rename_editor.clone();
15201 move |cx: &mut BlockContext| {
15202 let mut text_style = cx.editor_style.text.clone();
15203 if let Some(highlight_style) = old_highlight_id
15204 .and_then(|h| h.style(&cx.editor_style.syntax))
15205 {
15206 text_style = text_style.highlight(highlight_style);
15207 }
15208 div()
15209 .block_mouse_except_scroll()
15210 .pl(cx.anchor_x)
15211 .child(EditorElement::new(
15212 &rename_editor,
15213 EditorStyle {
15214 background: cx.theme().system().transparent,
15215 local_player: cx.editor_style.local_player,
15216 text: text_style,
15217 scrollbar_width: cx.editor_style.scrollbar_width,
15218 syntax: cx.editor_style.syntax.clone(),
15219 status: cx.editor_style.status.clone(),
15220 inlay_hints_style: HighlightStyle {
15221 font_weight: Some(FontWeight::BOLD),
15222 ..make_inlay_hints_style(cx.app)
15223 },
15224 inline_completion_styles: make_suggestion_styles(
15225 cx.app,
15226 ),
15227 ..EditorStyle::default()
15228 },
15229 ))
15230 .into_any_element()
15231 }
15232 }),
15233 priority: 0,
15234 render_in_minimap: true,
15235 }],
15236 Some(Autoscroll::fit()),
15237 cx,
15238 )[0];
15239 this.pending_rename = Some(RenameState {
15240 range,
15241 old_name,
15242 editor: rename_editor,
15243 block_id,
15244 });
15245 })?;
15246 }
15247
15248 Ok(())
15249 }))
15250 }
15251
15252 pub fn confirm_rename(
15253 &mut self,
15254 _: &ConfirmRename,
15255 window: &mut Window,
15256 cx: &mut Context<Self>,
15257 ) -> Option<Task<Result<()>>> {
15258 let rename = self.take_rename(false, window, cx)?;
15259 let workspace = self.workspace()?.downgrade();
15260 let (buffer, start) = self
15261 .buffer
15262 .read(cx)
15263 .text_anchor_for_position(rename.range.start, cx)?;
15264 let (end_buffer, _) = self
15265 .buffer
15266 .read(cx)
15267 .text_anchor_for_position(rename.range.end, cx)?;
15268 if buffer != end_buffer {
15269 return None;
15270 }
15271
15272 let old_name = rename.old_name;
15273 let new_name = rename.editor.read(cx).text(cx);
15274
15275 let rename = self.semantics_provider.as_ref()?.perform_rename(
15276 &buffer,
15277 start,
15278 new_name.clone(),
15279 cx,
15280 )?;
15281
15282 Some(cx.spawn_in(window, async move |editor, cx| {
15283 let project_transaction = rename.await?;
15284 Self::open_project_transaction(
15285 &editor,
15286 workspace,
15287 project_transaction,
15288 format!("Rename: {} → {}", old_name, new_name),
15289 cx,
15290 )
15291 .await?;
15292
15293 editor.update(cx, |editor, cx| {
15294 editor.refresh_document_highlights(cx);
15295 })?;
15296 Ok(())
15297 }))
15298 }
15299
15300 fn take_rename(
15301 &mut self,
15302 moving_cursor: bool,
15303 window: &mut Window,
15304 cx: &mut Context<Self>,
15305 ) -> Option<RenameState> {
15306 let rename = self.pending_rename.take()?;
15307 if rename.editor.focus_handle(cx).is_focused(window) {
15308 window.focus(&self.focus_handle);
15309 }
15310
15311 self.remove_blocks(
15312 [rename.block_id].into_iter().collect(),
15313 Some(Autoscroll::fit()),
15314 cx,
15315 );
15316 self.clear_highlights::<Rename>(cx);
15317 self.show_local_selections = true;
15318
15319 if moving_cursor {
15320 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
15321 editor.selections.newest::<usize>(cx).head()
15322 });
15323
15324 // Update the selection to match the position of the selection inside
15325 // the rename editor.
15326 let snapshot = self.buffer.read(cx).read(cx);
15327 let rename_range = rename.range.to_offset(&snapshot);
15328 let cursor_in_editor = snapshot
15329 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
15330 .min(rename_range.end);
15331 drop(snapshot);
15332
15333 self.change_selections(None, window, cx, |s| {
15334 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
15335 });
15336 } else {
15337 self.refresh_document_highlights(cx);
15338 }
15339
15340 Some(rename)
15341 }
15342
15343 pub fn pending_rename(&self) -> Option<&RenameState> {
15344 self.pending_rename.as_ref()
15345 }
15346
15347 fn format(
15348 &mut self,
15349 _: &Format,
15350 window: &mut Window,
15351 cx: &mut Context<Self>,
15352 ) -> Option<Task<Result<()>>> {
15353 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15354
15355 let project = match &self.project {
15356 Some(project) => project.clone(),
15357 None => return None,
15358 };
15359
15360 Some(self.perform_format(
15361 project,
15362 FormatTrigger::Manual,
15363 FormatTarget::Buffers,
15364 window,
15365 cx,
15366 ))
15367 }
15368
15369 fn format_selections(
15370 &mut self,
15371 _: &FormatSelections,
15372 window: &mut Window,
15373 cx: &mut Context<Self>,
15374 ) -> Option<Task<Result<()>>> {
15375 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15376
15377 let project = match &self.project {
15378 Some(project) => project.clone(),
15379 None => return None,
15380 };
15381
15382 let ranges = self
15383 .selections
15384 .all_adjusted(cx)
15385 .into_iter()
15386 .map(|selection| selection.range())
15387 .collect_vec();
15388
15389 Some(self.perform_format(
15390 project,
15391 FormatTrigger::Manual,
15392 FormatTarget::Ranges(ranges),
15393 window,
15394 cx,
15395 ))
15396 }
15397
15398 fn perform_format(
15399 &mut self,
15400 project: Entity<Project>,
15401 trigger: FormatTrigger,
15402 target: FormatTarget,
15403 window: &mut Window,
15404 cx: &mut Context<Self>,
15405 ) -> Task<Result<()>> {
15406 let buffer = self.buffer.clone();
15407 let (buffers, target) = match target {
15408 FormatTarget::Buffers => {
15409 let mut buffers = buffer.read(cx).all_buffers();
15410 if trigger == FormatTrigger::Save {
15411 buffers.retain(|buffer| buffer.read(cx).is_dirty());
15412 }
15413 (buffers, LspFormatTarget::Buffers)
15414 }
15415 FormatTarget::Ranges(selection_ranges) => {
15416 let multi_buffer = buffer.read(cx);
15417 let snapshot = multi_buffer.read(cx);
15418 let mut buffers = HashSet::default();
15419 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
15420 BTreeMap::new();
15421 for selection_range in selection_ranges {
15422 for (buffer, buffer_range, _) in
15423 snapshot.range_to_buffer_ranges(selection_range)
15424 {
15425 let buffer_id = buffer.remote_id();
15426 let start = buffer.anchor_before(buffer_range.start);
15427 let end = buffer.anchor_after(buffer_range.end);
15428 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
15429 buffer_id_to_ranges
15430 .entry(buffer_id)
15431 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
15432 .or_insert_with(|| vec![start..end]);
15433 }
15434 }
15435 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
15436 }
15437 };
15438
15439 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
15440 let selections_prev = transaction_id_prev
15441 .and_then(|transaction_id_prev| {
15442 // default to selections as they were after the last edit, if we have them,
15443 // instead of how they are now.
15444 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
15445 // will take you back to where you made the last edit, instead of staying where you scrolled
15446 self.selection_history
15447 .transaction(transaction_id_prev)
15448 .map(|t| t.0.clone())
15449 })
15450 .unwrap_or_else(|| {
15451 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
15452 self.selections.disjoint_anchors()
15453 });
15454
15455 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
15456 let format = project.update(cx, |project, cx| {
15457 project.format(buffers, target, true, trigger, cx)
15458 });
15459
15460 cx.spawn_in(window, async move |editor, cx| {
15461 let transaction = futures::select_biased! {
15462 transaction = format.log_err().fuse() => transaction,
15463 () = timeout => {
15464 log::warn!("timed out waiting for formatting");
15465 None
15466 }
15467 };
15468
15469 buffer
15470 .update(cx, |buffer, cx| {
15471 if let Some(transaction) = transaction {
15472 if !buffer.is_singleton() {
15473 buffer.push_transaction(&transaction.0, cx);
15474 }
15475 }
15476 cx.notify();
15477 })
15478 .ok();
15479
15480 if let Some(transaction_id_now) =
15481 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
15482 {
15483 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
15484 if has_new_transaction {
15485 _ = editor.update(cx, |editor, _| {
15486 editor
15487 .selection_history
15488 .insert_transaction(transaction_id_now, selections_prev);
15489 });
15490 }
15491 }
15492
15493 Ok(())
15494 })
15495 }
15496
15497 fn organize_imports(
15498 &mut self,
15499 _: &OrganizeImports,
15500 window: &mut Window,
15501 cx: &mut Context<Self>,
15502 ) -> Option<Task<Result<()>>> {
15503 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15504 let project = match &self.project {
15505 Some(project) => project.clone(),
15506 None => return None,
15507 };
15508 Some(self.perform_code_action_kind(
15509 project,
15510 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
15511 window,
15512 cx,
15513 ))
15514 }
15515
15516 fn perform_code_action_kind(
15517 &mut self,
15518 project: Entity<Project>,
15519 kind: CodeActionKind,
15520 window: &mut Window,
15521 cx: &mut Context<Self>,
15522 ) -> Task<Result<()>> {
15523 let buffer = self.buffer.clone();
15524 let buffers = buffer.read(cx).all_buffers();
15525 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
15526 let apply_action = project.update(cx, |project, cx| {
15527 project.apply_code_action_kind(buffers, kind, true, cx)
15528 });
15529 cx.spawn_in(window, async move |_, cx| {
15530 let transaction = futures::select_biased! {
15531 () = timeout => {
15532 log::warn!("timed out waiting for executing code action");
15533 None
15534 }
15535 transaction = apply_action.log_err().fuse() => transaction,
15536 };
15537 buffer
15538 .update(cx, |buffer, cx| {
15539 // check if we need this
15540 if let Some(transaction) = transaction {
15541 if !buffer.is_singleton() {
15542 buffer.push_transaction(&transaction.0, cx);
15543 }
15544 }
15545 cx.notify();
15546 })
15547 .ok();
15548 Ok(())
15549 })
15550 }
15551
15552 fn restart_language_server(
15553 &mut self,
15554 _: &RestartLanguageServer,
15555 _: &mut Window,
15556 cx: &mut Context<Self>,
15557 ) {
15558 if let Some(project) = self.project.clone() {
15559 self.buffer.update(cx, |multi_buffer, cx| {
15560 project.update(cx, |project, cx| {
15561 project.restart_language_servers_for_buffers(
15562 multi_buffer.all_buffers().into_iter().collect(),
15563 cx,
15564 );
15565 });
15566 })
15567 }
15568 }
15569
15570 fn stop_language_server(
15571 &mut self,
15572 _: &StopLanguageServer,
15573 _: &mut Window,
15574 cx: &mut Context<Self>,
15575 ) {
15576 if let Some(project) = self.project.clone() {
15577 self.buffer.update(cx, |multi_buffer, cx| {
15578 project.update(cx, |project, cx| {
15579 project.stop_language_servers_for_buffers(
15580 multi_buffer.all_buffers().into_iter().collect(),
15581 cx,
15582 );
15583 cx.emit(project::Event::RefreshInlayHints);
15584 });
15585 });
15586 }
15587 }
15588
15589 fn cancel_language_server_work(
15590 workspace: &mut Workspace,
15591 _: &actions::CancelLanguageServerWork,
15592 _: &mut Window,
15593 cx: &mut Context<Workspace>,
15594 ) {
15595 let project = workspace.project();
15596 let buffers = workspace
15597 .active_item(cx)
15598 .and_then(|item| item.act_as::<Editor>(cx))
15599 .map_or(HashSet::default(), |editor| {
15600 editor.read(cx).buffer.read(cx).all_buffers()
15601 });
15602 project.update(cx, |project, cx| {
15603 project.cancel_language_server_work_for_buffers(buffers, cx);
15604 });
15605 }
15606
15607 fn show_character_palette(
15608 &mut self,
15609 _: &ShowCharacterPalette,
15610 window: &mut Window,
15611 _: &mut Context<Self>,
15612 ) {
15613 window.show_character_palette();
15614 }
15615
15616 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
15617 if self.mode.is_minimap() {
15618 return;
15619 }
15620
15621 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
15622 let buffer = self.buffer.read(cx).snapshot(cx);
15623 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
15624 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
15625 let is_valid = buffer
15626 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
15627 .any(|entry| {
15628 entry.diagnostic.is_primary
15629 && !entry.range.is_empty()
15630 && entry.range.start == primary_range_start
15631 && entry.diagnostic.message == active_diagnostics.active_message
15632 });
15633
15634 if !is_valid {
15635 self.dismiss_diagnostics(cx);
15636 }
15637 }
15638 }
15639
15640 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
15641 match &self.active_diagnostics {
15642 ActiveDiagnostic::Group(group) => Some(group),
15643 _ => None,
15644 }
15645 }
15646
15647 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
15648 self.dismiss_diagnostics(cx);
15649 self.active_diagnostics = ActiveDiagnostic::All;
15650 }
15651
15652 fn activate_diagnostics(
15653 &mut self,
15654 buffer_id: BufferId,
15655 diagnostic: DiagnosticEntry<usize>,
15656 window: &mut Window,
15657 cx: &mut Context<Self>,
15658 ) {
15659 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15660 return;
15661 }
15662 self.dismiss_diagnostics(cx);
15663 let snapshot = self.snapshot(window, cx);
15664 let buffer = self.buffer.read(cx).snapshot(cx);
15665 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
15666 return;
15667 };
15668
15669 let diagnostic_group = buffer
15670 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
15671 .collect::<Vec<_>>();
15672
15673 let blocks =
15674 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
15675
15676 let blocks = self.display_map.update(cx, |display_map, cx| {
15677 display_map.insert_blocks(blocks, cx).into_iter().collect()
15678 });
15679 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
15680 active_range: buffer.anchor_before(diagnostic.range.start)
15681 ..buffer.anchor_after(diagnostic.range.end),
15682 active_message: diagnostic.diagnostic.message.clone(),
15683 group_id: diagnostic.diagnostic.group_id,
15684 blocks,
15685 });
15686 cx.notify();
15687 }
15688
15689 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
15690 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15691 return;
15692 };
15693
15694 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
15695 if let ActiveDiagnostic::Group(group) = prev {
15696 self.display_map.update(cx, |display_map, cx| {
15697 display_map.remove_blocks(group.blocks, cx);
15698 });
15699 cx.notify();
15700 }
15701 }
15702
15703 /// Disable inline diagnostics rendering for this editor.
15704 pub fn disable_inline_diagnostics(&mut self) {
15705 self.inline_diagnostics_enabled = false;
15706 self.inline_diagnostics_update = Task::ready(());
15707 self.inline_diagnostics.clear();
15708 }
15709
15710 pub fn diagnostics_enabled(&self) -> bool {
15711 self.mode.is_full()
15712 }
15713
15714 pub fn inline_diagnostics_enabled(&self) -> bool {
15715 self.diagnostics_enabled() && self.inline_diagnostics_enabled
15716 }
15717
15718 pub fn show_inline_diagnostics(&self) -> bool {
15719 self.show_inline_diagnostics
15720 }
15721
15722 pub fn toggle_inline_diagnostics(
15723 &mut self,
15724 _: &ToggleInlineDiagnostics,
15725 window: &mut Window,
15726 cx: &mut Context<Editor>,
15727 ) {
15728 self.show_inline_diagnostics = !self.show_inline_diagnostics;
15729 self.refresh_inline_diagnostics(false, window, cx);
15730 }
15731
15732 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
15733 self.diagnostics_max_severity = severity;
15734 self.display_map.update(cx, |display_map, _| {
15735 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
15736 });
15737 }
15738
15739 pub fn toggle_diagnostics(
15740 &mut self,
15741 _: &ToggleDiagnostics,
15742 window: &mut Window,
15743 cx: &mut Context<Editor>,
15744 ) {
15745 if !self.diagnostics_enabled() {
15746 return;
15747 }
15748
15749 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
15750 EditorSettings::get_global(cx)
15751 .diagnostics_max_severity
15752 .filter(|severity| severity != &DiagnosticSeverity::Off)
15753 .unwrap_or(DiagnosticSeverity::Hint)
15754 } else {
15755 DiagnosticSeverity::Off
15756 };
15757 self.set_max_diagnostics_severity(new_severity, cx);
15758 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
15759 self.active_diagnostics = ActiveDiagnostic::None;
15760 self.inline_diagnostics_update = Task::ready(());
15761 self.inline_diagnostics.clear();
15762 } else {
15763 self.refresh_inline_diagnostics(false, window, cx);
15764 }
15765
15766 cx.notify();
15767 }
15768
15769 pub fn toggle_minimap(
15770 &mut self,
15771 _: &ToggleMinimap,
15772 window: &mut Window,
15773 cx: &mut Context<Editor>,
15774 ) {
15775 if self.supports_minimap(cx) {
15776 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
15777 }
15778 }
15779
15780 fn refresh_inline_diagnostics(
15781 &mut self,
15782 debounce: bool,
15783 window: &mut Window,
15784 cx: &mut Context<Self>,
15785 ) {
15786 let max_severity = ProjectSettings::get_global(cx)
15787 .diagnostics
15788 .inline
15789 .max_severity
15790 .unwrap_or(self.diagnostics_max_severity);
15791
15792 if !self.inline_diagnostics_enabled()
15793 || !self.show_inline_diagnostics
15794 || max_severity == DiagnosticSeverity::Off
15795 {
15796 self.inline_diagnostics_update = Task::ready(());
15797 self.inline_diagnostics.clear();
15798 return;
15799 }
15800
15801 let debounce_ms = ProjectSettings::get_global(cx)
15802 .diagnostics
15803 .inline
15804 .update_debounce_ms;
15805 let debounce = if debounce && debounce_ms > 0 {
15806 Some(Duration::from_millis(debounce_ms))
15807 } else {
15808 None
15809 };
15810 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
15811 if let Some(debounce) = debounce {
15812 cx.background_executor().timer(debounce).await;
15813 }
15814 let Some(snapshot) = editor.upgrade().and_then(|editor| {
15815 editor
15816 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
15817 .ok()
15818 }) else {
15819 return;
15820 };
15821
15822 let new_inline_diagnostics = cx
15823 .background_spawn(async move {
15824 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
15825 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
15826 let message = diagnostic_entry
15827 .diagnostic
15828 .message
15829 .split_once('\n')
15830 .map(|(line, _)| line)
15831 .map(SharedString::new)
15832 .unwrap_or_else(|| {
15833 SharedString::from(diagnostic_entry.diagnostic.message)
15834 });
15835 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
15836 let (Ok(i) | Err(i)) = inline_diagnostics
15837 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
15838 inline_diagnostics.insert(
15839 i,
15840 (
15841 start_anchor,
15842 InlineDiagnostic {
15843 message,
15844 group_id: diagnostic_entry.diagnostic.group_id,
15845 start: diagnostic_entry.range.start.to_point(&snapshot),
15846 is_primary: diagnostic_entry.diagnostic.is_primary,
15847 severity: diagnostic_entry.diagnostic.severity,
15848 },
15849 ),
15850 );
15851 }
15852 inline_diagnostics
15853 })
15854 .await;
15855
15856 editor
15857 .update(cx, |editor, cx| {
15858 editor.inline_diagnostics = new_inline_diagnostics;
15859 cx.notify();
15860 })
15861 .ok();
15862 });
15863 }
15864
15865 pub fn set_selections_from_remote(
15866 &mut self,
15867 selections: Vec<Selection<Anchor>>,
15868 pending_selection: Option<Selection<Anchor>>,
15869 window: &mut Window,
15870 cx: &mut Context<Self>,
15871 ) {
15872 let old_cursor_position = self.selections.newest_anchor().head();
15873 self.selections.change_with(cx, |s| {
15874 s.select_anchors(selections);
15875 if let Some(pending_selection) = pending_selection {
15876 s.set_pending(pending_selection, SelectMode::Character);
15877 } else {
15878 s.clear_pending();
15879 }
15880 });
15881 self.selections_did_change(false, &old_cursor_position, true, window, cx);
15882 }
15883
15884 pub fn transact(
15885 &mut self,
15886 window: &mut Window,
15887 cx: &mut Context<Self>,
15888 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
15889 ) -> Option<TransactionId> {
15890 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15891 this.start_transaction_at(Instant::now(), window, cx);
15892 update(this, window, cx);
15893 this.end_transaction_at(Instant::now(), cx)
15894 })
15895 }
15896
15897 pub fn start_transaction_at(
15898 &mut self,
15899 now: Instant,
15900 window: &mut Window,
15901 cx: &mut Context<Self>,
15902 ) {
15903 self.end_selection(window, cx);
15904 if let Some(tx_id) = self
15905 .buffer
15906 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
15907 {
15908 self.selection_history
15909 .insert_transaction(tx_id, self.selections.disjoint_anchors());
15910 cx.emit(EditorEvent::TransactionBegun {
15911 transaction_id: tx_id,
15912 })
15913 }
15914 }
15915
15916 pub fn end_transaction_at(
15917 &mut self,
15918 now: Instant,
15919 cx: &mut Context<Self>,
15920 ) -> Option<TransactionId> {
15921 if let Some(transaction_id) = self
15922 .buffer
15923 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
15924 {
15925 if let Some((_, end_selections)) =
15926 self.selection_history.transaction_mut(transaction_id)
15927 {
15928 *end_selections = Some(self.selections.disjoint_anchors());
15929 } else {
15930 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
15931 }
15932
15933 cx.emit(EditorEvent::Edited { transaction_id });
15934 Some(transaction_id)
15935 } else {
15936 None
15937 }
15938 }
15939
15940 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
15941 if self.selection_mark_mode {
15942 self.change_selections(None, window, cx, |s| {
15943 s.move_with(|_, sel| {
15944 sel.collapse_to(sel.head(), SelectionGoal::None);
15945 });
15946 })
15947 }
15948 self.selection_mark_mode = true;
15949 cx.notify();
15950 }
15951
15952 pub fn swap_selection_ends(
15953 &mut self,
15954 _: &actions::SwapSelectionEnds,
15955 window: &mut Window,
15956 cx: &mut Context<Self>,
15957 ) {
15958 self.change_selections(None, window, cx, |s| {
15959 s.move_with(|_, sel| {
15960 if sel.start != sel.end {
15961 sel.reversed = !sel.reversed
15962 }
15963 });
15964 });
15965 self.request_autoscroll(Autoscroll::newest(), cx);
15966 cx.notify();
15967 }
15968
15969 pub fn toggle_fold(
15970 &mut self,
15971 _: &actions::ToggleFold,
15972 window: &mut Window,
15973 cx: &mut Context<Self>,
15974 ) {
15975 if self.is_singleton(cx) {
15976 let selection = self.selections.newest::<Point>(cx);
15977
15978 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15979 let range = if selection.is_empty() {
15980 let point = selection.head().to_display_point(&display_map);
15981 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
15982 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
15983 .to_point(&display_map);
15984 start..end
15985 } else {
15986 selection.range()
15987 };
15988 if display_map.folds_in_range(range).next().is_some() {
15989 self.unfold_lines(&Default::default(), window, cx)
15990 } else {
15991 self.fold(&Default::default(), window, cx)
15992 }
15993 } else {
15994 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15995 let buffer_ids: HashSet<_> = self
15996 .selections
15997 .disjoint_anchor_ranges()
15998 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15999 .collect();
16000
16001 let should_unfold = buffer_ids
16002 .iter()
16003 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
16004
16005 for buffer_id in buffer_ids {
16006 if should_unfold {
16007 self.unfold_buffer(buffer_id, cx);
16008 } else {
16009 self.fold_buffer(buffer_id, cx);
16010 }
16011 }
16012 }
16013 }
16014
16015 pub fn toggle_fold_recursive(
16016 &mut self,
16017 _: &actions::ToggleFoldRecursive,
16018 window: &mut Window,
16019 cx: &mut Context<Self>,
16020 ) {
16021 let selection = self.selections.newest::<Point>(cx);
16022
16023 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16024 let range = if selection.is_empty() {
16025 let point = selection.head().to_display_point(&display_map);
16026 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
16027 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
16028 .to_point(&display_map);
16029 start..end
16030 } else {
16031 selection.range()
16032 };
16033 if display_map.folds_in_range(range).next().is_some() {
16034 self.unfold_recursive(&Default::default(), window, cx)
16035 } else {
16036 self.fold_recursive(&Default::default(), window, cx)
16037 }
16038 }
16039
16040 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
16041 if self.is_singleton(cx) {
16042 let mut to_fold = Vec::new();
16043 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16044 let selections = self.selections.all_adjusted(cx);
16045
16046 for selection in selections {
16047 let range = selection.range().sorted();
16048 let buffer_start_row = range.start.row;
16049
16050 if range.start.row != range.end.row {
16051 let mut found = false;
16052 let mut row = range.start.row;
16053 while row <= range.end.row {
16054 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
16055 {
16056 found = true;
16057 row = crease.range().end.row + 1;
16058 to_fold.push(crease);
16059 } else {
16060 row += 1
16061 }
16062 }
16063 if found {
16064 continue;
16065 }
16066 }
16067
16068 for row in (0..=range.start.row).rev() {
16069 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16070 if crease.range().end.row >= buffer_start_row {
16071 to_fold.push(crease);
16072 if row <= range.start.row {
16073 break;
16074 }
16075 }
16076 }
16077 }
16078 }
16079
16080 self.fold_creases(to_fold, true, window, cx);
16081 } else {
16082 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16083 let buffer_ids = self
16084 .selections
16085 .disjoint_anchor_ranges()
16086 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16087 .collect::<HashSet<_>>();
16088 for buffer_id in buffer_ids {
16089 self.fold_buffer(buffer_id, cx);
16090 }
16091 }
16092 }
16093
16094 fn fold_at_level(
16095 &mut self,
16096 fold_at: &FoldAtLevel,
16097 window: &mut Window,
16098 cx: &mut Context<Self>,
16099 ) {
16100 if !self.buffer.read(cx).is_singleton() {
16101 return;
16102 }
16103
16104 let fold_at_level = fold_at.0;
16105 let snapshot = self.buffer.read(cx).snapshot(cx);
16106 let mut to_fold = Vec::new();
16107 let mut stack = vec![(0, snapshot.max_row().0, 1)];
16108
16109 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
16110 while start_row < end_row {
16111 match self
16112 .snapshot(window, cx)
16113 .crease_for_buffer_row(MultiBufferRow(start_row))
16114 {
16115 Some(crease) => {
16116 let nested_start_row = crease.range().start.row + 1;
16117 let nested_end_row = crease.range().end.row;
16118
16119 if current_level < fold_at_level {
16120 stack.push((nested_start_row, nested_end_row, current_level + 1));
16121 } else if current_level == fold_at_level {
16122 to_fold.push(crease);
16123 }
16124
16125 start_row = nested_end_row + 1;
16126 }
16127 None => start_row += 1,
16128 }
16129 }
16130 }
16131
16132 self.fold_creases(to_fold, true, window, cx);
16133 }
16134
16135 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
16136 if self.buffer.read(cx).is_singleton() {
16137 let mut fold_ranges = Vec::new();
16138 let snapshot = self.buffer.read(cx).snapshot(cx);
16139
16140 for row in 0..snapshot.max_row().0 {
16141 if let Some(foldable_range) = self
16142 .snapshot(window, cx)
16143 .crease_for_buffer_row(MultiBufferRow(row))
16144 {
16145 fold_ranges.push(foldable_range);
16146 }
16147 }
16148
16149 self.fold_creases(fold_ranges, true, window, cx);
16150 } else {
16151 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
16152 editor
16153 .update_in(cx, |editor, _, cx| {
16154 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
16155 editor.fold_buffer(buffer_id, cx);
16156 }
16157 })
16158 .ok();
16159 });
16160 }
16161 }
16162
16163 pub fn fold_function_bodies(
16164 &mut self,
16165 _: &actions::FoldFunctionBodies,
16166 window: &mut Window,
16167 cx: &mut Context<Self>,
16168 ) {
16169 let snapshot = self.buffer.read(cx).snapshot(cx);
16170
16171 let ranges = snapshot
16172 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
16173 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
16174 .collect::<Vec<_>>();
16175
16176 let creases = ranges
16177 .into_iter()
16178 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
16179 .collect();
16180
16181 self.fold_creases(creases, true, window, cx);
16182 }
16183
16184 pub fn fold_recursive(
16185 &mut self,
16186 _: &actions::FoldRecursive,
16187 window: &mut Window,
16188 cx: &mut Context<Self>,
16189 ) {
16190 let mut to_fold = Vec::new();
16191 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16192 let selections = self.selections.all_adjusted(cx);
16193
16194 for selection in selections {
16195 let range = selection.range().sorted();
16196 let buffer_start_row = range.start.row;
16197
16198 if range.start.row != range.end.row {
16199 let mut found = false;
16200 for row in range.start.row..=range.end.row {
16201 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16202 found = true;
16203 to_fold.push(crease);
16204 }
16205 }
16206 if found {
16207 continue;
16208 }
16209 }
16210
16211 for row in (0..=range.start.row).rev() {
16212 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16213 if crease.range().end.row >= buffer_start_row {
16214 to_fold.push(crease);
16215 } else {
16216 break;
16217 }
16218 }
16219 }
16220 }
16221
16222 self.fold_creases(to_fold, true, window, cx);
16223 }
16224
16225 pub fn fold_at(
16226 &mut self,
16227 buffer_row: MultiBufferRow,
16228 window: &mut Window,
16229 cx: &mut Context<Self>,
16230 ) {
16231 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16232
16233 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
16234 let autoscroll = self
16235 .selections
16236 .all::<Point>(cx)
16237 .iter()
16238 .any(|selection| crease.range().overlaps(&selection.range()));
16239
16240 self.fold_creases(vec![crease], autoscroll, window, cx);
16241 }
16242 }
16243
16244 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
16245 if self.is_singleton(cx) {
16246 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16247 let buffer = &display_map.buffer_snapshot;
16248 let selections = self.selections.all::<Point>(cx);
16249 let ranges = selections
16250 .iter()
16251 .map(|s| {
16252 let range = s.display_range(&display_map).sorted();
16253 let mut start = range.start.to_point(&display_map);
16254 let mut end = range.end.to_point(&display_map);
16255 start.column = 0;
16256 end.column = buffer.line_len(MultiBufferRow(end.row));
16257 start..end
16258 })
16259 .collect::<Vec<_>>();
16260
16261 self.unfold_ranges(&ranges, true, true, cx);
16262 } else {
16263 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16264 let buffer_ids = self
16265 .selections
16266 .disjoint_anchor_ranges()
16267 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16268 .collect::<HashSet<_>>();
16269 for buffer_id in buffer_ids {
16270 self.unfold_buffer(buffer_id, cx);
16271 }
16272 }
16273 }
16274
16275 pub fn unfold_recursive(
16276 &mut self,
16277 _: &UnfoldRecursive,
16278 _window: &mut Window,
16279 cx: &mut Context<Self>,
16280 ) {
16281 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16282 let selections = self.selections.all::<Point>(cx);
16283 let ranges = selections
16284 .iter()
16285 .map(|s| {
16286 let mut range = s.display_range(&display_map).sorted();
16287 *range.start.column_mut() = 0;
16288 *range.end.column_mut() = display_map.line_len(range.end.row());
16289 let start = range.start.to_point(&display_map);
16290 let end = range.end.to_point(&display_map);
16291 start..end
16292 })
16293 .collect::<Vec<_>>();
16294
16295 self.unfold_ranges(&ranges, true, true, cx);
16296 }
16297
16298 pub fn unfold_at(
16299 &mut self,
16300 buffer_row: MultiBufferRow,
16301 _window: &mut Window,
16302 cx: &mut Context<Self>,
16303 ) {
16304 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16305
16306 let intersection_range = Point::new(buffer_row.0, 0)
16307 ..Point::new(
16308 buffer_row.0,
16309 display_map.buffer_snapshot.line_len(buffer_row),
16310 );
16311
16312 let autoscroll = self
16313 .selections
16314 .all::<Point>(cx)
16315 .iter()
16316 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
16317
16318 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
16319 }
16320
16321 pub fn unfold_all(
16322 &mut self,
16323 _: &actions::UnfoldAll,
16324 _window: &mut Window,
16325 cx: &mut Context<Self>,
16326 ) {
16327 if self.buffer.read(cx).is_singleton() {
16328 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16329 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
16330 } else {
16331 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
16332 editor
16333 .update(cx, |editor, cx| {
16334 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
16335 editor.unfold_buffer(buffer_id, cx);
16336 }
16337 })
16338 .ok();
16339 });
16340 }
16341 }
16342
16343 pub fn fold_selected_ranges(
16344 &mut self,
16345 _: &FoldSelectedRanges,
16346 window: &mut Window,
16347 cx: &mut Context<Self>,
16348 ) {
16349 let selections = self.selections.all_adjusted(cx);
16350 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16351 let ranges = selections
16352 .into_iter()
16353 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
16354 .collect::<Vec<_>>();
16355 self.fold_creases(ranges, true, window, cx);
16356 }
16357
16358 pub fn fold_ranges<T: ToOffset + Clone>(
16359 &mut self,
16360 ranges: Vec<Range<T>>,
16361 auto_scroll: bool,
16362 window: &mut Window,
16363 cx: &mut Context<Self>,
16364 ) {
16365 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16366 let ranges = ranges
16367 .into_iter()
16368 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
16369 .collect::<Vec<_>>();
16370 self.fold_creases(ranges, auto_scroll, window, cx);
16371 }
16372
16373 pub fn fold_creases<T: ToOffset + Clone>(
16374 &mut self,
16375 creases: Vec<Crease<T>>,
16376 auto_scroll: bool,
16377 _window: &mut Window,
16378 cx: &mut Context<Self>,
16379 ) {
16380 if creases.is_empty() {
16381 return;
16382 }
16383
16384 let mut buffers_affected = HashSet::default();
16385 let multi_buffer = self.buffer().read(cx);
16386 for crease in &creases {
16387 if let Some((_, buffer, _)) =
16388 multi_buffer.excerpt_containing(crease.range().start.clone(), cx)
16389 {
16390 buffers_affected.insert(buffer.read(cx).remote_id());
16391 };
16392 }
16393
16394 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
16395
16396 if auto_scroll {
16397 self.request_autoscroll(Autoscroll::fit(), cx);
16398 }
16399
16400 cx.notify();
16401
16402 self.scrollbar_marker_state.dirty = true;
16403 self.folds_did_change(cx);
16404 }
16405
16406 /// Removes any folds whose ranges intersect any of the given ranges.
16407 pub fn unfold_ranges<T: ToOffset + Clone>(
16408 &mut self,
16409 ranges: &[Range<T>],
16410 inclusive: bool,
16411 auto_scroll: bool,
16412 cx: &mut Context<Self>,
16413 ) {
16414 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16415 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
16416 });
16417 self.folds_did_change(cx);
16418 }
16419
16420 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16421 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
16422 return;
16423 }
16424 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16425 self.display_map.update(cx, |display_map, cx| {
16426 display_map.fold_buffers([buffer_id], cx)
16427 });
16428 cx.emit(EditorEvent::BufferFoldToggled {
16429 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
16430 folded: true,
16431 });
16432 cx.notify();
16433 }
16434
16435 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16436 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
16437 return;
16438 }
16439 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16440 self.display_map.update(cx, |display_map, cx| {
16441 display_map.unfold_buffers([buffer_id], cx);
16442 });
16443 cx.emit(EditorEvent::BufferFoldToggled {
16444 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
16445 folded: false,
16446 });
16447 cx.notify();
16448 }
16449
16450 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
16451 self.display_map.read(cx).is_buffer_folded(buffer)
16452 }
16453
16454 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
16455 self.display_map.read(cx).folded_buffers()
16456 }
16457
16458 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16459 self.display_map.update(cx, |display_map, cx| {
16460 display_map.disable_header_for_buffer(buffer_id, cx);
16461 });
16462 cx.notify();
16463 }
16464
16465 /// Removes any folds with the given ranges.
16466 pub fn remove_folds_with_type<T: ToOffset + Clone>(
16467 &mut self,
16468 ranges: &[Range<T>],
16469 type_id: TypeId,
16470 auto_scroll: bool,
16471 cx: &mut Context<Self>,
16472 ) {
16473 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16474 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
16475 });
16476 self.folds_did_change(cx);
16477 }
16478
16479 fn remove_folds_with<T: ToOffset + Clone>(
16480 &mut self,
16481 ranges: &[Range<T>],
16482 auto_scroll: bool,
16483 cx: &mut Context<Self>,
16484 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
16485 ) {
16486 if ranges.is_empty() {
16487 return;
16488 }
16489
16490 let mut buffers_affected = HashSet::default();
16491 let multi_buffer = self.buffer().read(cx);
16492 for range in ranges {
16493 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
16494 buffers_affected.insert(buffer.read(cx).remote_id());
16495 };
16496 }
16497
16498 self.display_map.update(cx, update);
16499
16500 if auto_scroll {
16501 self.request_autoscroll(Autoscroll::fit(), cx);
16502 }
16503
16504 cx.notify();
16505 self.scrollbar_marker_state.dirty = true;
16506 self.active_indent_guides_state.dirty = true;
16507 }
16508
16509 pub fn update_fold_widths(
16510 &mut self,
16511 widths: impl IntoIterator<Item = (FoldId, Pixels)>,
16512 cx: &mut Context<Self>,
16513 ) -> bool {
16514 self.display_map
16515 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
16516 }
16517
16518 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
16519 self.display_map.read(cx).fold_placeholder.clone()
16520 }
16521
16522 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
16523 self.buffer.update(cx, |buffer, cx| {
16524 buffer.set_all_diff_hunks_expanded(cx);
16525 });
16526 }
16527
16528 pub fn expand_all_diff_hunks(
16529 &mut self,
16530 _: &ExpandAllDiffHunks,
16531 _window: &mut Window,
16532 cx: &mut Context<Self>,
16533 ) {
16534 self.buffer.update(cx, |buffer, cx| {
16535 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
16536 });
16537 }
16538
16539 pub fn toggle_selected_diff_hunks(
16540 &mut self,
16541 _: &ToggleSelectedDiffHunks,
16542 _window: &mut Window,
16543 cx: &mut Context<Self>,
16544 ) {
16545 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16546 self.toggle_diff_hunks_in_ranges(ranges, cx);
16547 }
16548
16549 pub fn diff_hunks_in_ranges<'a>(
16550 &'a self,
16551 ranges: &'a [Range<Anchor>],
16552 buffer: &'a MultiBufferSnapshot,
16553 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
16554 ranges.iter().flat_map(move |range| {
16555 let end_excerpt_id = range.end.excerpt_id;
16556 let range = range.to_point(buffer);
16557 let mut peek_end = range.end;
16558 if range.end.row < buffer.max_row().0 {
16559 peek_end = Point::new(range.end.row + 1, 0);
16560 }
16561 buffer
16562 .diff_hunks_in_range(range.start..peek_end)
16563 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
16564 })
16565 }
16566
16567 pub fn has_stageable_diff_hunks_in_ranges(
16568 &self,
16569 ranges: &[Range<Anchor>],
16570 snapshot: &MultiBufferSnapshot,
16571 ) -> bool {
16572 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
16573 hunks.any(|hunk| hunk.status().has_secondary_hunk())
16574 }
16575
16576 pub fn toggle_staged_selected_diff_hunks(
16577 &mut self,
16578 _: &::git::ToggleStaged,
16579 _: &mut Window,
16580 cx: &mut Context<Self>,
16581 ) {
16582 let snapshot = self.buffer.read(cx).snapshot(cx);
16583 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16584 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
16585 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16586 }
16587
16588 pub fn set_render_diff_hunk_controls(
16589 &mut self,
16590 render_diff_hunk_controls: RenderDiffHunkControlsFn,
16591 cx: &mut Context<Self>,
16592 ) {
16593 self.render_diff_hunk_controls = render_diff_hunk_controls;
16594 cx.notify();
16595 }
16596
16597 pub fn stage_and_next(
16598 &mut self,
16599 _: &::git::StageAndNext,
16600 window: &mut Window,
16601 cx: &mut Context<Self>,
16602 ) {
16603 self.do_stage_or_unstage_and_next(true, window, cx);
16604 }
16605
16606 pub fn unstage_and_next(
16607 &mut self,
16608 _: &::git::UnstageAndNext,
16609 window: &mut Window,
16610 cx: &mut Context<Self>,
16611 ) {
16612 self.do_stage_or_unstage_and_next(false, window, cx);
16613 }
16614
16615 pub fn stage_or_unstage_diff_hunks(
16616 &mut self,
16617 stage: bool,
16618 ranges: Vec<Range<Anchor>>,
16619 cx: &mut Context<Self>,
16620 ) {
16621 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
16622 cx.spawn(async move |this, cx| {
16623 task.await?;
16624 this.update(cx, |this, cx| {
16625 let snapshot = this.buffer.read(cx).snapshot(cx);
16626 let chunk_by = this
16627 .diff_hunks_in_ranges(&ranges, &snapshot)
16628 .chunk_by(|hunk| hunk.buffer_id);
16629 for (buffer_id, hunks) in &chunk_by {
16630 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
16631 }
16632 })
16633 })
16634 .detach_and_log_err(cx);
16635 }
16636
16637 fn save_buffers_for_ranges_if_needed(
16638 &mut self,
16639 ranges: &[Range<Anchor>],
16640 cx: &mut Context<Editor>,
16641 ) -> Task<Result<()>> {
16642 let multibuffer = self.buffer.read(cx);
16643 let snapshot = multibuffer.read(cx);
16644 let buffer_ids: HashSet<_> = ranges
16645 .iter()
16646 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
16647 .collect();
16648 drop(snapshot);
16649
16650 let mut buffers = HashSet::default();
16651 for buffer_id in buffer_ids {
16652 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
16653 let buffer = buffer_entity.read(cx);
16654 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
16655 {
16656 buffers.insert(buffer_entity);
16657 }
16658 }
16659 }
16660
16661 if let Some(project) = &self.project {
16662 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
16663 } else {
16664 Task::ready(Ok(()))
16665 }
16666 }
16667
16668 fn do_stage_or_unstage_and_next(
16669 &mut self,
16670 stage: bool,
16671 window: &mut Window,
16672 cx: &mut Context<Self>,
16673 ) {
16674 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
16675
16676 if ranges.iter().any(|range| range.start != range.end) {
16677 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16678 return;
16679 }
16680
16681 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16682 let snapshot = self.snapshot(window, cx);
16683 let position = self.selections.newest::<Point>(cx).head();
16684 let mut row = snapshot
16685 .buffer_snapshot
16686 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
16687 .find(|hunk| hunk.row_range.start.0 > position.row)
16688 .map(|hunk| hunk.row_range.start);
16689
16690 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
16691 // Outside of the project diff editor, wrap around to the beginning.
16692 if !all_diff_hunks_expanded {
16693 row = row.or_else(|| {
16694 snapshot
16695 .buffer_snapshot
16696 .diff_hunks_in_range(Point::zero()..position)
16697 .find(|hunk| hunk.row_range.end.0 < position.row)
16698 .map(|hunk| hunk.row_range.start)
16699 });
16700 }
16701
16702 if let Some(row) = row {
16703 let destination = Point::new(row.0, 0);
16704 let autoscroll = Autoscroll::center();
16705
16706 self.unfold_ranges(&[destination..destination], false, false, cx);
16707 self.change_selections(Some(autoscroll), window, cx, |s| {
16708 s.select_ranges([destination..destination]);
16709 });
16710 }
16711 }
16712
16713 fn do_stage_or_unstage(
16714 &self,
16715 stage: bool,
16716 buffer_id: BufferId,
16717 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
16718 cx: &mut App,
16719 ) -> Option<()> {
16720 let project = self.project.as_ref()?;
16721 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
16722 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
16723 let buffer_snapshot = buffer.read(cx).snapshot();
16724 let file_exists = buffer_snapshot
16725 .file()
16726 .is_some_and(|file| file.disk_state().exists());
16727 diff.update(cx, |diff, cx| {
16728 diff.stage_or_unstage_hunks(
16729 stage,
16730 &hunks
16731 .map(|hunk| buffer_diff::DiffHunk {
16732 buffer_range: hunk.buffer_range,
16733 diff_base_byte_range: hunk.diff_base_byte_range,
16734 secondary_status: hunk.secondary_status,
16735 range: Point::zero()..Point::zero(), // unused
16736 })
16737 .collect::<Vec<_>>(),
16738 &buffer_snapshot,
16739 file_exists,
16740 cx,
16741 )
16742 });
16743 None
16744 }
16745
16746 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
16747 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16748 self.buffer
16749 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
16750 }
16751
16752 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
16753 self.buffer.update(cx, |buffer, cx| {
16754 let ranges = vec![Anchor::min()..Anchor::max()];
16755 if !buffer.all_diff_hunks_expanded()
16756 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
16757 {
16758 buffer.collapse_diff_hunks(ranges, cx);
16759 true
16760 } else {
16761 false
16762 }
16763 })
16764 }
16765
16766 fn toggle_diff_hunks_in_ranges(
16767 &mut self,
16768 ranges: Vec<Range<Anchor>>,
16769 cx: &mut Context<Editor>,
16770 ) {
16771 self.buffer.update(cx, |buffer, cx| {
16772 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
16773 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
16774 })
16775 }
16776
16777 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
16778 self.buffer.update(cx, |buffer, cx| {
16779 let snapshot = buffer.snapshot(cx);
16780 let excerpt_id = range.end.excerpt_id;
16781 let point_range = range.to_point(&snapshot);
16782 let expand = !buffer.single_hunk_is_expanded(range, cx);
16783 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
16784 })
16785 }
16786
16787 pub(crate) fn apply_all_diff_hunks(
16788 &mut self,
16789 _: &ApplyAllDiffHunks,
16790 window: &mut Window,
16791 cx: &mut Context<Self>,
16792 ) {
16793 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16794
16795 let buffers = self.buffer.read(cx).all_buffers();
16796 for branch_buffer in buffers {
16797 branch_buffer.update(cx, |branch_buffer, cx| {
16798 branch_buffer.merge_into_base(Vec::new(), cx);
16799 });
16800 }
16801
16802 if let Some(project) = self.project.clone() {
16803 self.save(true, project, window, cx).detach_and_log_err(cx);
16804 }
16805 }
16806
16807 pub(crate) fn apply_selected_diff_hunks(
16808 &mut self,
16809 _: &ApplyDiffHunk,
16810 window: &mut Window,
16811 cx: &mut Context<Self>,
16812 ) {
16813 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16814 let snapshot = self.snapshot(window, cx);
16815 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
16816 let mut ranges_by_buffer = HashMap::default();
16817 self.transact(window, cx, |editor, _window, cx| {
16818 for hunk in hunks {
16819 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
16820 ranges_by_buffer
16821 .entry(buffer.clone())
16822 .or_insert_with(Vec::new)
16823 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
16824 }
16825 }
16826
16827 for (buffer, ranges) in ranges_by_buffer {
16828 buffer.update(cx, |buffer, cx| {
16829 buffer.merge_into_base(ranges, cx);
16830 });
16831 }
16832 });
16833
16834 if let Some(project) = self.project.clone() {
16835 self.save(true, project, window, cx).detach_and_log_err(cx);
16836 }
16837 }
16838
16839 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
16840 if hovered != self.gutter_hovered {
16841 self.gutter_hovered = hovered;
16842 cx.notify();
16843 }
16844 }
16845
16846 pub fn insert_blocks(
16847 &mut self,
16848 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
16849 autoscroll: Option<Autoscroll>,
16850 cx: &mut Context<Self>,
16851 ) -> Vec<CustomBlockId> {
16852 let blocks = self
16853 .display_map
16854 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
16855 if let Some(autoscroll) = autoscroll {
16856 self.request_autoscroll(autoscroll, cx);
16857 }
16858 cx.notify();
16859 blocks
16860 }
16861
16862 pub fn resize_blocks(
16863 &mut self,
16864 heights: HashMap<CustomBlockId, u32>,
16865 autoscroll: Option<Autoscroll>,
16866 cx: &mut Context<Self>,
16867 ) {
16868 self.display_map
16869 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
16870 if let Some(autoscroll) = autoscroll {
16871 self.request_autoscroll(autoscroll, cx);
16872 }
16873 cx.notify();
16874 }
16875
16876 pub fn replace_blocks(
16877 &mut self,
16878 renderers: HashMap<CustomBlockId, RenderBlock>,
16879 autoscroll: Option<Autoscroll>,
16880 cx: &mut Context<Self>,
16881 ) {
16882 self.display_map
16883 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
16884 if let Some(autoscroll) = autoscroll {
16885 self.request_autoscroll(autoscroll, cx);
16886 }
16887 cx.notify();
16888 }
16889
16890 pub fn remove_blocks(
16891 &mut self,
16892 block_ids: HashSet<CustomBlockId>,
16893 autoscroll: Option<Autoscroll>,
16894 cx: &mut Context<Self>,
16895 ) {
16896 self.display_map.update(cx, |display_map, cx| {
16897 display_map.remove_blocks(block_ids, cx)
16898 });
16899 if let Some(autoscroll) = autoscroll {
16900 self.request_autoscroll(autoscroll, cx);
16901 }
16902 cx.notify();
16903 }
16904
16905 pub fn row_for_block(
16906 &self,
16907 block_id: CustomBlockId,
16908 cx: &mut Context<Self>,
16909 ) -> Option<DisplayRow> {
16910 self.display_map
16911 .update(cx, |map, cx| map.row_for_block(block_id, cx))
16912 }
16913
16914 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
16915 self.focused_block = Some(focused_block);
16916 }
16917
16918 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
16919 self.focused_block.take()
16920 }
16921
16922 pub fn insert_creases(
16923 &mut self,
16924 creases: impl IntoIterator<Item = Crease<Anchor>>,
16925 cx: &mut Context<Self>,
16926 ) -> Vec<CreaseId> {
16927 self.display_map
16928 .update(cx, |map, cx| map.insert_creases(creases, cx))
16929 }
16930
16931 pub fn remove_creases(
16932 &mut self,
16933 ids: impl IntoIterator<Item = CreaseId>,
16934 cx: &mut Context<Self>,
16935 ) -> Vec<(CreaseId, Range<Anchor>)> {
16936 self.display_map
16937 .update(cx, |map, cx| map.remove_creases(ids, cx))
16938 }
16939
16940 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
16941 self.display_map
16942 .update(cx, |map, cx| map.snapshot(cx))
16943 .longest_row()
16944 }
16945
16946 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
16947 self.display_map
16948 .update(cx, |map, cx| map.snapshot(cx))
16949 .max_point()
16950 }
16951
16952 pub fn text(&self, cx: &App) -> String {
16953 self.buffer.read(cx).read(cx).text()
16954 }
16955
16956 pub fn is_empty(&self, cx: &App) -> bool {
16957 self.buffer.read(cx).read(cx).is_empty()
16958 }
16959
16960 pub fn text_option(&self, cx: &App) -> Option<String> {
16961 let text = self.text(cx);
16962 let text = text.trim();
16963
16964 if text.is_empty() {
16965 return None;
16966 }
16967
16968 Some(text.to_string())
16969 }
16970
16971 pub fn set_text(
16972 &mut self,
16973 text: impl Into<Arc<str>>,
16974 window: &mut Window,
16975 cx: &mut Context<Self>,
16976 ) {
16977 self.transact(window, cx, |this, _, cx| {
16978 this.buffer
16979 .read(cx)
16980 .as_singleton()
16981 .expect("you can only call set_text on editors for singleton buffers")
16982 .update(cx, |buffer, cx| buffer.set_text(text, cx));
16983 });
16984 }
16985
16986 pub fn display_text(&self, cx: &mut App) -> String {
16987 self.display_map
16988 .update(cx, |map, cx| map.snapshot(cx))
16989 .text()
16990 }
16991
16992 fn create_minimap(
16993 &self,
16994 minimap_settings: MinimapSettings,
16995 window: &mut Window,
16996 cx: &mut Context<Self>,
16997 ) -> Option<Entity<Self>> {
16998 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
16999 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
17000 }
17001
17002 fn initialize_new_minimap(
17003 &self,
17004 minimap_settings: MinimapSettings,
17005 window: &mut Window,
17006 cx: &mut Context<Self>,
17007 ) -> Entity<Self> {
17008 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
17009
17010 let mut minimap = Editor::new_internal(
17011 EditorMode::Minimap {
17012 parent: cx.weak_entity(),
17013 },
17014 self.buffer.clone(),
17015 self.project.clone(),
17016 Some(self.display_map.clone()),
17017 window,
17018 cx,
17019 );
17020 minimap.scroll_manager.clone_state(&self.scroll_manager);
17021 minimap.set_text_style_refinement(TextStyleRefinement {
17022 font_size: Some(MINIMAP_FONT_SIZE),
17023 font_weight: Some(MINIMAP_FONT_WEIGHT),
17024 ..Default::default()
17025 });
17026 minimap.update_minimap_configuration(minimap_settings, cx);
17027 cx.new(|_| minimap)
17028 }
17029
17030 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
17031 let current_line_highlight = minimap_settings
17032 .current_line_highlight
17033 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
17034 self.set_current_line_highlight(Some(current_line_highlight));
17035 }
17036
17037 pub fn minimap(&self) -> Option<&Entity<Self>> {
17038 self.minimap
17039 .as_ref()
17040 .filter(|_| self.minimap_visibility.visible())
17041 }
17042
17043 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
17044 let mut wrap_guides = smallvec![];
17045
17046 if self.show_wrap_guides == Some(false) {
17047 return wrap_guides;
17048 }
17049
17050 let settings = self.buffer.read(cx).language_settings(cx);
17051 if settings.show_wrap_guides {
17052 match self.soft_wrap_mode(cx) {
17053 SoftWrap::Column(soft_wrap) => {
17054 wrap_guides.push((soft_wrap as usize, true));
17055 }
17056 SoftWrap::Bounded(soft_wrap) => {
17057 wrap_guides.push((soft_wrap as usize, true));
17058 }
17059 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
17060 }
17061 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
17062 }
17063
17064 wrap_guides
17065 }
17066
17067 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
17068 let settings = self.buffer.read(cx).language_settings(cx);
17069 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
17070 match mode {
17071 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
17072 SoftWrap::None
17073 }
17074 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
17075 language_settings::SoftWrap::PreferredLineLength => {
17076 SoftWrap::Column(settings.preferred_line_length)
17077 }
17078 language_settings::SoftWrap::Bounded => {
17079 SoftWrap::Bounded(settings.preferred_line_length)
17080 }
17081 }
17082 }
17083
17084 pub fn set_soft_wrap_mode(
17085 &mut self,
17086 mode: language_settings::SoftWrap,
17087
17088 cx: &mut Context<Self>,
17089 ) {
17090 self.soft_wrap_mode_override = Some(mode);
17091 cx.notify();
17092 }
17093
17094 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
17095 self.hard_wrap = hard_wrap;
17096 cx.notify();
17097 }
17098
17099 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
17100 self.text_style_refinement = Some(style);
17101 }
17102
17103 /// called by the Element so we know what style we were most recently rendered with.
17104 pub(crate) fn set_style(
17105 &mut self,
17106 style: EditorStyle,
17107 window: &mut Window,
17108 cx: &mut Context<Self>,
17109 ) {
17110 // We intentionally do not inform the display map about the minimap style
17111 // so that wrapping is not recalculated and stays consistent for the editor
17112 // and its linked minimap.
17113 if !self.mode.is_minimap() {
17114 let rem_size = window.rem_size();
17115 self.display_map.update(cx, |map, cx| {
17116 map.set_font(
17117 style.text.font(),
17118 style.text.font_size.to_pixels(rem_size),
17119 cx,
17120 )
17121 });
17122 }
17123 self.style = Some(style);
17124 }
17125
17126 pub fn style(&self) -> Option<&EditorStyle> {
17127 self.style.as_ref()
17128 }
17129
17130 // Called by the element. This method is not designed to be called outside of the editor
17131 // element's layout code because it does not notify when rewrapping is computed synchronously.
17132 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
17133 self.display_map
17134 .update(cx, |map, cx| map.set_wrap_width(width, cx))
17135 }
17136
17137 pub fn set_soft_wrap(&mut self) {
17138 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
17139 }
17140
17141 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
17142 if self.soft_wrap_mode_override.is_some() {
17143 self.soft_wrap_mode_override.take();
17144 } else {
17145 let soft_wrap = match self.soft_wrap_mode(cx) {
17146 SoftWrap::GitDiff => return,
17147 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
17148 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
17149 language_settings::SoftWrap::None
17150 }
17151 };
17152 self.soft_wrap_mode_override = Some(soft_wrap);
17153 }
17154 cx.notify();
17155 }
17156
17157 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
17158 let Some(workspace) = self.workspace() else {
17159 return;
17160 };
17161 let fs = workspace.read(cx).app_state().fs.clone();
17162 let current_show = TabBarSettings::get_global(cx).show;
17163 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
17164 setting.show = Some(!current_show);
17165 });
17166 }
17167
17168 pub fn toggle_indent_guides(
17169 &mut self,
17170 _: &ToggleIndentGuides,
17171 _: &mut Window,
17172 cx: &mut Context<Self>,
17173 ) {
17174 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
17175 self.buffer
17176 .read(cx)
17177 .language_settings(cx)
17178 .indent_guides
17179 .enabled
17180 });
17181 self.show_indent_guides = Some(!currently_enabled);
17182 cx.notify();
17183 }
17184
17185 fn should_show_indent_guides(&self) -> Option<bool> {
17186 self.show_indent_guides
17187 }
17188
17189 pub fn toggle_line_numbers(
17190 &mut self,
17191 _: &ToggleLineNumbers,
17192 _: &mut Window,
17193 cx: &mut Context<Self>,
17194 ) {
17195 let mut editor_settings = EditorSettings::get_global(cx).clone();
17196 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
17197 EditorSettings::override_global(editor_settings, cx);
17198 }
17199
17200 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
17201 if let Some(show_line_numbers) = self.show_line_numbers {
17202 return show_line_numbers;
17203 }
17204 EditorSettings::get_global(cx).gutter.line_numbers
17205 }
17206
17207 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
17208 self.use_relative_line_numbers
17209 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
17210 }
17211
17212 pub fn toggle_relative_line_numbers(
17213 &mut self,
17214 _: &ToggleRelativeLineNumbers,
17215 _: &mut Window,
17216 cx: &mut Context<Self>,
17217 ) {
17218 let is_relative = self.should_use_relative_line_numbers(cx);
17219 self.set_relative_line_number(Some(!is_relative), cx)
17220 }
17221
17222 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
17223 self.use_relative_line_numbers = is_relative;
17224 cx.notify();
17225 }
17226
17227 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
17228 self.show_gutter = show_gutter;
17229 cx.notify();
17230 }
17231
17232 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
17233 self.show_scrollbars = ScrollbarAxes {
17234 horizontal: show,
17235 vertical: show,
17236 };
17237 cx.notify();
17238 }
17239
17240 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
17241 self.show_scrollbars.vertical = show;
17242 cx.notify();
17243 }
17244
17245 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
17246 self.show_scrollbars.horizontal = show;
17247 cx.notify();
17248 }
17249
17250 pub fn set_minimap_visibility(
17251 &mut self,
17252 minimap_visibility: MinimapVisibility,
17253 window: &mut Window,
17254 cx: &mut Context<Self>,
17255 ) {
17256 if self.minimap_visibility != minimap_visibility {
17257 if minimap_visibility.visible() && self.minimap.is_none() {
17258 let minimap_settings = EditorSettings::get_global(cx).minimap;
17259 self.minimap =
17260 self.create_minimap(minimap_settings.with_show_override(), window, cx);
17261 }
17262 self.minimap_visibility = minimap_visibility;
17263 cx.notify();
17264 }
17265 }
17266
17267 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17268 self.set_show_scrollbars(false, cx);
17269 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
17270 }
17271
17272 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17273 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
17274 }
17275
17276 /// Normally the text in full mode and auto height editors is padded on the
17277 /// left side by roughly half a character width for improved hit testing.
17278 ///
17279 /// Use this method to disable this for cases where this is not wanted (e.g.
17280 /// if you want to align the editor text with some other text above or below)
17281 /// or if you want to add this padding to single-line editors.
17282 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
17283 self.offset_content = offset_content;
17284 cx.notify();
17285 }
17286
17287 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
17288 self.show_line_numbers = Some(show_line_numbers);
17289 cx.notify();
17290 }
17291
17292 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
17293 self.disable_expand_excerpt_buttons = true;
17294 cx.notify();
17295 }
17296
17297 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
17298 self.show_git_diff_gutter = Some(show_git_diff_gutter);
17299 cx.notify();
17300 }
17301
17302 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
17303 self.show_code_actions = Some(show_code_actions);
17304 cx.notify();
17305 }
17306
17307 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
17308 self.show_runnables = Some(show_runnables);
17309 cx.notify();
17310 }
17311
17312 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
17313 self.show_breakpoints = Some(show_breakpoints);
17314 cx.notify();
17315 }
17316
17317 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
17318 if self.display_map.read(cx).masked != masked {
17319 self.display_map.update(cx, |map, _| map.masked = masked);
17320 }
17321 cx.notify()
17322 }
17323
17324 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
17325 self.show_wrap_guides = Some(show_wrap_guides);
17326 cx.notify();
17327 }
17328
17329 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
17330 self.show_indent_guides = Some(show_indent_guides);
17331 cx.notify();
17332 }
17333
17334 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
17335 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
17336 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
17337 if let Some(dir) = file.abs_path(cx).parent() {
17338 return Some(dir.to_owned());
17339 }
17340 }
17341
17342 if let Some(project_path) = buffer.read(cx).project_path(cx) {
17343 return Some(project_path.path.to_path_buf());
17344 }
17345 }
17346
17347 None
17348 }
17349
17350 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
17351 self.active_excerpt(cx)?
17352 .1
17353 .read(cx)
17354 .file()
17355 .and_then(|f| f.as_local())
17356 }
17357
17358 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
17359 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
17360 let buffer = buffer.read(cx);
17361 if let Some(project_path) = buffer.project_path(cx) {
17362 let project = self.project.as_ref()?.read(cx);
17363 project.absolute_path(&project_path, cx)
17364 } else {
17365 buffer
17366 .file()
17367 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
17368 }
17369 })
17370 }
17371
17372 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
17373 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
17374 let project_path = buffer.read(cx).project_path(cx)?;
17375 let project = self.project.as_ref()?.read(cx);
17376 let entry = project.entry_for_path(&project_path, cx)?;
17377 let path = entry.path.to_path_buf();
17378 Some(path)
17379 })
17380 }
17381
17382 pub fn reveal_in_finder(
17383 &mut self,
17384 _: &RevealInFileManager,
17385 _window: &mut Window,
17386 cx: &mut Context<Self>,
17387 ) {
17388 if let Some(target) = self.target_file(cx) {
17389 cx.reveal_path(&target.abs_path(cx));
17390 }
17391 }
17392
17393 pub fn copy_path(
17394 &mut self,
17395 _: &zed_actions::workspace::CopyPath,
17396 _window: &mut Window,
17397 cx: &mut Context<Self>,
17398 ) {
17399 if let Some(path) = self.target_file_abs_path(cx) {
17400 if let Some(path) = path.to_str() {
17401 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17402 }
17403 }
17404 }
17405
17406 pub fn copy_relative_path(
17407 &mut self,
17408 _: &zed_actions::workspace::CopyRelativePath,
17409 _window: &mut Window,
17410 cx: &mut Context<Self>,
17411 ) {
17412 if let Some(path) = self.target_file_path(cx) {
17413 if let Some(path) = path.to_str() {
17414 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17415 }
17416 }
17417 }
17418
17419 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
17420 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
17421 buffer.read(cx).project_path(cx)
17422 } else {
17423 None
17424 }
17425 }
17426
17427 // Returns true if the editor handled a go-to-line request
17428 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
17429 maybe!({
17430 let breakpoint_store = self.breakpoint_store.as_ref()?;
17431
17432 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
17433 else {
17434 self.clear_row_highlights::<ActiveDebugLine>();
17435 return None;
17436 };
17437
17438 let position = active_stack_frame.position;
17439 let buffer_id = position.buffer_id?;
17440 let snapshot = self
17441 .project
17442 .as_ref()?
17443 .read(cx)
17444 .buffer_for_id(buffer_id, cx)?
17445 .read(cx)
17446 .snapshot();
17447
17448 let mut handled = false;
17449 for (id, ExcerptRange { context, .. }) in
17450 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
17451 {
17452 if context.start.cmp(&position, &snapshot).is_ge()
17453 || context.end.cmp(&position, &snapshot).is_lt()
17454 {
17455 continue;
17456 }
17457 let snapshot = self.buffer.read(cx).snapshot(cx);
17458 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
17459
17460 handled = true;
17461 self.clear_row_highlights::<ActiveDebugLine>();
17462
17463 self.go_to_line::<ActiveDebugLine>(
17464 multibuffer_anchor,
17465 Some(cx.theme().colors().editor_debugger_active_line_background),
17466 window,
17467 cx,
17468 );
17469
17470 cx.notify();
17471 }
17472
17473 handled.then_some(())
17474 })
17475 .is_some()
17476 }
17477
17478 pub fn copy_file_name_without_extension(
17479 &mut self,
17480 _: &CopyFileNameWithoutExtension,
17481 _: &mut Window,
17482 cx: &mut Context<Self>,
17483 ) {
17484 if let Some(file) = self.target_file(cx) {
17485 if let Some(file_stem) = file.path().file_stem() {
17486 if let Some(name) = file_stem.to_str() {
17487 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17488 }
17489 }
17490 }
17491 }
17492
17493 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
17494 if let Some(file) = self.target_file(cx) {
17495 if let Some(file_name) = file.path().file_name() {
17496 if let Some(name) = file_name.to_str() {
17497 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17498 }
17499 }
17500 }
17501 }
17502
17503 pub fn toggle_git_blame(
17504 &mut self,
17505 _: &::git::Blame,
17506 window: &mut Window,
17507 cx: &mut Context<Self>,
17508 ) {
17509 self.show_git_blame_gutter = !self.show_git_blame_gutter;
17510
17511 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
17512 self.start_git_blame(true, window, cx);
17513 }
17514
17515 cx.notify();
17516 }
17517
17518 pub fn toggle_git_blame_inline(
17519 &mut self,
17520 _: &ToggleGitBlameInline,
17521 window: &mut Window,
17522 cx: &mut Context<Self>,
17523 ) {
17524 self.toggle_git_blame_inline_internal(true, window, cx);
17525 cx.notify();
17526 }
17527
17528 pub fn open_git_blame_commit(
17529 &mut self,
17530 _: &OpenGitBlameCommit,
17531 window: &mut Window,
17532 cx: &mut Context<Self>,
17533 ) {
17534 self.open_git_blame_commit_internal(window, cx);
17535 }
17536
17537 fn open_git_blame_commit_internal(
17538 &mut self,
17539 window: &mut Window,
17540 cx: &mut Context<Self>,
17541 ) -> Option<()> {
17542 let blame = self.blame.as_ref()?;
17543 let snapshot = self.snapshot(window, cx);
17544 let cursor = self.selections.newest::<Point>(cx).head();
17545 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
17546 let blame_entry = blame
17547 .update(cx, |blame, cx| {
17548 blame
17549 .blame_for_rows(
17550 &[RowInfo {
17551 buffer_id: Some(buffer.remote_id()),
17552 buffer_row: Some(point.row),
17553 ..Default::default()
17554 }],
17555 cx,
17556 )
17557 .next()
17558 })
17559 .flatten()?;
17560 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
17561 let repo = blame.read(cx).repository(cx)?;
17562 let workspace = self.workspace()?.downgrade();
17563 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
17564 None
17565 }
17566
17567 pub fn git_blame_inline_enabled(&self) -> bool {
17568 self.git_blame_inline_enabled
17569 }
17570
17571 pub fn toggle_selection_menu(
17572 &mut self,
17573 _: &ToggleSelectionMenu,
17574 _: &mut Window,
17575 cx: &mut Context<Self>,
17576 ) {
17577 self.show_selection_menu = self
17578 .show_selection_menu
17579 .map(|show_selections_menu| !show_selections_menu)
17580 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
17581
17582 cx.notify();
17583 }
17584
17585 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
17586 self.show_selection_menu
17587 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
17588 }
17589
17590 fn start_git_blame(
17591 &mut self,
17592 user_triggered: bool,
17593 window: &mut Window,
17594 cx: &mut Context<Self>,
17595 ) {
17596 if let Some(project) = self.project.as_ref() {
17597 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
17598 return;
17599 };
17600
17601 if buffer.read(cx).file().is_none() {
17602 return;
17603 }
17604
17605 let focused = self.focus_handle(cx).contains_focused(window, cx);
17606
17607 let project = project.clone();
17608 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
17609 self.blame_subscription =
17610 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
17611 self.blame = Some(blame);
17612 }
17613 }
17614
17615 fn toggle_git_blame_inline_internal(
17616 &mut self,
17617 user_triggered: bool,
17618 window: &mut Window,
17619 cx: &mut Context<Self>,
17620 ) {
17621 if self.git_blame_inline_enabled {
17622 self.git_blame_inline_enabled = false;
17623 self.show_git_blame_inline = false;
17624 self.show_git_blame_inline_delay_task.take();
17625 } else {
17626 self.git_blame_inline_enabled = true;
17627 self.start_git_blame_inline(user_triggered, window, cx);
17628 }
17629
17630 cx.notify();
17631 }
17632
17633 fn start_git_blame_inline(
17634 &mut self,
17635 user_triggered: bool,
17636 window: &mut Window,
17637 cx: &mut Context<Self>,
17638 ) {
17639 self.start_git_blame(user_triggered, window, cx);
17640
17641 if ProjectSettings::get_global(cx)
17642 .git
17643 .inline_blame_delay()
17644 .is_some()
17645 {
17646 self.start_inline_blame_timer(window, cx);
17647 } else {
17648 self.show_git_blame_inline = true
17649 }
17650 }
17651
17652 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
17653 self.blame.as_ref()
17654 }
17655
17656 pub fn show_git_blame_gutter(&self) -> bool {
17657 self.show_git_blame_gutter
17658 }
17659
17660 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
17661 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
17662 }
17663
17664 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
17665 self.show_git_blame_inline
17666 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
17667 && !self.newest_selection_head_on_empty_line(cx)
17668 && self.has_blame_entries(cx)
17669 }
17670
17671 fn has_blame_entries(&self, cx: &App) -> bool {
17672 self.blame()
17673 .map_or(false, |blame| blame.read(cx).has_generated_entries())
17674 }
17675
17676 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
17677 let cursor_anchor = self.selections.newest_anchor().head();
17678
17679 let snapshot = self.buffer.read(cx).snapshot(cx);
17680 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
17681
17682 snapshot.line_len(buffer_row) == 0
17683 }
17684
17685 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
17686 let buffer_and_selection = maybe!({
17687 let selection = self.selections.newest::<Point>(cx);
17688 let selection_range = selection.range();
17689
17690 let multi_buffer = self.buffer().read(cx);
17691 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17692 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
17693
17694 let (buffer, range, _) = if selection.reversed {
17695 buffer_ranges.first()
17696 } else {
17697 buffer_ranges.last()
17698 }?;
17699
17700 let selection = text::ToPoint::to_point(&range.start, &buffer).row
17701 ..text::ToPoint::to_point(&range.end, &buffer).row;
17702 Some((
17703 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
17704 selection,
17705 ))
17706 });
17707
17708 let Some((buffer, selection)) = buffer_and_selection else {
17709 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
17710 };
17711
17712 let Some(project) = self.project.as_ref() else {
17713 return Task::ready(Err(anyhow!("editor does not have project")));
17714 };
17715
17716 project.update(cx, |project, cx| {
17717 project.get_permalink_to_line(&buffer, selection, cx)
17718 })
17719 }
17720
17721 pub fn copy_permalink_to_line(
17722 &mut self,
17723 _: &CopyPermalinkToLine,
17724 window: &mut Window,
17725 cx: &mut Context<Self>,
17726 ) {
17727 let permalink_task = self.get_permalink_to_line(cx);
17728 let workspace = self.workspace();
17729
17730 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
17731 Ok(permalink) => {
17732 cx.update(|_, cx| {
17733 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
17734 })
17735 .ok();
17736 }
17737 Err(err) => {
17738 let message = format!("Failed to copy permalink: {err}");
17739
17740 anyhow::Result::<()>::Err(err).log_err();
17741
17742 if let Some(workspace) = workspace {
17743 workspace
17744 .update_in(cx, |workspace, _, cx| {
17745 struct CopyPermalinkToLine;
17746
17747 workspace.show_toast(
17748 Toast::new(
17749 NotificationId::unique::<CopyPermalinkToLine>(),
17750 message,
17751 ),
17752 cx,
17753 )
17754 })
17755 .ok();
17756 }
17757 }
17758 })
17759 .detach();
17760 }
17761
17762 pub fn copy_file_location(
17763 &mut self,
17764 _: &CopyFileLocation,
17765 _: &mut Window,
17766 cx: &mut Context<Self>,
17767 ) {
17768 let selection = self.selections.newest::<Point>(cx).start.row + 1;
17769 if let Some(file) = self.target_file(cx) {
17770 if let Some(path) = file.path().to_str() {
17771 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
17772 }
17773 }
17774 }
17775
17776 pub fn open_permalink_to_line(
17777 &mut self,
17778 _: &OpenPermalinkToLine,
17779 window: &mut Window,
17780 cx: &mut Context<Self>,
17781 ) {
17782 let permalink_task = self.get_permalink_to_line(cx);
17783 let workspace = self.workspace();
17784
17785 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
17786 Ok(permalink) => {
17787 cx.update(|_, cx| {
17788 cx.open_url(permalink.as_ref());
17789 })
17790 .ok();
17791 }
17792 Err(err) => {
17793 let message = format!("Failed to open permalink: {err}");
17794
17795 anyhow::Result::<()>::Err(err).log_err();
17796
17797 if let Some(workspace) = workspace {
17798 workspace
17799 .update(cx, |workspace, cx| {
17800 struct OpenPermalinkToLine;
17801
17802 workspace.show_toast(
17803 Toast::new(
17804 NotificationId::unique::<OpenPermalinkToLine>(),
17805 message,
17806 ),
17807 cx,
17808 )
17809 })
17810 .ok();
17811 }
17812 }
17813 })
17814 .detach();
17815 }
17816
17817 pub fn insert_uuid_v4(
17818 &mut self,
17819 _: &InsertUuidV4,
17820 window: &mut Window,
17821 cx: &mut Context<Self>,
17822 ) {
17823 self.insert_uuid(UuidVersion::V4, window, cx);
17824 }
17825
17826 pub fn insert_uuid_v7(
17827 &mut self,
17828 _: &InsertUuidV7,
17829 window: &mut Window,
17830 cx: &mut Context<Self>,
17831 ) {
17832 self.insert_uuid(UuidVersion::V7, window, cx);
17833 }
17834
17835 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
17836 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
17837 self.transact(window, cx, |this, window, cx| {
17838 let edits = this
17839 .selections
17840 .all::<Point>(cx)
17841 .into_iter()
17842 .map(|selection| {
17843 let uuid = match version {
17844 UuidVersion::V4 => uuid::Uuid::new_v4(),
17845 UuidVersion::V7 => uuid::Uuid::now_v7(),
17846 };
17847
17848 (selection.range(), uuid.to_string())
17849 });
17850 this.edit(edits, cx);
17851 this.refresh_inline_completion(true, false, window, cx);
17852 });
17853 }
17854
17855 pub fn open_selections_in_multibuffer(
17856 &mut self,
17857 _: &OpenSelectionsInMultibuffer,
17858 window: &mut Window,
17859 cx: &mut Context<Self>,
17860 ) {
17861 let multibuffer = self.buffer.read(cx);
17862
17863 let Some(buffer) = multibuffer.as_singleton() else {
17864 return;
17865 };
17866
17867 let Some(workspace) = self.workspace() else {
17868 return;
17869 };
17870
17871 let locations = self
17872 .selections
17873 .disjoint_anchors()
17874 .iter()
17875 .map(|range| Location {
17876 buffer: buffer.clone(),
17877 range: range.start.text_anchor..range.end.text_anchor,
17878 })
17879 .collect::<Vec<_>>();
17880
17881 let title = multibuffer.title(cx).to_string();
17882
17883 cx.spawn_in(window, async move |_, cx| {
17884 workspace.update_in(cx, |workspace, window, cx| {
17885 Self::open_locations_in_multibuffer(
17886 workspace,
17887 locations,
17888 format!("Selections for '{title}'"),
17889 false,
17890 MultibufferSelectionMode::All,
17891 window,
17892 cx,
17893 );
17894 })
17895 })
17896 .detach();
17897 }
17898
17899 /// Adds a row highlight for the given range. If a row has multiple highlights, the
17900 /// last highlight added will be used.
17901 ///
17902 /// If the range ends at the beginning of a line, then that line will not be highlighted.
17903 pub fn highlight_rows<T: 'static>(
17904 &mut self,
17905 range: Range<Anchor>,
17906 color: Hsla,
17907 options: RowHighlightOptions,
17908 cx: &mut Context<Self>,
17909 ) {
17910 let snapshot = self.buffer().read(cx).snapshot(cx);
17911 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
17912 let ix = row_highlights.binary_search_by(|highlight| {
17913 Ordering::Equal
17914 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
17915 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
17916 });
17917
17918 if let Err(mut ix) = ix {
17919 let index = post_inc(&mut self.highlight_order);
17920
17921 // If this range intersects with the preceding highlight, then merge it with
17922 // the preceding highlight. Otherwise insert a new highlight.
17923 let mut merged = false;
17924 if ix > 0 {
17925 let prev_highlight = &mut row_highlights[ix - 1];
17926 if prev_highlight
17927 .range
17928 .end
17929 .cmp(&range.start, &snapshot)
17930 .is_ge()
17931 {
17932 ix -= 1;
17933 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
17934 prev_highlight.range.end = range.end;
17935 }
17936 merged = true;
17937 prev_highlight.index = index;
17938 prev_highlight.color = color;
17939 prev_highlight.options = options;
17940 }
17941 }
17942
17943 if !merged {
17944 row_highlights.insert(
17945 ix,
17946 RowHighlight {
17947 range: range.clone(),
17948 index,
17949 color,
17950 options,
17951 type_id: TypeId::of::<T>(),
17952 },
17953 );
17954 }
17955
17956 // If any of the following highlights intersect with this one, merge them.
17957 while let Some(next_highlight) = row_highlights.get(ix + 1) {
17958 let highlight = &row_highlights[ix];
17959 if next_highlight
17960 .range
17961 .start
17962 .cmp(&highlight.range.end, &snapshot)
17963 .is_le()
17964 {
17965 if next_highlight
17966 .range
17967 .end
17968 .cmp(&highlight.range.end, &snapshot)
17969 .is_gt()
17970 {
17971 row_highlights[ix].range.end = next_highlight.range.end;
17972 }
17973 row_highlights.remove(ix + 1);
17974 } else {
17975 break;
17976 }
17977 }
17978 }
17979 }
17980
17981 /// Remove any highlighted row ranges of the given type that intersect the
17982 /// given ranges.
17983 pub fn remove_highlighted_rows<T: 'static>(
17984 &mut self,
17985 ranges_to_remove: Vec<Range<Anchor>>,
17986 cx: &mut Context<Self>,
17987 ) {
17988 let snapshot = self.buffer().read(cx).snapshot(cx);
17989 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
17990 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
17991 row_highlights.retain(|highlight| {
17992 while let Some(range_to_remove) = ranges_to_remove.peek() {
17993 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
17994 Ordering::Less | Ordering::Equal => {
17995 ranges_to_remove.next();
17996 }
17997 Ordering::Greater => {
17998 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
17999 Ordering::Less | Ordering::Equal => {
18000 return false;
18001 }
18002 Ordering::Greater => break,
18003 }
18004 }
18005 }
18006 }
18007
18008 true
18009 })
18010 }
18011
18012 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
18013 pub fn clear_row_highlights<T: 'static>(&mut self) {
18014 self.highlighted_rows.remove(&TypeId::of::<T>());
18015 }
18016
18017 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
18018 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
18019 self.highlighted_rows
18020 .get(&TypeId::of::<T>())
18021 .map_or(&[] as &[_], |vec| vec.as_slice())
18022 .iter()
18023 .map(|highlight| (highlight.range.clone(), highlight.color))
18024 }
18025
18026 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
18027 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
18028 /// Allows to ignore certain kinds of highlights.
18029 pub fn highlighted_display_rows(
18030 &self,
18031 window: &mut Window,
18032 cx: &mut App,
18033 ) -> BTreeMap<DisplayRow, LineHighlight> {
18034 let snapshot = self.snapshot(window, cx);
18035 let mut used_highlight_orders = HashMap::default();
18036 self.highlighted_rows
18037 .iter()
18038 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
18039 .fold(
18040 BTreeMap::<DisplayRow, LineHighlight>::new(),
18041 |mut unique_rows, highlight| {
18042 let start = highlight.range.start.to_display_point(&snapshot);
18043 let end = highlight.range.end.to_display_point(&snapshot);
18044 let start_row = start.row().0;
18045 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
18046 && end.column() == 0
18047 {
18048 end.row().0.saturating_sub(1)
18049 } else {
18050 end.row().0
18051 };
18052 for row in start_row..=end_row {
18053 let used_index =
18054 used_highlight_orders.entry(row).or_insert(highlight.index);
18055 if highlight.index >= *used_index {
18056 *used_index = highlight.index;
18057 unique_rows.insert(
18058 DisplayRow(row),
18059 LineHighlight {
18060 include_gutter: highlight.options.include_gutter,
18061 border: None,
18062 background: highlight.color.into(),
18063 type_id: Some(highlight.type_id),
18064 },
18065 );
18066 }
18067 }
18068 unique_rows
18069 },
18070 )
18071 }
18072
18073 pub fn highlighted_display_row_for_autoscroll(
18074 &self,
18075 snapshot: &DisplaySnapshot,
18076 ) -> Option<DisplayRow> {
18077 self.highlighted_rows
18078 .values()
18079 .flat_map(|highlighted_rows| highlighted_rows.iter())
18080 .filter_map(|highlight| {
18081 if highlight.options.autoscroll {
18082 Some(highlight.range.start.to_display_point(snapshot).row())
18083 } else {
18084 None
18085 }
18086 })
18087 .min()
18088 }
18089
18090 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
18091 self.highlight_background::<SearchWithinRange>(
18092 ranges,
18093 |colors| colors.editor_document_highlight_read_background,
18094 cx,
18095 )
18096 }
18097
18098 pub fn set_breadcrumb_header(&mut self, new_header: String) {
18099 self.breadcrumb_header = Some(new_header);
18100 }
18101
18102 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
18103 self.clear_background_highlights::<SearchWithinRange>(cx);
18104 }
18105
18106 pub fn highlight_background<T: 'static>(
18107 &mut self,
18108 ranges: &[Range<Anchor>],
18109 color_fetcher: fn(&ThemeColors) -> Hsla,
18110 cx: &mut Context<Self>,
18111 ) {
18112 self.background_highlights
18113 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
18114 self.scrollbar_marker_state.dirty = true;
18115 cx.notify();
18116 }
18117
18118 pub fn clear_background_highlights<T: 'static>(
18119 &mut self,
18120 cx: &mut Context<Self>,
18121 ) -> Option<BackgroundHighlight> {
18122 let text_highlights = self.background_highlights.remove(&TypeId::of::<T>())?;
18123 if !text_highlights.1.is_empty() {
18124 self.scrollbar_marker_state.dirty = true;
18125 cx.notify();
18126 }
18127 Some(text_highlights)
18128 }
18129
18130 pub fn highlight_gutter<T: 'static>(
18131 &mut self,
18132 ranges: &[Range<Anchor>],
18133 color_fetcher: fn(&App) -> Hsla,
18134 cx: &mut Context<Self>,
18135 ) {
18136 self.gutter_highlights
18137 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
18138 cx.notify();
18139 }
18140
18141 pub fn clear_gutter_highlights<T: 'static>(
18142 &mut self,
18143 cx: &mut Context<Self>,
18144 ) -> Option<GutterHighlight> {
18145 cx.notify();
18146 self.gutter_highlights.remove(&TypeId::of::<T>())
18147 }
18148
18149 #[cfg(feature = "test-support")]
18150 pub fn all_text_background_highlights(
18151 &self,
18152 window: &mut Window,
18153 cx: &mut Context<Self>,
18154 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18155 let snapshot = self.snapshot(window, cx);
18156 let buffer = &snapshot.buffer_snapshot;
18157 let start = buffer.anchor_before(0);
18158 let end = buffer.anchor_after(buffer.len());
18159 let theme = cx.theme().colors();
18160 self.background_highlights_in_range(start..end, &snapshot, theme)
18161 }
18162
18163 #[cfg(feature = "test-support")]
18164 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
18165 let snapshot = self.buffer().read(cx).snapshot(cx);
18166
18167 let highlights = self
18168 .background_highlights
18169 .get(&TypeId::of::<items::BufferSearchHighlights>());
18170
18171 if let Some((_color, ranges)) = highlights {
18172 ranges
18173 .iter()
18174 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
18175 .collect_vec()
18176 } else {
18177 vec![]
18178 }
18179 }
18180
18181 fn document_highlights_for_position<'a>(
18182 &'a self,
18183 position: Anchor,
18184 buffer: &'a MultiBufferSnapshot,
18185 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
18186 let read_highlights = self
18187 .background_highlights
18188 .get(&TypeId::of::<DocumentHighlightRead>())
18189 .map(|h| &h.1);
18190 let write_highlights = self
18191 .background_highlights
18192 .get(&TypeId::of::<DocumentHighlightWrite>())
18193 .map(|h| &h.1);
18194 let left_position = position.bias_left(buffer);
18195 let right_position = position.bias_right(buffer);
18196 read_highlights
18197 .into_iter()
18198 .chain(write_highlights)
18199 .flat_map(move |ranges| {
18200 let start_ix = match ranges.binary_search_by(|probe| {
18201 let cmp = probe.end.cmp(&left_position, buffer);
18202 if cmp.is_ge() {
18203 Ordering::Greater
18204 } else {
18205 Ordering::Less
18206 }
18207 }) {
18208 Ok(i) | Err(i) => i,
18209 };
18210
18211 ranges[start_ix..]
18212 .iter()
18213 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
18214 })
18215 }
18216
18217 pub fn has_background_highlights<T: 'static>(&self) -> bool {
18218 self.background_highlights
18219 .get(&TypeId::of::<T>())
18220 .map_or(false, |(_, highlights)| !highlights.is_empty())
18221 }
18222
18223 pub fn background_highlights_in_range(
18224 &self,
18225 search_range: Range<Anchor>,
18226 display_snapshot: &DisplaySnapshot,
18227 theme: &ThemeColors,
18228 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18229 let mut results = Vec::new();
18230 for (color_fetcher, ranges) in self.background_highlights.values() {
18231 let color = color_fetcher(theme);
18232 let start_ix = match ranges.binary_search_by(|probe| {
18233 let cmp = probe
18234 .end
18235 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18236 if cmp.is_gt() {
18237 Ordering::Greater
18238 } else {
18239 Ordering::Less
18240 }
18241 }) {
18242 Ok(i) | Err(i) => i,
18243 };
18244 for range in &ranges[start_ix..] {
18245 if range
18246 .start
18247 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18248 .is_ge()
18249 {
18250 break;
18251 }
18252
18253 let start = range.start.to_display_point(display_snapshot);
18254 let end = range.end.to_display_point(display_snapshot);
18255 results.push((start..end, color))
18256 }
18257 }
18258 results
18259 }
18260
18261 pub fn background_highlight_row_ranges<T: 'static>(
18262 &self,
18263 search_range: Range<Anchor>,
18264 display_snapshot: &DisplaySnapshot,
18265 count: usize,
18266 ) -> Vec<RangeInclusive<DisplayPoint>> {
18267 let mut results = Vec::new();
18268 let Some((_, ranges)) = self.background_highlights.get(&TypeId::of::<T>()) else {
18269 return vec![];
18270 };
18271
18272 let start_ix = match ranges.binary_search_by(|probe| {
18273 let cmp = probe
18274 .end
18275 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18276 if cmp.is_gt() {
18277 Ordering::Greater
18278 } else {
18279 Ordering::Less
18280 }
18281 }) {
18282 Ok(i) | Err(i) => i,
18283 };
18284 let mut push_region = |start: Option<Point>, end: Option<Point>| {
18285 if let (Some(start_display), Some(end_display)) = (start, end) {
18286 results.push(
18287 start_display.to_display_point(display_snapshot)
18288 ..=end_display.to_display_point(display_snapshot),
18289 );
18290 }
18291 };
18292 let mut start_row: Option<Point> = None;
18293 let mut end_row: Option<Point> = None;
18294 if ranges.len() > count {
18295 return Vec::new();
18296 }
18297 for range in &ranges[start_ix..] {
18298 if range
18299 .start
18300 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18301 .is_ge()
18302 {
18303 break;
18304 }
18305 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
18306 if let Some(current_row) = &end_row {
18307 if end.row == current_row.row {
18308 continue;
18309 }
18310 }
18311 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
18312 if start_row.is_none() {
18313 assert_eq!(end_row, None);
18314 start_row = Some(start);
18315 end_row = Some(end);
18316 continue;
18317 }
18318 if let Some(current_end) = end_row.as_mut() {
18319 if start.row > current_end.row + 1 {
18320 push_region(start_row, end_row);
18321 start_row = Some(start);
18322 end_row = Some(end);
18323 } else {
18324 // Merge two hunks.
18325 *current_end = end;
18326 }
18327 } else {
18328 unreachable!();
18329 }
18330 }
18331 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
18332 push_region(start_row, end_row);
18333 results
18334 }
18335
18336 pub fn gutter_highlights_in_range(
18337 &self,
18338 search_range: Range<Anchor>,
18339 display_snapshot: &DisplaySnapshot,
18340 cx: &App,
18341 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18342 let mut results = Vec::new();
18343 for (color_fetcher, ranges) in self.gutter_highlights.values() {
18344 let color = color_fetcher(cx);
18345 let start_ix = match ranges.binary_search_by(|probe| {
18346 let cmp = probe
18347 .end
18348 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18349 if cmp.is_gt() {
18350 Ordering::Greater
18351 } else {
18352 Ordering::Less
18353 }
18354 }) {
18355 Ok(i) | Err(i) => i,
18356 };
18357 for range in &ranges[start_ix..] {
18358 if range
18359 .start
18360 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18361 .is_ge()
18362 {
18363 break;
18364 }
18365
18366 let start = range.start.to_display_point(display_snapshot);
18367 let end = range.end.to_display_point(display_snapshot);
18368 results.push((start..end, color))
18369 }
18370 }
18371 results
18372 }
18373
18374 /// Get the text ranges corresponding to the redaction query
18375 pub fn redacted_ranges(
18376 &self,
18377 search_range: Range<Anchor>,
18378 display_snapshot: &DisplaySnapshot,
18379 cx: &App,
18380 ) -> Vec<Range<DisplayPoint>> {
18381 display_snapshot
18382 .buffer_snapshot
18383 .redacted_ranges(search_range, |file| {
18384 if let Some(file) = file {
18385 file.is_private()
18386 && EditorSettings::get(
18387 Some(SettingsLocation {
18388 worktree_id: file.worktree_id(cx),
18389 path: file.path().as_ref(),
18390 }),
18391 cx,
18392 )
18393 .redact_private_values
18394 } else {
18395 false
18396 }
18397 })
18398 .map(|range| {
18399 range.start.to_display_point(display_snapshot)
18400 ..range.end.to_display_point(display_snapshot)
18401 })
18402 .collect()
18403 }
18404
18405 pub fn highlight_text<T: 'static>(
18406 &mut self,
18407 ranges: Vec<Range<Anchor>>,
18408 style: HighlightStyle,
18409 cx: &mut Context<Self>,
18410 ) {
18411 self.display_map.update(cx, |map, _| {
18412 map.highlight_text(TypeId::of::<T>(), ranges, style)
18413 });
18414 cx.notify();
18415 }
18416
18417 pub(crate) fn highlight_inlays<T: 'static>(
18418 &mut self,
18419 highlights: Vec<InlayHighlight>,
18420 style: HighlightStyle,
18421 cx: &mut Context<Self>,
18422 ) {
18423 self.display_map.update(cx, |map, _| {
18424 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
18425 });
18426 cx.notify();
18427 }
18428
18429 pub fn text_highlights<'a, T: 'static>(
18430 &'a self,
18431 cx: &'a App,
18432 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
18433 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
18434 }
18435
18436 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
18437 let cleared = self
18438 .display_map
18439 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
18440 if cleared {
18441 cx.notify();
18442 }
18443 }
18444
18445 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
18446 (self.read_only(cx) || self.blink_manager.read(cx).visible())
18447 && self.focus_handle.is_focused(window)
18448 }
18449
18450 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
18451 self.show_cursor_when_unfocused = is_enabled;
18452 cx.notify();
18453 }
18454
18455 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
18456 cx.notify();
18457 }
18458
18459 fn on_debug_session_event(
18460 &mut self,
18461 _session: Entity<Session>,
18462 event: &SessionEvent,
18463 cx: &mut Context<Self>,
18464 ) {
18465 match event {
18466 SessionEvent::InvalidateInlineValue => {
18467 self.refresh_inline_values(cx);
18468 }
18469 _ => {}
18470 }
18471 }
18472
18473 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
18474 let Some(project) = self.project.clone() else {
18475 return;
18476 };
18477
18478 if !self.inline_value_cache.enabled {
18479 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
18480 self.splice_inlays(&inlays, Vec::new(), cx);
18481 return;
18482 }
18483
18484 let current_execution_position = self
18485 .highlighted_rows
18486 .get(&TypeId::of::<ActiveDebugLine>())
18487 .and_then(|lines| lines.last().map(|line| line.range.start));
18488
18489 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
18490 let inline_values = editor
18491 .update(cx, |editor, cx| {
18492 let Some(current_execution_position) = current_execution_position else {
18493 return Some(Task::ready(Ok(Vec::new())));
18494 };
18495
18496 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
18497 let snapshot = buffer.snapshot(cx);
18498
18499 let excerpt = snapshot.excerpt_containing(
18500 current_execution_position..current_execution_position,
18501 )?;
18502
18503 editor.buffer.read(cx).buffer(excerpt.buffer_id())
18504 })?;
18505
18506 let range =
18507 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
18508
18509 project.inline_values(buffer, range, cx)
18510 })
18511 .ok()
18512 .flatten()?
18513 .await
18514 .context("refreshing debugger inlays")
18515 .log_err()?;
18516
18517 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
18518
18519 for (buffer_id, inline_value) in inline_values
18520 .into_iter()
18521 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
18522 {
18523 buffer_inline_values
18524 .entry(buffer_id)
18525 .or_default()
18526 .push(inline_value);
18527 }
18528
18529 editor
18530 .update(cx, |editor, cx| {
18531 let snapshot = editor.buffer.read(cx).snapshot(cx);
18532 let mut new_inlays = Vec::default();
18533
18534 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
18535 let buffer_id = buffer_snapshot.remote_id();
18536 buffer_inline_values
18537 .get(&buffer_id)
18538 .into_iter()
18539 .flatten()
18540 .for_each(|hint| {
18541 let inlay = Inlay::debugger_hint(
18542 post_inc(&mut editor.next_inlay_id),
18543 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
18544 hint.text(),
18545 );
18546
18547 new_inlays.push(inlay);
18548 });
18549 }
18550
18551 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
18552 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
18553
18554 editor.splice_inlays(&inlay_ids, new_inlays, cx);
18555 })
18556 .ok()?;
18557 Some(())
18558 });
18559 }
18560
18561 fn on_buffer_event(
18562 &mut self,
18563 multibuffer: &Entity<MultiBuffer>,
18564 event: &multi_buffer::Event,
18565 window: &mut Window,
18566 cx: &mut Context<Self>,
18567 ) {
18568 match event {
18569 multi_buffer::Event::Edited {
18570 singleton_buffer_edited,
18571 edited_buffer: buffer_edited,
18572 } => {
18573 self.scrollbar_marker_state.dirty = true;
18574 self.active_indent_guides_state.dirty = true;
18575 self.refresh_active_diagnostics(cx);
18576 self.refresh_code_actions(window, cx);
18577 self.refresh_selected_text_highlights(true, window, cx);
18578 refresh_matching_bracket_highlights(self, window, cx);
18579 if self.has_active_inline_completion() {
18580 self.update_visible_inline_completion(window, cx);
18581 }
18582 if let Some(buffer) = buffer_edited {
18583 let buffer_id = buffer.read(cx).remote_id();
18584 if !self.registered_buffers.contains_key(&buffer_id) {
18585 if let Some(project) = self.project.as_ref() {
18586 project.update(cx, |project, cx| {
18587 self.registered_buffers.insert(
18588 buffer_id,
18589 project.register_buffer_with_language_servers(&buffer, cx),
18590 );
18591 })
18592 }
18593 }
18594 }
18595 cx.emit(EditorEvent::BufferEdited);
18596 cx.emit(SearchEvent::MatchesInvalidated);
18597 if *singleton_buffer_edited {
18598 if let Some(project) = &self.project {
18599 #[allow(clippy::mutable_key_type)]
18600 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
18601 multibuffer
18602 .all_buffers()
18603 .into_iter()
18604 .filter_map(|buffer| {
18605 buffer.update(cx, |buffer, cx| {
18606 let language = buffer.language()?;
18607 let should_discard = project.update(cx, |project, cx| {
18608 project.is_local()
18609 && !project.has_language_servers_for(buffer, cx)
18610 });
18611 should_discard.not().then_some(language.clone())
18612 })
18613 })
18614 .collect::<HashSet<_>>()
18615 });
18616 if !languages_affected.is_empty() {
18617 self.refresh_inlay_hints(
18618 InlayHintRefreshReason::BufferEdited(languages_affected),
18619 cx,
18620 );
18621 }
18622 }
18623 }
18624
18625 let Some(project) = &self.project else { return };
18626 let (telemetry, is_via_ssh) = {
18627 let project = project.read(cx);
18628 let telemetry = project.client().telemetry().clone();
18629 let is_via_ssh = project.is_via_ssh();
18630 (telemetry, is_via_ssh)
18631 };
18632 refresh_linked_ranges(self, window, cx);
18633 telemetry.log_edit_event("editor", is_via_ssh);
18634 }
18635 multi_buffer::Event::ExcerptsAdded {
18636 buffer,
18637 predecessor,
18638 excerpts,
18639 } => {
18640 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18641 let buffer_id = buffer.read(cx).remote_id();
18642 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
18643 if let Some(project) = &self.project {
18644 update_uncommitted_diff_for_buffer(
18645 cx.entity(),
18646 project,
18647 [buffer.clone()],
18648 self.buffer.clone(),
18649 cx,
18650 )
18651 .detach();
18652 }
18653 }
18654 cx.emit(EditorEvent::ExcerptsAdded {
18655 buffer: buffer.clone(),
18656 predecessor: *predecessor,
18657 excerpts: excerpts.clone(),
18658 });
18659 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18660 }
18661 multi_buffer::Event::ExcerptsRemoved {
18662 ids,
18663 removed_buffer_ids,
18664 } => {
18665 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
18666 let buffer = self.buffer.read(cx);
18667 self.registered_buffers
18668 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
18669 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18670 cx.emit(EditorEvent::ExcerptsRemoved {
18671 ids: ids.clone(),
18672 removed_buffer_ids: removed_buffer_ids.clone(),
18673 })
18674 }
18675 multi_buffer::Event::ExcerptsEdited {
18676 excerpt_ids,
18677 buffer_ids,
18678 } => {
18679 self.display_map.update(cx, |map, cx| {
18680 map.unfold_buffers(buffer_ids.iter().copied(), cx)
18681 });
18682 cx.emit(EditorEvent::ExcerptsEdited {
18683 ids: excerpt_ids.clone(),
18684 })
18685 }
18686 multi_buffer::Event::ExcerptsExpanded { ids } => {
18687 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18688 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
18689 }
18690 multi_buffer::Event::Reparsed(buffer_id) => {
18691 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18692 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18693
18694 cx.emit(EditorEvent::Reparsed(*buffer_id));
18695 }
18696 multi_buffer::Event::DiffHunksToggled => {
18697 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18698 }
18699 multi_buffer::Event::LanguageChanged(buffer_id) => {
18700 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
18701 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18702 cx.emit(EditorEvent::Reparsed(*buffer_id));
18703 cx.notify();
18704 }
18705 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
18706 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
18707 multi_buffer::Event::FileHandleChanged
18708 | multi_buffer::Event::Reloaded
18709 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
18710 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
18711 multi_buffer::Event::DiagnosticsUpdated => {
18712 self.refresh_active_diagnostics(cx);
18713 self.refresh_inline_diagnostics(true, window, cx);
18714 self.scrollbar_marker_state.dirty = true;
18715 cx.notify();
18716 }
18717 _ => {}
18718 };
18719 }
18720
18721 pub fn start_temporary_diff_override(&mut self) {
18722 self.load_diff_task.take();
18723 self.temporary_diff_override = true;
18724 }
18725
18726 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
18727 self.temporary_diff_override = false;
18728 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
18729 self.buffer.update(cx, |buffer, cx| {
18730 buffer.set_all_diff_hunks_collapsed(cx);
18731 });
18732
18733 if let Some(project) = self.project.clone() {
18734 self.load_diff_task = Some(
18735 update_uncommitted_diff_for_buffer(
18736 cx.entity(),
18737 &project,
18738 self.buffer.read(cx).all_buffers(),
18739 self.buffer.clone(),
18740 cx,
18741 )
18742 .shared(),
18743 );
18744 }
18745 }
18746
18747 fn on_display_map_changed(
18748 &mut self,
18749 _: Entity<DisplayMap>,
18750 _: &mut Window,
18751 cx: &mut Context<Self>,
18752 ) {
18753 cx.notify();
18754 }
18755
18756 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18757 let new_severity = if self.diagnostics_enabled() {
18758 EditorSettings::get_global(cx)
18759 .diagnostics_max_severity
18760 .unwrap_or(DiagnosticSeverity::Hint)
18761 } else {
18762 DiagnosticSeverity::Off
18763 };
18764 self.set_max_diagnostics_severity(new_severity, cx);
18765 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18766 self.update_edit_prediction_settings(cx);
18767 self.refresh_inline_completion(true, false, window, cx);
18768 self.refresh_inlay_hints(
18769 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
18770 self.selections.newest_anchor().head(),
18771 &self.buffer.read(cx).snapshot(cx),
18772 cx,
18773 )),
18774 cx,
18775 );
18776
18777 let old_cursor_shape = self.cursor_shape;
18778
18779 {
18780 let editor_settings = EditorSettings::get_global(cx);
18781 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
18782 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
18783 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
18784 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
18785 }
18786
18787 if old_cursor_shape != self.cursor_shape {
18788 cx.emit(EditorEvent::CursorShapeChanged);
18789 }
18790
18791 let project_settings = ProjectSettings::get_global(cx);
18792 self.serialize_dirty_buffers =
18793 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
18794
18795 if self.mode.is_full() {
18796 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
18797 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
18798 if self.show_inline_diagnostics != show_inline_diagnostics {
18799 self.show_inline_diagnostics = show_inline_diagnostics;
18800 self.refresh_inline_diagnostics(false, window, cx);
18801 }
18802
18803 if self.git_blame_inline_enabled != inline_blame_enabled {
18804 self.toggle_git_blame_inline_internal(false, window, cx);
18805 }
18806
18807 let minimap_settings = EditorSettings::get_global(cx).minimap;
18808 if self.minimap_visibility != MinimapVisibility::Disabled {
18809 if self.minimap_visibility.settings_visibility()
18810 != minimap_settings.minimap_enabled()
18811 {
18812 self.set_minimap_visibility(
18813 MinimapVisibility::for_mode(self.mode(), cx),
18814 window,
18815 cx,
18816 );
18817 } else if let Some(minimap_entity) = self.minimap.as_ref() {
18818 minimap_entity.update(cx, |minimap_editor, cx| {
18819 minimap_editor.update_minimap_configuration(minimap_settings, cx)
18820 })
18821 }
18822 }
18823 }
18824
18825 cx.notify();
18826 }
18827
18828 pub fn set_searchable(&mut self, searchable: bool) {
18829 self.searchable = searchable;
18830 }
18831
18832 pub fn searchable(&self) -> bool {
18833 self.searchable
18834 }
18835
18836 fn open_proposed_changes_editor(
18837 &mut self,
18838 _: &OpenProposedChangesEditor,
18839 window: &mut Window,
18840 cx: &mut Context<Self>,
18841 ) {
18842 let Some(workspace) = self.workspace() else {
18843 cx.propagate();
18844 return;
18845 };
18846
18847 let selections = self.selections.all::<usize>(cx);
18848 let multi_buffer = self.buffer.read(cx);
18849 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18850 let mut new_selections_by_buffer = HashMap::default();
18851 for selection in selections {
18852 for (buffer, range, _) in
18853 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
18854 {
18855 let mut range = range.to_point(buffer);
18856 range.start.column = 0;
18857 range.end.column = buffer.line_len(range.end.row);
18858 new_selections_by_buffer
18859 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
18860 .or_insert(Vec::new())
18861 .push(range)
18862 }
18863 }
18864
18865 let proposed_changes_buffers = new_selections_by_buffer
18866 .into_iter()
18867 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
18868 .collect::<Vec<_>>();
18869 let proposed_changes_editor = cx.new(|cx| {
18870 ProposedChangesEditor::new(
18871 "Proposed changes",
18872 proposed_changes_buffers,
18873 self.project.clone(),
18874 window,
18875 cx,
18876 )
18877 });
18878
18879 window.defer(cx, move |window, cx| {
18880 workspace.update(cx, |workspace, cx| {
18881 workspace.active_pane().update(cx, |pane, cx| {
18882 pane.add_item(
18883 Box::new(proposed_changes_editor),
18884 true,
18885 true,
18886 None,
18887 window,
18888 cx,
18889 );
18890 });
18891 });
18892 });
18893 }
18894
18895 pub fn open_excerpts_in_split(
18896 &mut self,
18897 _: &OpenExcerptsSplit,
18898 window: &mut Window,
18899 cx: &mut Context<Self>,
18900 ) {
18901 self.open_excerpts_common(None, true, window, cx)
18902 }
18903
18904 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
18905 self.open_excerpts_common(None, false, window, cx)
18906 }
18907
18908 fn open_excerpts_common(
18909 &mut self,
18910 jump_data: Option<JumpData>,
18911 split: bool,
18912 window: &mut Window,
18913 cx: &mut Context<Self>,
18914 ) {
18915 let Some(workspace) = self.workspace() else {
18916 cx.propagate();
18917 return;
18918 };
18919
18920 if self.buffer.read(cx).is_singleton() {
18921 cx.propagate();
18922 return;
18923 }
18924
18925 let mut new_selections_by_buffer = HashMap::default();
18926 match &jump_data {
18927 Some(JumpData::MultiBufferPoint {
18928 excerpt_id,
18929 position,
18930 anchor,
18931 line_offset_from_top,
18932 }) => {
18933 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18934 if let Some(buffer) = multi_buffer_snapshot
18935 .buffer_id_for_excerpt(*excerpt_id)
18936 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
18937 {
18938 let buffer_snapshot = buffer.read(cx).snapshot();
18939 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
18940 language::ToPoint::to_point(anchor, &buffer_snapshot)
18941 } else {
18942 buffer_snapshot.clip_point(*position, Bias::Left)
18943 };
18944 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
18945 new_selections_by_buffer.insert(
18946 buffer,
18947 (
18948 vec![jump_to_offset..jump_to_offset],
18949 Some(*line_offset_from_top),
18950 ),
18951 );
18952 }
18953 }
18954 Some(JumpData::MultiBufferRow {
18955 row,
18956 line_offset_from_top,
18957 }) => {
18958 let point = MultiBufferPoint::new(row.0, 0);
18959 if let Some((buffer, buffer_point, _)) =
18960 self.buffer.read(cx).point_to_buffer_point(point, cx)
18961 {
18962 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
18963 new_selections_by_buffer
18964 .entry(buffer)
18965 .or_insert((Vec::new(), Some(*line_offset_from_top)))
18966 .0
18967 .push(buffer_offset..buffer_offset)
18968 }
18969 }
18970 None => {
18971 let selections = self.selections.all::<usize>(cx);
18972 let multi_buffer = self.buffer.read(cx);
18973 for selection in selections {
18974 for (snapshot, range, _, anchor) in multi_buffer
18975 .snapshot(cx)
18976 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
18977 {
18978 if let Some(anchor) = anchor {
18979 // selection is in a deleted hunk
18980 let Some(buffer_id) = anchor.buffer_id else {
18981 continue;
18982 };
18983 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
18984 continue;
18985 };
18986 let offset = text::ToOffset::to_offset(
18987 &anchor.text_anchor,
18988 &buffer_handle.read(cx).snapshot(),
18989 );
18990 let range = offset..offset;
18991 new_selections_by_buffer
18992 .entry(buffer_handle)
18993 .or_insert((Vec::new(), None))
18994 .0
18995 .push(range)
18996 } else {
18997 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
18998 else {
18999 continue;
19000 };
19001 new_selections_by_buffer
19002 .entry(buffer_handle)
19003 .or_insert((Vec::new(), None))
19004 .0
19005 .push(range)
19006 }
19007 }
19008 }
19009 }
19010 }
19011
19012 new_selections_by_buffer
19013 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
19014
19015 if new_selections_by_buffer.is_empty() {
19016 return;
19017 }
19018
19019 // We defer the pane interaction because we ourselves are a workspace item
19020 // and activating a new item causes the pane to call a method on us reentrantly,
19021 // which panics if we're on the stack.
19022 window.defer(cx, move |window, cx| {
19023 workspace.update(cx, |workspace, cx| {
19024 let pane = if split {
19025 workspace.adjacent_pane(window, cx)
19026 } else {
19027 workspace.active_pane().clone()
19028 };
19029
19030 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
19031 let editor = buffer
19032 .read(cx)
19033 .file()
19034 .is_none()
19035 .then(|| {
19036 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
19037 // so `workspace.open_project_item` will never find them, always opening a new editor.
19038 // Instead, we try to activate the existing editor in the pane first.
19039 let (editor, pane_item_index) =
19040 pane.read(cx).items().enumerate().find_map(|(i, item)| {
19041 let editor = item.downcast::<Editor>()?;
19042 let singleton_buffer =
19043 editor.read(cx).buffer().read(cx).as_singleton()?;
19044 if singleton_buffer == buffer {
19045 Some((editor, i))
19046 } else {
19047 None
19048 }
19049 })?;
19050 pane.update(cx, |pane, cx| {
19051 pane.activate_item(pane_item_index, true, true, window, cx)
19052 });
19053 Some(editor)
19054 })
19055 .flatten()
19056 .unwrap_or_else(|| {
19057 workspace.open_project_item::<Self>(
19058 pane.clone(),
19059 buffer,
19060 true,
19061 true,
19062 window,
19063 cx,
19064 )
19065 });
19066
19067 editor.update(cx, |editor, cx| {
19068 let autoscroll = match scroll_offset {
19069 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
19070 None => Autoscroll::newest(),
19071 };
19072 let nav_history = editor.nav_history.take();
19073 editor.change_selections(Some(autoscroll), window, cx, |s| {
19074 s.select_ranges(ranges);
19075 });
19076 editor.nav_history = nav_history;
19077 });
19078 }
19079 })
19080 });
19081 }
19082
19083 // For now, don't allow opening excerpts in buffers that aren't backed by
19084 // regular project files.
19085 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
19086 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
19087 }
19088
19089 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
19090 let snapshot = self.buffer.read(cx).read(cx);
19091 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
19092 Some(
19093 ranges
19094 .iter()
19095 .map(move |range| {
19096 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
19097 })
19098 .collect(),
19099 )
19100 }
19101
19102 fn selection_replacement_ranges(
19103 &self,
19104 range: Range<OffsetUtf16>,
19105 cx: &mut App,
19106 ) -> Vec<Range<OffsetUtf16>> {
19107 let selections = self.selections.all::<OffsetUtf16>(cx);
19108 let newest_selection = selections
19109 .iter()
19110 .max_by_key(|selection| selection.id)
19111 .unwrap();
19112 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
19113 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
19114 let snapshot = self.buffer.read(cx).read(cx);
19115 selections
19116 .into_iter()
19117 .map(|mut selection| {
19118 selection.start.0 =
19119 (selection.start.0 as isize).saturating_add(start_delta) as usize;
19120 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
19121 snapshot.clip_offset_utf16(selection.start, Bias::Left)
19122 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
19123 })
19124 .collect()
19125 }
19126
19127 fn report_editor_event(
19128 &self,
19129 event_type: &'static str,
19130 file_extension: Option<String>,
19131 cx: &App,
19132 ) {
19133 if cfg!(any(test, feature = "test-support")) {
19134 return;
19135 }
19136
19137 let Some(project) = &self.project else { return };
19138
19139 // If None, we are in a file without an extension
19140 let file = self
19141 .buffer
19142 .read(cx)
19143 .as_singleton()
19144 .and_then(|b| b.read(cx).file());
19145 let file_extension = file_extension.or(file
19146 .as_ref()
19147 .and_then(|file| Path::new(file.file_name(cx)).extension())
19148 .and_then(|e| e.to_str())
19149 .map(|a| a.to_string()));
19150
19151 let vim_mode = vim_enabled(cx);
19152
19153 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
19154 let copilot_enabled = edit_predictions_provider
19155 == language::language_settings::EditPredictionProvider::Copilot;
19156 let copilot_enabled_for_language = self
19157 .buffer
19158 .read(cx)
19159 .language_settings(cx)
19160 .show_edit_predictions;
19161
19162 let project = project.read(cx);
19163 telemetry::event!(
19164 event_type,
19165 file_extension,
19166 vim_mode,
19167 copilot_enabled,
19168 copilot_enabled_for_language,
19169 edit_predictions_provider,
19170 is_via_ssh = project.is_via_ssh(),
19171 );
19172 }
19173
19174 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
19175 /// with each line being an array of {text, highlight} objects.
19176 fn copy_highlight_json(
19177 &mut self,
19178 _: &CopyHighlightJson,
19179 window: &mut Window,
19180 cx: &mut Context<Self>,
19181 ) {
19182 #[derive(Serialize)]
19183 struct Chunk<'a> {
19184 text: String,
19185 highlight: Option<&'a str>,
19186 }
19187
19188 let snapshot = self.buffer.read(cx).snapshot(cx);
19189 let range = self
19190 .selected_text_range(false, window, cx)
19191 .and_then(|selection| {
19192 if selection.range.is_empty() {
19193 None
19194 } else {
19195 Some(selection.range)
19196 }
19197 })
19198 .unwrap_or_else(|| 0..snapshot.len());
19199
19200 let chunks = snapshot.chunks(range, true);
19201 let mut lines = Vec::new();
19202 let mut line: VecDeque<Chunk> = VecDeque::new();
19203
19204 let Some(style) = self.style.as_ref() else {
19205 return;
19206 };
19207
19208 for chunk in chunks {
19209 let highlight = chunk
19210 .syntax_highlight_id
19211 .and_then(|id| id.name(&style.syntax));
19212 let mut chunk_lines = chunk.text.split('\n').peekable();
19213 while let Some(text) = chunk_lines.next() {
19214 let mut merged_with_last_token = false;
19215 if let Some(last_token) = line.back_mut() {
19216 if last_token.highlight == highlight {
19217 last_token.text.push_str(text);
19218 merged_with_last_token = true;
19219 }
19220 }
19221
19222 if !merged_with_last_token {
19223 line.push_back(Chunk {
19224 text: text.into(),
19225 highlight,
19226 });
19227 }
19228
19229 if chunk_lines.peek().is_some() {
19230 if line.len() > 1 && line.front().unwrap().text.is_empty() {
19231 line.pop_front();
19232 }
19233 if line.len() > 1 && line.back().unwrap().text.is_empty() {
19234 line.pop_back();
19235 }
19236
19237 lines.push(mem::take(&mut line));
19238 }
19239 }
19240 }
19241
19242 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
19243 return;
19244 };
19245 cx.write_to_clipboard(ClipboardItem::new_string(lines));
19246 }
19247
19248 pub fn open_context_menu(
19249 &mut self,
19250 _: &OpenContextMenu,
19251 window: &mut Window,
19252 cx: &mut Context<Self>,
19253 ) {
19254 self.request_autoscroll(Autoscroll::newest(), cx);
19255 let position = self.selections.newest_display(cx).start;
19256 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
19257 }
19258
19259 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
19260 &self.inlay_hint_cache
19261 }
19262
19263 pub fn replay_insert_event(
19264 &mut self,
19265 text: &str,
19266 relative_utf16_range: Option<Range<isize>>,
19267 window: &mut Window,
19268 cx: &mut Context<Self>,
19269 ) {
19270 if !self.input_enabled {
19271 cx.emit(EditorEvent::InputIgnored { text: text.into() });
19272 return;
19273 }
19274 if let Some(relative_utf16_range) = relative_utf16_range {
19275 let selections = self.selections.all::<OffsetUtf16>(cx);
19276 self.change_selections(None, window, cx, |s| {
19277 let new_ranges = selections.into_iter().map(|range| {
19278 let start = OffsetUtf16(
19279 range
19280 .head()
19281 .0
19282 .saturating_add_signed(relative_utf16_range.start),
19283 );
19284 let end = OffsetUtf16(
19285 range
19286 .head()
19287 .0
19288 .saturating_add_signed(relative_utf16_range.end),
19289 );
19290 start..end
19291 });
19292 s.select_ranges(new_ranges);
19293 });
19294 }
19295
19296 self.handle_input(text, window, cx);
19297 }
19298
19299 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
19300 let Some(provider) = self.semantics_provider.as_ref() else {
19301 return false;
19302 };
19303
19304 let mut supports = false;
19305 self.buffer().update(cx, |this, cx| {
19306 this.for_each_buffer(|buffer| {
19307 supports |= provider.supports_inlay_hints(buffer, cx);
19308 });
19309 });
19310
19311 supports
19312 }
19313
19314 pub fn is_focused(&self, window: &Window) -> bool {
19315 self.focus_handle.is_focused(window)
19316 }
19317
19318 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19319 cx.emit(EditorEvent::Focused);
19320
19321 if let Some(descendant) = self
19322 .last_focused_descendant
19323 .take()
19324 .and_then(|descendant| descendant.upgrade())
19325 {
19326 window.focus(&descendant);
19327 } else {
19328 if let Some(blame) = self.blame.as_ref() {
19329 blame.update(cx, GitBlame::focus)
19330 }
19331
19332 self.blink_manager.update(cx, BlinkManager::enable);
19333 self.show_cursor_names(window, cx);
19334 self.buffer.update(cx, |buffer, cx| {
19335 buffer.finalize_last_transaction(cx);
19336 if self.leader_id.is_none() {
19337 buffer.set_active_selections(
19338 &self.selections.disjoint_anchors(),
19339 self.selections.line_mode,
19340 self.cursor_shape,
19341 cx,
19342 );
19343 }
19344 });
19345 }
19346 }
19347
19348 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
19349 cx.emit(EditorEvent::FocusedIn)
19350 }
19351
19352 fn handle_focus_out(
19353 &mut self,
19354 event: FocusOutEvent,
19355 _window: &mut Window,
19356 cx: &mut Context<Self>,
19357 ) {
19358 if event.blurred != self.focus_handle {
19359 self.last_focused_descendant = Some(event.blurred);
19360 }
19361 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
19362 }
19363
19364 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19365 self.blink_manager.update(cx, BlinkManager::disable);
19366 self.buffer
19367 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
19368
19369 if let Some(blame) = self.blame.as_ref() {
19370 blame.update(cx, GitBlame::blur)
19371 }
19372 if !self.hover_state.focused(window, cx) {
19373 hide_hover(self, cx);
19374 }
19375 if !self
19376 .context_menu
19377 .borrow()
19378 .as_ref()
19379 .is_some_and(|context_menu| context_menu.focused(window, cx))
19380 {
19381 self.hide_context_menu(window, cx);
19382 }
19383 self.discard_inline_completion(false, cx);
19384 cx.emit(EditorEvent::Blurred);
19385 cx.notify();
19386 }
19387
19388 pub fn register_action<A: Action>(
19389 &mut self,
19390 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
19391 ) -> Subscription {
19392 let id = self.next_editor_action_id.post_inc();
19393 let listener = Arc::new(listener);
19394 self.editor_actions.borrow_mut().insert(
19395 id,
19396 Box::new(move |window, _| {
19397 let listener = listener.clone();
19398 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
19399 let action = action.downcast_ref().unwrap();
19400 if phase == DispatchPhase::Bubble {
19401 listener(action, window, cx)
19402 }
19403 })
19404 }),
19405 );
19406
19407 let editor_actions = self.editor_actions.clone();
19408 Subscription::new(move || {
19409 editor_actions.borrow_mut().remove(&id);
19410 })
19411 }
19412
19413 pub fn file_header_size(&self) -> u32 {
19414 FILE_HEADER_HEIGHT
19415 }
19416
19417 pub fn restore(
19418 &mut self,
19419 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
19420 window: &mut Window,
19421 cx: &mut Context<Self>,
19422 ) {
19423 let workspace = self.workspace();
19424 let project = self.project.as_ref();
19425 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
19426 let mut tasks = Vec::new();
19427 for (buffer_id, changes) in revert_changes {
19428 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
19429 buffer.update(cx, |buffer, cx| {
19430 buffer.edit(
19431 changes
19432 .into_iter()
19433 .map(|(range, text)| (range, text.to_string())),
19434 None,
19435 cx,
19436 );
19437 });
19438
19439 if let Some(project) =
19440 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
19441 {
19442 project.update(cx, |project, cx| {
19443 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
19444 })
19445 }
19446 }
19447 }
19448 tasks
19449 });
19450 cx.spawn_in(window, async move |_, cx| {
19451 for (buffer, task) in save_tasks {
19452 let result = task.await;
19453 if result.is_err() {
19454 let Some(path) = buffer
19455 .read_with(cx, |buffer, cx| buffer.project_path(cx))
19456 .ok()
19457 else {
19458 continue;
19459 };
19460 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
19461 let Some(task) = cx
19462 .update_window_entity(&workspace, |workspace, window, cx| {
19463 workspace
19464 .open_path_preview(path, None, false, false, false, window, cx)
19465 })
19466 .ok()
19467 else {
19468 continue;
19469 };
19470 task.await.log_err();
19471 }
19472 }
19473 }
19474 })
19475 .detach();
19476 self.change_selections(None, window, cx, |selections| selections.refresh());
19477 }
19478
19479 pub fn to_pixel_point(
19480 &self,
19481 source: multi_buffer::Anchor,
19482 editor_snapshot: &EditorSnapshot,
19483 window: &mut Window,
19484 ) -> Option<gpui::Point<Pixels>> {
19485 let source_point = source.to_display_point(editor_snapshot);
19486 self.display_to_pixel_point(source_point, editor_snapshot, window)
19487 }
19488
19489 pub fn display_to_pixel_point(
19490 &self,
19491 source: DisplayPoint,
19492 editor_snapshot: &EditorSnapshot,
19493 window: &mut Window,
19494 ) -> Option<gpui::Point<Pixels>> {
19495 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
19496 let text_layout_details = self.text_layout_details(window);
19497 let scroll_top = text_layout_details
19498 .scroll_anchor
19499 .scroll_position(editor_snapshot)
19500 .y;
19501
19502 if source.row().as_f32() < scroll_top.floor() {
19503 return None;
19504 }
19505 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
19506 let source_y = line_height * (source.row().as_f32() - scroll_top);
19507 Some(gpui::Point::new(source_x, source_y))
19508 }
19509
19510 pub fn has_visible_completions_menu(&self) -> bool {
19511 !self.edit_prediction_preview_is_active()
19512 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
19513 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
19514 })
19515 }
19516
19517 pub fn register_addon<T: Addon>(&mut self, instance: T) {
19518 if self.mode.is_minimap() {
19519 return;
19520 }
19521 self.addons
19522 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
19523 }
19524
19525 pub fn unregister_addon<T: Addon>(&mut self) {
19526 self.addons.remove(&std::any::TypeId::of::<T>());
19527 }
19528
19529 pub fn addon<T: Addon>(&self) -> Option<&T> {
19530 let type_id = std::any::TypeId::of::<T>();
19531 self.addons
19532 .get(&type_id)
19533 .and_then(|item| item.to_any().downcast_ref::<T>())
19534 }
19535
19536 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
19537 let type_id = std::any::TypeId::of::<T>();
19538 self.addons
19539 .get_mut(&type_id)
19540 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
19541 }
19542
19543 fn character_size(&self, window: &mut Window) -> gpui::Size<Pixels> {
19544 let text_layout_details = self.text_layout_details(window);
19545 let style = &text_layout_details.editor_style;
19546 let font_id = window.text_system().resolve_font(&style.text.font());
19547 let font_size = style.text.font_size.to_pixels(window.rem_size());
19548 let line_height = style.text.line_height_in_pixels(window.rem_size());
19549 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
19550
19551 gpui::Size::new(em_width, line_height)
19552 }
19553
19554 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
19555 self.load_diff_task.clone()
19556 }
19557
19558 fn read_metadata_from_db(
19559 &mut self,
19560 item_id: u64,
19561 workspace_id: WorkspaceId,
19562 window: &mut Window,
19563 cx: &mut Context<Editor>,
19564 ) {
19565 if self.is_singleton(cx)
19566 && !self.mode.is_minimap()
19567 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
19568 {
19569 let buffer_snapshot = OnceCell::new();
19570
19571 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
19572 if !folds.is_empty() {
19573 let snapshot =
19574 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
19575 self.fold_ranges(
19576 folds
19577 .into_iter()
19578 .map(|(start, end)| {
19579 snapshot.clip_offset(start, Bias::Left)
19580 ..snapshot.clip_offset(end, Bias::Right)
19581 })
19582 .collect(),
19583 false,
19584 window,
19585 cx,
19586 );
19587 }
19588 }
19589
19590 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
19591 if !selections.is_empty() {
19592 let snapshot =
19593 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
19594 self.change_selections(None, window, cx, |s| {
19595 s.select_ranges(selections.into_iter().map(|(start, end)| {
19596 snapshot.clip_offset(start, Bias::Left)
19597 ..snapshot.clip_offset(end, Bias::Right)
19598 }));
19599 });
19600 }
19601 };
19602 }
19603
19604 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
19605 }
19606}
19607
19608fn vim_enabled(cx: &App) -> bool {
19609 cx.global::<SettingsStore>()
19610 .raw_user_settings()
19611 .get("vim_mode")
19612 == Some(&serde_json::Value::Bool(true))
19613}
19614
19615fn process_completion_for_edit(
19616 completion: &Completion,
19617 intent: CompletionIntent,
19618 buffer: &Entity<Buffer>,
19619 cursor_position: &text::Anchor,
19620 cx: &mut Context<Editor>,
19621) -> CompletionEdit {
19622 let buffer = buffer.read(cx);
19623 let buffer_snapshot = buffer.snapshot();
19624 let (snippet, new_text) = if completion.is_snippet() {
19625 let mut snippet_source = completion.new_text.clone();
19626 if let Some(scope) = buffer_snapshot.language_scope_at(cursor_position) {
19627 if scope.prefers_label_for_snippet_in_completion() {
19628 if let Some(label) = completion.label() {
19629 if matches!(
19630 completion.kind(),
19631 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
19632 ) {
19633 snippet_source = label;
19634 }
19635 }
19636 }
19637 }
19638 match Snippet::parse(&snippet_source).log_err() {
19639 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
19640 None => (None, completion.new_text.clone()),
19641 }
19642 } else {
19643 (None, completion.new_text.clone())
19644 };
19645
19646 let mut range_to_replace = {
19647 let replace_range = &completion.replace_range;
19648 if let CompletionSource::Lsp {
19649 insert_range: Some(insert_range),
19650 ..
19651 } = &completion.source
19652 {
19653 debug_assert_eq!(
19654 insert_range.start, replace_range.start,
19655 "insert_range and replace_range should start at the same position"
19656 );
19657 debug_assert!(
19658 insert_range
19659 .start
19660 .cmp(&cursor_position, &buffer_snapshot)
19661 .is_le(),
19662 "insert_range should start before or at cursor position"
19663 );
19664 debug_assert!(
19665 replace_range
19666 .start
19667 .cmp(&cursor_position, &buffer_snapshot)
19668 .is_le(),
19669 "replace_range should start before or at cursor position"
19670 );
19671 debug_assert!(
19672 insert_range
19673 .end
19674 .cmp(&cursor_position, &buffer_snapshot)
19675 .is_le(),
19676 "insert_range should end before or at cursor position"
19677 );
19678
19679 let should_replace = match intent {
19680 CompletionIntent::CompleteWithInsert => false,
19681 CompletionIntent::CompleteWithReplace => true,
19682 CompletionIntent::Complete | CompletionIntent::Compose => {
19683 let insert_mode =
19684 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
19685 .completions
19686 .lsp_insert_mode;
19687 match insert_mode {
19688 LspInsertMode::Insert => false,
19689 LspInsertMode::Replace => true,
19690 LspInsertMode::ReplaceSubsequence => {
19691 let mut text_to_replace = buffer.chars_for_range(
19692 buffer.anchor_before(replace_range.start)
19693 ..buffer.anchor_after(replace_range.end),
19694 );
19695 let mut current_needle = text_to_replace.next();
19696 for haystack_ch in completion.label.text.chars() {
19697 if let Some(needle_ch) = current_needle {
19698 if haystack_ch.eq_ignore_ascii_case(&needle_ch) {
19699 current_needle = text_to_replace.next();
19700 }
19701 }
19702 }
19703 current_needle.is_none()
19704 }
19705 LspInsertMode::ReplaceSuffix => {
19706 if replace_range
19707 .end
19708 .cmp(&cursor_position, &buffer_snapshot)
19709 .is_gt()
19710 {
19711 let range_after_cursor = *cursor_position..replace_range.end;
19712 let text_after_cursor = buffer
19713 .text_for_range(
19714 buffer.anchor_before(range_after_cursor.start)
19715 ..buffer.anchor_after(range_after_cursor.end),
19716 )
19717 .collect::<String>()
19718 .to_ascii_lowercase();
19719 completion
19720 .label
19721 .text
19722 .to_ascii_lowercase()
19723 .ends_with(&text_after_cursor)
19724 } else {
19725 true
19726 }
19727 }
19728 }
19729 }
19730 };
19731
19732 if should_replace {
19733 replace_range.clone()
19734 } else {
19735 insert_range.clone()
19736 }
19737 } else {
19738 replace_range.clone()
19739 }
19740 };
19741
19742 if range_to_replace
19743 .end
19744 .cmp(&cursor_position, &buffer_snapshot)
19745 .is_lt()
19746 {
19747 range_to_replace.end = *cursor_position;
19748 }
19749
19750 CompletionEdit {
19751 new_text,
19752 replace_range: range_to_replace.to_offset(&buffer),
19753 snippet,
19754 }
19755}
19756
19757struct CompletionEdit {
19758 new_text: String,
19759 replace_range: Range<usize>,
19760 snippet: Option<Snippet>,
19761}
19762
19763fn insert_extra_newline_brackets(
19764 buffer: &MultiBufferSnapshot,
19765 range: Range<usize>,
19766 language: &language::LanguageScope,
19767) -> bool {
19768 let leading_whitespace_len = buffer
19769 .reversed_chars_at(range.start)
19770 .take_while(|c| c.is_whitespace() && *c != '\n')
19771 .map(|c| c.len_utf8())
19772 .sum::<usize>();
19773 let trailing_whitespace_len = buffer
19774 .chars_at(range.end)
19775 .take_while(|c| c.is_whitespace() && *c != '\n')
19776 .map(|c| c.len_utf8())
19777 .sum::<usize>();
19778 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
19779
19780 language.brackets().any(|(pair, enabled)| {
19781 let pair_start = pair.start.trim_end();
19782 let pair_end = pair.end.trim_start();
19783
19784 enabled
19785 && pair.newline
19786 && buffer.contains_str_at(range.end, pair_end)
19787 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
19788 })
19789}
19790
19791fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
19792 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
19793 [(buffer, range, _)] => (*buffer, range.clone()),
19794 _ => return false,
19795 };
19796 let pair = {
19797 let mut result: Option<BracketMatch> = None;
19798
19799 for pair in buffer
19800 .all_bracket_ranges(range.clone())
19801 .filter(move |pair| {
19802 pair.open_range.start <= range.start && pair.close_range.end >= range.end
19803 })
19804 {
19805 let len = pair.close_range.end - pair.open_range.start;
19806
19807 if let Some(existing) = &result {
19808 let existing_len = existing.close_range.end - existing.open_range.start;
19809 if len > existing_len {
19810 continue;
19811 }
19812 }
19813
19814 result = Some(pair);
19815 }
19816
19817 result
19818 };
19819 let Some(pair) = pair else {
19820 return false;
19821 };
19822 pair.newline_only
19823 && buffer
19824 .chars_for_range(pair.open_range.end..range.start)
19825 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
19826 .all(|c| c.is_whitespace() && c != '\n')
19827}
19828
19829fn update_uncommitted_diff_for_buffer(
19830 editor: Entity<Editor>,
19831 project: &Entity<Project>,
19832 buffers: impl IntoIterator<Item = Entity<Buffer>>,
19833 buffer: Entity<MultiBuffer>,
19834 cx: &mut App,
19835) -> Task<()> {
19836 let mut tasks = Vec::new();
19837 project.update(cx, |project, cx| {
19838 for buffer in buffers {
19839 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
19840 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
19841 }
19842 }
19843 });
19844 cx.spawn(async move |cx| {
19845 let diffs = future::join_all(tasks).await;
19846 if editor
19847 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
19848 .unwrap_or(false)
19849 {
19850 return;
19851 }
19852
19853 buffer
19854 .update(cx, |buffer, cx| {
19855 for diff in diffs.into_iter().flatten() {
19856 buffer.add_diff(diff, cx);
19857 }
19858 })
19859 .ok();
19860 })
19861}
19862
19863fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
19864 let tab_size = tab_size.get() as usize;
19865 let mut width = offset;
19866
19867 for ch in text.chars() {
19868 width += if ch == '\t' {
19869 tab_size - (width % tab_size)
19870 } else {
19871 1
19872 };
19873 }
19874
19875 width - offset
19876}
19877
19878#[cfg(test)]
19879mod tests {
19880 use super::*;
19881
19882 #[test]
19883 fn test_string_size_with_expanded_tabs() {
19884 let nz = |val| NonZeroU32::new(val).unwrap();
19885 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
19886 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
19887 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
19888 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
19889 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
19890 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
19891 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
19892 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
19893 }
19894}
19895
19896/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
19897struct WordBreakingTokenizer<'a> {
19898 input: &'a str,
19899}
19900
19901impl<'a> WordBreakingTokenizer<'a> {
19902 fn new(input: &'a str) -> Self {
19903 Self { input }
19904 }
19905}
19906
19907fn is_char_ideographic(ch: char) -> bool {
19908 use unicode_script::Script::*;
19909 use unicode_script::UnicodeScript;
19910 matches!(ch.script(), Han | Tangut | Yi)
19911}
19912
19913fn is_grapheme_ideographic(text: &str) -> bool {
19914 text.chars().any(is_char_ideographic)
19915}
19916
19917fn is_grapheme_whitespace(text: &str) -> bool {
19918 text.chars().any(|x| x.is_whitespace())
19919}
19920
19921fn should_stay_with_preceding_ideograph(text: &str) -> bool {
19922 text.chars().next().map_or(false, |ch| {
19923 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
19924 })
19925}
19926
19927#[derive(PartialEq, Eq, Debug, Clone, Copy)]
19928enum WordBreakToken<'a> {
19929 Word { token: &'a str, grapheme_len: usize },
19930 InlineWhitespace { token: &'a str, grapheme_len: usize },
19931 Newline,
19932}
19933
19934impl<'a> Iterator for WordBreakingTokenizer<'a> {
19935 /// Yields a span, the count of graphemes in the token, and whether it was
19936 /// whitespace. Note that it also breaks at word boundaries.
19937 type Item = WordBreakToken<'a>;
19938
19939 fn next(&mut self) -> Option<Self::Item> {
19940 use unicode_segmentation::UnicodeSegmentation;
19941 if self.input.is_empty() {
19942 return None;
19943 }
19944
19945 let mut iter = self.input.graphemes(true).peekable();
19946 let mut offset = 0;
19947 let mut grapheme_len = 0;
19948 if let Some(first_grapheme) = iter.next() {
19949 let is_newline = first_grapheme == "\n";
19950 let is_whitespace = is_grapheme_whitespace(first_grapheme);
19951 offset += first_grapheme.len();
19952 grapheme_len += 1;
19953 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
19954 if let Some(grapheme) = iter.peek().copied() {
19955 if should_stay_with_preceding_ideograph(grapheme) {
19956 offset += grapheme.len();
19957 grapheme_len += 1;
19958 }
19959 }
19960 } else {
19961 let mut words = self.input[offset..].split_word_bound_indices().peekable();
19962 let mut next_word_bound = words.peek().copied();
19963 if next_word_bound.map_or(false, |(i, _)| i == 0) {
19964 next_word_bound = words.next();
19965 }
19966 while let Some(grapheme) = iter.peek().copied() {
19967 if next_word_bound.map_or(false, |(i, _)| i == offset) {
19968 break;
19969 };
19970 if is_grapheme_whitespace(grapheme) != is_whitespace
19971 || (grapheme == "\n") != is_newline
19972 {
19973 break;
19974 };
19975 offset += grapheme.len();
19976 grapheme_len += 1;
19977 iter.next();
19978 }
19979 }
19980 let token = &self.input[..offset];
19981 self.input = &self.input[offset..];
19982 if token == "\n" {
19983 Some(WordBreakToken::Newline)
19984 } else if is_whitespace {
19985 Some(WordBreakToken::InlineWhitespace {
19986 token,
19987 grapheme_len,
19988 })
19989 } else {
19990 Some(WordBreakToken::Word {
19991 token,
19992 grapheme_len,
19993 })
19994 }
19995 } else {
19996 None
19997 }
19998 }
19999}
20000
20001#[test]
20002fn test_word_breaking_tokenizer() {
20003 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
20004 ("", &[]),
20005 (" ", &[whitespace(" ", 2)]),
20006 ("Ʒ", &[word("Ʒ", 1)]),
20007 ("Ǽ", &[word("Ǽ", 1)]),
20008 ("⋑", &[word("⋑", 1)]),
20009 ("⋑⋑", &[word("⋑⋑", 2)]),
20010 (
20011 "原理,进而",
20012 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
20013 ),
20014 (
20015 "hello world",
20016 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
20017 ),
20018 (
20019 "hello, world",
20020 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
20021 ),
20022 (
20023 " hello world",
20024 &[
20025 whitespace(" ", 2),
20026 word("hello", 5),
20027 whitespace(" ", 1),
20028 word("world", 5),
20029 ],
20030 ),
20031 (
20032 "这是什么 \n 钢笔",
20033 &[
20034 word("这", 1),
20035 word("是", 1),
20036 word("什", 1),
20037 word("么", 1),
20038 whitespace(" ", 1),
20039 newline(),
20040 whitespace(" ", 1),
20041 word("钢", 1),
20042 word("笔", 1),
20043 ],
20044 ),
20045 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
20046 ];
20047
20048 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
20049 WordBreakToken::Word {
20050 token,
20051 grapheme_len,
20052 }
20053 }
20054
20055 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
20056 WordBreakToken::InlineWhitespace {
20057 token,
20058 grapheme_len,
20059 }
20060 }
20061
20062 fn newline() -> WordBreakToken<'static> {
20063 WordBreakToken::Newline
20064 }
20065
20066 for (input, result) in tests {
20067 assert_eq!(
20068 WordBreakingTokenizer::new(input)
20069 .collect::<Vec<_>>()
20070 .as_slice(),
20071 *result,
20072 );
20073 }
20074}
20075
20076fn wrap_with_prefix(
20077 line_prefix: String,
20078 unwrapped_text: String,
20079 wrap_column: usize,
20080 tab_size: NonZeroU32,
20081 preserve_existing_whitespace: bool,
20082) -> String {
20083 let line_prefix_len = char_len_with_expanded_tabs(0, &line_prefix, tab_size);
20084 let mut wrapped_text = String::new();
20085 let mut current_line = line_prefix.clone();
20086
20087 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
20088 let mut current_line_len = line_prefix_len;
20089 let mut in_whitespace = false;
20090 for token in tokenizer {
20091 let have_preceding_whitespace = in_whitespace;
20092 match token {
20093 WordBreakToken::Word {
20094 token,
20095 grapheme_len,
20096 } => {
20097 in_whitespace = false;
20098 if current_line_len + grapheme_len > wrap_column
20099 && current_line_len != line_prefix_len
20100 {
20101 wrapped_text.push_str(current_line.trim_end());
20102 wrapped_text.push('\n');
20103 current_line.truncate(line_prefix.len());
20104 current_line_len = line_prefix_len;
20105 }
20106 current_line.push_str(token);
20107 current_line_len += grapheme_len;
20108 }
20109 WordBreakToken::InlineWhitespace {
20110 mut token,
20111 mut grapheme_len,
20112 } => {
20113 in_whitespace = true;
20114 if have_preceding_whitespace && !preserve_existing_whitespace {
20115 continue;
20116 }
20117 if !preserve_existing_whitespace {
20118 token = " ";
20119 grapheme_len = 1;
20120 }
20121 if current_line_len + grapheme_len > wrap_column {
20122 wrapped_text.push_str(current_line.trim_end());
20123 wrapped_text.push('\n');
20124 current_line.truncate(line_prefix.len());
20125 current_line_len = line_prefix_len;
20126 } else if current_line_len != line_prefix_len || preserve_existing_whitespace {
20127 current_line.push_str(token);
20128 current_line_len += grapheme_len;
20129 }
20130 }
20131 WordBreakToken::Newline => {
20132 in_whitespace = true;
20133 if preserve_existing_whitespace {
20134 wrapped_text.push_str(current_line.trim_end());
20135 wrapped_text.push('\n');
20136 current_line.truncate(line_prefix.len());
20137 current_line_len = line_prefix_len;
20138 } else if have_preceding_whitespace {
20139 continue;
20140 } else if current_line_len + 1 > wrap_column && current_line_len != line_prefix_len
20141 {
20142 wrapped_text.push_str(current_line.trim_end());
20143 wrapped_text.push('\n');
20144 current_line.truncate(line_prefix.len());
20145 current_line_len = line_prefix_len;
20146 } else if current_line_len != line_prefix_len {
20147 current_line.push(' ');
20148 current_line_len += 1;
20149 }
20150 }
20151 }
20152 }
20153
20154 if !current_line.is_empty() {
20155 wrapped_text.push_str(¤t_line);
20156 }
20157 wrapped_text
20158}
20159
20160#[test]
20161fn test_wrap_with_prefix() {
20162 assert_eq!(
20163 wrap_with_prefix(
20164 "# ".to_string(),
20165 "abcdefg".to_string(),
20166 4,
20167 NonZeroU32::new(4).unwrap(),
20168 false,
20169 ),
20170 "# abcdefg"
20171 );
20172 assert_eq!(
20173 wrap_with_prefix(
20174 "".to_string(),
20175 "\thello world".to_string(),
20176 8,
20177 NonZeroU32::new(4).unwrap(),
20178 false,
20179 ),
20180 "hello\nworld"
20181 );
20182 assert_eq!(
20183 wrap_with_prefix(
20184 "// ".to_string(),
20185 "xx \nyy zz aa bb cc".to_string(),
20186 12,
20187 NonZeroU32::new(4).unwrap(),
20188 false,
20189 ),
20190 "// xx yy zz\n// aa bb cc"
20191 );
20192 assert_eq!(
20193 wrap_with_prefix(
20194 String::new(),
20195 "这是什么 \n 钢笔".to_string(),
20196 3,
20197 NonZeroU32::new(4).unwrap(),
20198 false,
20199 ),
20200 "这是什\n么 钢\n笔"
20201 );
20202}
20203
20204pub trait CollaborationHub {
20205 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
20206 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
20207 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
20208}
20209
20210impl CollaborationHub for Entity<Project> {
20211 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
20212 self.read(cx).collaborators()
20213 }
20214
20215 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
20216 self.read(cx).user_store().read(cx).participant_indices()
20217 }
20218
20219 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
20220 let this = self.read(cx);
20221 let user_ids = this.collaborators().values().map(|c| c.user_id);
20222 this.user_store().read(cx).participant_names(user_ids, cx)
20223 }
20224}
20225
20226pub trait SemanticsProvider {
20227 fn hover(
20228 &self,
20229 buffer: &Entity<Buffer>,
20230 position: text::Anchor,
20231 cx: &mut App,
20232 ) -> Option<Task<Vec<project::Hover>>>;
20233
20234 fn inline_values(
20235 &self,
20236 buffer_handle: Entity<Buffer>,
20237 range: Range<text::Anchor>,
20238 cx: &mut App,
20239 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
20240
20241 fn inlay_hints(
20242 &self,
20243 buffer_handle: Entity<Buffer>,
20244 range: Range<text::Anchor>,
20245 cx: &mut App,
20246 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
20247
20248 fn resolve_inlay_hint(
20249 &self,
20250 hint: InlayHint,
20251 buffer_handle: Entity<Buffer>,
20252 server_id: LanguageServerId,
20253 cx: &mut App,
20254 ) -> Option<Task<anyhow::Result<InlayHint>>>;
20255
20256 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
20257
20258 fn document_highlights(
20259 &self,
20260 buffer: &Entity<Buffer>,
20261 position: text::Anchor,
20262 cx: &mut App,
20263 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
20264
20265 fn definitions(
20266 &self,
20267 buffer: &Entity<Buffer>,
20268 position: text::Anchor,
20269 kind: GotoDefinitionKind,
20270 cx: &mut App,
20271 ) -> Option<Task<Result<Vec<LocationLink>>>>;
20272
20273 fn range_for_rename(
20274 &self,
20275 buffer: &Entity<Buffer>,
20276 position: text::Anchor,
20277 cx: &mut App,
20278 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
20279
20280 fn perform_rename(
20281 &self,
20282 buffer: &Entity<Buffer>,
20283 position: text::Anchor,
20284 new_name: String,
20285 cx: &mut App,
20286 ) -> Option<Task<Result<ProjectTransaction>>>;
20287}
20288
20289pub trait CompletionProvider {
20290 fn completions(
20291 &self,
20292 excerpt_id: ExcerptId,
20293 buffer: &Entity<Buffer>,
20294 buffer_position: text::Anchor,
20295 trigger: CompletionContext,
20296 window: &mut Window,
20297 cx: &mut Context<Editor>,
20298 ) -> Task<Result<Vec<CompletionResponse>>>;
20299
20300 fn resolve_completions(
20301 &self,
20302 buffer: Entity<Buffer>,
20303 completion_indices: Vec<usize>,
20304 completions: Rc<RefCell<Box<[Completion]>>>,
20305 cx: &mut Context<Editor>,
20306 ) -> Task<Result<bool>>;
20307
20308 fn apply_additional_edits_for_completion(
20309 &self,
20310 _buffer: Entity<Buffer>,
20311 _completions: Rc<RefCell<Box<[Completion]>>>,
20312 _completion_index: usize,
20313 _push_to_history: bool,
20314 _cx: &mut Context<Editor>,
20315 ) -> Task<Result<Option<language::Transaction>>> {
20316 Task::ready(Ok(None))
20317 }
20318
20319 fn is_completion_trigger(
20320 &self,
20321 buffer: &Entity<Buffer>,
20322 position: language::Anchor,
20323 text: &str,
20324 trigger_in_words: bool,
20325 menu_is_open: bool,
20326 cx: &mut Context<Editor>,
20327 ) -> bool;
20328
20329 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
20330
20331 fn sort_completions(&self) -> bool {
20332 true
20333 }
20334
20335 fn filter_completions(&self) -> bool {
20336 true
20337 }
20338}
20339
20340pub trait CodeActionProvider {
20341 fn id(&self) -> Arc<str>;
20342
20343 fn code_actions(
20344 &self,
20345 buffer: &Entity<Buffer>,
20346 range: Range<text::Anchor>,
20347 window: &mut Window,
20348 cx: &mut App,
20349 ) -> Task<Result<Vec<CodeAction>>>;
20350
20351 fn apply_code_action(
20352 &self,
20353 buffer_handle: Entity<Buffer>,
20354 action: CodeAction,
20355 excerpt_id: ExcerptId,
20356 push_to_history: bool,
20357 window: &mut Window,
20358 cx: &mut App,
20359 ) -> Task<Result<ProjectTransaction>>;
20360}
20361
20362impl CodeActionProvider for Entity<Project> {
20363 fn id(&self) -> Arc<str> {
20364 "project".into()
20365 }
20366
20367 fn code_actions(
20368 &self,
20369 buffer: &Entity<Buffer>,
20370 range: Range<text::Anchor>,
20371 _window: &mut Window,
20372 cx: &mut App,
20373 ) -> Task<Result<Vec<CodeAction>>> {
20374 self.update(cx, |project, cx| {
20375 let code_lens = project.code_lens(buffer, range.clone(), cx);
20376 let code_actions = project.code_actions(buffer, range, None, cx);
20377 cx.background_spawn(async move {
20378 let (code_lens, code_actions) = join(code_lens, code_actions).await;
20379 Ok(code_lens
20380 .context("code lens fetch")?
20381 .into_iter()
20382 .chain(code_actions.context("code action fetch")?)
20383 .collect())
20384 })
20385 })
20386 }
20387
20388 fn apply_code_action(
20389 &self,
20390 buffer_handle: Entity<Buffer>,
20391 action: CodeAction,
20392 _excerpt_id: ExcerptId,
20393 push_to_history: bool,
20394 _window: &mut Window,
20395 cx: &mut App,
20396 ) -> Task<Result<ProjectTransaction>> {
20397 self.update(cx, |project, cx| {
20398 project.apply_code_action(buffer_handle, action, push_to_history, cx)
20399 })
20400 }
20401}
20402
20403fn snippet_completions(
20404 project: &Project,
20405 buffer: &Entity<Buffer>,
20406 buffer_position: text::Anchor,
20407 cx: &mut App,
20408) -> Task<Result<CompletionResponse>> {
20409 let languages = buffer.read(cx).languages_at(buffer_position);
20410 let snippet_store = project.snippets().read(cx);
20411
20412 let scopes: Vec<_> = languages
20413 .iter()
20414 .filter_map(|language| {
20415 let language_name = language.lsp_id();
20416 let snippets = snippet_store.snippets_for(Some(language_name), cx);
20417
20418 if snippets.is_empty() {
20419 None
20420 } else {
20421 Some((language.default_scope(), snippets))
20422 }
20423 })
20424 .collect();
20425
20426 if scopes.is_empty() {
20427 return Task::ready(Ok(CompletionResponse {
20428 completions: vec![],
20429 is_incomplete: false,
20430 }));
20431 }
20432
20433 let snapshot = buffer.read(cx).text_snapshot();
20434 let chars: String = snapshot
20435 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
20436 .collect();
20437 let executor = cx.background_executor().clone();
20438
20439 cx.background_spawn(async move {
20440 let mut is_incomplete = false;
20441 let mut completions: Vec<Completion> = Vec::new();
20442 for (scope, snippets) in scopes.into_iter() {
20443 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
20444 let mut last_word = chars
20445 .chars()
20446 .take_while(|c| classifier.is_word(*c))
20447 .collect::<String>();
20448 last_word = last_word.chars().rev().collect();
20449
20450 if last_word.is_empty() {
20451 return Ok(CompletionResponse {
20452 completions: vec![],
20453 is_incomplete: true,
20454 });
20455 }
20456
20457 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
20458 let to_lsp = |point: &text::Anchor| {
20459 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
20460 point_to_lsp(end)
20461 };
20462 let lsp_end = to_lsp(&buffer_position);
20463
20464 let candidates = snippets
20465 .iter()
20466 .enumerate()
20467 .flat_map(|(ix, snippet)| {
20468 snippet
20469 .prefix
20470 .iter()
20471 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
20472 })
20473 .collect::<Vec<StringMatchCandidate>>();
20474
20475 const MAX_RESULTS: usize = 100;
20476 let mut matches = fuzzy::match_strings(
20477 &candidates,
20478 &last_word,
20479 last_word.chars().any(|c| c.is_uppercase()),
20480 MAX_RESULTS,
20481 &Default::default(),
20482 executor.clone(),
20483 )
20484 .await;
20485
20486 if matches.len() >= MAX_RESULTS {
20487 is_incomplete = true;
20488 }
20489
20490 // Remove all candidates where the query's start does not match the start of any word in the candidate
20491 if let Some(query_start) = last_word.chars().next() {
20492 matches.retain(|string_match| {
20493 split_words(&string_match.string).any(|word| {
20494 // Check that the first codepoint of the word as lowercase matches the first
20495 // codepoint of the query as lowercase
20496 word.chars()
20497 .flat_map(|codepoint| codepoint.to_lowercase())
20498 .zip(query_start.to_lowercase())
20499 .all(|(word_cp, query_cp)| word_cp == query_cp)
20500 })
20501 });
20502 }
20503
20504 let matched_strings = matches
20505 .into_iter()
20506 .map(|m| m.string)
20507 .collect::<HashSet<_>>();
20508
20509 completions.extend(snippets.iter().filter_map(|snippet| {
20510 let matching_prefix = snippet
20511 .prefix
20512 .iter()
20513 .find(|prefix| matched_strings.contains(*prefix))?;
20514 let start = as_offset - last_word.len();
20515 let start = snapshot.anchor_before(start);
20516 let range = start..buffer_position;
20517 let lsp_start = to_lsp(&start);
20518 let lsp_range = lsp::Range {
20519 start: lsp_start,
20520 end: lsp_end,
20521 };
20522 Some(Completion {
20523 replace_range: range,
20524 new_text: snippet.body.clone(),
20525 source: CompletionSource::Lsp {
20526 insert_range: None,
20527 server_id: LanguageServerId(usize::MAX),
20528 resolved: true,
20529 lsp_completion: Box::new(lsp::CompletionItem {
20530 label: snippet.prefix.first().unwrap().clone(),
20531 kind: Some(CompletionItemKind::SNIPPET),
20532 label_details: snippet.description.as_ref().map(|description| {
20533 lsp::CompletionItemLabelDetails {
20534 detail: Some(description.clone()),
20535 description: None,
20536 }
20537 }),
20538 insert_text_format: Some(InsertTextFormat::SNIPPET),
20539 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
20540 lsp::InsertReplaceEdit {
20541 new_text: snippet.body.clone(),
20542 insert: lsp_range,
20543 replace: lsp_range,
20544 },
20545 )),
20546 filter_text: Some(snippet.body.clone()),
20547 sort_text: Some(char::MAX.to_string()),
20548 ..lsp::CompletionItem::default()
20549 }),
20550 lsp_defaults: None,
20551 },
20552 label: CodeLabel {
20553 text: matching_prefix.clone(),
20554 runs: Vec::new(),
20555 filter_range: 0..matching_prefix.len(),
20556 },
20557 icon_path: None,
20558 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
20559 single_line: snippet.name.clone().into(),
20560 plain_text: snippet
20561 .description
20562 .clone()
20563 .map(|description| description.into()),
20564 }),
20565 insert_text_mode: None,
20566 confirm: None,
20567 })
20568 }))
20569 }
20570
20571 Ok(CompletionResponse {
20572 completions,
20573 is_incomplete,
20574 })
20575 })
20576}
20577
20578impl CompletionProvider for Entity<Project> {
20579 fn completions(
20580 &self,
20581 _excerpt_id: ExcerptId,
20582 buffer: &Entity<Buffer>,
20583 buffer_position: text::Anchor,
20584 options: CompletionContext,
20585 _window: &mut Window,
20586 cx: &mut Context<Editor>,
20587 ) -> Task<Result<Vec<CompletionResponse>>> {
20588 self.update(cx, |project, cx| {
20589 let snippets = snippet_completions(project, buffer, buffer_position, cx);
20590 let project_completions = project.completions(buffer, buffer_position, options, cx);
20591 cx.background_spawn(async move {
20592 let mut responses = project_completions.await?;
20593 let snippets = snippets.await?;
20594 if !snippets.completions.is_empty() {
20595 responses.push(snippets);
20596 }
20597 Ok(responses)
20598 })
20599 })
20600 }
20601
20602 fn resolve_completions(
20603 &self,
20604 buffer: Entity<Buffer>,
20605 completion_indices: Vec<usize>,
20606 completions: Rc<RefCell<Box<[Completion]>>>,
20607 cx: &mut Context<Editor>,
20608 ) -> Task<Result<bool>> {
20609 self.update(cx, |project, cx| {
20610 project.lsp_store().update(cx, |lsp_store, cx| {
20611 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
20612 })
20613 })
20614 }
20615
20616 fn apply_additional_edits_for_completion(
20617 &self,
20618 buffer: Entity<Buffer>,
20619 completions: Rc<RefCell<Box<[Completion]>>>,
20620 completion_index: usize,
20621 push_to_history: bool,
20622 cx: &mut Context<Editor>,
20623 ) -> Task<Result<Option<language::Transaction>>> {
20624 self.update(cx, |project, cx| {
20625 project.lsp_store().update(cx, |lsp_store, cx| {
20626 lsp_store.apply_additional_edits_for_completion(
20627 buffer,
20628 completions,
20629 completion_index,
20630 push_to_history,
20631 cx,
20632 )
20633 })
20634 })
20635 }
20636
20637 fn is_completion_trigger(
20638 &self,
20639 buffer: &Entity<Buffer>,
20640 position: language::Anchor,
20641 text: &str,
20642 trigger_in_words: bool,
20643 menu_is_open: bool,
20644 cx: &mut Context<Editor>,
20645 ) -> bool {
20646 let mut chars = text.chars();
20647 let char = if let Some(char) = chars.next() {
20648 char
20649 } else {
20650 return false;
20651 };
20652 if chars.next().is_some() {
20653 return false;
20654 }
20655
20656 let buffer = buffer.read(cx);
20657 let snapshot = buffer.snapshot();
20658 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
20659 return false;
20660 }
20661 let classifier = snapshot.char_classifier_at(position).for_completion(true);
20662 if trigger_in_words && classifier.is_word(char) {
20663 return true;
20664 }
20665
20666 buffer.completion_triggers().contains(text)
20667 }
20668}
20669
20670impl SemanticsProvider for Entity<Project> {
20671 fn hover(
20672 &self,
20673 buffer: &Entity<Buffer>,
20674 position: text::Anchor,
20675 cx: &mut App,
20676 ) -> Option<Task<Vec<project::Hover>>> {
20677 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
20678 }
20679
20680 fn document_highlights(
20681 &self,
20682 buffer: &Entity<Buffer>,
20683 position: text::Anchor,
20684 cx: &mut App,
20685 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
20686 Some(self.update(cx, |project, cx| {
20687 project.document_highlights(buffer, position, cx)
20688 }))
20689 }
20690
20691 fn definitions(
20692 &self,
20693 buffer: &Entity<Buffer>,
20694 position: text::Anchor,
20695 kind: GotoDefinitionKind,
20696 cx: &mut App,
20697 ) -> Option<Task<Result<Vec<LocationLink>>>> {
20698 Some(self.update(cx, |project, cx| match kind {
20699 GotoDefinitionKind::Symbol => project.definition(&buffer, position, cx),
20700 GotoDefinitionKind::Declaration => project.declaration(&buffer, position, cx),
20701 GotoDefinitionKind::Type => project.type_definition(&buffer, position, cx),
20702 GotoDefinitionKind::Implementation => project.implementation(&buffer, position, cx),
20703 }))
20704 }
20705
20706 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
20707 // TODO: make this work for remote projects
20708 self.update(cx, |project, cx| {
20709 if project
20710 .active_debug_session(cx)
20711 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
20712 {
20713 return true;
20714 }
20715
20716 buffer.update(cx, |buffer, cx| {
20717 project.any_language_server_supports_inlay_hints(buffer, cx)
20718 })
20719 })
20720 }
20721
20722 fn inline_values(
20723 &self,
20724 buffer_handle: Entity<Buffer>,
20725
20726 range: Range<text::Anchor>,
20727 cx: &mut App,
20728 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
20729 self.update(cx, |project, cx| {
20730 let (session, active_stack_frame) = project.active_debug_session(cx)?;
20731
20732 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
20733 })
20734 }
20735
20736 fn inlay_hints(
20737 &self,
20738 buffer_handle: Entity<Buffer>,
20739 range: Range<text::Anchor>,
20740 cx: &mut App,
20741 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
20742 Some(self.update(cx, |project, cx| {
20743 project.inlay_hints(buffer_handle, range, cx)
20744 }))
20745 }
20746
20747 fn resolve_inlay_hint(
20748 &self,
20749 hint: InlayHint,
20750 buffer_handle: Entity<Buffer>,
20751 server_id: LanguageServerId,
20752 cx: &mut App,
20753 ) -> Option<Task<anyhow::Result<InlayHint>>> {
20754 Some(self.update(cx, |project, cx| {
20755 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
20756 }))
20757 }
20758
20759 fn range_for_rename(
20760 &self,
20761 buffer: &Entity<Buffer>,
20762 position: text::Anchor,
20763 cx: &mut App,
20764 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
20765 Some(self.update(cx, |project, cx| {
20766 let buffer = buffer.clone();
20767 let task = project.prepare_rename(buffer.clone(), position, cx);
20768 cx.spawn(async move |_, cx| {
20769 Ok(match task.await? {
20770 PrepareRenameResponse::Success(range) => Some(range),
20771 PrepareRenameResponse::InvalidPosition => None,
20772 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
20773 // Fallback on using TreeSitter info to determine identifier range
20774 buffer.read_with(cx, |buffer, _| {
20775 let snapshot = buffer.snapshot();
20776 let (range, kind) = snapshot.surrounding_word(position);
20777 if kind != Some(CharKind::Word) {
20778 return None;
20779 }
20780 Some(
20781 snapshot.anchor_before(range.start)
20782 ..snapshot.anchor_after(range.end),
20783 )
20784 })?
20785 }
20786 })
20787 })
20788 }))
20789 }
20790
20791 fn perform_rename(
20792 &self,
20793 buffer: &Entity<Buffer>,
20794 position: text::Anchor,
20795 new_name: String,
20796 cx: &mut App,
20797 ) -> Option<Task<Result<ProjectTransaction>>> {
20798 Some(self.update(cx, |project, cx| {
20799 project.perform_rename(buffer.clone(), position, new_name, cx)
20800 }))
20801 }
20802}
20803
20804fn inlay_hint_settings(
20805 location: Anchor,
20806 snapshot: &MultiBufferSnapshot,
20807 cx: &mut Context<Editor>,
20808) -> InlayHintSettings {
20809 let file = snapshot.file_at(location);
20810 let language = snapshot.language_at(location).map(|l| l.name());
20811 language_settings(language, file, cx).inlay_hints
20812}
20813
20814fn consume_contiguous_rows(
20815 contiguous_row_selections: &mut Vec<Selection<Point>>,
20816 selection: &Selection<Point>,
20817 display_map: &DisplaySnapshot,
20818 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
20819) -> (MultiBufferRow, MultiBufferRow) {
20820 contiguous_row_selections.push(selection.clone());
20821 let start_row = MultiBufferRow(selection.start.row);
20822 let mut end_row = ending_row(selection, display_map);
20823
20824 while let Some(next_selection) = selections.peek() {
20825 if next_selection.start.row <= end_row.0 {
20826 end_row = ending_row(next_selection, display_map);
20827 contiguous_row_selections.push(selections.next().unwrap().clone());
20828 } else {
20829 break;
20830 }
20831 }
20832 (start_row, end_row)
20833}
20834
20835fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
20836 if next_selection.end.column > 0 || next_selection.is_empty() {
20837 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
20838 } else {
20839 MultiBufferRow(next_selection.end.row)
20840 }
20841}
20842
20843impl EditorSnapshot {
20844 pub fn remote_selections_in_range<'a>(
20845 &'a self,
20846 range: &'a Range<Anchor>,
20847 collaboration_hub: &dyn CollaborationHub,
20848 cx: &'a App,
20849 ) -> impl 'a + Iterator<Item = RemoteSelection> {
20850 let participant_names = collaboration_hub.user_names(cx);
20851 let participant_indices = collaboration_hub.user_participant_indices(cx);
20852 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
20853 let collaborators_by_replica_id = collaborators_by_peer_id
20854 .values()
20855 .map(|collaborator| (collaborator.replica_id, collaborator))
20856 .collect::<HashMap<_, _>>();
20857 self.buffer_snapshot
20858 .selections_in_range(range, false)
20859 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
20860 if replica_id == AGENT_REPLICA_ID {
20861 Some(RemoteSelection {
20862 replica_id,
20863 selection,
20864 cursor_shape,
20865 line_mode,
20866 collaborator_id: CollaboratorId::Agent,
20867 user_name: Some("Agent".into()),
20868 color: cx.theme().players().agent(),
20869 })
20870 } else {
20871 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
20872 let participant_index = participant_indices.get(&collaborator.user_id).copied();
20873 let user_name = participant_names.get(&collaborator.user_id).cloned();
20874 Some(RemoteSelection {
20875 replica_id,
20876 selection,
20877 cursor_shape,
20878 line_mode,
20879 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
20880 user_name,
20881 color: if let Some(index) = participant_index {
20882 cx.theme().players().color_for_participant(index.0)
20883 } else {
20884 cx.theme().players().absent()
20885 },
20886 })
20887 }
20888 })
20889 }
20890
20891 pub fn hunks_for_ranges(
20892 &self,
20893 ranges: impl IntoIterator<Item = Range<Point>>,
20894 ) -> Vec<MultiBufferDiffHunk> {
20895 let mut hunks = Vec::new();
20896 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
20897 HashMap::default();
20898 for query_range in ranges {
20899 let query_rows =
20900 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
20901 for hunk in self.buffer_snapshot.diff_hunks_in_range(
20902 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
20903 ) {
20904 // Include deleted hunks that are adjacent to the query range, because
20905 // otherwise they would be missed.
20906 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
20907 if hunk.status().is_deleted() {
20908 intersects_range |= hunk.row_range.start == query_rows.end;
20909 intersects_range |= hunk.row_range.end == query_rows.start;
20910 }
20911 if intersects_range {
20912 if !processed_buffer_rows
20913 .entry(hunk.buffer_id)
20914 .or_default()
20915 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
20916 {
20917 continue;
20918 }
20919 hunks.push(hunk);
20920 }
20921 }
20922 }
20923
20924 hunks
20925 }
20926
20927 fn display_diff_hunks_for_rows<'a>(
20928 &'a self,
20929 display_rows: Range<DisplayRow>,
20930 folded_buffers: &'a HashSet<BufferId>,
20931 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
20932 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
20933 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
20934
20935 self.buffer_snapshot
20936 .diff_hunks_in_range(buffer_start..buffer_end)
20937 .filter_map(|hunk| {
20938 if folded_buffers.contains(&hunk.buffer_id) {
20939 return None;
20940 }
20941
20942 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
20943 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
20944
20945 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
20946 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
20947
20948 let display_hunk = if hunk_display_start.column() != 0 {
20949 DisplayDiffHunk::Folded {
20950 display_row: hunk_display_start.row(),
20951 }
20952 } else {
20953 let mut end_row = hunk_display_end.row();
20954 if hunk_display_end.column() > 0 {
20955 end_row.0 += 1;
20956 }
20957 let is_created_file = hunk.is_created_file();
20958 DisplayDiffHunk::Unfolded {
20959 status: hunk.status(),
20960 diff_base_byte_range: hunk.diff_base_byte_range,
20961 display_row_range: hunk_display_start.row()..end_row,
20962 multi_buffer_range: Anchor::range_in_buffer(
20963 hunk.excerpt_id,
20964 hunk.buffer_id,
20965 hunk.buffer_range,
20966 ),
20967 is_created_file,
20968 }
20969 };
20970
20971 Some(display_hunk)
20972 })
20973 }
20974
20975 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
20976 self.display_snapshot.buffer_snapshot.language_at(position)
20977 }
20978
20979 pub fn is_focused(&self) -> bool {
20980 self.is_focused
20981 }
20982
20983 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
20984 self.placeholder_text.as_ref()
20985 }
20986
20987 pub fn scroll_position(&self) -> gpui::Point<f32> {
20988 self.scroll_anchor.scroll_position(&self.display_snapshot)
20989 }
20990
20991 fn gutter_dimensions(
20992 &self,
20993 font_id: FontId,
20994 font_size: Pixels,
20995 max_line_number_width: Pixels,
20996 cx: &App,
20997 ) -> Option<GutterDimensions> {
20998 if !self.show_gutter {
20999 return None;
21000 }
21001
21002 let em_width = cx.text_system().em_width(font_id, font_size).log_err()?;
21003 let em_advance = cx.text_system().em_advance(font_id, font_size).log_err()?;
21004
21005 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
21006 matches!(
21007 ProjectSettings::get_global(cx).git.git_gutter,
21008 Some(GitGutterSetting::TrackedFiles)
21009 )
21010 });
21011 let gutter_settings = EditorSettings::get_global(cx).gutter;
21012 let show_line_numbers = self
21013 .show_line_numbers
21014 .unwrap_or(gutter_settings.line_numbers);
21015 let line_gutter_width = if show_line_numbers {
21016 // Avoid flicker-like gutter resizes when the line number gains another digit and only resize the gutter on files with N*10^5 lines.
21017 let min_width_for_number_on_gutter = em_advance * MIN_LINE_NUMBER_DIGITS as f32;
21018 max_line_number_width.max(min_width_for_number_on_gutter)
21019 } else {
21020 0.0.into()
21021 };
21022
21023 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
21024 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
21025
21026 let git_blame_entries_width =
21027 self.git_blame_gutter_max_author_length
21028 .map(|max_author_length| {
21029 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
21030 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
21031
21032 /// The number of characters to dedicate to gaps and margins.
21033 const SPACING_WIDTH: usize = 4;
21034
21035 let max_char_count = max_author_length.min(renderer.max_author_length())
21036 + ::git::SHORT_SHA_LENGTH
21037 + MAX_RELATIVE_TIMESTAMP.len()
21038 + SPACING_WIDTH;
21039
21040 em_advance * max_char_count
21041 });
21042
21043 let is_singleton = self.buffer_snapshot.is_singleton();
21044
21045 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
21046 left_padding += if !is_singleton {
21047 em_width * 4.0
21048 } else if show_runnables || show_breakpoints {
21049 em_width * 3.0
21050 } else if show_git_gutter && show_line_numbers {
21051 em_width * 2.0
21052 } else if show_git_gutter || show_line_numbers {
21053 em_width
21054 } else {
21055 px(0.)
21056 };
21057
21058 let shows_folds = is_singleton && gutter_settings.folds;
21059
21060 let right_padding = if shows_folds && show_line_numbers {
21061 em_width * 4.0
21062 } else if shows_folds || (!is_singleton && show_line_numbers) {
21063 em_width * 3.0
21064 } else if show_line_numbers {
21065 em_width
21066 } else {
21067 px(0.)
21068 };
21069
21070 Some(GutterDimensions {
21071 left_padding,
21072 right_padding,
21073 width: line_gutter_width + left_padding + right_padding,
21074 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
21075 git_blame_entries_width,
21076 })
21077 }
21078
21079 pub fn render_crease_toggle(
21080 &self,
21081 buffer_row: MultiBufferRow,
21082 row_contains_cursor: bool,
21083 editor: Entity<Editor>,
21084 window: &mut Window,
21085 cx: &mut App,
21086 ) -> Option<AnyElement> {
21087 let folded = self.is_line_folded(buffer_row);
21088 let mut is_foldable = false;
21089
21090 if let Some(crease) = self
21091 .crease_snapshot
21092 .query_row(buffer_row, &self.buffer_snapshot)
21093 {
21094 is_foldable = true;
21095 match crease {
21096 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
21097 if let Some(render_toggle) = render_toggle {
21098 let toggle_callback =
21099 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
21100 if folded {
21101 editor.update(cx, |editor, cx| {
21102 editor.fold_at(buffer_row, window, cx)
21103 });
21104 } else {
21105 editor.update(cx, |editor, cx| {
21106 editor.unfold_at(buffer_row, window, cx)
21107 });
21108 }
21109 });
21110 return Some((render_toggle)(
21111 buffer_row,
21112 folded,
21113 toggle_callback,
21114 window,
21115 cx,
21116 ));
21117 }
21118 }
21119 }
21120 }
21121
21122 is_foldable |= self.starts_indent(buffer_row);
21123
21124 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
21125 Some(
21126 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
21127 .toggle_state(folded)
21128 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
21129 if folded {
21130 this.unfold_at(buffer_row, window, cx);
21131 } else {
21132 this.fold_at(buffer_row, window, cx);
21133 }
21134 }))
21135 .into_any_element(),
21136 )
21137 } else {
21138 None
21139 }
21140 }
21141
21142 pub fn render_crease_trailer(
21143 &self,
21144 buffer_row: MultiBufferRow,
21145 window: &mut Window,
21146 cx: &mut App,
21147 ) -> Option<AnyElement> {
21148 let folded = self.is_line_folded(buffer_row);
21149 if let Crease::Inline { render_trailer, .. } = self
21150 .crease_snapshot
21151 .query_row(buffer_row, &self.buffer_snapshot)?
21152 {
21153 let render_trailer = render_trailer.as_ref()?;
21154 Some(render_trailer(buffer_row, folded, window, cx))
21155 } else {
21156 None
21157 }
21158 }
21159}
21160
21161impl Deref for EditorSnapshot {
21162 type Target = DisplaySnapshot;
21163
21164 fn deref(&self) -> &Self::Target {
21165 &self.display_snapshot
21166 }
21167}
21168
21169#[derive(Clone, Debug, PartialEq, Eq)]
21170pub enum EditorEvent {
21171 InputIgnored {
21172 text: Arc<str>,
21173 },
21174 InputHandled {
21175 utf16_range_to_replace: Option<Range<isize>>,
21176 text: Arc<str>,
21177 },
21178 ExcerptsAdded {
21179 buffer: Entity<Buffer>,
21180 predecessor: ExcerptId,
21181 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
21182 },
21183 ExcerptsRemoved {
21184 ids: Vec<ExcerptId>,
21185 removed_buffer_ids: Vec<BufferId>,
21186 },
21187 BufferFoldToggled {
21188 ids: Vec<ExcerptId>,
21189 folded: bool,
21190 },
21191 ExcerptsEdited {
21192 ids: Vec<ExcerptId>,
21193 },
21194 ExcerptsExpanded {
21195 ids: Vec<ExcerptId>,
21196 },
21197 BufferEdited,
21198 Edited {
21199 transaction_id: clock::Lamport,
21200 },
21201 Reparsed(BufferId),
21202 Focused,
21203 FocusedIn,
21204 Blurred,
21205 DirtyChanged,
21206 Saved,
21207 TitleChanged,
21208 DiffBaseChanged,
21209 SelectionsChanged {
21210 local: bool,
21211 },
21212 ScrollPositionChanged {
21213 local: bool,
21214 autoscroll: bool,
21215 },
21216 Closed,
21217 TransactionUndone {
21218 transaction_id: clock::Lamport,
21219 },
21220 TransactionBegun {
21221 transaction_id: clock::Lamport,
21222 },
21223 Reloaded,
21224 CursorShapeChanged,
21225 PushedToNavHistory {
21226 anchor: Anchor,
21227 is_deactivate: bool,
21228 },
21229}
21230
21231impl EventEmitter<EditorEvent> for Editor {}
21232
21233impl Focusable for Editor {
21234 fn focus_handle(&self, _cx: &App) -> FocusHandle {
21235 self.focus_handle.clone()
21236 }
21237}
21238
21239impl Render for Editor {
21240 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21241 let settings = ThemeSettings::get_global(cx);
21242
21243 let mut text_style = match self.mode {
21244 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
21245 color: cx.theme().colors().editor_foreground,
21246 font_family: settings.ui_font.family.clone(),
21247 font_features: settings.ui_font.features.clone(),
21248 font_fallbacks: settings.ui_font.fallbacks.clone(),
21249 font_size: rems(0.875).into(),
21250 font_weight: settings.ui_font.weight,
21251 line_height: relative(settings.buffer_line_height.value()),
21252 ..Default::default()
21253 },
21254 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
21255 color: cx.theme().colors().editor_foreground,
21256 font_family: settings.buffer_font.family.clone(),
21257 font_features: settings.buffer_font.features.clone(),
21258 font_fallbacks: settings.buffer_font.fallbacks.clone(),
21259 font_size: settings.buffer_font_size(cx).into(),
21260 font_weight: settings.buffer_font.weight,
21261 line_height: relative(settings.buffer_line_height.value()),
21262 ..Default::default()
21263 },
21264 };
21265 if let Some(text_style_refinement) = &self.text_style_refinement {
21266 text_style.refine(text_style_refinement)
21267 }
21268
21269 let background = match self.mode {
21270 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
21271 EditorMode::AutoHeight { max_lines: _ } => cx.theme().system().transparent,
21272 EditorMode::Full { .. } => cx.theme().colors().editor_background,
21273 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
21274 };
21275
21276 EditorElement::new(
21277 &cx.entity(),
21278 EditorStyle {
21279 background,
21280 local_player: cx.theme().players().local(),
21281 text: text_style,
21282 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
21283 syntax: cx.theme().syntax().clone(),
21284 status: cx.theme().status().clone(),
21285 inlay_hints_style: make_inlay_hints_style(cx),
21286 inline_completion_styles: make_suggestion_styles(cx),
21287 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
21288 show_underlines: !self.mode.is_minimap(),
21289 },
21290 )
21291 }
21292}
21293
21294impl EntityInputHandler for Editor {
21295 fn text_for_range(
21296 &mut self,
21297 range_utf16: Range<usize>,
21298 adjusted_range: &mut Option<Range<usize>>,
21299 _: &mut Window,
21300 cx: &mut Context<Self>,
21301 ) -> Option<String> {
21302 let snapshot = self.buffer.read(cx).read(cx);
21303 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
21304 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
21305 if (start.0..end.0) != range_utf16 {
21306 adjusted_range.replace(start.0..end.0);
21307 }
21308 Some(snapshot.text_for_range(start..end).collect())
21309 }
21310
21311 fn selected_text_range(
21312 &mut self,
21313 ignore_disabled_input: bool,
21314 _: &mut Window,
21315 cx: &mut Context<Self>,
21316 ) -> Option<UTF16Selection> {
21317 // Prevent the IME menu from appearing when holding down an alphabetic key
21318 // while input is disabled.
21319 if !ignore_disabled_input && !self.input_enabled {
21320 return None;
21321 }
21322
21323 let selection = self.selections.newest::<OffsetUtf16>(cx);
21324 let range = selection.range();
21325
21326 Some(UTF16Selection {
21327 range: range.start.0..range.end.0,
21328 reversed: selection.reversed,
21329 })
21330 }
21331
21332 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
21333 let snapshot = self.buffer.read(cx).read(cx);
21334 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
21335 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
21336 }
21337
21338 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
21339 self.clear_highlights::<InputComposition>(cx);
21340 self.ime_transaction.take();
21341 }
21342
21343 fn replace_text_in_range(
21344 &mut self,
21345 range_utf16: Option<Range<usize>>,
21346 text: &str,
21347 window: &mut Window,
21348 cx: &mut Context<Self>,
21349 ) {
21350 if !self.input_enabled {
21351 cx.emit(EditorEvent::InputIgnored { text: text.into() });
21352 return;
21353 }
21354
21355 self.transact(window, cx, |this, window, cx| {
21356 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
21357 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
21358 Some(this.selection_replacement_ranges(range_utf16, cx))
21359 } else {
21360 this.marked_text_ranges(cx)
21361 };
21362
21363 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
21364 let newest_selection_id = this.selections.newest_anchor().id;
21365 this.selections
21366 .all::<OffsetUtf16>(cx)
21367 .iter()
21368 .zip(ranges_to_replace.iter())
21369 .find_map(|(selection, range)| {
21370 if selection.id == newest_selection_id {
21371 Some(
21372 (range.start.0 as isize - selection.head().0 as isize)
21373 ..(range.end.0 as isize - selection.head().0 as isize),
21374 )
21375 } else {
21376 None
21377 }
21378 })
21379 });
21380
21381 cx.emit(EditorEvent::InputHandled {
21382 utf16_range_to_replace: range_to_replace,
21383 text: text.into(),
21384 });
21385
21386 if let Some(new_selected_ranges) = new_selected_ranges {
21387 this.change_selections(None, window, cx, |selections| {
21388 selections.select_ranges(new_selected_ranges)
21389 });
21390 this.backspace(&Default::default(), window, cx);
21391 }
21392
21393 this.handle_input(text, window, cx);
21394 });
21395
21396 if let Some(transaction) = self.ime_transaction {
21397 self.buffer.update(cx, |buffer, cx| {
21398 buffer.group_until_transaction(transaction, cx);
21399 });
21400 }
21401
21402 self.unmark_text(window, cx);
21403 }
21404
21405 fn replace_and_mark_text_in_range(
21406 &mut self,
21407 range_utf16: Option<Range<usize>>,
21408 text: &str,
21409 new_selected_range_utf16: Option<Range<usize>>,
21410 window: &mut Window,
21411 cx: &mut Context<Self>,
21412 ) {
21413 if !self.input_enabled {
21414 return;
21415 }
21416
21417 let transaction = self.transact(window, cx, |this, window, cx| {
21418 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
21419 let snapshot = this.buffer.read(cx).read(cx);
21420 if let Some(relative_range_utf16) = range_utf16.as_ref() {
21421 for marked_range in &mut marked_ranges {
21422 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
21423 marked_range.start.0 += relative_range_utf16.start;
21424 marked_range.start =
21425 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
21426 marked_range.end =
21427 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
21428 }
21429 }
21430 Some(marked_ranges)
21431 } else if let Some(range_utf16) = range_utf16 {
21432 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
21433 Some(this.selection_replacement_ranges(range_utf16, cx))
21434 } else {
21435 None
21436 };
21437
21438 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
21439 let newest_selection_id = this.selections.newest_anchor().id;
21440 this.selections
21441 .all::<OffsetUtf16>(cx)
21442 .iter()
21443 .zip(ranges_to_replace.iter())
21444 .find_map(|(selection, range)| {
21445 if selection.id == newest_selection_id {
21446 Some(
21447 (range.start.0 as isize - selection.head().0 as isize)
21448 ..(range.end.0 as isize - selection.head().0 as isize),
21449 )
21450 } else {
21451 None
21452 }
21453 })
21454 });
21455
21456 cx.emit(EditorEvent::InputHandled {
21457 utf16_range_to_replace: range_to_replace,
21458 text: text.into(),
21459 });
21460
21461 if let Some(ranges) = ranges_to_replace {
21462 this.change_selections(None, window, cx, |s| s.select_ranges(ranges));
21463 }
21464
21465 let marked_ranges = {
21466 let snapshot = this.buffer.read(cx).read(cx);
21467 this.selections
21468 .disjoint_anchors()
21469 .iter()
21470 .map(|selection| {
21471 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
21472 })
21473 .collect::<Vec<_>>()
21474 };
21475
21476 if text.is_empty() {
21477 this.unmark_text(window, cx);
21478 } else {
21479 this.highlight_text::<InputComposition>(
21480 marked_ranges.clone(),
21481 HighlightStyle {
21482 underline: Some(UnderlineStyle {
21483 thickness: px(1.),
21484 color: None,
21485 wavy: false,
21486 }),
21487 ..Default::default()
21488 },
21489 cx,
21490 );
21491 }
21492
21493 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
21494 let use_autoclose = this.use_autoclose;
21495 let use_auto_surround = this.use_auto_surround;
21496 this.set_use_autoclose(false);
21497 this.set_use_auto_surround(false);
21498 this.handle_input(text, window, cx);
21499 this.set_use_autoclose(use_autoclose);
21500 this.set_use_auto_surround(use_auto_surround);
21501
21502 if let Some(new_selected_range) = new_selected_range_utf16 {
21503 let snapshot = this.buffer.read(cx).read(cx);
21504 let new_selected_ranges = marked_ranges
21505 .into_iter()
21506 .map(|marked_range| {
21507 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
21508 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
21509 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
21510 snapshot.clip_offset_utf16(new_start, Bias::Left)
21511 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
21512 })
21513 .collect::<Vec<_>>();
21514
21515 drop(snapshot);
21516 this.change_selections(None, window, cx, |selections| {
21517 selections.select_ranges(new_selected_ranges)
21518 });
21519 }
21520 });
21521
21522 self.ime_transaction = self.ime_transaction.or(transaction);
21523 if let Some(transaction) = self.ime_transaction {
21524 self.buffer.update(cx, |buffer, cx| {
21525 buffer.group_until_transaction(transaction, cx);
21526 });
21527 }
21528
21529 if self.text_highlights::<InputComposition>(cx).is_none() {
21530 self.ime_transaction.take();
21531 }
21532 }
21533
21534 fn bounds_for_range(
21535 &mut self,
21536 range_utf16: Range<usize>,
21537 element_bounds: gpui::Bounds<Pixels>,
21538 window: &mut Window,
21539 cx: &mut Context<Self>,
21540 ) -> Option<gpui::Bounds<Pixels>> {
21541 let text_layout_details = self.text_layout_details(window);
21542 let gpui::Size {
21543 width: em_width,
21544 height: line_height,
21545 } = self.character_size(window);
21546
21547 let snapshot = self.snapshot(window, cx);
21548 let scroll_position = snapshot.scroll_position();
21549 let scroll_left = scroll_position.x * em_width;
21550
21551 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
21552 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
21553 + self.gutter_dimensions.width
21554 + self.gutter_dimensions.margin;
21555 let y = line_height * (start.row().as_f32() - scroll_position.y);
21556
21557 Some(Bounds {
21558 origin: element_bounds.origin + point(x, y),
21559 size: size(em_width, line_height),
21560 })
21561 }
21562
21563 fn character_index_for_point(
21564 &mut self,
21565 point: gpui::Point<Pixels>,
21566 _window: &mut Window,
21567 _cx: &mut Context<Self>,
21568 ) -> Option<usize> {
21569 let position_map = self.last_position_map.as_ref()?;
21570 if !position_map.text_hitbox.contains(&point) {
21571 return None;
21572 }
21573 let display_point = position_map.point_for_position(point).previous_valid;
21574 let anchor = position_map
21575 .snapshot
21576 .display_point_to_anchor(display_point, Bias::Left);
21577 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
21578 Some(utf16_offset.0)
21579 }
21580}
21581
21582trait SelectionExt {
21583 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
21584 fn spanned_rows(
21585 &self,
21586 include_end_if_at_line_start: bool,
21587 map: &DisplaySnapshot,
21588 ) -> Range<MultiBufferRow>;
21589}
21590
21591impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
21592 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
21593 let start = self
21594 .start
21595 .to_point(&map.buffer_snapshot)
21596 .to_display_point(map);
21597 let end = self
21598 .end
21599 .to_point(&map.buffer_snapshot)
21600 .to_display_point(map);
21601 if self.reversed {
21602 end..start
21603 } else {
21604 start..end
21605 }
21606 }
21607
21608 fn spanned_rows(
21609 &self,
21610 include_end_if_at_line_start: bool,
21611 map: &DisplaySnapshot,
21612 ) -> Range<MultiBufferRow> {
21613 let start = self.start.to_point(&map.buffer_snapshot);
21614 let mut end = self.end.to_point(&map.buffer_snapshot);
21615 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
21616 end.row -= 1;
21617 }
21618
21619 let buffer_start = map.prev_line_boundary(start).0;
21620 let buffer_end = map.next_line_boundary(end).0;
21621 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
21622 }
21623}
21624
21625impl<T: InvalidationRegion> InvalidationStack<T> {
21626 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
21627 where
21628 S: Clone + ToOffset,
21629 {
21630 while let Some(region) = self.last() {
21631 let all_selections_inside_invalidation_ranges =
21632 if selections.len() == region.ranges().len() {
21633 selections
21634 .iter()
21635 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
21636 .all(|(selection, invalidation_range)| {
21637 let head = selection.head().to_offset(buffer);
21638 invalidation_range.start <= head && invalidation_range.end >= head
21639 })
21640 } else {
21641 false
21642 };
21643
21644 if all_selections_inside_invalidation_ranges {
21645 break;
21646 } else {
21647 self.pop();
21648 }
21649 }
21650 }
21651}
21652
21653impl<T> Default for InvalidationStack<T> {
21654 fn default() -> Self {
21655 Self(Default::default())
21656 }
21657}
21658
21659impl<T> Deref for InvalidationStack<T> {
21660 type Target = Vec<T>;
21661
21662 fn deref(&self) -> &Self::Target {
21663 &self.0
21664 }
21665}
21666
21667impl<T> DerefMut for InvalidationStack<T> {
21668 fn deref_mut(&mut self) -> &mut Self::Target {
21669 &mut self.0
21670 }
21671}
21672
21673impl InvalidationRegion for SnippetState {
21674 fn ranges(&self) -> &[Range<Anchor>] {
21675 &self.ranges[self.active_index]
21676 }
21677}
21678
21679fn inline_completion_edit_text(
21680 current_snapshot: &BufferSnapshot,
21681 edits: &[(Range<Anchor>, String)],
21682 edit_preview: &EditPreview,
21683 include_deletions: bool,
21684 cx: &App,
21685) -> HighlightedText {
21686 let edits = edits
21687 .iter()
21688 .map(|(anchor, text)| {
21689 (
21690 anchor.start.text_anchor..anchor.end.text_anchor,
21691 text.clone(),
21692 )
21693 })
21694 .collect::<Vec<_>>();
21695
21696 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
21697}
21698
21699pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
21700 match severity {
21701 lsp::DiagnosticSeverity::ERROR => colors.error,
21702 lsp::DiagnosticSeverity::WARNING => colors.warning,
21703 lsp::DiagnosticSeverity::INFORMATION => colors.info,
21704 lsp::DiagnosticSeverity::HINT => colors.info,
21705 _ => colors.ignored,
21706 }
21707}
21708
21709pub fn styled_runs_for_code_label<'a>(
21710 label: &'a CodeLabel,
21711 syntax_theme: &'a theme::SyntaxTheme,
21712) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
21713 let fade_out = HighlightStyle {
21714 fade_out: Some(0.35),
21715 ..Default::default()
21716 };
21717
21718 let mut prev_end = label.filter_range.end;
21719 label
21720 .runs
21721 .iter()
21722 .enumerate()
21723 .flat_map(move |(ix, (range, highlight_id))| {
21724 let style = if let Some(style) = highlight_id.style(syntax_theme) {
21725 style
21726 } else {
21727 return Default::default();
21728 };
21729 let mut muted_style = style;
21730 muted_style.highlight(fade_out);
21731
21732 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
21733 if range.start >= label.filter_range.end {
21734 if range.start > prev_end {
21735 runs.push((prev_end..range.start, fade_out));
21736 }
21737 runs.push((range.clone(), muted_style));
21738 } else if range.end <= label.filter_range.end {
21739 runs.push((range.clone(), style));
21740 } else {
21741 runs.push((range.start..label.filter_range.end, style));
21742 runs.push((label.filter_range.end..range.end, muted_style));
21743 }
21744 prev_end = cmp::max(prev_end, range.end);
21745
21746 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
21747 runs.push((prev_end..label.text.len(), fade_out));
21748 }
21749
21750 runs
21751 })
21752}
21753
21754pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
21755 let mut prev_index = 0;
21756 let mut prev_codepoint: Option<char> = None;
21757 text.char_indices()
21758 .chain([(text.len(), '\0')])
21759 .filter_map(move |(index, codepoint)| {
21760 let prev_codepoint = prev_codepoint.replace(codepoint)?;
21761 let is_boundary = index == text.len()
21762 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
21763 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
21764 if is_boundary {
21765 let chunk = &text[prev_index..index];
21766 prev_index = index;
21767 Some(chunk)
21768 } else {
21769 None
21770 }
21771 })
21772}
21773
21774pub trait RangeToAnchorExt: Sized {
21775 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
21776
21777 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
21778 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
21779 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
21780 }
21781}
21782
21783impl<T: ToOffset> RangeToAnchorExt for Range<T> {
21784 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
21785 let start_offset = self.start.to_offset(snapshot);
21786 let end_offset = self.end.to_offset(snapshot);
21787 if start_offset == end_offset {
21788 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
21789 } else {
21790 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
21791 }
21792 }
21793}
21794
21795pub trait RowExt {
21796 fn as_f32(&self) -> f32;
21797
21798 fn next_row(&self) -> Self;
21799
21800 fn previous_row(&self) -> Self;
21801
21802 fn minus(&self, other: Self) -> u32;
21803}
21804
21805impl RowExt for DisplayRow {
21806 fn as_f32(&self) -> f32 {
21807 self.0 as f32
21808 }
21809
21810 fn next_row(&self) -> Self {
21811 Self(self.0 + 1)
21812 }
21813
21814 fn previous_row(&self) -> Self {
21815 Self(self.0.saturating_sub(1))
21816 }
21817
21818 fn minus(&self, other: Self) -> u32 {
21819 self.0 - other.0
21820 }
21821}
21822
21823impl RowExt for MultiBufferRow {
21824 fn as_f32(&self) -> f32 {
21825 self.0 as f32
21826 }
21827
21828 fn next_row(&self) -> Self {
21829 Self(self.0 + 1)
21830 }
21831
21832 fn previous_row(&self) -> Self {
21833 Self(self.0.saturating_sub(1))
21834 }
21835
21836 fn minus(&self, other: Self) -> u32 {
21837 self.0 - other.0
21838 }
21839}
21840
21841trait RowRangeExt {
21842 type Row;
21843
21844 fn len(&self) -> usize;
21845
21846 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
21847}
21848
21849impl RowRangeExt for Range<MultiBufferRow> {
21850 type Row = MultiBufferRow;
21851
21852 fn len(&self) -> usize {
21853 (self.end.0 - self.start.0) as usize
21854 }
21855
21856 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
21857 (self.start.0..self.end.0).map(MultiBufferRow)
21858 }
21859}
21860
21861impl RowRangeExt for Range<DisplayRow> {
21862 type Row = DisplayRow;
21863
21864 fn len(&self) -> usize {
21865 (self.end.0 - self.start.0) as usize
21866 }
21867
21868 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
21869 (self.start.0..self.end.0).map(DisplayRow)
21870 }
21871}
21872
21873/// If select range has more than one line, we
21874/// just point the cursor to range.start.
21875fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
21876 if range.start.row == range.end.row {
21877 range
21878 } else {
21879 range.start..range.start
21880 }
21881}
21882pub struct KillRing(ClipboardItem);
21883impl Global for KillRing {}
21884
21885const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
21886
21887enum BreakpointPromptEditAction {
21888 Log,
21889 Condition,
21890 HitCondition,
21891}
21892
21893struct BreakpointPromptEditor {
21894 pub(crate) prompt: Entity<Editor>,
21895 editor: WeakEntity<Editor>,
21896 breakpoint_anchor: Anchor,
21897 breakpoint: Breakpoint,
21898 edit_action: BreakpointPromptEditAction,
21899 block_ids: HashSet<CustomBlockId>,
21900 editor_margins: Arc<Mutex<EditorMargins>>,
21901 _subscriptions: Vec<Subscription>,
21902}
21903
21904impl BreakpointPromptEditor {
21905 const MAX_LINES: u8 = 4;
21906
21907 fn new(
21908 editor: WeakEntity<Editor>,
21909 breakpoint_anchor: Anchor,
21910 breakpoint: Breakpoint,
21911 edit_action: BreakpointPromptEditAction,
21912 window: &mut Window,
21913 cx: &mut Context<Self>,
21914 ) -> Self {
21915 let base_text = match edit_action {
21916 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
21917 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
21918 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
21919 }
21920 .map(|msg| msg.to_string())
21921 .unwrap_or_default();
21922
21923 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
21924 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
21925
21926 let prompt = cx.new(|cx| {
21927 let mut prompt = Editor::new(
21928 EditorMode::AutoHeight {
21929 max_lines: Self::MAX_LINES as usize,
21930 },
21931 buffer,
21932 None,
21933 window,
21934 cx,
21935 );
21936 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
21937 prompt.set_show_cursor_when_unfocused(false, cx);
21938 prompt.set_placeholder_text(
21939 match edit_action {
21940 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
21941 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
21942 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
21943 },
21944 cx,
21945 );
21946
21947 prompt
21948 });
21949
21950 Self {
21951 prompt,
21952 editor,
21953 breakpoint_anchor,
21954 breakpoint,
21955 edit_action,
21956 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
21957 block_ids: Default::default(),
21958 _subscriptions: vec![],
21959 }
21960 }
21961
21962 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
21963 self.block_ids.extend(block_ids)
21964 }
21965
21966 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
21967 if let Some(editor) = self.editor.upgrade() {
21968 let message = self
21969 .prompt
21970 .read(cx)
21971 .buffer
21972 .read(cx)
21973 .as_singleton()
21974 .expect("A multi buffer in breakpoint prompt isn't possible")
21975 .read(cx)
21976 .as_rope()
21977 .to_string();
21978
21979 editor.update(cx, |editor, cx| {
21980 editor.edit_breakpoint_at_anchor(
21981 self.breakpoint_anchor,
21982 self.breakpoint.clone(),
21983 match self.edit_action {
21984 BreakpointPromptEditAction::Log => {
21985 BreakpointEditAction::EditLogMessage(message.into())
21986 }
21987 BreakpointPromptEditAction::Condition => {
21988 BreakpointEditAction::EditCondition(message.into())
21989 }
21990 BreakpointPromptEditAction::HitCondition => {
21991 BreakpointEditAction::EditHitCondition(message.into())
21992 }
21993 },
21994 cx,
21995 );
21996
21997 editor.remove_blocks(self.block_ids.clone(), None, cx);
21998 cx.focus_self(window);
21999 });
22000 }
22001 }
22002
22003 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
22004 self.editor
22005 .update(cx, |editor, cx| {
22006 editor.remove_blocks(self.block_ids.clone(), None, cx);
22007 window.focus(&editor.focus_handle);
22008 })
22009 .log_err();
22010 }
22011
22012 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
22013 let settings = ThemeSettings::get_global(cx);
22014 let text_style = TextStyle {
22015 color: if self.prompt.read(cx).read_only(cx) {
22016 cx.theme().colors().text_disabled
22017 } else {
22018 cx.theme().colors().text
22019 },
22020 font_family: settings.buffer_font.family.clone(),
22021 font_fallbacks: settings.buffer_font.fallbacks.clone(),
22022 font_size: settings.buffer_font_size(cx).into(),
22023 font_weight: settings.buffer_font.weight,
22024 line_height: relative(settings.buffer_line_height.value()),
22025 ..Default::default()
22026 };
22027 EditorElement::new(
22028 &self.prompt,
22029 EditorStyle {
22030 background: cx.theme().colors().editor_background,
22031 local_player: cx.theme().players().local(),
22032 text: text_style,
22033 ..Default::default()
22034 },
22035 )
22036 }
22037}
22038
22039impl Render for BreakpointPromptEditor {
22040 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22041 let editor_margins = *self.editor_margins.lock();
22042 let gutter_dimensions = editor_margins.gutter;
22043 h_flex()
22044 .key_context("Editor")
22045 .bg(cx.theme().colors().editor_background)
22046 .border_y_1()
22047 .border_color(cx.theme().status().info_border)
22048 .size_full()
22049 .py(window.line_height() / 2.5)
22050 .on_action(cx.listener(Self::confirm))
22051 .on_action(cx.listener(Self::cancel))
22052 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
22053 .child(div().flex_1().child(self.render_prompt_editor(cx)))
22054 }
22055}
22056
22057impl Focusable for BreakpointPromptEditor {
22058 fn focus_handle(&self, cx: &App) -> FocusHandle {
22059 self.prompt.focus_handle(cx)
22060 }
22061}
22062
22063fn all_edits_insertions_or_deletions(
22064 edits: &Vec<(Range<Anchor>, String)>,
22065 snapshot: &MultiBufferSnapshot,
22066) -> bool {
22067 let mut all_insertions = true;
22068 let mut all_deletions = true;
22069
22070 for (range, new_text) in edits.iter() {
22071 let range_is_empty = range.to_offset(&snapshot).is_empty();
22072 let text_is_empty = new_text.is_empty();
22073
22074 if range_is_empty != text_is_empty {
22075 if range_is_empty {
22076 all_deletions = false;
22077 } else {
22078 all_insertions = false;
22079 }
22080 } else {
22081 return false;
22082 }
22083
22084 if !all_insertions && !all_deletions {
22085 return false;
22086 }
22087 }
22088 all_insertions || all_deletions
22089}
22090
22091struct MissingEditPredictionKeybindingTooltip;
22092
22093impl Render for MissingEditPredictionKeybindingTooltip {
22094 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22095 ui::tooltip_container(window, cx, |container, _, cx| {
22096 container
22097 .flex_shrink_0()
22098 .max_w_80()
22099 .min_h(rems_from_px(124.))
22100 .justify_between()
22101 .child(
22102 v_flex()
22103 .flex_1()
22104 .text_ui_sm(cx)
22105 .child(Label::new("Conflict with Accept Keybinding"))
22106 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
22107 )
22108 .child(
22109 h_flex()
22110 .pb_1()
22111 .gap_1()
22112 .items_end()
22113 .w_full()
22114 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
22115 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
22116 }))
22117 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
22118 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
22119 })),
22120 )
22121 })
22122 }
22123}
22124
22125#[derive(Debug, Clone, Copy, PartialEq)]
22126pub struct LineHighlight {
22127 pub background: Background,
22128 pub border: Option<gpui::Hsla>,
22129 pub include_gutter: bool,
22130 pub type_id: Option<TypeId>,
22131}
22132
22133fn render_diff_hunk_controls(
22134 row: u32,
22135 status: &DiffHunkStatus,
22136 hunk_range: Range<Anchor>,
22137 is_created_file: bool,
22138 line_height: Pixels,
22139 editor: &Entity<Editor>,
22140 _window: &mut Window,
22141 cx: &mut App,
22142) -> AnyElement {
22143 h_flex()
22144 .h(line_height)
22145 .mr_1()
22146 .gap_1()
22147 .px_0p5()
22148 .pb_1()
22149 .border_x_1()
22150 .border_b_1()
22151 .border_color(cx.theme().colors().border_variant)
22152 .rounded_b_lg()
22153 .bg(cx.theme().colors().editor_background)
22154 .gap_1()
22155 .block_mouse_except_scroll()
22156 .shadow_md()
22157 .child(if status.has_secondary_hunk() {
22158 Button::new(("stage", row as u64), "Stage")
22159 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
22160 .tooltip({
22161 let focus_handle = editor.focus_handle(cx);
22162 move |window, cx| {
22163 Tooltip::for_action_in(
22164 "Stage Hunk",
22165 &::git::ToggleStaged,
22166 &focus_handle,
22167 window,
22168 cx,
22169 )
22170 }
22171 })
22172 .on_click({
22173 let editor = editor.clone();
22174 move |_event, _window, cx| {
22175 editor.update(cx, |editor, cx| {
22176 editor.stage_or_unstage_diff_hunks(
22177 true,
22178 vec![hunk_range.start..hunk_range.start],
22179 cx,
22180 );
22181 });
22182 }
22183 })
22184 } else {
22185 Button::new(("unstage", row as u64), "Unstage")
22186 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
22187 .tooltip({
22188 let focus_handle = editor.focus_handle(cx);
22189 move |window, cx| {
22190 Tooltip::for_action_in(
22191 "Unstage Hunk",
22192 &::git::ToggleStaged,
22193 &focus_handle,
22194 window,
22195 cx,
22196 )
22197 }
22198 })
22199 .on_click({
22200 let editor = editor.clone();
22201 move |_event, _window, cx| {
22202 editor.update(cx, |editor, cx| {
22203 editor.stage_or_unstage_diff_hunks(
22204 false,
22205 vec![hunk_range.start..hunk_range.start],
22206 cx,
22207 );
22208 });
22209 }
22210 })
22211 })
22212 .child(
22213 Button::new(("restore", row as u64), "Restore")
22214 .tooltip({
22215 let focus_handle = editor.focus_handle(cx);
22216 move |window, cx| {
22217 Tooltip::for_action_in(
22218 "Restore Hunk",
22219 &::git::Restore,
22220 &focus_handle,
22221 window,
22222 cx,
22223 )
22224 }
22225 })
22226 .on_click({
22227 let editor = editor.clone();
22228 move |_event, window, cx| {
22229 editor.update(cx, |editor, cx| {
22230 let snapshot = editor.snapshot(window, cx);
22231 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
22232 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
22233 });
22234 }
22235 })
22236 .disabled(is_created_file),
22237 )
22238 .when(
22239 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
22240 |el| {
22241 el.child(
22242 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
22243 .shape(IconButtonShape::Square)
22244 .icon_size(IconSize::Small)
22245 // .disabled(!has_multiple_hunks)
22246 .tooltip({
22247 let focus_handle = editor.focus_handle(cx);
22248 move |window, cx| {
22249 Tooltip::for_action_in(
22250 "Next Hunk",
22251 &GoToHunk,
22252 &focus_handle,
22253 window,
22254 cx,
22255 )
22256 }
22257 })
22258 .on_click({
22259 let editor = editor.clone();
22260 move |_event, window, cx| {
22261 editor.update(cx, |editor, cx| {
22262 let snapshot = editor.snapshot(window, cx);
22263 let position =
22264 hunk_range.end.to_point(&snapshot.buffer_snapshot);
22265 editor.go_to_hunk_before_or_after_position(
22266 &snapshot,
22267 position,
22268 Direction::Next,
22269 window,
22270 cx,
22271 );
22272 editor.expand_selected_diff_hunks(cx);
22273 });
22274 }
22275 }),
22276 )
22277 .child(
22278 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
22279 .shape(IconButtonShape::Square)
22280 .icon_size(IconSize::Small)
22281 // .disabled(!has_multiple_hunks)
22282 .tooltip({
22283 let focus_handle = editor.focus_handle(cx);
22284 move |window, cx| {
22285 Tooltip::for_action_in(
22286 "Previous Hunk",
22287 &GoToPreviousHunk,
22288 &focus_handle,
22289 window,
22290 cx,
22291 )
22292 }
22293 })
22294 .on_click({
22295 let editor = editor.clone();
22296 move |_event, window, cx| {
22297 editor.update(cx, |editor, cx| {
22298 let snapshot = editor.snapshot(window, cx);
22299 let point =
22300 hunk_range.start.to_point(&snapshot.buffer_snapshot);
22301 editor.go_to_hunk_before_or_after_position(
22302 &snapshot,
22303 point,
22304 Direction::Prev,
22305 window,
22306 cx,
22307 );
22308 editor.expand_selected_diff_hunks(cx);
22309 });
22310 }
22311 }),
22312 )
22313 },
22314 )
22315 .into_any_element()
22316}